diff --git a/resources/celerity/map.php b/resources/celerity/map.php index f45c16ec69..d9eb3a42e0 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,10 +7,10 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => '82aca405', - 'conpherence.pkg.js' => '6249a1cf', - 'core.pkg.css' => '1bf8fa70', - 'core.pkg.js' => '021685f1', + 'conpherence.pkg.css' => '437d3b5a', + 'conpherence.pkg.js' => '281b1a73', + 'core.pkg.css' => 'b2ad82f4', + 'core.pkg.js' => 'bf3b5cdf', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', @@ -37,7 +37,7 @@ return array( 'rsrc/css/application/base/main-menu-view.css' => '5294060f', 'rsrc/css/application/base/notification-menu.css' => '6a697e43', 'rsrc/css/application/base/phui-theme.css' => '9f261c6b', - 'rsrc/css/application/base/standard-page-view.css' => '894d8a25', + 'rsrc/css/application/base/standard-page-view.css' => '89da5a9c', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '0ede4c9b', @@ -45,11 +45,11 @@ return array( 'rsrc/css/application/config/config-template.css' => '8f18fa41', 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', - 'rsrc/css/application/conpherence/durable-column.css' => '292c71f0', + 'rsrc/css/application/conpherence/durable-column.css' => '89ea6bef', 'rsrc/css/application/conpherence/header-pane.css' => '4082233d', 'rsrc/css/application/conpherence/menu.css' => '3d8e5c9c', 'rsrc/css/application/conpherence/message-pane.css' => 'd1fc13e1', - 'rsrc/css/application/conpherence/notification.css' => '965db05b', + 'rsrc/css/application/conpherence/notification.css' => 'cef0a3fc', 'rsrc/css/application/conpherence/participant-pane.css' => '604a8b02', 'rsrc/css/application/conpherence/transaction.css' => '85129c68', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', @@ -103,7 +103,7 @@ return array( 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', 'rsrc/css/application/search/application-search-view.css' => '66ee5d46', - 'rsrc/css/application/search/search-results.css' => '64ad079a', + 'rsrc/css/application/search/search-results.css' => 'f87d23ad', 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', @@ -133,20 +133,20 @@ return array( 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '269cbc99', - 'rsrc/css/phui/phui-button.css' => '14bfba79', + 'rsrc/css/phui/phui-button.css' => '96787bae', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', - 'rsrc/css/phui/phui-comment-form.css' => '7d903c2d', + 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', - 'rsrc/css/phui/phui-curtain-view.css' => '947bf1a4', + 'rsrc/css/phui/phui-curtain-view.css' => '679743bb', 'rsrc/css/phui/phui-document-pro.css' => 'f56738ed', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'c32e8dec', 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', 'rsrc/css/phui/phui-fontkit.css' => '1320ed01', 'rsrc/css/phui/phui-form-view.css' => '6175808d', - 'rsrc/css/phui/phui-form.css' => 'b62c01d8', + 'rsrc/css/phui/phui-form.css' => 'a5570f70', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', 'rsrc/css/phui/phui-header-view.css' => '9cf828ce', 'rsrc/css/phui/phui-hovercard.css' => 'ae091fc5', @@ -157,7 +157,7 @@ return array( 'rsrc/css/phui/phui-info-view.css' => 'ec92802a', 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', 'rsrc/css/phui/phui-lightbox.css' => '0a035e40', - 'rsrc/css/phui/phui-list.css' => 'a3ec3cf1', + 'rsrc/css/phui/phui-list.css' => '12eb8ce6', 'rsrc/css/phui/phui-object-box.css' => '8b289e3d', 'rsrc/css/phui/phui-pager.css' => '77d8a794', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', @@ -166,8 +166,8 @@ return array( 'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', - 'rsrc/css/phui/phui-tag-view.css' => '84d65f26', - 'rsrc/css/phui/phui-timeline-view.css' => 'bf45789e', + 'rsrc/css/phui/phui-tag-view.css' => 'cc4fd402', + 'rsrc/css/phui/phui-timeline-view.css' => '1d7ef61d', 'rsrc/css/phui/phui-two-column-view.css' => 'ce9fa0b7', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', @@ -375,7 +375,7 @@ return array( 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'aa3bd034', 'rsrc/js/application/conpherence/behavior-menu.js' => '7524fcfa', 'rsrc/js/application/conpherence/behavior-participant-pane.js' => '8604caa8', - 'rsrc/js/application/conpherence/behavior-pontificate.js' => 'f2e58483', + 'rsrc/js/application/conpherence/behavior-pontificate.js' => '55616e04', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', 'rsrc/js/application/conpherence/behavior-toggle-widget.js' => '3dbf94d5', 'rsrc/js/application/countdown/timer.js' => 'e4cc26b3', @@ -472,7 +472,7 @@ return array( 'rsrc/js/core/KeyboardShortcutManager.js' => '4a021c10', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 'rsrc/js/core/Notification.js' => 'ccf1cbf8', - 'rsrc/js/core/Prefab.js' => '8d40ae75', + 'rsrc/js/core/Prefab.js' => 'c5af80a2', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '320810c8', 'rsrc/js/core/Title.js' => '485aaa6c', @@ -503,14 +503,14 @@ return array( 'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-phabricator-nav.js' => '08675c6d', - 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'a0777ea3', + 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '0ca788bd', 'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207', 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', 'rsrc/js/core/behavior-remarkup-preview.js' => '4b700e9e', 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', - 'rsrc/js/core/behavior-search-typeahead.js' => '06c32383', + 'rsrc/js/core/behavior-search-typeahead.js' => '0f2a0820', 'rsrc/js/core/behavior-select-content.js' => 'bf5374ef', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-setup-check-https.js' => '491416b3', @@ -528,7 +528,7 @@ return array( 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b', - 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'd5b2abf3', + 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'd713a2c5', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', @@ -552,11 +552,11 @@ return array( 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', 'config-page-css' => 'c1d5121b', - 'conpherence-durable-column-view' => '292c71f0', + 'conpherence-durable-column-view' => '89ea6bef', 'conpherence-header-pane-css' => '4082233d', 'conpherence-menu-css' => '3d8e5c9c', 'conpherence-message-pane-css' => 'd1fc13e1', - 'conpherence-notification-css' => '965db05b', + 'conpherence-notification-css' => 'cef0a3fc', 'conpherence-participant-pane-css' => '604a8b02', 'conpherence-thread-manager' => 'c8b5ee6f', 'conpherence-transaction-css' => '85129c68', @@ -600,7 +600,7 @@ return array( 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-menu' => '7524fcfa', 'javelin-behavior-conpherence-participant-pane' => '8604caa8', - 'javelin-behavior-conpherence-pontificate' => 'f2e58483', + 'javelin-behavior-conpherence-pontificate' => '55616e04', 'javelin-behavior-conpherence-search' => '9bbf3762', 'javelin-behavior-countdown-timer' => 'e4cc26b3', 'javelin-behavior-dark-console' => 'f411b6ae', @@ -664,9 +664,9 @@ return array( 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => 'e0ec7f2f', 'javelin-behavior-phabricator-oncopy' => '2926fff2', - 'javelin-behavior-phabricator-remarkup-assist' => 'a0777ea3', + 'javelin-behavior-phabricator-remarkup-assist' => '0ca788bd', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', - 'javelin-behavior-phabricator-search-typeahead' => '06c32383', + 'javelin-behavior-phabricator-search-typeahead' => '0f2a0820', 'javelin-behavior-phabricator-show-older-transactions' => '94c65b72', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', @@ -792,13 +792,13 @@ return array( 'phabricator-notification-menu-css' => '6a697e43', 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', - 'phabricator-prefab' => '8d40ae75', + 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => '17c0fb37', - 'phabricator-search-results-css' => '64ad079a', + 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => '4383192f', - 'phabricator-standard-page-view' => '894d8a25', + 'phabricator-standard-page-view' => '89da5a9c', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', 'phabricator-tooltip' => '8fadb715', @@ -829,24 +829,24 @@ return array( 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', - 'phui-button-css' => '14bfba79', + 'phui-button-css' => '96787bae', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', 'phui-calendar-month-css' => '8e10e92c', 'phui-chart-css' => '6bf6f78e', 'phui-cms-css' => '504b4b23', - 'phui-comment-form-css' => '7d903c2d', + 'phui-comment-form-css' => '57af2e14', 'phui-comment-panel-css' => 'f50152ad', 'phui-crumbs-view-css' => '6ece3bbb', - 'phui-curtain-view-css' => '947bf1a4', + 'phui-curtain-view-css' => '679743bb', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'c32e8dec', 'phui-document-view-pro-css' => 'f56738ed', 'phui-feed-story-css' => '44a9c8e9', 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '1320ed01', - 'phui-form-css' => 'b62c01d8', + 'phui-form-css' => 'a5570f70', 'phui-form-view-css' => '6175808d', 'phui-head-thing-view-css' => 'fd311e5f', 'phui-header-view-css' => '9cf828ce', @@ -860,7 +860,7 @@ return array( 'phui-inline-comment-view-css' => 'be663c95', 'phui-invisible-character-view-css' => '6993d9f0', 'phui-lightbox-css' => '0a035e40', - 'phui-list-view-css' => 'a3ec3cf1', + 'phui-list-view-css' => '12eb8ce6', 'phui-object-box-css' => '8b289e3d', 'phui-oi-big-ui-css' => '19f9369b', 'phui-oi-color-css' => 'cd2b9b77', @@ -875,9 +875,9 @@ return array( 'phui-segment-bar-view-css' => 'b1d1b892', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => 'd5263e49', - 'phui-tag-view-css' => '84d65f26', + 'phui-tag-view-css' => 'cc4fd402', 'phui-theme-css' => '9f261c6b', - 'phui-timeline-view-css' => 'bf45789e', + 'phui-timeline-view-css' => '1d7ef61d', 'phui-two-column-view-css' => 'ce9fa0b7', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', @@ -885,7 +885,7 @@ return array( 'phui-workpanel-view-css' => 'a3a63478', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => 'b3465b9b', - 'phuix-autocomplete' => 'd5b2abf3', + 'phuix-autocomplete' => 'd713a2c5', 'phuix-dropdown-menu' => '8018ee50', 'phuix-form-control-view' => '83e03671', 'phuix-icon-view' => 'bff6884b', @@ -943,17 +943,6 @@ return array( 'javelin-stratcom', 'javelin-workflow', ), - '06c32383' => array( - 'javelin-behavior', - 'javelin-typeahead-ondemand-source', - 'javelin-typeahead', - 'javelin-dom', - 'javelin-uri', - 'javelin-util', - 'javelin-stratcom', - 'phabricator-prefab', - 'phuix-icon-view', - ), '0825c27a' => array( 'javelin-behavior', 'javelin-dom', @@ -988,6 +977,28 @@ return array( 'javelin-dom', 'javelin-router', ), + '0ca788bd' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-phtize', + 'phabricator-textareautils', + 'javelin-workflow', + 'javelin-vector', + 'phuix-autocomplete', + 'javelin-mask', + ), + '0f2a0820' => array( + 'javelin-behavior', + 'javelin-typeahead-ondemand-source', + 'javelin-typeahead', + 'javelin-dom', + 'javelin-uri', + 'javelin-util', + 'javelin-stratcom', + 'phabricator-prefab', + 'phuix-icon-view', + ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -1298,6 +1309,14 @@ return array( 'javelin-request', 'javelin-typeahead-source', ), + '55616e04' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-workflow', + 'javelin-stratcom', + 'conpherence-thread-manager', + ), '558829c2' => array( 'javelin-stratcom', 'javelin-behavior', @@ -1569,18 +1588,6 @@ return array( 'javelin-stratcom', 'javelin-install', ), - '8d40ae75' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-typeahead', - 'javelin-tokenizer', - 'javelin-typeahead-preloaded-source', - 'javelin-typeahead-ondemand-source', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - ), '8fadb715' => array( 'javelin-install', 'javelin-util', @@ -1686,17 +1693,6 @@ return array( 'javelin-dom', 'javelin-vector', ), - 'a0777ea3' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-phtize', - 'phabricator-textareautils', - 'javelin-workflow', - 'javelin-vector', - 'phuix-autocomplete', - 'javelin-mask', - ), 'a0b57eb8' => array( 'javelin-behavior', 'javelin-dom', @@ -1947,6 +1943,18 @@ return array( 'c587b80f' => array( 'javelin-install', ), + 'c5af80a2' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-typeahead', + 'javelin-tokenizer', + 'javelin-typeahead-preloaded-source', + 'javelin-typeahead-ondemand-source', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + ), 'c7ccd872' => array( 'phui-fontkit-css', ), @@ -2047,12 +2055,6 @@ return array( 'javelin-uri', 'phabricator-notification', ), - 'd5b2abf3' => array( - 'javelin-install', - 'javelin-dom', - 'phuix-icon-view', - 'phabricator-prefab', - ), 'd6a7e717' => array( 'multirow-row-manager', 'javelin-install', @@ -2062,6 +2064,12 @@ return array( 'javelin-json', 'phabricator-prefab', ), + 'd713a2c5' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-icon-view', + 'phabricator-prefab', + ), 'd7a74243' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2165,14 +2173,6 @@ return array( 'f12cbc9f' => array( 'phui-oi-list-view-css', ), - 'f2e58483' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-workflow', - 'javelin-stratcom', - 'conpherence-thread-manager', - ), 'f411b6ae' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/resources/sql/autopatches/20170410.calendar.01.repair.php b/resources/sql/autopatches/20170410.calendar.01.repair.php new file mode 100644 index 0000000000..7d0000e581 --- /dev/null +++ b/resources/sql/autopatches/20170410.calendar.01.repair.php @@ -0,0 +1,42 @@ +establishConnection('w'); +$table_name = $table->getTableName(); + +$viewer = PhabricatorUser::getOmnipotentUser(); +$all_events = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->execute(); +foreach ($all_events as $event) { + $id = $event->getID(); + + if (!$event->getInstanceOfEventPHID()) { + // Not a child event, so no instance epoch. + continue; + } + + if ($event->getUTCInstanceEpoch()) { + // Already has an instance epoch. + continue; + } + + try { + $event->updateUTCEpochs(); + } catch (Exception $ex) { + phlog($ex); + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET utcInstanceEpoch = %nd WHERE id = %d', + $table_name, + $event->getUTCInstanceEpoch(), + $id); +} diff --git a/resources/sql/autopatches/20170412.conpherence.01.picturecrop.sql b/resources/sql/autopatches/20170412.conpherence.01.picturecrop.sql new file mode 100644 index 0000000000..760a19f119 --- /dev/null +++ b/resources/sql/autopatches/20170412.conpherence.01.picturecrop.sql @@ -0,0 +1,2 @@ +DELETE FROM {$NAMESPACE}_conpherence.conpherence_transaction + WHERE transactionType = 'picture-crop'; diff --git a/resources/sql/autopatches/20170413.conpherence.01.recentparty.sql b/resources/sql/autopatches/20170413.conpherence.01.recentparty.sql new file mode 100644 index 0000000000..996a058c5b --- /dev/null +++ b/resources/sql/autopatches/20170413.conpherence.01.recentparty.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_conpherence.conpherence_thread + DROP COLUMN recentParticipantPHIDs; diff --git a/resources/sql/stopwords_myisam.txt b/resources/sql/stopwords_myisam.txt new file mode 100644 index 0000000000..9a0bde3e9b --- /dev/null +++ b/resources/sql/stopwords_myisam.txt @@ -0,0 +1,543 @@ +a's +able +about +above +according +accordingly +across +actually +after +afterwards +again +against +ain't +all +allow +allows +almost +alone +along +already +also +although +always +am +among +amongst +an +and +another +any +anybody +anyhow +anyone +anything +anyway +anyways +anywhere +apart +appear +appreciate +appropriate +are +aren't +around +as +aside +ask +asking +associated +at +available +away +awfully +be +became +because +become +becomes +becoming +been +before +beforehand +behind +being +believe +below +beside +besides +best +better +between +beyond +both +brief +but +by +c'mon +c's +came +can +can't +cannot +cant +cause +causes +certain +certainly +changes +clearly +co +com +come +comes +concerning +consequently +consider +considering +contain +containing +contains +corresponding +could +couldn't +course +currently +definitely +described +despite +did +didn't +different +do +does +doesn't +doing +don't +done +down +downwards +during +each +edu +eg +eight +either +else +elsewhere +enough +entirely +especially +et +etc +even +ever +every +everybody +everyone +everything +everywhere +ex +exactly +example +except +far +few +fifth +first +five +followed +following +follows +for +former +formerly +forth +four +from +further +furthermore +get +gets +getting +given +gives +go +goes +going +gone +got +gotten +greetings +had +hadn't +happens +hardly +has +hasn't +have +haven't +having +he +he's +hello +help +hence +her +here +here's +hereafter +hereby +herein +hereupon +hers +herself +hi +him +himself +his +hither +hopefully +how +howbeit +however +i'd +i'll +i'm +i've +ie +if +ignored +immediate +in +inasmuch +inc +indeed +indicate +indicated +indicates +inner +insofar +instead +into +inward +is +isn't +it +it'd +it'll +it's +its +itself +just +keep +keeps +kept +know +known +knows +last +lately +later +latter +latterly +least +less +lest +let +let's +like +liked +likely +little +look +looking +looks +ltd +mainly +many +may +maybe +me +mean +meanwhile +merely +might +more +moreover +most +mostly +much +must +my +myself +name +namely +nd +near +nearly +necessary +need +needs +neither +never +nevertheless +new +next +nine +no +nobody +non +none +noone +nor +normally +not +nothing +novel +now +nowhere +obviously +of +off +often +oh +ok +okay +old +on +once +one +ones +only +onto +or +other +others +otherwise +ought +our +ours +ourselves +out +outside +over +overall +own +particular +particularly +per +perhaps +placed +please +plus +possible +presumably +probably +provides +que +quite +qv +rather +rd +re +really +reasonably +regarding +regardless +regards +relatively +respectively +right +said +same +saw +say +saying +says +second +secondly +see +seeing +seem +seemed +seeming +seems +seen +self +selves +sensible +sent +serious +seriously +seven +several +shall +she +should +shouldn't +since +six +so +some +somebody +somehow +someone +something +sometime +sometimes +somewhat +somewhere +soon +sorry +specified +specify +specifying +still +sub +such +sup +sure +t's +take +taken +tell +tends +th +than +thank +thanks +thanx +that +that's +thats +the +their +theirs +them +themselves +then +thence +there +there's +thereafter +thereby +therefore +therein +theres +thereupon +these +they +they'd +they'll +they're +they've +think +third +this +thorough +thoroughly +those +though +three +through +throughout +thru +thus +to +together +too +took +toward +towards +tried +tries +truly +try +trying +twice +two +un +under +unfortunately +unless +unlikely +until +unto +up +upon +us +use +used +useful +uses +using +usually +value +various +very +via +viz +vs +want +wants +was +wasn't +way +we +we'd +we'll +we're +we've +welcome +well +went +were +weren't +what +what's +whatever +when +whence +whenever +where +where's +whereafter +whereas +whereby +wherein +whereupon +wherever +whether +which +while +whither +who +who's +whoever +whole +whom +whose +why +will +willing +wish +with +within +without +won't +wonder +would +wouldn't +yes +yet +you +you'd +you'll +you're +you've +your +yours +yourself +yourselves +zero diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b567e5a9e9..44a3ca7ad3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -316,15 +316,20 @@ phutil_register_library_map(array( 'ConpherenceTestCase' => 'applications/conpherence/__tests__/ConpherenceTestCase.php', 'ConpherenceThread' => 'applications/conpherence/storage/ConpherenceThread.php', 'ConpherenceThreadDatasource' => 'applications/conpherence/typeahead/ConpherenceThreadDatasource.php', + 'ConpherenceThreadDateMarkerTransaction' => 'applications/conpherence/xaction/ConpherenceThreadDateMarkerTransaction.php', 'ConpherenceThreadIndexEngineExtension' => 'applications/conpherence/engineextension/ConpherenceThreadIndexEngineExtension.php', 'ConpherenceThreadListView' => 'applications/conpherence/view/ConpherenceThreadListView.php', 'ConpherenceThreadMailReceiver' => 'applications/conpherence/mail/ConpherenceThreadMailReceiver.php', 'ConpherenceThreadMembersPolicyRule' => 'applications/conpherence/policyrule/ConpherenceThreadMembersPolicyRule.php', + 'ConpherenceThreadPictureTransaction' => 'applications/conpherence/xaction/ConpherenceThreadPictureTransaction.php', 'ConpherenceThreadQuery' => 'applications/conpherence/query/ConpherenceThreadQuery.php', 'ConpherenceThreadRemarkupRule' => 'applications/conpherence/remarkup/ConpherenceThreadRemarkupRule.php', 'ConpherenceThreadSearchController' => 'applications/conpherence/controller/ConpherenceThreadSearchController.php', 'ConpherenceThreadSearchEngine' => 'applications/conpherence/query/ConpherenceThreadSearchEngine.php', 'ConpherenceThreadTitleNgrams' => 'applications/conpherence/storage/ConpherenceThreadTitleNgrams.php', + 'ConpherenceThreadTitleTransaction' => 'applications/conpherence/xaction/ConpherenceThreadTitleTransaction.php', + 'ConpherenceThreadTopicTransaction' => 'applications/conpherence/xaction/ConpherenceThreadTopicTransaction.php', + 'ConpherenceThreadTransactionType' => 'applications/conpherence/xaction/ConpherenceThreadTransactionType.php', 'ConpherenceTransaction' => 'applications/conpherence/storage/ConpherenceTransaction.php', 'ConpherenceTransactionComment' => 'applications/conpherence/storage/ConpherenceTransactionComment.php', 'ConpherenceTransactionQuery' => 'applications/conpherence/query/ConpherenceTransactionQuery.php', @@ -334,6 +339,8 @@ phutil_register_library_map(array( 'ConpherenceUpdateController' => 'applications/conpherence/controller/ConpherenceUpdateController.php', 'ConpherenceUpdateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php', 'ConpherenceViewController' => 'applications/conpherence/controller/ConpherenceViewController.php', + 'CountdownEditConduitAPIMethod' => 'applications/countdown/conduit/CountdownEditConduitAPIMethod.php', + 'CountdownSearchConduitAPIMethod' => 'applications/countdown/conduit/CountdownSearchConduitAPIMethod.php', 'DarkConsoleController' => 'applications/console/controller/DarkConsoleController.php', 'DarkConsoleCore' => 'applications/console/core/DarkConsoleCore.php', 'DarkConsoleDataController' => 'applications/console/controller/DarkConsoleDataController.php', @@ -2366,6 +2373,7 @@ phutil_register_library_map(array( 'PhabricatorConfigPageView' => 'applications/config/view/PhabricatorConfigPageView.php', 'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php', 'PhabricatorConfigPurgeCacheController' => 'applications/config/controller/PhabricatorConfigPurgeCacheController.php', + 'PhabricatorConfigRegexOptionType' => 'applications/config/custom/PhabricatorConfigRegexOptionType.php', 'PhabricatorConfigRequestExceptionHandlerModule' => 'applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php', 'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php', 'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php', @@ -2387,6 +2395,7 @@ phutil_register_library_map(array( 'PhabricatorConpherenceColumnVisibleSetting' => 'applications/settings/setting/PhabricatorConpherenceColumnVisibleSetting.php', 'PhabricatorConpherenceNotificationsSetting' => 'applications/settings/setting/PhabricatorConpherenceNotificationsSetting.php', 'PhabricatorConpherencePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php', + 'PhabricatorConpherenceProfileMenuItem' => 'applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php', 'PhabricatorConpherenceThreadPHIDType' => 'applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php', 'PhabricatorConpherenceWidgetVisibleSetting' => 'applications/settings/setting/PhabricatorConpherenceWidgetVisibleSetting.php', 'PhabricatorConsoleApplication' => 'applications/console/application/PhabricatorConsoleApplication.php', @@ -2408,10 +2417,11 @@ phutil_register_library_map(array( 'PhabricatorCountdownDAO' => 'applications/countdown/storage/PhabricatorCountdownDAO.php', 'PhabricatorCountdownDefaultEditCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultEditCapability.php', 'PhabricatorCountdownDefaultViewCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultViewCapability.php', - 'PhabricatorCountdownDeleteController' => 'applications/countdown/controller/PhabricatorCountdownDeleteController.php', + 'PhabricatorCountdownDescriptionTransaction' => 'applications/countdown/xaction/PhabricatorCountdownDescriptionTransaction.php', 'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php', 'PhabricatorCountdownEditEngine' => 'applications/countdown/editor/PhabricatorCountdownEditEngine.php', 'PhabricatorCountdownEditor' => 'applications/countdown/editor/PhabricatorCountdownEditor.php', + 'PhabricatorCountdownEpochTransaction' => 'applications/countdown/xaction/PhabricatorCountdownEpochTransaction.php', 'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php', 'PhabricatorCountdownMailReceiver' => 'applications/countdown/mail/PhabricatorCountdownMailReceiver.php', 'PhabricatorCountdownQuery' => 'applications/countdown/query/PhabricatorCountdownQuery.php', @@ -2419,9 +2429,11 @@ phutil_register_library_map(array( 'PhabricatorCountdownReplyHandler' => 'applications/countdown/mail/PhabricatorCountdownReplyHandler.php', 'PhabricatorCountdownSchemaSpec' => 'applications/countdown/storage/PhabricatorCountdownSchemaSpec.php', 'PhabricatorCountdownSearchEngine' => 'applications/countdown/query/PhabricatorCountdownSearchEngine.php', + 'PhabricatorCountdownTitleTransaction' => 'applications/countdown/xaction/PhabricatorCountdownTitleTransaction.php', 'PhabricatorCountdownTransaction' => 'applications/countdown/storage/PhabricatorCountdownTransaction.php', 'PhabricatorCountdownTransactionComment' => 'applications/countdown/storage/PhabricatorCountdownTransactionComment.php', 'PhabricatorCountdownTransactionQuery' => 'applications/countdown/query/PhabricatorCountdownTransactionQuery.php', + 'PhabricatorCountdownTransactionType' => 'applications/countdown/xaction/PhabricatorCountdownTransactionType.php', 'PhabricatorCountdownView' => 'applications/countdown/view/PhabricatorCountdownView.php', 'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php', 'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php', @@ -2840,7 +2852,9 @@ phutil_register_library_map(array( 'PhabricatorFulltextEngineExtensionModule' => 'applications/search/index/PhabricatorFulltextEngineExtensionModule.php', 'PhabricatorFulltextIndexEngineExtension' => 'applications/search/engineextension/PhabricatorFulltextIndexEngineExtension.php', 'PhabricatorFulltextInterface' => 'applications/search/interface/PhabricatorFulltextInterface.php', + 'PhabricatorFulltextResultSet' => 'applications/search/query/PhabricatorFulltextResultSet.php', 'PhabricatorFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php', + 'PhabricatorFulltextToken' => 'applications/search/query/PhabricatorFulltextToken.php', 'PhabricatorFundApplication' => 'applications/fund/application/PhabricatorFundApplication.php', 'PhabricatorGDSetupCheck' => 'applications/config/check/PhabricatorGDSetupCheck.php', 'PhabricatorGarbageCollector' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php', @@ -3414,6 +3428,7 @@ phutil_register_library_map(array( 'PhabricatorPhortuneContentSource' => 'applications/phortune/contentsource/PhabricatorPhortuneContentSource.php', 'PhabricatorPhortuneManagementInvoiceWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php', 'PhabricatorPhortuneManagementWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementWorkflow.php', + 'PhabricatorPhortuneTestCase' => 'applications/phortune/__tests__/PhabricatorPhortuneTestCase.php', 'PhabricatorPhragmentApplication' => 'applications/phragment/application/PhabricatorPhragmentApplication.php', 'PhabricatorPhrequentApplication' => 'applications/phrequent/application/PhabricatorPhrequentApplication.php', 'PhabricatorPhrictionApplication' => 'applications/phriction/application/PhabricatorPhrictionApplication.php', @@ -3940,6 +3955,7 @@ phutil_register_library_map(array( 'PhabricatorStorageManagementDatabasesWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php', 'PhabricatorStorageManagementDestroyWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php', 'PhabricatorStorageManagementDumpWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php', + 'PhabricatorStorageManagementOptimizeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementOptimizeWorkflow.php', 'PhabricatorStorageManagementPartitionWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementPartitionWorkflow.php', 'PhabricatorStorageManagementProbeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php', 'PhabricatorStorageManagementQuickstartWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php', @@ -4319,15 +4335,24 @@ phutil_register_library_map(array( 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', 'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php', 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', + 'PhortuneAccountAddManagerController' => 'applications/phortune/controller/account/PhortuneAccountAddManagerController.php', + 'PhortuneAccountBillingController' => 'applications/phortune/controller/account/PhortuneAccountBillingController.php', 'PhortuneAccountChargeListController' => 'applications/phortune/controller/account/PhortuneAccountChargeListController.php', + 'PhortuneAccountController' => 'applications/phortune/controller/account/PhortuneAccountController.php', 'PhortuneAccountEditController' => 'applications/phortune/controller/account/PhortuneAccountEditController.php', + 'PhortuneAccountEditEngine' => 'applications/phortune/editor/PhortuneAccountEditEngine.php', 'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php', 'PhortuneAccountHasMemberEdgeType' => 'applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php', 'PhortuneAccountListController' => 'applications/phortune/controller/account/PhortuneAccountListController.php', + 'PhortuneAccountManagerController' => 'applications/phortune/controller/account/PhortuneAccountManagerController.php', + 'PhortuneAccountNameTransaction' => 'applications/phortune/xaction/PhortuneAccountNameTransaction.php', 'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php', + 'PhortuneAccountProfileController' => 'applications/phortune/controller/account/PhortuneAccountProfileController.php', 'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php', + 'PhortuneAccountSubscriptionController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionController.php', 'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php', 'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php', + 'PhortuneAccountTransactionType' => 'applications/phortune/xaction/PhortuneAccountTransactionType.php', 'PhortuneAccountViewController' => 'applications/phortune/controller/account/PhortuneAccountViewController.php', 'PhortuneAdHocCart' => 'applications/phortune/cart/PhortuneAdHocCart.php', 'PhortuneAdHocProduct' => 'applications/phortune/product/PhortuneAdHocProduct.php', @@ -4365,20 +4390,30 @@ phutil_register_library_map(array( 'PhortuneMemberHasAccountEdgeType' => 'applications/phortune/edge/PhortuneMemberHasAccountEdgeType.php', 'PhortuneMemberHasMerchantEdgeType' => 'applications/phortune/edge/PhortuneMemberHasMerchantEdgeType.php', 'PhortuneMerchant' => 'applications/phortune/storage/PhortuneMerchant.php', + 'PhortuneMerchantAddManagerController' => 'applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php', 'PhortuneMerchantCapability' => 'applications/phortune/capability/PhortuneMerchantCapability.php', + 'PhortuneMerchantContactInfoTransaction' => 'applications/phortune/xaction/PhortuneMerchantContactInfoTransaction.php', 'PhortuneMerchantController' => 'applications/phortune/controller/merchant/PhortuneMerchantController.php', + 'PhortuneMerchantDescriptionTransaction' => 'applications/phortune/xaction/PhortuneMerchantDescriptionTransaction.php', 'PhortuneMerchantEditController' => 'applications/phortune/controller/merchant/PhortuneMerchantEditController.php', 'PhortuneMerchantEditEngine' => 'applications/phortune/editor/PhortuneMerchantEditEngine.php', 'PhortuneMerchantEditor' => 'applications/phortune/editor/PhortuneMerchantEditor.php', 'PhortuneMerchantHasMemberEdgeType' => 'applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php', 'PhortuneMerchantInvoiceCreateController' => 'applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php', + 'PhortuneMerchantInvoiceEmailTransaction' => 'applications/phortune/xaction/PhortuneMerchantInvoiceEmailTransaction.php', + 'PhortuneMerchantInvoiceFooterTransaction' => 'applications/phortune/xaction/PhortuneMerchantInvoiceFooterTransaction.php', 'PhortuneMerchantListController' => 'applications/phortune/controller/merchant/PhortuneMerchantListController.php', + 'PhortuneMerchantManagerController' => 'applications/phortune/controller/merchant/PhortuneMerchantManagerController.php', + 'PhortuneMerchantNameTransaction' => 'applications/phortune/xaction/PhortuneMerchantNameTransaction.php', 'PhortuneMerchantPHIDType' => 'applications/phortune/phid/PhortuneMerchantPHIDType.php', 'PhortuneMerchantPictureController' => 'applications/phortune/controller/merchant/PhortuneMerchantPictureController.php', + 'PhortuneMerchantPictureTransaction' => 'applications/phortune/xaction/PhortuneMerchantPictureTransaction.php', + 'PhortuneMerchantProfileController' => 'applications/phortune/controller/merchant/PhortuneMerchantProfileController.php', 'PhortuneMerchantQuery' => 'applications/phortune/query/PhortuneMerchantQuery.php', 'PhortuneMerchantSearchEngine' => 'applications/phortune/query/PhortuneMerchantSearchEngine.php', 'PhortuneMerchantTransaction' => 'applications/phortune/storage/PhortuneMerchantTransaction.php', 'PhortuneMerchantTransactionQuery' => 'applications/phortune/query/PhortuneMerchantTransactionQuery.php', + 'PhortuneMerchantTransactionType' => 'applications/phortune/xaction/PhortuneMerchantTransactionType.php', 'PhortuneMerchantViewController' => 'applications/phortune/controller/merchant/PhortuneMerchantViewController.php', 'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php', 'PhortuneOrderTableView' => 'applications/phortune/view/PhortuneOrderTableView.php', @@ -4691,6 +4726,7 @@ phutil_register_library_map(array( 'javelin_tag' => 'infrastructure/javelin/markup.php', 'phabricator_date' => 'view/viewutils.php', 'phabricator_datetime' => 'view/viewutils.php', + 'phabricator_datetimezone' => 'view/viewutils.php', 'phabricator_form' => 'infrastructure/javelin/markup.php', 'phabricator_format_local_time' => 'view/viewutils.php', 'phabricator_relative_date' => 'view/viewutils.php', @@ -5075,16 +5111,21 @@ phutil_register_library_map(array( 'PhabricatorNgramsInterface', ), 'ConpherenceThreadDatasource' => 'PhabricatorTypeaheadDatasource', + 'ConpherenceThreadDateMarkerTransaction' => 'ConpherenceThreadTransactionType', 'ConpherenceThreadIndexEngineExtension' => 'PhabricatorIndexEngineExtension', 'ConpherenceThreadListView' => 'AphrontView', 'ConpherenceThreadMailReceiver' => 'PhabricatorObjectMailReceiver', 'ConpherenceThreadMembersPolicyRule' => 'PhabricatorPolicyRule', + 'ConpherenceThreadPictureTransaction' => 'ConpherenceThreadTransactionType', 'ConpherenceThreadQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'ConpherenceThreadRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ConpherenceThreadSearchController' => 'ConpherenceController', 'ConpherenceThreadSearchEngine' => 'PhabricatorApplicationSearchEngine', 'ConpherenceThreadTitleNgrams' => 'PhabricatorSearchNgrams', - 'ConpherenceTransaction' => 'PhabricatorApplicationTransaction', + 'ConpherenceThreadTitleTransaction' => 'ConpherenceThreadTransactionType', + 'ConpherenceThreadTopicTransaction' => 'ConpherenceThreadTransactionType', + 'ConpherenceThreadTransactionType' => 'PhabricatorModularTransactionType', + 'ConpherenceTransaction' => 'PhabricatorModularTransaction', 'ConpherenceTransactionComment' => 'PhabricatorApplicationTransactionComment', 'ConpherenceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'ConpherenceTransactionRenderer' => 'Phobject', @@ -5093,6 +5134,8 @@ phutil_register_library_map(array( 'ConpherenceUpdateController' => 'ConpherenceController', 'ConpherenceUpdateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceViewController' => 'ConpherenceController', + 'CountdownEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', + 'CountdownSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'DarkConsoleController' => 'PhabricatorController', 'DarkConsoleCore' => 'Phobject', 'DarkConsoleDataController' => 'PhabricatorController', @@ -7433,6 +7476,7 @@ phutil_register_library_map(array( 'PhabricatorConfigPageView' => 'AphrontTagView', 'PhabricatorConfigProxySource' => 'PhabricatorConfigSource', 'PhabricatorConfigPurgeCacheController' => 'PhabricatorConfigController', + 'PhabricatorConfigRegexOptionType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorConfigRequestExceptionHandlerModule' => 'PhabricatorConfigModule', 'PhabricatorConfigResponse' => 'AphrontStandaloneHTMLResponse', 'PhabricatorConfigSchemaQuery' => 'Phobject', @@ -7454,6 +7498,7 @@ phutil_register_library_map(array( 'PhabricatorConpherenceColumnVisibleSetting' => 'PhabricatorInternalSetting', 'PhabricatorConpherenceNotificationsSetting' => 'PhabricatorSelectSetting', 'PhabricatorConpherencePreferencesSettingsPanel' => 'PhabricatorEditEngineSettingsPanel', + 'PhabricatorConpherenceProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorConpherenceThreadPHIDType' => 'PhabricatorPHIDType', 'PhabricatorConpherenceWidgetVisibleSetting' => 'PhabricatorInternalSetting', 'PhabricatorConsoleApplication' => 'PhabricatorApplication', @@ -7477,6 +7522,8 @@ phutil_register_library_map(array( 'PhabricatorTokenReceiverInterface', 'PhabricatorSpacesInterface', 'PhabricatorProjectInterface', + 'PhabricatorDestructibleInterface', + 'PhabricatorConduitResultInterface', ), 'PhabricatorCountdownApplication' => 'PhabricatorApplication', 'PhabricatorCountdownController' => 'PhabricatorController', @@ -7484,10 +7531,11 @@ phutil_register_library_map(array( 'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO', 'PhabricatorCountdownDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PhabricatorCountdownDefaultViewCapability' => 'PhabricatorPolicyCapability', - 'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController', + 'PhabricatorCountdownDescriptionTransaction' => 'PhabricatorCountdownTransactionType', 'PhabricatorCountdownEditController' => 'PhabricatorCountdownController', 'PhabricatorCountdownEditEngine' => 'PhabricatorEditEngine', 'PhabricatorCountdownEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorCountdownEpochTransaction' => 'PhabricatorCountdownTransactionType', 'PhabricatorCountdownListController' => 'PhabricatorCountdownController', 'PhabricatorCountdownMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorCountdownQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', @@ -7495,9 +7543,11 @@ phutil_register_library_map(array( 'PhabricatorCountdownReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorCountdownSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorCountdownSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhabricatorCountdownTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorCountdownTitleTransaction' => 'PhabricatorCountdownTransactionType', + 'PhabricatorCountdownTransaction' => 'PhabricatorModularTransaction', 'PhabricatorCountdownTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorCountdownTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorCountdownTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorCountdownView' => 'AphrontView', 'PhabricatorCountdownViewController' => 'PhabricatorCountdownController', 'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery', @@ -7976,7 +8026,9 @@ phutil_register_library_map(array( 'PhabricatorFulltextEngineExtension' => 'Phobject', 'PhabricatorFulltextEngineExtensionModule' => 'PhabricatorConfigModule', 'PhabricatorFulltextIndexEngineExtension' => 'PhabricatorIndexEngineExtension', + 'PhabricatorFulltextResultSet' => 'Phobject', 'PhabricatorFulltextStorageEngine' => 'Phobject', + 'PhabricatorFulltextToken' => 'Phobject', 'PhabricatorFundApplication' => 'PhabricatorApplication', 'PhabricatorGDSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorGarbageCollector' => 'Phobject', @@ -8629,6 +8681,7 @@ phutil_register_library_map(array( 'PhabricatorPhortuneContentSource' => 'PhabricatorContentSource', 'PhabricatorPhortuneManagementInvoiceWorkflow' => 'PhabricatorPhortuneManagementWorkflow', 'PhabricatorPhortuneManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'PhabricatorPhortuneTestCase' => 'PhabricatorTestCase', 'PhabricatorPhragmentApplication' => 'PhabricatorApplication', 'PhabricatorPhrequentApplication' => 'PhabricatorApplication', 'PhabricatorPhrictionApplication' => 'PhabricatorApplication', @@ -9119,7 +9172,7 @@ phutil_register_library_map(array( 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentFieldType' => 'Phobject', - 'PhabricatorSearchDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorSearchDocumentQuery' => 'PhabricatorPolicyAwareQuery', 'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentTypeDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorSearchEditController' => 'PhabricatorSearchBaseController', @@ -9286,6 +9339,7 @@ phutil_register_library_map(array( 'PhabricatorStorageManagementDatabasesWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementDestroyWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementDumpWorkflow' => 'PhabricatorStorageManagementWorkflow', + 'PhabricatorStorageManagementOptimizeWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementPartitionWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementProbeWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementQuickstartWorkflow' => 'PhabricatorStorageManagementWorkflow', @@ -9759,16 +9813,25 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), + 'PhortuneAccountAddManagerController' => 'PhortuneController', + 'PhortuneAccountBillingController' => 'PhortuneAccountProfileController', 'PhortuneAccountChargeListController' => 'PhortuneController', + 'PhortuneAccountController' => 'PhortuneController', 'PhortuneAccountEditController' => 'PhortuneController', + 'PhortuneAccountEditEngine' => 'PhabricatorEditEngine', 'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneAccountHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhortuneAccountListController' => 'PhortuneController', + 'PhortuneAccountManagerController' => 'PhortuneAccountProfileController', + 'PhortuneAccountNameTransaction' => 'PhortuneAccountTransactionType', 'PhortuneAccountPHIDType' => 'PhabricatorPHIDType', + 'PhortuneAccountProfileController' => 'PhortuneAccountController', 'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', - 'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction', + 'PhortuneAccountSubscriptionController' => 'PhortuneAccountProfileController', + 'PhortuneAccountTransaction' => 'PhabricatorModularTransaction', 'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery', - 'PhortuneAccountViewController' => 'PhortuneController', + 'PhortuneAccountTransactionType' => 'PhabricatorModularTransactionType', + 'PhortuneAccountViewController' => 'PhortuneAccountProfileController', 'PhortuneAdHocCart' => 'PhortuneCartImplementation', 'PhortuneAdHocProduct' => 'PhortuneProductImplementation', 'PhortuneCart' => array( @@ -9816,21 +9879,31 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), + 'PhortuneMerchantAddManagerController' => 'PhortuneController', 'PhortuneMerchantCapability' => 'PhabricatorPolicyCapability', + 'PhortuneMerchantContactInfoTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantController' => 'PhortuneController', + 'PhortuneMerchantDescriptionTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantEditController' => 'PhortuneMerchantController', 'PhortuneMerchantEditEngine' => 'PhabricatorEditEngine', 'PhortuneMerchantEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneMerchantHasMemberEdgeType' => 'PhabricatorEdgeType', - 'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantController', + 'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantProfileController', + 'PhortuneMerchantInvoiceEmailTransaction' => 'PhortuneMerchantTransactionType', + 'PhortuneMerchantInvoiceFooterTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantListController' => 'PhortuneMerchantController', + 'PhortuneMerchantManagerController' => 'PhortuneMerchantProfileController', + 'PhortuneMerchantNameTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantPHIDType' => 'PhabricatorPHIDType', - 'PhortuneMerchantPictureController' => 'PhortuneMerchantController', + 'PhortuneMerchantPictureController' => 'PhortuneMerchantProfileController', + 'PhortuneMerchantPictureTransaction' => 'PhortuneMerchantTransactionType', + 'PhortuneMerchantProfileController' => 'PhortuneController', 'PhortuneMerchantQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneMerchantSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhortuneMerchantTransaction' => 'PhabricatorApplicationTransaction', + 'PhortuneMerchantTransaction' => 'PhabricatorModularTransaction', 'PhortuneMerchantTransactionQuery' => 'PhabricatorApplicationTransactionQuery', - 'PhortuneMerchantViewController' => 'PhortuneMerchantController', + 'PhortuneMerchantTransactionType' => 'PhabricatorModularTransactionType', + 'PhortuneMerchantViewController' => 'PhortuneMerchantProfileController', 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl', 'PhortuneOrderTableView' => 'AphrontView', 'PhortunePayPalPaymentProvider' => 'PhortunePaymentProvider', diff --git a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php index 4d04707598..0962b7b56a 100644 --- a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php +++ b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php @@ -197,9 +197,7 @@ final class PhabricatorAuthSSHKeyEditor // After making any change to an SSH key, drop the authfile cache so it // is regenerated the next time anyone authenticates. - $cache = PhabricatorCaches::getMutableCache(); - $authfile_key = PhabricatorAuthSSHKeyQuery::AUTHFILE_CACHEKEY; - $cache->deleteKey($authfile_key); + PhabricatorAuthSSHKeyQuery::deleteSSHKeyCache(); return $xactions; } diff --git a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php index 6ba047d100..77b666ea44 100644 --- a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php +++ b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php @@ -11,6 +11,12 @@ final class PhabricatorAuthSSHKeyQuery private $keys; private $isActive; + public static function deleteSSHKeyCache() { + $cache = PhabricatorCaches::getMutableCache(); + $authfile_key = self::AUTHFILE_CACHEKEY; + $cache->deleteKey($authfile_key); + } + public function withIDs(array $ids) { $this->ids = $ids; return $this; diff --git a/src/applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php b/src/applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php index 7568f7d8dc..8c619c7fb2 100644 --- a/src/applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php +++ b/src/applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php @@ -58,4 +58,12 @@ final class PhabricatorCalendarEventNotificationView return phabricator_datetime($epoch, $viewer); } + public function getDisplayTimeWithTimezone() { + $viewer = $this->getViewer(); + + $epoch = $this->getEpoch(); + return phabricator_datetimezone($epoch, $viewer); + } + + } diff --git a/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php b/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php index b0a762b577..4ae007fc0c 100644 --- a/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php +++ b/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php @@ -268,7 +268,7 @@ final class PhabricatorCalendarNotificationEngine '%s is starting in %s minute(s), at %s.', $event->getEvent()->getName(), $event->getDisplayMinutes(), - $event->getDisplayTime())); + $event->getDisplayTimeWithTimezone())); $body->addLinkSection( pht('EVENT DETAIL'), diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index bf1d9b43e2..9b7189cfdf 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -226,7 +226,7 @@ final class PhabricatorCalendarEventQuery $set = $event->newRecurrenceSet(); $recurrences = $set->getEventsBetween( - null, + $start_date, $end_date, $limit + 1); diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index e566a77b76..3a09d2e021 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -391,6 +391,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return ($epoch - $window); } + public function getEndDateTimeEpochForCache() { + return $this->getEndDateTimeEpoch(); + } + protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, diff --git a/src/applications/config/custom/PhabricatorConfigRegexOptionType.php b/src/applications/config/custom/PhabricatorConfigRegexOptionType.php new file mode 100644 index 0000000000..4f39543160 --- /dev/null +++ b/src/applications/config/custom/PhabricatorConfigRegexOptionType.php @@ -0,0 +1,18 @@ + $spec) { + $ok = preg_match($pattern, ''); + if ($ok === false) { + throw new Exception( + pht( + 'The following regex is malformed and cannot be used: %s', + $pattern)); + } + } + } + +} diff --git a/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php b/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php index 75d3e8ad6d..27596f7f07 100644 --- a/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php +++ b/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php @@ -120,11 +120,11 @@ final class PhabricatorSyntaxHighlightingConfigOptions 'this is where that list is defined.')), $this->newOption( 'syntax.filemap', - 'wild', + 'custom:PhabricatorConfigRegexOptionType', array( - '@\.arcconfig$@' => 'js', - '@\.arclint$@' => 'js', - '@\.divinerconfig$@' => 'js', + '@\.arcconfig$@' => 'json', + '@\.arclint$@' => 'json', + '@\.divinerconfig$@' => 'json', )) ->setSummary( pht('Override what language files (based on filename) highlight as.')) @@ -138,12 +138,14 @@ final class PhabricatorSyntaxHighlightingConfigOptions 'be tested against the filename. They should map to either an '. 'explicit language as a string value, or a numeric index into '. 'the captured groups as an integer.')) - ->addExample('{"@\\.xyz$@": "php"}', pht('Highlight %s as PHP.', '*.xyz')) ->addExample( - '{"@/httpd\\.conf@": "apacheconf"}', + '{"@\\\.xyz$@": "php"}', + pht('Highlight %s as PHP.', '*.xyz')) + ->addExample( + '{"@/httpd\\\.conf@": "apacheconf"}', pht('Highlight httpd.conf as "apacheconf".')) ->addExample( - '{"@\\.([^.]+)\\.bak$@": 1}', + '{"@\\\.([^.]+)\\\.bak$@": 1}', pht( "Treat all '*.x.bak' file as '.x'. NOTE: We map to capturing group ". "1 by specifying the mapping as '1'")), diff --git a/src/applications/conpherence/ConpherenceTransactionRenderer.php b/src/applications/conpherence/ConpherenceTransactionRenderer.php index 187247e063..5a6eaffaf2 100644 --- a/src/applications/conpherence/ConpherenceTransactionRenderer.php +++ b/src/applications/conpherence/ConpherenceTransactionRenderer.php @@ -60,7 +60,8 @@ final class ConpherenceTransactionRenderer extends Phobject { // between days. some setup required! $previous_transaction = null; $date_marker_transaction = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_DATE_MARKER) + ->setTransactionType( + ConpherenceThreadDateMarkerTransaction::TRANSACTIONTYPE) ->makeEphemeral(); $date_marker_transaction_view = id(new ConpherenceTransactionView()) ->setUser($user) diff --git a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php index f34dd25110..85c4b9c1f1 100644 --- a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php +++ b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php @@ -16,9 +16,6 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { $this->assertTrue((bool)$conpherence->getID()); $this->assertEqual(1, count($conpherence->getParticipants())); - $this->assertEqual( - $participant_phids, - $conpherence->getRecentParticipantPHIDs()); } public function testNUserRoomCreate() { @@ -38,9 +35,6 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { $this->assertTrue((bool)$conpherence->getID()); $this->assertEqual(4, count($conpherence->getParticipants())); - $this->assertEqual( - $participant_phids, - $conpherence->getRecentParticipantPHIDs()); } public function testRoomParticipantAddition() { @@ -58,16 +52,11 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { $this->assertTrue((bool)$conpherence->getID()); $this->assertEqual(2, count($conpherence->getParticipants())); - $this->assertEqual( - $participant_phids, - $conpherence->getRecentParticipantPHIDs()); // test add by creator $participant_phids[] = $friend_2->getPHID(); $this->addParticipants($creator, $conpherence, array($friend_2->getPHID())); - $this->assertEqual( - $participant_phids, - $conpherence->getRecentParticipantPHIDs()); + $this->assertEqual(3, count($conpherence->getParticipants())); // test add by other participant, so recent participation should // meaningfully change @@ -81,9 +70,7 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { $friend_2, $conpherence, array($friend_3->getPHID())); - $this->assertEqual( - $participant_phids, - $conpherence->getRecentParticipantPHIDs()); + $this->assertEqual(4, count($conpherence->getParticipants())); } public function testRoomParticipantDeletion() { @@ -123,7 +110,8 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('+' => $participant_phids)); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) + ->setTransactionType( + ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) ->setNewValue(pht('Test')); id(new ConpherenceEditor()) diff --git a/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php index c66a90f585..fd602058a3 100644 --- a/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php +++ b/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php @@ -71,7 +71,6 @@ final class ConpherenceQueryThreadConduitAPIMethod 'conpherencePHID' => $conpherence->getPHID(), 'conpherenceTitle' => $conpherence->getTitle(), 'messageCount' => $conpherence->getMessageCount(), - 'recentParticipantPHIDs' => $conpherence->getRecentParticipantPHIDs(), 'conpherenceURI' => $this->getConpherenceURI($conpherence), ); } diff --git a/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php index 01b86f9a42..c92cc464ed 100644 --- a/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php +++ b/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php @@ -83,7 +83,8 @@ final class ConpherenceUpdateThreadConduitAPIMethod } if ($title) { $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) + ->setTransactionType( + ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); } if ($message) { diff --git a/src/applications/conpherence/controller/ConpherenceColumnViewController.php b/src/applications/conpherence/controller/ConpherenceColumnViewController.php index de83379aab..aa4f94edfc 100644 --- a/src/applications/conpherence/controller/ConpherenceColumnViewController.php +++ b/src/applications/conpherence/controller/ConpherenceColumnViewController.php @@ -17,7 +17,6 @@ final class ConpherenceColumnViewController extends ->setViewer($user) ->withPHIDs($conpherence_phids) ->needProfileImage(true) - ->needParticipantCache(true) ->execute(); $latest_conpherences = mpull($latest_conpherences, null, 'getPHID'); $latest_conpherences = array_select_keys( diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index e6690a55d1..09708b7148 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -78,10 +78,6 @@ abstract class ConpherenceController extends PhabricatorController { } $participating = $conpherence->getParticipantIfExists($viewer->getPHID()); - $can_join = PhabricatorPolicyFilter::hasCapability( - $viewer, - $conpherence, - PhabricatorPolicyCapability::CAN_JOIN); $header->addActionItem( id(new PHUIIconCircleView()) @@ -129,7 +125,7 @@ abstract class ConpherenceController extends PhabricatorController { ->setColor('green') ->addClass('conpherence-search-toggle')); - if ($can_join && !$participating) { + if (!$participating) { $action = ConpherenceUpdateActions::JOIN_ROOM; $uri = $this->getApplicationURI('update/'.$conpherence->getID().'/'); $button = phutil_tag( diff --git a/src/applications/conpherence/controller/ConpherenceListController.php b/src/applications/conpherence/controller/ConpherenceListController.php index 4e81526004..00968af576 100644 --- a/src/applications/conpherence/controller/ConpherenceListController.php +++ b/src/applications/conpherence/controller/ConpherenceListController.php @@ -159,7 +159,6 @@ final class ConpherenceListController extends ConpherenceController { ->setViewer($user) ->withPHIDs($conpherence_phids) ->needProfileImage(true) - ->needParticipantCache(true) ->execute(); // this will re-sort by participation data diff --git a/src/applications/conpherence/controller/ConpherenceNewRoomController.php b/src/applications/conpherence/controller/ConpherenceNewRoomController.php index 25ae99a468..d70395dbc9 100644 --- a/src/applications/conpherence/controller/ConpherenceNewRoomController.php +++ b/src/applications/conpherence/controller/ConpherenceNewRoomController.php @@ -7,7 +7,6 @@ final class ConpherenceNewRoomController extends ConpherenceController { $title = pht('New Room'); $e_title = true; - $v_message = null; $validation_exception = null; $conpherence = ConpherenceThread::initializeNewRoom($user); @@ -17,7 +16,7 @@ final class ConpherenceNewRoomController extends ConpherenceController { $xactions = array(); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) + ->setTransactionType(ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) ->setNewValue($request->getStr('title')); $participants = $request->getArr('participants'); @@ -27,7 +26,7 @@ final class ConpherenceNewRoomController extends ConpherenceController { ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('+' => $participants)); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TOPIC) + ->setTransactionType(ConpherenceThreadTopicTransaction::TRANSACTIONTYPE) ->setNewValue($request->getStr('topic')); $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) @@ -35,18 +34,6 @@ final class ConpherenceNewRoomController extends ConpherenceController { $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) ->setNewValue($request->getStr('editPolicy')); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_JOIN_POLICY) - ->setNewValue($request->getStr('joinPolicy')); - - $v_message = $request->getStr('message'); - if (strlen($v_message)) { - $message_xactions = $editor->generateTransactionsFromText( - $user, - $conpherence, - $v_message); - $xactions = array_merge($xactions, $message_xactions); - } try { $editor @@ -60,11 +47,11 @@ final class ConpherenceNewRoomController extends ConpherenceController { } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; - $e_title = $ex->getShortMessage(ConpherenceTransaction::TYPE_TITLE); + $e_title = $ex->getShortMessage( + ConpherenceThreadTitleTransaction::TRANSACTIONTYPE); $conpherence->setViewPolicy($request->getStr('viewPolicy')); $conpherence->setEditPolicy($request->getStr('editPolicy')); - $conpherence->setJoinPolicy($request->getStr('joinPolicy')); } } else { if ($request->getStr('participant')) { @@ -119,19 +106,7 @@ final class ConpherenceNewRoomController extends ConpherenceController { ->setName('editPolicy') ->setPolicyObject($conpherence) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('joinPolicy') - ->setPolicyObject($conpherence) - ->setCapability(PhabricatorPolicyCapability::CAN_JOIN) - ->setPolicies($policies)) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($user) - ->setName('message') - ->setLabel(pht('First Message')) - ->setValue($v_message)); + ->setPolicies($policies)); $dialog->appendChild($form); diff --git a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php index 03f9c926ca..ed728c6ab7 100644 --- a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php +++ b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php @@ -20,8 +20,7 @@ final class ConpherenceNotificationPanelController ->withPHIDs(array_keys($participant_data)) ->needProfileImage(true) ->needTransactions(true) - ->setTransactionLimit(50) - ->needParticipantCache(true) + ->setTransactionLimit(100) ->execute(); } diff --git a/src/applications/conpherence/controller/ConpherenceRoomPictureController.php b/src/applications/conpherence/controller/ConpherenceRoomPictureController.php index 8ddeae7098..6a39481377 100644 --- a/src/applications/conpherence/controller/ConpherenceRoomPictureController.php +++ b/src/applications/conpherence/controller/ConpherenceRoomPictureController.php @@ -76,7 +76,8 @@ final class ConpherenceRoomPictureController $xactions = array(); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_PICTURE) + ->setTransactionType( + ConpherenceThreadPictureTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new ConpherenceEditor()) diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 724b802566..9cc666109c 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -24,9 +24,6 @@ final class ConpherenceUpdateController case ConpherenceUpdateActions::METADATA: $needed_capabilities[] = PhabricatorPolicyCapability::CAN_EDIT; break; - case ConpherenceUpdateActions::JOIN_ROOM: - $needed_capabilities[] = PhabricatorPolicyCapability::CAN_JOIN; - break; case ConpherenceUpdateActions::NOTIFICATIONS: $need_participants = true; break; @@ -140,10 +137,12 @@ final class ConpherenceUpdateController $title = $request->getStr('title'); $topic = $request->getStr('topic'); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) + ->setTransactionType( + ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TOPIC) + ->setTransactionType( + ConpherenceThreadTopicTransaction::TRANSACTIONTYPE) ->setNewValue($topic); $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) @@ -151,9 +150,6 @@ final class ConpherenceUpdateController $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) ->setNewValue($request->getStr('editPolicy')); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_JOIN_POLICY) - ->setNewValue($request->getStr('joinPolicy')); if (!$request->getExists('force_ajax')) { $response_mode = 'redirect'; } @@ -254,16 +250,9 @@ final class ConpherenceUpdateController $participant = $conpherence->getParticipantIfExists($user->getPHID()); if (!$participant) { - $can_join = PhabricatorPolicyFilter::hasCapability( - $user, - $conpherence, - PhabricatorPolicyCapability::CAN_JOIN); - if ($can_join) { + if ($user->isLoggedIn()) { $text = pht( 'Notification settings are available after joining the room.'); - } else if ($user->isLoggedIn()) { - $text = pht( - 'Notification settings not applicable to rooms you can not join.'); } else { $text = pht( 'Notification settings are available after logging in and joining '. @@ -457,12 +446,6 @@ final class ConpherenceUpdateController ->setName('editPolicy') ->setPolicyObject($conpherence) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('joinPolicy') - ->setPolicyObject($conpherence) - ->setCapability(PhabricatorPolicyCapability::CAN_JOIN) ->setPolicies($policies)); $view = id(new AphrontDialogView()) @@ -487,7 +470,6 @@ final class ConpherenceUpdateController $latest_transaction_id) { $need_transactions = false; - $need_participant_cache = true; switch ($action) { case ConpherenceUpdateActions::METADATA: case ConpherenceUpdateActions::LOAD: @@ -508,7 +490,6 @@ final class ConpherenceUpdateController ->setViewer($user) ->setAfterTransactionID($latest_transaction_id) ->needProfileImage(true) - ->needParticipantCache($need_participant_cache) ->needParticipants(true) ->needTransactions($need_transactions) ->withIDs(array($conpherence_id)) diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 8d93f03b7d..6b953b9de4 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -20,7 +20,6 @@ final class ConpherenceViewController extends ->setViewer($user) ->withIDs(array($conpherence_id)) ->needProfileImage(true) - ->needParticipantCache(true) ->needTransactions(true) ->setTransactionLimit($this->getMainQueryLimit()); @@ -119,11 +118,6 @@ final class ConpherenceViewController extends return id(new AphrontAjaxResponse())->setContent($content); } - $can_join = PhabricatorPolicyFilter::hasCapability( - $user, - $conpherence, - PhabricatorPolicyCapability::CAN_JOIN); - $layout = id(new ConpherenceLayoutView()) ->setUser($user) ->setBaseURI($this->getApplicationURI()) @@ -151,12 +145,9 @@ final class ConpherenceViewController extends $conpherence = $this->getConpherence(); $user = $this->getRequest()->getUser(); - $can_join = PhabricatorPolicyFilter::hasCapability( - $user, - $conpherence, - PhabricatorPolicyCapability::CAN_JOIN); + $participating = $conpherence->getParticipantIfExists($user->getPHID()); - if (!$can_join && !$participating && $user->isLoggedIn()) { + if (!$participating && $user->isLoggedIn()) { return null; } $draft = PhabricatorDraft::newFromUserAndKey( @@ -184,6 +175,7 @@ final class ConpherenceViewController extends id(new PhabricatorRemarkupControl()) ->setUser($user) ->setName('text') + ->setSendOnEnter(true) ->setValue($draft->getDraft())); $status_view = phutil_tag( diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 2a57711840..e358700348 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -41,12 +41,14 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { ->setNewValue(array('+' => $participant_phids)); if ($title) { $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) + ->setTransactionType( + ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); } if (strlen($topic)) { $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TOPIC) + ->setTransactionType( + ConpherenceThreadTopicTransaction::TRANSACTIONTYPE) ->setNewValue($topic); } @@ -85,30 +87,24 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorTransactions::TYPE_COMMENT; - - $types[] = ConpherenceTransaction::TYPE_TITLE; - $types[] = ConpherenceTransaction::TYPE_TOPIC; $types[] = ConpherenceTransaction::TYPE_PARTICIPANTS; - $types[] = ConpherenceTransaction::TYPE_PICTURE; + + $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; return $types; } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this room.', $author); + } + protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_TITLE: - return $object->getTitle(); - case ConpherenceTransaction::TYPE_TOPIC: - return $object->getTopic(); - case ConpherenceTransaction::TYPE_PICTURE: - return $object->getProfileImagePHID(); case ConpherenceTransaction::TYPE_PARTICIPANTS: if ($this->getIsNewObject()) { return array(); @@ -122,10 +118,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_TITLE: - case ConpherenceTransaction::TYPE_TOPIC: - case ConpherenceTransaction::TYPE_PICTURE: - return $xaction->getNewValue(); case ConpherenceTransaction::TYPE_PARTICIPANTS: return $this->getPHIDTransactionNewValue($xaction); } @@ -198,7 +190,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { ->setSeenMessageCount($message_count) ->save(); $object->attachParticipants($participants); - $object->setRecentParticipantPHIDs(array_keys($participants)); } break; } @@ -209,48 +200,12 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { - $make_author_recent_participant = true; switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_TITLE: - $object->setTitle($xaction->getNewValue()); - break; - case ConpherenceTransaction::TYPE_TOPIC: - $object->setTopic($xaction->getNewValue()); - break; - case ConpherenceTransaction::TYPE_PICTURE: - $object->setProfileImagePHID($xaction->getNewValue()); - break; case ConpherenceTransaction::TYPE_PARTICIPANTS: - if (!$this->getIsNewObject()) { - $old_map = array_fuse($xaction->getOldValue()); - $new_map = array_fuse($xaction->getNewValue()); - // if we added people, add them to the end of "recent" participants - $add = array_keys(array_diff_key($new_map, $old_map)); - // if we remove people, then definintely remove them from "recent" - // participants - $del = array_keys(array_diff_key($old_map, $new_map)); - if ($add || $del) { - $participants = $object->getRecentParticipantPHIDs(); - if ($add) { - $participants = array_merge($participants, $add); - } - if ($del) { - $participants = array_diff($participants, $del); - $actor = $this->requireActor(); - if (in_array($actor->getPHID(), $del)) { - $make_author_recent_participant = false; - } - } - $participants = array_slice(array_unique($participants), 0, 10); - $object->setRecentParticipantPHIDs($participants); - } - } + if (!$this->getIsNewObject()) {} break; } - if ($make_author_recent_participant) { - $this->makeAuthorMostRecentParticipant($object, $xaction); - } } protected function applyBuiltinInternalTransaction( @@ -266,17 +221,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return parent::applyBuiltinInternalTransaction($object, $xaction); } - private function makeAuthorMostRecentParticipant( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $participants = $object->getRecentParticipantPHIDs(); - array_unshift($participants, $xaction->getAuthorPHID()); - $participants = array_slice(array_unique($participants), 0, 10); - - $object->setRecentParticipantPHIDs($participants); - } - protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { @@ -335,40 +279,40 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $message_count++; + + // update everyone's participation status on a message -only- + $xaction_phid = $xaction->getPHID(); + $behind = ConpherenceParticipationStatus::BEHIND; + $up_to_date = ConpherenceParticipationStatus::UP_TO_DATE; + $participants = $object->getParticipants(); + $user = $this->getActor(); + $time = time(); + foreach ($participants as $phid => $participant) { + if ($phid != $user->getPHID()) { + if ($participant->getParticipationStatus() != $behind) { + $participant->setBehindTransactionPHID($xaction_phid); + $participant->setSeenMessageCount( + $object->getMessageCount() - $message_count); + } + $participant->setParticipationStatus($behind); + $participant->setDateTouched($time); + } else { + $participant->setSeenMessageCount($object->getMessageCount()); + $participant->setBehindTransactionPHID($xaction_phid); + $participant->setParticipationStatus($up_to_date); + $participant->setDateTouched($time); + } + $participant->save(); + } + + PhabricatorUserCache::clearCaches( + PhabricatorUserMessageCountCacheType::KEY_COUNT, + array_keys($participants)); + break; } } - // update everyone's participation status on the last xaction -only- - $xaction = end($xactions); - $xaction_phid = $xaction->getPHID(); - $behind = ConpherenceParticipationStatus::BEHIND; - $up_to_date = ConpherenceParticipationStatus::UP_TO_DATE; - $participants = $object->getParticipants(); - $user = $this->getActor(); - $time = time(); - foreach ($participants as $phid => $participant) { - if ($phid != $user->getPHID()) { - if ($participant->getParticipationStatus() != $behind) { - $participant->setBehindTransactionPHID($xaction_phid); - $participant->setSeenMessageCount( - $object->getMessageCount() - $message_count); - } - $participant->setParticipationStatus($behind); - $participant->setDateTouched($time); - } else { - $participant->setSeenMessageCount($object->getMessageCount()); - $participant->setBehindTransactionPHID($xaction_phid); - $participant->setParticipationStatus($up_to_date); - $participant->setDateTouched($time); - } - $participant->save(); - } - - PhabricatorUserCache::clearCaches( - PhabricatorUserMessageCountCacheType::KEY_COUNT, - array_keys($participants)); - if ($xactions) { $data = array( 'type' => 'message', @@ -399,27 +343,15 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $actor_phid = $this->requireActor()->getPHID(); - $is_join = (($add === array($actor_phid)) && !$rem); - $is_leave = (($rem === array($actor_phid)) && !$add); + // You need CAN_EDIT to change participants other than yourself. + PhabricatorPolicyFilter::requireCapability( + $this->requireActor(), + $object, + PhabricatorPolicyCapability::CAN_EDIT); - if ($is_join) { - // You need CAN_JOIN to join a room. - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_JOIN); - } else if ($is_leave) { - // You don't need any capabilities to leave a conpherence thread. - } else { - // You need CAN_EDIT to change participants other than yourself. - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_EDIT); - } break; - case ConpherenceTransaction::TYPE_TITLE: - case ConpherenceTransaction::TYPE_TOPIC: + case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE: + case ConpherenceThreadTopicTransaction::TRANSACTIONTYPE: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, @@ -434,8 +366,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $type = $u->getTransactionType(); switch ($type) { - case ConpherenceTransaction::TYPE_TITLE: - return $v; case ConpherenceTransaction::TYPE_PARTICIPANTS: return $this->mergePHIDOrEdgeTransactions($u, $v); } @@ -541,23 +471,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return PhabricatorEnv::getEnvConfig('metamta.conpherence.subject-prefix'); } - protected function shouldPublishFeedStory( - PhabricatorLiskDAO $object, - array $xactions) { - - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_TITLE: - case ConpherenceTransaction::TYPE_TOPIC: - case ConpherenceTransaction::TYPE_PICTURE: - return true; - default: - return false; - } - } - return false; - } - protected function supportsSearch() { return true; } @@ -570,26 +483,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { - case ConpherenceTransaction::TYPE_TITLE: - if (empty($xactions)) { - break; - } - $missing = $this->validateIsEmptyTextField( - $object->getTitle(), - $xactions); - - if ($missing) { - $detail = pht('Room title is required.'); - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - $detail, - last($xactions)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; case ConpherenceTransaction::TYPE_PARTICIPANTS: foreach ($xactions as $xaction) { $new_phids = $this->getPHIDTransactionNewValue($xaction, array()); diff --git a/src/applications/conpherence/query/ConpherenceFulltextQuery.php b/src/applications/conpherence/query/ConpherenceFulltextQuery.php index 43de7455ae..ba734049f8 100644 --- a/src/applications/conpherence/query/ConpherenceFulltextQuery.php +++ b/src/applications/conpherence/query/ConpherenceFulltextQuery.php @@ -56,9 +56,9 @@ final class ConpherenceFulltextQuery } if (strlen($this->fulltext)) { - $compiled_query = PhabricatorSearchDocument::newQueryCompiler() - ->setQuery($this->fulltext) - ->compileQuery(); + $compiler = PhabricatorSearchDocument::newQueryCompiler(); + $tokens = $compiler->newTokens($this->fulltext); + $compiled_query = $compiler->compileQuery($tokens); $where[] = qsprintf( $conn_r, diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index 48dc3323e0..1bb96537dc 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -10,18 +10,12 @@ final class ConpherenceThreadQuery private $participantPHIDs; private $needParticipants; private $needTransactions; - private $needParticipantCache; private $afterTransactionID; private $beforeTransactionID; private $transactionLimit; private $fulltext; private $needProfileImage; - public function needParticipantCache($participant_cache) { - $this->needParticipantCache = $participant_cache; - return $this; - } - public function needParticipants($need) { $this->needParticipants = $need; return $this; @@ -101,9 +95,6 @@ final class ConpherenceThreadQuery if ($conpherences) { $conpherences = mpull($conpherences, null, 'getPHID'); $this->loadParticipantsAndInitHandles($conpherences); - if ($this->needParticipantCache) { - $this->loadCoreHandles($conpherences, 'getRecentParticipantPHIDs'); - } if ($this->needParticipants) { $this->loadCoreHandles($conpherences, 'getParticipantPHIDs'); } diff --git a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php index 4e0a89d266..cbaf43b0a9 100644 --- a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php +++ b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php @@ -13,7 +13,6 @@ final class ConpherenceThreadSearchEngine public function newQuery() { return id(new ConpherenceThreadQuery()) - ->needParticipantCache(true) ->needProfileImage(true); } @@ -92,14 +91,6 @@ final class ConpherenceThreadSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $conpherences, - PhabricatorSavedQuery $query) { - - $recent = mpull($conpherences, 'getRecentParticipantPHIDs'); - return array_unique(array_mergev($recent)); - } - protected function renderResultList( array $conpherences, PhabricatorSavedQuery $query, @@ -153,7 +144,7 @@ final class ConpherenceThreadSearchEngine $list->setUser($viewer); foreach ($conpherences as $conpherence_phid => $conpherence) { $created = phabricator_date($conpherence->getDateCreated(), $viewer); - $title = $conpherence->getDisplayTitle($viewer); + $title = $conpherence->getTitle(); $monogram = $conpherence->getMonogram(); $icon_name = $conpherence->getPolicyIconName($policy_objects); diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index e912f52591..f0d0e60bb3 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -12,7 +12,6 @@ final class ConpherenceThread extends ConpherenceDAO protected $topic; protected $profileImagePHID; protected $messageCount; - protected $recentParticipantPHIDs = array(); protected $mailKey; protected $viewPolicy; protected $editPolicy; @@ -33,15 +32,12 @@ final class ConpherenceThread extends ConpherenceDAO ->attachParticipants(array()) ->setViewPolicy($default_policy) ->setEditPolicy($default_policy) - ->setJoinPolicy($default_policy); + ->setJoinPolicy(''); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, - self::CONFIG_SERIALIZATION => array( - 'recentParticipantPHIDs' => self::SERIALIZATION_JSON, - ), self::CONFIG_COLUMN_SCHEMA => array( 'title' => 'text255?', 'topic' => 'text255', @@ -165,72 +161,6 @@ final class ConpherenceThread extends ConpherenceDAO return pht('Private Room'); } - /** - * Get the thread's display title for a user. - * - * If a thread doesn't have a title set, this will return a string describing - * recent participants. - * - * @param PhabricatorUser Viewer. - * @return string Thread title. - */ - public function getDisplayTitle(PhabricatorUser $viewer) { - $title = $this->getTitle(); - if (strlen($title)) { - return $title; - } - - return $this->getRecentParticipantsString($viewer); - } - - - /** - * Get recent participants (other than the viewer) as a string. - * - * For example, this method might return "alincoln, htaft, gwashington...". - * - * @param PhabricatorUser Viewer. - * @return string Description of other participants. - */ - private function getRecentParticipantsString(PhabricatorUser $viewer) { - $handles = $this->getHandles(); - $phids = $this->getOtherRecentParticipantPHIDs($viewer); - - if (count($phids) == 0) { - $phids[] = $viewer->getPHID(); - $more = false; - } else { - $limit = 3; - $more = (count($phids) > $limit); - $phids = array_slice($phids, 0, $limit); - } - - $names = array_select_keys($handles, $phids); - $names = mpull($names, 'getName'); - $names = implode(', ', $names); - - if ($more) { - $names = $names.'...'; - } - - return $names; - } - - - /** - * Get PHIDs for recent participants who are not the viewer. - * - * @param PhabricatorUser Viewer. - * @return list Participants who are not the viewer. - */ - private function getOtherRecentParticipantPHIDs(PhabricatorUser $viewer) { - $phids = $this->getRecentParticipantPHIDs(); - $phids = array_fuse($phids); - unset($phids[$viewer->getPHID()]); - return array_values($phids); - } - - public function getDisplayData(PhabricatorUser $viewer) { $handles = $this->getHandles(); @@ -240,71 +170,33 @@ final class ConpherenceThread extends ConpherenceDAO $transactions = array(); } - if ($transactions) { - $subtitle_mode = 'message'; - } else { - $subtitle_mode = 'recent'; - } - - $lucky_phid = head($this->getOtherRecentParticipantPHIDs($viewer)); - if ($lucky_phid) { - $lucky_handle = $handles[$lucky_phid]; - } else { - // This will be just the user talking to themselves. Weirdo. - $lucky_handle = reset($handles); - } - $img_src = $this->getProfileImageURI(); - $message_title = null; - if ($subtitle_mode == 'message') { - $message_transaction = null; - $action_transaction = null; - foreach ($transactions as $transaction) { - if ($message_transaction || $action_transaction) { - break; - } - switch ($transaction->getTransactionType()) { - case PhabricatorTransactions::TYPE_COMMENT: - $message_transaction = $transaction; - break; - case ConpherenceTransaction::TYPE_TITLE: - case ConpherenceTransaction::TYPE_TOPIC: - case ConpherenceTransaction::TYPE_PICTURE: - case ConpherenceTransaction::TYPE_PARTICIPANTS: - $action_transaction = $transaction; - break; - default: - break; - } - } + $message_transaction = null; + foreach ($transactions as $transaction) { if ($message_transaction) { - $message_handle = $handles[$message_transaction->getAuthorPHID()]; - $message_title = sprintf( - '%s: %s', - $message_handle->getName(), - id(new PhutilUTF8StringTruncator()) - ->setMaximumGlyphs(60) - ->truncateString( - $message_transaction->getComment()->getContent())); + break; } - if ($action_transaction) { - $message_title = id(clone $action_transaction) - ->setRenderingTarget(PhabricatorApplicationTransaction::TARGET_TEXT) - ->getTitle(); + switch ($transaction->getTransactionType()) { + case PhabricatorTransactions::TYPE_COMMENT: + $message_transaction = $transaction; + break; + default: + break; } } - switch ($subtitle_mode) { - case 'recent': - $subtitle = $this->getRecentParticipantsString($viewer); - break; - case 'message': - if ($message_title) { - $subtitle = $message_title; - } else { - $subtitle = $this->getRecentParticipantsString($viewer); - } - break; + if ($message_transaction) { + $message_handle = $handles[$message_transaction->getAuthorPHID()]; + $subtitle = sprintf( + '%s: %s', + $message_handle->getName(), + id(new PhutilUTF8StringTruncator()) + ->setMaximumGlyphs(60) + ->truncateString( + $message_transaction->getComment()->getContent())); + } else { + // Kinda lame, but maybe add last message to cache? + $subtitle = pht('No recent messages'); } $user_participation = $this->getParticipantIfExists($viewer->getPHID()); @@ -315,7 +207,7 @@ final class ConpherenceThread extends ConpherenceDAO } $unread_count = $this->getMessageCount() - $user_seen_count; - $title = $this->getDisplayTitle($viewer); + $title = $this->getTitle(); $topic = $this->getTopic(); return array( @@ -336,7 +228,6 @@ final class ConpherenceThread extends ConpherenceDAO return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, - PhabricatorPolicyCapability::CAN_JOIN, ); } @@ -346,8 +237,6 @@ final class ConpherenceThread extends ConpherenceDAO return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); - case PhabricatorPolicyCapability::CAN_JOIN: - return $this->getJoinPolicy(); } return PhabricatorPolicies::POLICY_NOONE; } @@ -360,7 +249,6 @@ final class ConpherenceThread extends ConpherenceDAO switch ($capability) { case PhabricatorPolicyCapability::CAN_EDIT: - case PhabricatorPolicyCapability::CAN_JOIN: return false; } diff --git a/src/applications/conpherence/storage/ConpherenceTransaction.php b/src/applications/conpherence/storage/ConpherenceTransaction.php index b311c80ca5..2fb3fca380 100644 --- a/src/applications/conpherence/storage/ConpherenceTransaction.php +++ b/src/applications/conpherence/storage/ConpherenceTransaction.php @@ -1,13 +1,9 @@ getTransactionType()) { case self::TYPE_PARTICIPANTS: @@ -38,13 +38,6 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { switch ($this->getTransactionType()) { case self::TYPE_PARTICIPANTS: return ($old === null); - case self::TYPE_TITLE: - case self::TYPE_TOPIC: - case self::TYPE_PICTURE: - case self::TYPE_DATE_MARKER: - return false; - case self::TYPE_PICTURE_CROP: - return true; } return parent::shouldHide(); @@ -57,14 +50,6 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - case self::TYPE_TOPIC: - case PhabricatorTransactions::TYPE_VIEW_POLICY: - case PhabricatorTransactions::TYPE_EDIT_POLICY: - case PhabricatorTransactions::TYPE_JOIN_POLICY: - case self::TYPE_PICTURE: - return $this->getRoomTitle(); - break; case self::TYPE_PARTICIPANTS: $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -97,125 +82,6 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { return parent::getTitle(); } - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_TITLE: - if (strlen($old) && strlen($new)) { - return pht( - '%s renamed %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); - } else { - return pht( - '%s created the room %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - break; - case self::TYPE_TOPIC: - if (strlen($new)) { - return pht( - '%s set the topic of %s to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new); - } else if (strlen($old)) { - return pht( - '%s deleted the topic in %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_PICTURE: - return pht( - '%s updated the room image for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - } - return parent::getTitleForFeed(); - } - - private function getRoomTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old && $new) { - $title = pht( - '%s renamed this room from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } else if ($old) { - $title = pht( - '%s deleted the room name "%s".', - $this->renderHandleLink($author_phid), - $old); - } else { - $title = pht( - '%s named this room "%s".', - $this->renderHandleLink($author_phid), - $new); - } - return $title; - break; - case self::TYPE_TOPIC: - if ($new) { - $title = pht( - '%s set the topic of this room to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else if ($old) { - $title = pht( - '%s deleted the room topic "%s"', - $this->renderHandleLink($author_phid), - $old); - } - return $title; - break; - case self::TYPE_PICTURE: - return pht( - '%s updated the room image.', - $this->renderHandleLink($author_phid)); - break; - case PhabricatorTransactions::TYPE_VIEW_POLICY: - return pht( - '%s changed the visibility of this room from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderPolicyName($old, 'old'), - $this->renderPolicyName($new, 'new')); - break; - case PhabricatorTransactions::TYPE_EDIT_POLICY: - return pht( - '%s changed the edit policy of this room from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderPolicyName($old, 'old'), - $this->renderPolicyName($new, 'new')); - break; - case PhabricatorTransactions::TYPE_JOIN_POLICY: - return pht( - '%s changed the join policy of this room from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderPolicyName($old, 'old'), - $this->renderPolicyName($new, 'new')); - break; - } - } - public function getRequiredHandlePHIDs() { $phids = parent::getRequiredHandlePHIDs(); @@ -224,10 +90,6 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { $phids[] = $this->getAuthorPHID(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - case self::TYPE_PICTURE: - case self::TYPE_DATE_MARKER: - break; case self::TYPE_PARTICIPANTS: $phids = array_merge($phids, $this->getOldValue()); $phids = array_merge($phids, $this->getNewValue()); diff --git a/src/applications/conpherence/view/ConpherenceTransactionView.php b/src/applications/conpherence/view/ConpherenceTransactionView.php index c18a672ca2..d976fb604f 100644 --- a/src/applications/conpherence/view/ConpherenceTransactionView.php +++ b/src/applications/conpherence/view/ConpherenceTransactionView.php @@ -67,7 +67,7 @@ final class ConpherenceTransactionView extends AphrontView { $transaction = $this->getConpherenceTransaction(); switch ($transaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_DATE_MARKER: + case ConpherenceThreadDateMarkerTransaction::TRANSACTIONTYPE: return javelin_tag( 'div', array( @@ -216,17 +216,6 @@ final class ConpherenceTransactionView extends AphrontView { $content = null; $handles = $this->getHandles(); switch ($transaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_TITLE: - case ConpherenceTransaction::TYPE_TOPIC: - case ConpherenceTransaction::TYPE_PICTURE: - case ConpherenceTransaction::TYPE_PARTICIPANTS: - case PhabricatorTransactions::TYPE_VIEW_POLICY: - case PhabricatorTransactions::TYPE_EDIT_POLICY: - case PhabricatorTransactions::TYPE_JOIN_POLICY: - case PhabricatorTransactions::TYPE_EDGE: - $content = $transaction->getTitle(); - $this->addClass('conpherence-edited'); - break; case PhabricatorTransactions::TYPE_COMMENT: $this->addClass('conpherence-comment'); $author = $handles[$transaction->getAuthorPHID()]; @@ -236,6 +225,10 @@ final class ConpherenceTransactionView extends AphrontView { PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); $content_class = 'conpherence-message'; break; + default: + $content = $transaction->getTitle(); + $this->addClass('conpherence-edited'); + break; } $view = phutil_tag( diff --git a/src/applications/conpherence/xaction/ConpherenceThreadDateMarkerTransaction.php b/src/applications/conpherence/xaction/ConpherenceThreadDateMarkerTransaction.php new file mode 100644 index 0000000000..3a7819ba4e --- /dev/null +++ b/src/applications/conpherence/xaction/ConpherenceThreadDateMarkerTransaction.php @@ -0,0 +1,8 @@ +getProfileImagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setProfileImagePHID($value); + } + + public function getTitle() { + return pht( + '%s updated the room image.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the room image for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + +} diff --git a/src/applications/conpherence/xaction/ConpherenceThreadTitleTransaction.php b/src/applications/conpherence/xaction/ConpherenceThreadTitleTransaction.php new file mode 100644 index 0000000000..130a841a23 --- /dev/null +++ b/src/applications/conpherence/xaction/ConpherenceThreadTitleTransaction.php @@ -0,0 +1,75 @@ +getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setTitle($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($old) && strlen($new)) { + return pht( + '%s renamed this room from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } else { + return pht( + '%s created this room.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($old) && strlen($new)) { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } else { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Rooms must have a title.')); + } + + $max_length = $object->getColumnMaximumByteLength('title'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The title can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/conpherence/xaction/ConpherenceThreadTopicTransaction.php b/src/applications/conpherence/xaction/ConpherenceThreadTopicTransaction.php new file mode 100644 index 0000000000..23c590f409 --- /dev/null +++ b/src/applications/conpherence/xaction/ConpherenceThreadTopicTransaction.php @@ -0,0 +1,69 @@ +getTopic(); + } + + public function applyInternalEffects($object, $value) { + $object->setTopic($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($new)) { + return pht( + '%s set the room topic to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s removed the room topic.', + $this->renderAuthor()); + } + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($new)) { + return pht( + '%s set the room topic to %s in %s.', + $this->renderAuthor(), + $this->renderNewValue(), + $this->renderObject()); + } else { + return pht( + '%s removed the room topic for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('topic'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The topic can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/conpherence/xaction/ConpherenceThreadTransactionType.php b/src/applications/conpherence/xaction/ConpherenceThreadTransactionType.php new file mode 100644 index 0000000000..82f4f63d4e --- /dev/null +++ b/src/applications/conpherence/xaction/ConpherenceThreadTransactionType.php @@ -0,0 +1,4 @@ + array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorCountdownListController', - '(?P[1-9]\d*)/' - => 'PhabricatorCountdownViewController', 'comment/(?P[1-9]\d*)/' => 'PhabricatorCountdownCommentController', $this->getEditRoutePattern('edit/') => 'PhabricatorCountdownEditController', - 'delete/(?P[1-9]\d*)/' - => 'PhabricatorCountdownDeleteController', ), ); } diff --git a/src/applications/countdown/conduit/CountdownEditConduitAPIMethod.php b/src/applications/countdown/conduit/CountdownEditConduitAPIMethod.php new file mode 100644 index 0000000000..f4b39d0dd5 --- /dev/null +++ b/src/applications/countdown/conduit/CountdownEditConduitAPIMethod.php @@ -0,0 +1,18 @@ +newApplicationMenu() ->setSearchEngine(new PhabricatorCountdownSearchEngine()); } - - } diff --git a/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php b/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php deleted file mode 100644 index 81129f3beb..0000000000 --- a/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php +++ /dev/null @@ -1,45 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $countdown = id(new PhabricatorCountdownQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - - if (!$countdown) { - return new Aphront404Response(); - } - - if ($request->isFormPost()) { - $countdown->delete(); - return id(new AphrontRedirectResponse()) - ->setURI('/countdown/'); - } - - $inst = pht( - 'Are you sure you want to delete the countdown %s?', - $countdown->getTitle()); - - $dialog = new AphrontDialogView(); - $dialog->setUser($request->getUser()); - $dialog->setTitle(pht('Really delete this countdown?')); - $dialog->appendChild(phutil_tag('p', array(), $inst)); - $dialog->addSubmitButton(pht('Delete')); - $dialog->addCancelButton('/countdown/'); - $dialog->setSubmitURI($request->getPath()); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - -} diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php index 56911bf43b..db63613df2 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php @@ -28,7 +28,7 @@ final class PhabricatorCountdownViewController $crumbs = $this ->buildApplicationCrumbs() - ->addTextCrumb("C{$id}") + ->addTextCrumb($countdown->getMonogram()) ->setBorder(true); $epoch = $countdown->getEpoch(); @@ -102,14 +102,6 @@ final class PhabricatorCountdownViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $curtain->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-times') - ->setName(pht('Delete Countdown')) - ->setHref($this->getApplicationURI("delete/{$id}/")) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); - return $curtain; } diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php b/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php index c1d5f6753a..844cd6d9df 100644 --- a/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php +++ b/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php @@ -81,7 +81,8 @@ final class PhabricatorCountdownEditEngine ->setKey('name') ->setLabel(pht('Name')) ->setIsRequired(true) - ->setTransactionType(PhabricatorCountdownTransaction::TYPE_TITLE) + ->setTransactionType( + PhabricatorCountdownTitleTransaction::TRANSACTIONTYPE) ->setDescription(pht('The countdown name.')) ->setConduitDescription(pht('Rename the countdown.')) ->setConduitTypeDescription(pht('New countdown name.')) @@ -89,7 +90,8 @@ final class PhabricatorCountdownEditEngine id(new PhabricatorEpochEditField()) ->setKey('epoch') ->setLabel(pht('End Date')) - ->setTransactionType(PhabricatorCountdownTransaction::TYPE_EPOCH) + ->setTransactionType( + PhabricatorCountdownEpochTransaction::TRANSACTIONTYPE) ->setDescription(pht('Date when the countdown ends.')) ->setConduitDescription(pht('Change the end date of the countdown.')) ->setConduitTypeDescription(pht('New countdown end date.')) @@ -97,7 +99,8 @@ final class PhabricatorCountdownEditEngine id(new PhabricatorRemarkupEditField()) ->setKey('description') ->setLabel(pht('Description')) - ->setTransactionType(PhabricatorCountdownTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + PhabricatorCountdownDescriptionTransaction::TRANSACTIONTYPE) ->setDescription(pht('Description of the countdown.')) ->setConduitDescription(pht('Change the countdown description.')) ->setConduitTypeDescription(pht('New description.')) diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditor.php b/src/applications/countdown/editor/PhabricatorCountdownEditor.php index 37f23f6bd6..322b2ee2c3 100644 --- a/src/applications/countdown/editor/PhabricatorCountdownEditor.php +++ b/src/applications/countdown/editor/PhabricatorCountdownEditor.php @@ -14,10 +14,6 @@ final class PhabricatorCountdownEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorCountdownTransaction::TYPE_TITLE; - $types[] = PhabricatorCountdownTransaction::TYPE_EPOCH; - $types[] = PhabricatorCountdownTransaction::TYPE_DESCRIPTION; - $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_SPACE; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; @@ -27,126 +23,6 @@ final class PhabricatorCountdownEditor return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorCountdownTransaction::TYPE_TITLE: - return $object->getTitle(); - case PhabricatorCountdownTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case PhabricatorCountdownTransaction::TYPE_EPOCH: - return $object->getEpoch(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorCountdownTransaction::TYPE_TITLE: - return $xaction->getNewValue(); - case PhabricatorCountdownTransaction::TYPE_DESCRIPTION: - return $xaction->getNewValue(); - case PhabricatorCountdownTransaction::TYPE_EPOCH: - return $xaction->getNewValue()->getEpoch(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $type = $xaction->getTransactionType(); - switch ($type) { - case PhabricatorCountdownTransaction::TYPE_TITLE: - $object->setTitle($xaction->getNewValue()); - return; - case PhabricatorCountdownTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - return; - case PhabricatorCountdownTransaction::TYPE_EPOCH: - $object->setEpoch($xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $type = $xaction->getTransactionType(); - switch ($type) { - case PhabricatorCountdownTransaction::TYPE_TITLE: - return; - case PhabricatorCountdownTransaction::TYPE_DESCRIPTION: - return; - case PhabricatorCountdownTransaction::TYPE_EPOCH: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhabricatorCountdownTransaction::TYPE_TITLE: - $missing = $this->validateIsEmptyTextField( - $object->getTitle(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('You must give the countdown a name.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case PhabricatorCountdownTransaction::TYPE_EPOCH: - if (!$object->getEpoch() && !$xactions) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - 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; - } - - return $errors; - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php b/src/applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php index 573d89c843..7e6278c8ea 100644 --- a/src/applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php +++ b/src/applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php @@ -35,9 +35,9 @@ final class PhabricatorCountdownCountdownPHIDType extends PhabricatorPHIDType { $name = $countdown->getTitle(); $id = $countdown->getID(); - $handle->setName("C{$id}"); - $handle->setFullName("C{$id}: {$name}"); - $handle->setURI("/countdown/{$id}/"); + $handle->setName($countdown->getMonogram()); + $handle->setFullName(pht('%s: %s', $countdown->getMonogram(), $name)); + $handle->setURI($countdown->getURI()); } } diff --git a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php index 0abf9e453a..2f9fe9c0e8 100644 --- a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php +++ b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php @@ -108,9 +108,9 @@ final class PhabricatorCountdownSearchEngine $item = id(new PHUIObjectItemView()) ->setUser($viewer) ->setObject($countdown) - ->setObjectName("C{$id}") + ->setObjectName($countdown->getMonogram()) ->setHeader($countdown->getTitle()) - ->setHref($this->getApplicationURI("{$id}/")) + ->setHref($countdown->getURI()) ->addByline( pht( 'Created by %s', diff --git a/src/applications/countdown/storage/PhabricatorCountdown.php b/src/applications/countdown/storage/PhabricatorCountdown.php index 34249b9ab4..cfc6690085 100644 --- a/src/applications/countdown/storage/PhabricatorCountdown.php +++ b/src/applications/countdown/storage/PhabricatorCountdown.php @@ -8,7 +8,9 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO PhabricatorApplicationTransactionInterface, PhabricatorTokenReceiverInterface, PhabricatorSpacesInterface, - PhabricatorProjectInterface { + PhabricatorProjectInterface, + PhabricatorDestructibleInterface, + PhabricatorConduitResultInterface { protected $title; protected $authorPHID; @@ -141,8 +143,54 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO /* -( PhabricatorSpacesInterface )------------------------------------------- */ + public function getSpacePHID() { return $this->spacePHID; } +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + + $this->openTransaction(); + $this->delete(); + $this->saveTransaction(); + } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('title') + ->setType('string') + ->setDescription(pht('The title of the countdown.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('description') + ->setType('remarkup') + ->setDescription(pht('The description of the countdown.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('epoch') + ->setType('epoch') + ->setDescription(pht('The end date of the countdown.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'title' => $this->getTitle(), + 'description' => array( + 'raw' => $this->getDescription(), + ), + 'epoch' => (int)$this->getEpoch(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } diff --git a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php index 247466ffe1..78e0a54ced 100644 --- a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php +++ b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php @@ -1,11 +1,7 @@ getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_TITLE: - return pht( - '%s renamed this countdown from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - case self::TYPE_DESCRIPTION: - return pht( - '%s edited the description of this countdown.', - $this->renderHandleLink($author_phid)); - case self::TYPE_EPOCH: - return pht( - '%s updated this countdown to end on %s.', - $this->renderHandleLink($author_phid), - phabricator_datetime($new, $this->getViewer())); - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_TITLE: - return pht( - '%s renamed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_DESCRIPTION: - return pht( - '%s edited the description of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_EPOCH: - return pht( - '%s edited the end date of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - - return parent::getTitleForFeed(); + public function getBaseTransactionClass() { + return 'PhabricatorCountdownTransactionType'; } public function getMailTags() { @@ -88,9 +30,9 @@ final class PhabricatorCountdownTransaction case PhabricatorTransactions::TYPE_COMMENT: $tags[] = self::MAILTAG_COMMENT; break; - case self::TYPE_TITLE: - case self::TYPE_EPOCH: - case self::TYPE_DESCRIPTION: + case PhabricatorCountdownTitleTransaction::TRANSACTIONTYPE: + case PhabricatorCountdownEpochTransaction::TRANSACTIONTYPE: + case PhabricatorCountdownDescriptionTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_DETAILS; break; default: @@ -100,21 +42,4 @@ final class PhabricatorCountdownTransaction return $tags; } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($this->getOldValue() !== null); - } - - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); - } - } diff --git a/src/applications/countdown/view/PhabricatorCountdownView.php b/src/applications/countdown/view/PhabricatorCountdownView.php index 4e975c3626..f88c5a9d9c 100644 --- a/src/applications/countdown/view/PhabricatorCountdownView.php +++ b/src/applications/countdown/view/PhabricatorCountdownView.php @@ -14,12 +14,12 @@ final class PhabricatorCountdownView extends AphrontView { require_celerity_resource('phabricator-countdown-css'); $header_text = array( - 'C'.$countdown->getID(), + $countdown->getMonogram(), ' ', phutil_tag( 'a', array( - 'href' => '/countdown/'.$countdown->getID(), + 'href' => $countdown->getURI(), ), $countdown->getTitle()), ); diff --git a/src/applications/countdown/xaction/PhabricatorCountdownDescriptionTransaction.php b/src/applications/countdown/xaction/PhabricatorCountdownDescriptionTransaction.php new file mode 100644 index 0000000000..a742ac2125 --- /dev/null +++ b/src/applications/countdown/xaction/PhabricatorCountdownDescriptionTransaction.php @@ -0,0 +1,55 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + '%s updated the countdown description.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the countdown description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO COUNTDOWN DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } +} diff --git a/src/applications/countdown/xaction/PhabricatorCountdownEpochTransaction.php b/src/applications/countdown/xaction/PhabricatorCountdownEpochTransaction.php new file mode 100644 index 0000000000..905e0771d8 --- /dev/null +++ b/src/applications/countdown/xaction/PhabricatorCountdownEpochTransaction.php @@ -0,0 +1,58 @@ +getEpoch(); + } + + public function generateNewValue($object, $value) { + return $value->newPhutilDateTime() + ->newAbsoluteDateTime() + ->getEpoch(); + } + + public function applyInternalEffects($object, $value) { + $object->setEpoch($value); + } + + public function getTitle() { + return pht( + '%s updated the countdown end from %s to %s.', + $this->renderAuthor(), + $this->renderOldDate(), + $this->renderNewDate()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the countdown end for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldDate(), + $this->renderNewDate()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$object->getEpoch() && !$xactions) { + $errors[] = $this->newRequiredError( + pht('You must give the countdown an end date.')); + return $errors; + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + if (!$value->isValid()) { + $errors[] = $this->newInvalidError( + pht('You must give the countdown an end date.')); + } + } + + return $errors; + } +} diff --git a/src/applications/countdown/xaction/PhabricatorCountdownTitleTransaction.php b/src/applications/countdown/xaction/PhabricatorCountdownTitleTransaction.php new file mode 100644 index 0000000000..dca96c10c1 --- /dev/null +++ b/src/applications/countdown/xaction/PhabricatorCountdownTitleTransaction.php @@ -0,0 +1,54 @@ +getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setTitle($value); + } + + public function getTitle() { + return pht( + '%s updated the title for this countdown from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the title for this countdown from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) { + $errors[] = $this->newRequiredError(pht('Countdowns must have a title.')); + } + + $max_length = $object->getColumnMaximumByteLength('title'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Countdown titles must not be longer than %s character(s).', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/countdown/xaction/PhabricatorCountdownTransactionType.php b/src/applications/countdown/xaction/PhabricatorCountdownTransactionType.php new file mode 100644 index 0000000000..be0a8c6f78 --- /dev/null +++ b/src/applications/countdown/xaction/PhabricatorCountdownTransactionType.php @@ -0,0 +1,4 @@ +getActionOptions($viewer, $revision); // Show the options if the user can select on behalf of two or more - // reviewers, or can force-accept on behalf of one or more reviewers. + // reviewers, or can force-accept on behalf of one or more reviewers, + // or can accept on behalf of a reviewer other than themselves (see + // T12533). $can_multi = (count($options) > 1); $can_force = (count($value) < count($options)); - if ($can_multi || $can_force) { + $not_self = (head_key($options) != $viewer->getPHID()); + + if ($can_multi || $can_force || $not_self) { $field->setOptions($options); $field->setValue($value); } diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index 7217f284b4..f7e72be2aa 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -41,8 +41,8 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $ttl = $file->getTTL(); if ($ttl !== null) { $ttl_tag = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_STATE) - ->setBackgroundColor(PHUITagView::COLOR_YELLOW) + ->setType(PHUITagView::TYPE_SHADE) + ->setShade(PHUITagView::COLOR_YELLOW) ->setName(pht('Temporary')); $header->addTag($ttl_tag); } @@ -50,8 +50,8 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $partial = $file->getIsPartial(); if ($partial) { $partial_tag = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_STATE) - ->setBackgroundColor(PHUITagView::COLOR_ORANGE) + ->setType(PHUITagView::TYPE_SHADE) + ->setShade(PHUITagView::COLOR_ORANGE) ->setName(pht('Partial Upload')); $header->addTag($partial_tag); } diff --git a/src/applications/people/editor/PhabricatorUserEditor.php b/src/applications/people/editor/PhabricatorUserEditor.php index 3370fb428b..cb5ed65f8d 100644 --- a/src/applications/people/editor/PhabricatorUserEditor.php +++ b/src/applications/people/editor/PhabricatorUserEditor.php @@ -195,6 +195,10 @@ final class PhabricatorUserEditor extends PhabricatorEditor { $user->saveTransaction(); + // The SSH key cache currently includes usernames, so dirty it. See T12554 + // for discussion. + PhabricatorAuthSSHKeyQuery::deleteSSHKeyCache(); + $user->sendUsernameChangeEmail($actor, $old_username); } diff --git a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php index 778384e946..1f82d78423 100644 --- a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php +++ b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php @@ -151,7 +151,7 @@ final class PhabricatorMentionRemarkupRule extends PhutilRemarkupRule { } if (!$user->isResponsive()) { - $tag->setDotColor(PHUITagView::COLOR_GREY); + $tag->setDotColor(PHUITagView::COLOR_VIOLET); } else { if ($user->getAwayUntil()) { $away = PhabricatorCalendarEventInvitee::AVAILABILITY_AWAY; diff --git a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php index 2d773121a0..b2a456cd51 100644 --- a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php +++ b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php @@ -62,7 +62,7 @@ final class PhabricatorPeopleUserPHIDType extends PhabricatorPHIDType { $availability = null; if (!$user->isResponsive()) { - $availability = PhabricatorObjectHandle::AVAILABILITY_DISABLED; + $availability = PhabricatorObjectHandle::AVAILABILITY_NOEMAIL; } else { $until = $user->getAwayUntil(); if ($until) { diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index 7367382e50..e756a9a4fd 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -465,7 +465,7 @@ final class PhabricatorPeopleQuery while (true) { foreach ($events as $event) { $from = $event->getStartDateTimeEpochForCache(); - $to = $event->getEndDateTimeEpoch(); + $to = $event->getEndDateTimeEpochForCache(); if (($from <= $cursor) && ($to > $cursor)) { $cursor = $to; if (!$next_event) { @@ -483,7 +483,7 @@ final class PhabricatorPeopleQuery $availability_type = $invitee->getDisplayAvailability($next_event); $availability = array( 'until' => $cursor, - 'eventPHID' => $event->getPHID(), + 'eventPHID' => $next_event->getPHID(), 'availability' => $availability_type, ); @@ -496,7 +496,7 @@ final class PhabricatorPeopleQuery // simultaneously we should accommodate that. However, it's complex // to compute, rare, and probably not confusing most of the time. - $availability_ttl = $next_event->getStartDateTimeEpochForCache(); + $availability_ttl = $next_event->getEndDateTimeEpochForCache(); } else { $availability = array( 'until' => null, diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index 4a35d9956c..2a1c1394a6 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -1082,7 +1082,7 @@ final class PhabricatorUser 'UPDATE %T SET availabilityCache = %s, availabilityCacheTTL = %nd WHERE id = %d', $this->getTableName(), - json_encode($availability), + phutil_json_encode($availability), $ttl, $this->getID()); unset($unguarded); diff --git a/src/applications/people/view/PhabricatorUserCardView.php b/src/applications/people/view/PhabricatorUserCardView.php index 5b6d34b90c..4f4f15a33d 100644 --- a/src/applications/people/view/PhabricatorUserCardView.php +++ b/src/applications/people/view/PhabricatorUserCardView.php @@ -68,7 +68,7 @@ final class PhabricatorUserCardView extends AphrontTagView { } else if (!$user->getIsEmailVerified()) { $tag_icon = 'fa-envelope'; $tag_title = pht('Email Not Verified'); - $tag_shade = PHUITagView::COLOR_RED; + $tag_shade = PHUITagView::COLOR_VIOLET; } else if ($user->getIsAdmin()) { $tag_icon = 'fa-star'; $tag_title = pht('Administrator'); diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php index e6230c4ae6..49255fd9e8 100644 --- a/src/applications/phid/PhabricatorObjectHandle.php +++ b/src/applications/phid/PhabricatorObjectHandle.php @@ -6,6 +6,7 @@ final class PhabricatorObjectHandle const AVAILABILITY_FULL = 'full'; const AVAILABILITY_NONE = 'none'; + const AVAILABILITY_NOEMAIL = 'no-email'; const AVAILABILITY_PARTIAL = 'partial'; const AVAILABILITY_DISABLED = 'disabled'; diff --git a/src/applications/phortune/__tests__/PhabricatorPhortuneTestCase.php b/src/applications/phortune/__tests__/PhabricatorPhortuneTestCase.php new file mode 100644 index 0000000000..7be37483be --- /dev/null +++ b/src/applications/phortune/__tests__/PhabricatorPhortuneTestCase.php @@ -0,0 +1,43 @@ + true, + ); + } + + public function testNewPhortuneAccount() { + $user = $this->generateNewTestUser(); + $content_source = $this->newContentSource(); + + $accounts = PhortuneAccountQuery::loadAccountsForUser( + $user, + $content_source); + + $this->assertEqual( + 1, + count($accounts), + pht('Creation of default account for users with no accounts.')); + + // Reload the account. The user should be able to view and edit it, and + // should be a member. + + $account = head($accounts); + $account = id(new PhortuneAccountQuery()) + ->setViewer($user) + ->withPHIDs(array($account->getPHID())) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + $this->assertEqual(true, ($account instanceof PhortuneAccount)); + $this->assertEqual(array($user->getPHID()), $account->getMemberPHIDs()); + } + +} diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 15c312773b..0ffc138f65 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -67,7 +67,18 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { ), 'account/' => array( '' => 'PhortuneAccountListController', + $this->getEditRoutePattern('edit/') + => 'PhortuneAccountEditController', 'edit/(?:(?P\d+)/)?' => 'PhortuneAccountEditController', + 'add/manager/(?:(?P\d+)/)?' + => 'PhortuneAccountAddManagerController', + 'billing/(?:(?P\d+)/)?' => 'PhortuneAccountBillingController', + 'subscription/(?:(?P\d+)/)?' + => 'PhortuneAccountSubscriptionController', + 'manager/' => array( + '(?:(?P\d+)/)?' => 'PhortuneAccountManagerController', + 'add/(?:(?P\d+)/)?' => 'PhortuneAccountAddManagerController', + ), ), 'product/' => array( '' => 'PhortuneProductListController', @@ -87,6 +98,10 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { => 'PhortuneMerchantEditController', 'orders/(?P\d+)/(?:query/(?P[^/]+)/)?' => 'PhortuneCartListController', + 'manager/' => array( + '(?:(?P\d+)/)?' => 'PhortuneMerchantManagerController', + 'add/(?:(?P\d+)/)?' => 'PhortuneMerchantAddManagerController', + ), '(?P\d+)/' => array( 'cart/(?P\d+)/' => array( '' => 'PhortuneCartViewController', diff --git a/src/applications/phortune/controller/PhortuneLandingController.php b/src/applications/phortune/controller/PhortuneLandingController.php index 2a019c5df9..e6906095d2 100644 --- a/src/applications/phortune/controller/PhortuneLandingController.php +++ b/src/applications/phortune/controller/PhortuneLandingController.php @@ -5,17 +5,9 @@ final class PhortuneLandingController extends PhortuneController { public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $accounts = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withMemberPHIDs(array($viewer->getPHID())) - ->execute(); - - if (!$accounts) { - $account = PhortuneAccount::createNewAccount( - $viewer, - PhabricatorContentSource::newFromRequest($request)); - $accounts = array($account); - } + $accounts = PhortuneAccountQuery::loadAccountsForUser( + $viewer, + PhabricatorContentSource::newFromRequest($request)); if (count($accounts) == 1) { $account = head($accounts); diff --git a/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php b/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php new file mode 100644 index 0000000000..34bb0a480b --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php @@ -0,0 +1,75 @@ +getViewer(); + $id = $request->getURIData('id'); + + $account = id(new PhortuneAccountQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$account) { + return new Aphront404Response(); + } + + $v_managers = array(); + $e_managers = null; + $account_uri = $this->getApplicationURI("/account/manager/{$id}/"); + + if ($request->isFormPost()) { + $xactions = array(); + $v_managers = $request->getArr('managerPHIDs'); + $type_edge = PhabricatorTransactions::TYPE_EDGE; + + $xactions[] = id(new PhortuneAccountTransaction()) + ->setTransactionType($type_edge) + ->setMetadataValue( + 'edge:type', + PhortuneAccountHasMemberEdgeType::EDGECONST) + ->setNewValue( + array( + '+' => array_fuse($v_managers), + )); + + $editor = id(new PhortuneAccountEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { + $editor->applyTransactions($account, $xactions); + + return id(new AphrontRedirectResponse())->setURI($account_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + $e_managers = $ex->getShortMessage($type_edge); + } + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new PhabricatorPeopleDatasource()) + ->setLabel(pht('Managers')) + ->setName('managerPHIDs') + ->setValue($v_managers) + ->setError($e_managers)); + + return $this->newDialog() + ->setTitle(pht('Add New Manager')) + ->appendForm($form) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->addCancelButton($account_uri) + ->addSubmitButton(pht('Add Manager')); + + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountBillingController.php b/src/applications/phortune/controller/account/PhortuneAccountBillingController.php new file mode 100644 index 0000000000..0660358af7 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountBillingController.php @@ -0,0 +1,161 @@ +loadAccount(); + if ($response) { + return $response; + } + + $account = $this->getAccount(); + $title = $account->getName(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Billing')); + + $header = $this->buildHeaderView(); + $methods = $this->buildPaymentMethodsSection($account); + $charge_history = $this->buildChargeHistorySection($account); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $methods, + $charge_history, + )); + + $navigation = $this->buildSideNavView('billing'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + + } + + private function buildPaymentMethodsSection(PhortuneAccount $account) { + $viewer = $this->getViewer(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_EDIT); + + $id = $account->getID(); + + // TODO: Allow adding a card here directly + $add = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('New Payment Method')) + ->setIcon('fa-plus') + ->setHref($this->getApplicationURI("{$id}/card/new/")); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Payment Methods')); + + $list = id(new PHUIObjectItemListView()) + ->setUser($viewer) + ->setFlush(true) + ->setNoDataString( + pht('No payment methods associated with this account.')); + + $methods = id(new PhortunePaymentMethodQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->withStatuses( + array( + PhortunePaymentMethod::STATUS_ACTIVE, + )) + ->execute(); + + foreach ($methods as $method) { + $id = $method->getID(); + + $item = new PHUIObjectItemView(); + $item->setHeader($method->getFullDisplayName()); + + switch ($method->getStatus()) { + case PhortunePaymentMethod::STATUS_ACTIVE: + $item->setStatusIcon('fa-check green'); + + $disable_uri = $this->getApplicationURI('card/'.$id.'/disable/'); + $item->addAction( + id(new PHUIListItemView()) + ->setIcon('fa-times') + ->setHref($disable_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + break; + case PhortunePaymentMethod::STATUS_DISABLED: + $item->setStatusIcon('fa-ban lightbluetext'); + $item->setDisabled(true); + break; + } + + $provider = $method->buildPaymentProvider(); + $item->addAttribute($provider->getPaymentMethodProviderDescription()); + + $edit_uri = $this->getApplicationURI('card/'.$id.'/edit/'); + + $item->addAction( + id(new PHUIListItemView()) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + $list->addItem($item); + } + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + } + + private function buildChargeHistorySection(PhortuneAccount $account) { + $viewer = $this->getViewer(); + + $charges = id(new PhortuneChargeQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->needCarts(true) + ->setLimit(10) + ->execute(); + + $phids = array(); + foreach ($charges as $charge) { + $phids[] = $charge->getProviderPHID(); + $phids[] = $charge->getCartPHID(); + $phids[] = $charge->getMerchantPHID(); + $phids[] = $charge->getPaymentMethodPHID(); + } + + $handles = $this->loadViewerHandles($phids); + + $charges_uri = $this->getApplicationURI($account->getID().'/charge/'); + + $table = id(new PhortuneChargeTableView()) + ->setUser($viewer) + ->setCharges($charges) + ->setHandles($handles); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Charge History')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-list') + ->setHref($charges_uri) + ->setText(pht('View All Charges'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountController.php b/src/applications/phortune/controller/account/PhortuneAccountController.php new file mode 100644 index 0000000000..2ba3b393a2 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountController.php @@ -0,0 +1,64 @@ +account; + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $account = $this->getAccount(); + if ($account) { + $crumbs->addTextCrumb($account->getName(), $account->getURI()); + } + + return $crumbs; + } + + protected function loadAccount() { + // TODO: Currently, you must be able to edit an account to view the detail + // page, because the account must be broadly visible so merchants can + // process orders but merchants should not be able to see all the details + // of an account. Ideally the profile pages should be visible to merchants, + // too, just with less information. + return $this->loadAccountForEdit(); + } + + + protected function loadAccountForEdit() { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + + $account_id = $request->getURIData('accountID'); + if (!$account_id) { + $account_id = $request->getURIData('id'); + } + + if (!$account_id) { + return new Aphront404Response(); + } + + $account = id(new PhortuneAccountQuery()) + ->setViewer($viewer) + ->withIDs(array($account_id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$account) { + return new Aphront404Response(); + } + + $this->account = $account; + + return null; + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountEditController.php b/src/applications/phortune/controller/account/PhortuneAccountEditController.php index 104623219b..d90abcbd64 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountEditController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountEditController.php @@ -1,137 +1,11 @@ getViewer(); - $id = $request->getURIData('id'); - - if ($id) { - $account = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$account) { - return new Aphront404Response(); - } - $is_new = false; - } else { - $account = PhortuneAccount::initializeNewAccount($viewer); - $account->attachMemberPHIDs(array($viewer->getPHID())); - $is_new = true; - } - - $v_name = $account->getName(); - $e_name = true; - - $v_members = $account->getMemberPHIDs(); - $e_members = null; - - $validation_exception = null; - - if ($request->isFormPost()) { - $v_name = $request->getStr('name'); - $v_members = $request->getArr('memberPHIDs'); - - $type_name = PhortuneAccountTransaction::TYPE_NAME; - $type_edge = PhabricatorTransactions::TYPE_EDGE; - - $xactions = array(); - $xactions[] = id(new PhortuneAccountTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new PhortuneAccountTransaction()) - ->setTransactionType($type_edge) - ->setMetadataValue( - 'edge:type', - PhortuneAccountHasMemberEdgeType::EDGECONST) - ->setNewValue( - array( - '=' => array_fuse($v_members), - )); - - $editor = id(new PhortuneAccountEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $editor->applyTransactions($account, $xactions); - - $account_uri = $this->getApplicationURI($account->getID().'/'); - return id(new AphrontRedirectResponse())->setURI($account_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - $e_name = $ex->getShortMessage($type_name); - $e_members = $ex->getShortMessage($type_edge); - } - } - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->setBorder(true); - - if ($is_new) { - $cancel_uri = $this->getApplicationURI('account/'); - $crumbs->addTextCrumb(pht('Accounts'), $cancel_uri); - $crumbs->addTextCrumb(pht('Create Account')); - - $title = pht('Create Payment Account'); - $submit_button = pht('Create Account'); - } else { - $cancel_uri = $this->getApplicationURI($account->getID().'/'); - $crumbs->addTextCrumb($account->getName(), $cancel_uri); - $crumbs->addTextCrumb(pht('Edit')); - - $title = pht('Edit %s', $account->getName()); - $submit_button = pht('Save Changes'); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name')) - ->setValue($v_name) - ->setError($e_name)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setLabel(pht('Members')) - ->setName('memberPHIDs') - ->setValue($v_members) - ->setError($e_members)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue($submit_button) - ->addCancelButton($cancel_uri)); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Account')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setValidationException($validation_exception) - ->setForm($form); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); + return id(new PhortuneAccountEditEngine()) + ->setController($this) + ->buildResponse(); } - } diff --git a/src/applications/phortune/controller/account/PhortuneAccountManagerController.php b/src/applications/phortune/controller/account/PhortuneAccountManagerController.php new file mode 100644 index 0000000000..502fbfe52e --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountManagerController.php @@ -0,0 +1,85 @@ +loadAccount(); + if ($response) { + return $response; + } + + $account = $this->getAccount(); + $title = $account->getName(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Managers')); + + $header = $this->buildHeaderView(); + $members = $this->buildMembersSection($account); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $members, + )); + + $navigation = $this->buildSideNavView('managers'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + + } + + private function buildMembersSection(PhortuneAccount $account) { + $viewer = $this->getViewer(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_EDIT); + + $id = $account->getID(); + + $add = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('New Manager')) + ->setIcon('fa-plus') + ->setWorkflow(true) + ->setDisabled(!$can_edit) + ->setHref("/phortune/account/manager/add/{$id}/"); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Account Managers')) + ->addActionLink($add); + + $list = id(new PHUIObjectItemListView()) + ->setUser($viewer); + + $member_phids = $account->getMemberPHIDs(); + $handles = $viewer->loadHandles($member_phids); + + foreach ($member_phids as $member_phid) { + $image_uri = $handles[$member_phid]->getImageURI(); + $image_href = $handles[$member_phid]->getURI(); + $person = $handles[$member_phid]; + + $member = id(new PHUIObjectItemView()) + ->setImageURI($image_uri) + ->setHref($image_href) + ->setHeader($person->getFullName()) + ->addAttribute(pht('Account Manager')); + + $list->addItem($member); + } + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php new file mode 100644 index 0000000000..2a5448d5cc --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php @@ -0,0 +1,68 @@ +buildSideNavView()->getMenu(); + } + + protected function buildHeaderView() { + $viewer = $this->getViewer(); + $account = $this->getAccount(); + $title = $account->getName(); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($title) + ->setHeaderIcon('fa-user-circle'); + + return $header; + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + $crumbs->setBorder(true); + return $crumbs; + } + + protected function buildSideNavView($filter = null) { + $viewer = $this->getViewer(); + $account = $this->getAccount(); + $id = $account->getID(); + + $nav = id(new AphrontSideNavFilterView()) + ->setBaseURI(new PhutilURI($this->getApplicationURI())); + + $nav->addLabel(pht('Account')); + + $nav->addFilter( + 'overview', + pht('Overview'), + $this->getApplicationURI("/{$id}/"), + 'fa-user-circle'); + + $nav->addFilter( + 'subscriptions', + pht('Subscriptions'), + $this->getApplicationURI("/account/subscription/{$id}/"), + 'fa-retweet'); + + $nav->addFilter( + 'billing', + pht('Billing / History'), + $this->getApplicationURI("/account/billing/{$id}/"), + 'fa-credit-card'); + + $nav->addFilter( + 'managers', + pht('Managers'), + $this->getApplicationURI("/account/manager/{$id}/"), + 'fa-group'); + + $nav->selectFilter($filter); + + return $nav; + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php new file mode 100644 index 0000000000..418507e9c2 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php @@ -0,0 +1,62 @@ +loadAccount(); + if ($response) { + return $response; + } + + $account = $this->getAccount(); + $title = $account->getName(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Subscriptions')); + + $header = $this->buildHeaderView(); + $subscriptions = $this->buildSubscriptionsSection($account); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $subscriptions, + )); + + $navigation = $this->buildSideNavView('subscriptions'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + + } + + private function buildSubscriptionsSection(PhortuneAccount $account) { + $viewer = $this->getViewer(); + + $subscriptions = id(new PhortuneSubscriptionQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->setLimit(25) + ->execute(); + + $handles = $this->loadViewerHandles(mpull($subscriptions, 'getPHID')); + + $table = id(new PhortuneSubscriptionTableView()) + ->setUser($viewer) + ->setHandles($handles) + ->setSubscriptions($subscriptions); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Subscriptions')); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountViewController.php b/src/applications/phortune/controller/account/PhortuneAccountViewController.php index 97ea1beea8..6537577921 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountViewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountViewController.php @@ -1,32 +1,19 @@ getViewer(); - - // TODO: Currently, you must be able to edit an account to view the detail - // page, because the account must be broadly visible so merchants can - // process orders but merchants should not be able to see all the details - // of an account. Ideally this page should be visible to merchants, too, - // just with less information. - $can_edit = true; - - $account = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getURIData('accountID'))) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$account) { - return new Aphront404Response(); + $response = $this->loadAccount(); + if ($response) { + return $response; } + $account = $this->getAccount(); $title = $account->getName(); + $viewer = $this->getViewer(); + $invoices = id(new PhortuneCartQuery()) ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) @@ -35,19 +22,14 @@ final class PhortuneAccountViewController extends PhortuneController { ->execute(); $crumbs = $this->buildApplicationCrumbs(); - $this->addAccountCrumb($crumbs, $account, $link = false); $crumbs->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-credit-card'); + $header = $this->buildHeaderView(); - $curtain = $this->buildCurtainView($account, $invoices); + $curtain = $this->buildCurtainView($account); + $status = $this->buildStatusView($account, $invoices); $invoices = $this->buildInvoicesSection($account, $invoices); $purchase_history = $this->buildPurchaseHistorySection($account); - $charge_history = $this->buildChargeHistorySection($account); - $subscriptions = $this->buildSubscriptionsSection($account); - $payment_methods = $this->buildPaymentMethodsSection($account); $timeline = $this->buildTransactionTimeline( $account, @@ -58,22 +40,34 @@ final class PhortuneAccountViewController extends PhortuneController { ->setHeader($header) ->setCurtain($curtain) ->setMainColumn(array( + $status, $invoices, $purchase_history, - $charge_history, - $subscriptions, - $payment_methods, $timeline, )); + $navigation = $this->buildSideNavView('overview'); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($navigation) ->appendChild($view); } - private function buildCurtainView(PhortuneAccount $account, $invoices) { + private function buildStatusView(PhortuneAccount $account, $invoices) { + $status_items = $this->getStatusItemsForAccount($account, $invoices); + $view = array(); + foreach ($status_items as $item) { + $view[] = id(new PHUIInfoView()) + ->setSeverity(idx($item, 'severity')) + ->appendChild(idx($item, 'note')); + } + return $view; + } + + private function buildCurtainView(PhortuneAccount $account) { $viewer = $this->getViewer(); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -92,19 +86,6 @@ final class PhortuneAccountViewController extends PhortuneController { ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $status_items = $this->getStatusItemsForAccount($account, $invoices); - $status_view = new PHUIStatusListView(); - foreach ($status_items as $item) { - $status_view->addItem( - id(new PHUIStatusItemView()) - ->setIcon( - idx($item, 'icon'), - idx($item, 'color'), - idx($item, 'label')) - ->setTarget(idx($item, 'target')) - ->setNote(idx($item, 'note'))); - } - $member_phids = $account->getMemberPHIDs(); $handles = $viewer->loadHandles($member_phids); @@ -125,89 +106,12 @@ final class PhortuneAccountViewController extends PhortuneController { } $curtain->newPanel() - ->setHeaderText(pht('Status')) - ->appendChild($status_view); - - $curtain->newPanel() - ->setHeaderText(pht('Members')) + ->setHeaderText(pht('Managers')) ->appendChild($member_list); return $curtain; } - private function buildPaymentMethodsSection(PhortuneAccount $account) { - $viewer = $this->getViewer(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $account, - PhabricatorPolicyCapability::CAN_EDIT); - - $id = $account->getID(); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Payment Methods')); - - $list = id(new PHUIObjectItemListView()) - ->setUser($viewer) - ->setFlush(true) - ->setNoDataString( - pht('No payment methods associated with this account.')); - - $methods = id(new PhortunePaymentMethodQuery()) - ->setViewer($viewer) - ->withAccountPHIDs(array($account->getPHID())) - ->withStatuses( - array( - PhortunePaymentMethod::STATUS_ACTIVE, - )) - ->execute(); - - foreach ($methods as $method) { - $id = $method->getID(); - - $item = new PHUIObjectItemView(); - $item->setHeader($method->getFullDisplayName()); - - switch ($method->getStatus()) { - case PhortunePaymentMethod::STATUS_ACTIVE: - $item->setStatusIcon('fa-check green'); - - $disable_uri = $this->getApplicationURI('card/'.$id.'/disable/'); - $item->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-times') - ->setHref($disable_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); - break; - case PhortunePaymentMethod::STATUS_DISABLED: - $item->setStatusIcon('fa-ban lightbluetext'); - $item->setDisabled(true); - break; - } - - $provider = $method->buildPaymentProvider(); - $item->addAttribute($provider->getPaymentMethodProviderDescription()); - - $edit_uri = $this->getApplicationURI('card/'.$id.'/edit/'); - - $item->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-pencil') - ->setHref($edit_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - - $list->addItem($item); - } - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setObjectList($list); - } - private function buildInvoicesSection( PhortuneAccount $account, array $carts) { @@ -289,84 +193,6 @@ final class PhortuneAccountViewController extends PhortuneController { ->setTable($table); } - private function buildChargeHistorySection(PhortuneAccount $account) { - $viewer = $this->getViewer(); - - $charges = id(new PhortuneChargeQuery()) - ->setViewer($viewer) - ->withAccountPHIDs(array($account->getPHID())) - ->needCarts(true) - ->setLimit(10) - ->execute(); - - $phids = array(); - foreach ($charges as $charge) { - $phids[] = $charge->getProviderPHID(); - $phids[] = $charge->getCartPHID(); - $phids[] = $charge->getMerchantPHID(); - $phids[] = $charge->getPaymentMethodPHID(); - } - - $handles = $this->loadViewerHandles($phids); - - $charges_uri = $this->getApplicationURI($account->getID().'/charge/'); - - $table = id(new PhortuneChargeTableView()) - ->setUser($viewer) - ->setCharges($charges) - ->setHandles($handles); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recent Charges')) - ->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-list') - ->setHref($charges_uri) - ->setText(pht('View All Charges'))); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); - } - - private function buildSubscriptionsSection(PhortuneAccount $account) { - $viewer = $this->getViewer(); - - $subscriptions = id(new PhortuneSubscriptionQuery()) - ->setViewer($viewer) - ->withAccountPHIDs(array($account->getPHID())) - ->setLimit(10) - ->execute(); - - $subscriptions_uri = $this->getApplicationURI( - $account->getID().'/subscription/'); - - $handles = $this->loadViewerHandles(mpull($subscriptions, 'getPHID')); - - $table = id(new PhortuneSubscriptionTableView()) - ->setUser($viewer) - ->setHandles($handles) - ->setSubscriptions($subscriptions); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recent Subscriptions')) - ->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setIcon( - id(new PHUIIconView()) - ->setIcon('fa-list')) - ->setHref($subscriptions_uri) - ->setText(pht('View All Subscriptions'))); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); - } - protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); @@ -382,25 +208,25 @@ final class PhortuneAccountViewController extends PhortuneController { private function getStatusItemsForAccount( PhortuneAccount $account, array $invoices) { + $viewer = $this->getViewer(); assert_instances_of($invoices, 'PhortuneCart'); - $items = array(); + $methods = id(new PhortunePaymentMethodQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->withStatuses( + array( + PhortunePaymentMethod::STATUS_ACTIVE, + )) + ->execute(); + if ($invoices) { $items[] = array( - 'icon' => PHUIStatusItemView::ICON_WARNING, - 'color' => 'yellow', - 'target' => pht('Invoices'), + 'severity' => PHUIInfoView::SEVERITY_ERROR, 'note' => pht('You have %d unpaid invoice(s).', count($invoices)), ); - } else { - $items[] = array( - 'icon' => PHUIStatusItemView::ICON_ACCEPT, - 'color' => 'green', - 'target' => pht('Invoices'), - 'note' => pht('This account has no unpaid invoices.'), - ); } // TODO: If a payment method has expired or is expiring soon, we should diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php new file mode 100644 index 0000000000..3ef0a53874 --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php @@ -0,0 +1,76 @@ +getViewer(); + $id = $request->getURIData('id'); + + $merchant = id(new PhortuneMerchantQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->needProfileImage(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$merchant) { + return new Aphront404Response(); + } + + $v_members = array(); + $e_members = null; + $merchant_uri = $this->getApplicationURI("/merchant/manager/{$id}/"); + + if ($request->isFormPost()) { + $xactions = array(); + $v_members = $request->getArr('memberPHIDs'); + $type_edge = PhabricatorTransactions::TYPE_EDGE; + + $xactions[] = id(new PhortuneMerchantTransaction()) + ->setTransactionType($type_edge) + ->setMetadataValue( + 'edge:type', + PhortuneMerchantHasMemberEdgeType::EDGECONST) + ->setNewValue( + array( + '+' => array_fuse($v_members), + )); + + $editor = id(new PhortuneMerchantEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { + $editor->applyTransactions($merchant, $xactions); + + return id(new AphrontRedirectResponse())->setURI($merchant_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + $e_members = $ex->getShortMessage($type_edge); + } + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new PhabricatorPeopleDatasource()) + ->setLabel(pht('Members')) + ->setName('memberPHIDs') + ->setValue($v_members) + ->setError($e_members)); + + return $this->newDialog() + ->setTitle(pht('Add New Manager')) + ->appendForm($form) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->addCancelButton($merchant_uri) + ->addSubmitButton(pht('Add Manager')); + + } + +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php index ccdc4f7ac1..300525cdd2 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php @@ -1,7 +1,7 @@ getUser(); @@ -11,6 +11,7 @@ final class PhortuneMerchantInvoiceCreateController return new Aphront404Response(); } + $this->setMerchant($merchant); $merchant_id = $merchant->getID(); $cancel_uri = $this->getApplicationURI("/merchant/{$merchant_id}/"); @@ -88,8 +89,7 @@ final class PhortuneMerchantInvoiceCreateController $title = pht('New Invoice'); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($merchant->getName()); - $crumbs->setBorder(true); + $crumbs->addTextCrumb($title); $v_title = $request->getStr('title'); $e_title = true; @@ -245,9 +245,12 @@ final class PhortuneMerchantInvoiceCreateController $box, )); + $navigation = $this->buildSideNavView('orders'); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($navigation) ->appendChild($view); } diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantManagerController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantManagerController.php new file mode 100644 index 0000000000..0b9c0c6598 --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantManagerController.php @@ -0,0 +1,91 @@ +getViewer(); + $id = $request->getURIData('id'); + + $merchant = id(new PhortuneMerchantQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->needProfileImage(true) + ->executeOne(); + if (!$merchant) { + return new Aphront404Response(); + } + + $this->setMerchant($merchant); + $header = $this->buildHeaderView(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Managers')); + + $header = $this->buildHeaderView(); + $members = $this->buildMembersSection($merchant); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $members, + )); + + $navigation = $this->buildSideNavView('managers'); + + return $this->newPage() + ->setTitle(pht('Managers')) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + + } + + private function buildMembersSection(PhortuneMerchant $merchant) { + $viewer = $this->getViewer(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $merchant, + PhabricatorPolicyCapability::CAN_EDIT); + + $id = $merchant->getID(); + + $add = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('New Manager')) + ->setIcon('fa-plus') + ->setWorkflow(true) + ->setHref("/phortune/merchant/manager/add/{$id}/"); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Merchant Account Managers')) + ->addActionLink($add); + + $list = id(new PHUIObjectItemListView()) + ->setUser($viewer); + + $member_phids = $merchant->getMemberPHIDs(); + $handles = $viewer->loadHandles($member_phids); + + foreach ($member_phids as $member_phid) { + $image_uri = $handles[$member_phid]->getImageURI(); + $image_href = $handles[$member_phid]->getURI(); + $person = $handles[$member_phid]; + + $member = id(new PHUIObjectItemView()) + ->setImageURI($image_uri) + ->setHref($image_href) + ->setHeader($person->getFullName()) + ->addAttribute(pht('Merchant Manager')); + + $list->addItem($member); + } + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + } + +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php index 74d7ea061c..469887172f 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php @@ -1,7 +1,7 @@ getViewer(); @@ -21,7 +21,8 @@ final class PhortuneMerchantPictureController return new Aphront404Response(); } - $uri = $merchant->getViewURI(); + $this->setMerchant($merchant); + $uri = $merchant->getURI(); $supported_formats = PhabricatorFile::getTransformableImageFormats(); $e_file = true; @@ -76,7 +77,8 @@ final class PhortuneMerchantPictureController $xactions = array(); $xactions[] = id(new PhortuneMerchantTransaction()) - ->setTransactionType(PhortuneMerchantTransaction::TYPE_PICTURE) + ->setTransactionType( + PhortuneMerchantPictureTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new PhortuneMerchantEditor()) @@ -91,7 +93,7 @@ final class PhortuneMerchantPictureController } } - $title = pht('Edit Merchant Picture'); + $title = pht('Edit Logo'); $form = id(new PHUIFormLayoutView()) ->setUser($viewer); @@ -207,12 +209,10 @@ final class PhortuneMerchantPictureController ->setForm($upload_form); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($merchant->getName(), $uri); - $crumbs->addTextCrumb(pht('Merchant Logo')); - $crumbs->setBorder(true); + $crumbs->addTextCrumb(pht('Edit Logo')); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Edit Merchant Logo')) + ->setHeader(pht('Edit Logo')) ->setHeaderIcon('fa-camera'); $view = id(new PHUITwoColumnView()) @@ -222,9 +222,12 @@ final class PhortuneMerchantPictureController $upload_box, )); + $navigation = $this->buildSideNavView(); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($navigation) ->appendChild( array( $view, diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantProfileController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantProfileController.php new file mode 100644 index 0000000000..45911bfef9 --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantProfileController.php @@ -0,0 +1,92 @@ +merchant = $merchant; + return $this; + } + + public function getMerchant() { + return $this->merchant; + } + + public function buildApplicationMenu() { + return $this->buildSideNavView()->getMenu(); + } + + protected function buildHeaderView() { + $viewer = $this->getViewer(); + $merchant = $this->getMerchant(); + $title = $merchant->getName(); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setUser($viewer) + ->setPolicyObject($merchant) + ->setImage($merchant->getProfileImageURI()); + + return $header; + } + + protected function buildApplicationCrumbs() { + $merchant = $this->getMerchant(); + $id = $merchant->getID(); + $merchant_uri = $this->getApplicationURI("/merchant/{$id}/"); + + $crumbs = parent::buildApplicationCrumbs(); + $crumbs->addTextCrumb($merchant->getName(), $merchant_uri); + $crumbs->setBorder(true); + return $crumbs; + } + + protected function buildSideNavView($filter = null) { + $viewer = $this->getViewer(); + $merchant = $this->getMerchant(); + $id = $merchant->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $merchant, + PhabricatorPolicyCapability::CAN_EDIT); + + $nav = id(new AphrontSideNavFilterView()) + ->setBaseURI(new PhutilURI($this->getApplicationURI())); + + $nav->addLabel(pht('Merchant')); + + $nav->addFilter( + 'overview', + pht('Overview'), + $this->getApplicationURI("/merchant/{$id}/"), + 'fa-building-o'); + + if ($can_edit) { + $nav->addFilter( + 'orders', + pht('Orders'), + $this->getApplicationURI("merchant/orders/{$id}/"), + 'fa-retweet'); + + $nav->addFilter( + 'subscriptions', + pht('Subscriptions'), + $this->getApplicationURI("merchant/{$id}/subscription/"), + 'fa-shopping-cart'); + + $nav->addFilter( + 'managers', + pht('Managers'), + $this->getApplicationURI("/merchant/manager/{$id}/"), + 'fa-group'); + } + + $nav->selectFilter($filter); + + return $nav; + } + +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php index 2f5232158d..10dee4b0ea 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php @@ -1,7 +1,7 @@ getViewer(); @@ -16,21 +16,15 @@ final class PhortuneMerchantViewController return new Aphront404Response(); } + $this->setMerchant($merchant); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($merchant->getName()); - $crumbs->setBorder(true); + $header = $this->buildHeaderView(); $title = pht( 'Merchant %d %s', $merchant->getID(), $merchant->getName()); - $header = id(new PHUIHeaderView()) - ->setHeader($merchant->getName()) - ->setUser($viewer) - ->setPolicyObject($merchant) - ->setImage($merchant->getProfileImageURI()); - $providers = id(new PhortunePaymentProviderConfigQuery()) ->setViewer($viewer) ->withMerchantPHIDs(array($merchant->getPHID())) @@ -48,6 +42,8 @@ final class PhortuneMerchantViewController new PhortuneMerchantTransactionQuery()); $timeline->setShouldTerminate(true); + $navigation = $this->buildSideNavView('overview'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) @@ -60,6 +56,7 @@ final class PhortuneMerchantViewController return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($navigation) ->appendChild($view); } @@ -196,22 +193,6 @@ final class PhortuneMerchantViewController ->setWorkflow(!$can_edit) ->setHref($this->getApplicationURI("merchant/picture/{$id}/"))); - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View Orders')) - ->setIcon('fa-shopping-cart') - ->setHref($this->getApplicationURI("merchant/orders/{$id}/")) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View Subscriptions')) - ->setIcon('fa-moon-o') - ->setHref($this->getApplicationURI("merchant/{$id}/subscription/")) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('New Invoice')) @@ -240,7 +221,7 @@ final class PhortuneMerchantViewController } $curtain->newPanel() - ->setHeaderText(pht('Members')) + ->setHeaderText(pht('Managers')) ->appendChild($member_list); return $curtain; diff --git a/src/applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php b/src/applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php index 89b14ae67f..71a0e56a33 100644 --- a/src/applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php +++ b/src/applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php @@ -14,7 +14,7 @@ final class PhortuneAccountHasMemberEdgeType extends PhabricatorEdgeType { $add_edges) { return pht( - '%s added %s account member(s): %s.', + '%s added %s account manager(s): %s.', $actor, $add_count, $add_edges); @@ -26,7 +26,7 @@ final class PhortuneAccountHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s removed %s account member(s): %s.', + '%s removed %s account manager(s): %s.', $actor, $rem_count, $rem_edges); @@ -41,7 +41,7 @@ final class PhortuneAccountHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s edited %s account member(s), added %s: %s; removed %s: %s.', + '%s edited %s account manager(s), added %s: %s; removed %s: %s.', $actor, $total_count, $add_count, @@ -57,7 +57,7 @@ final class PhortuneAccountHasMemberEdgeType extends PhabricatorEdgeType { $add_edges) { return pht( - '%s added %s account member(s) to %s: %s.', + '%s added %s account manager(s) to %s: %s.', $actor, $add_count, $object, @@ -71,7 +71,7 @@ final class PhortuneAccountHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s removed %s account member(s) from %s: %s.', + '%s removed %s account manager(s) from %s: %s.', $actor, $rem_count, $object, @@ -88,7 +88,7 @@ final class PhortuneAccountHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s edited %s account member(s) for %s, added %s: %s; removed %s: %s.', + '%s edited %s account manager(s) for %s, added %s: %s; removed %s: %s.', $actor, $total_count, $object, diff --git a/src/applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php b/src/applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php index 8122b06827..2c94e69b83 100644 --- a/src/applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php +++ b/src/applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php @@ -14,7 +14,7 @@ final class PhortuneMerchantHasMemberEdgeType extends PhabricatorEdgeType { $add_edges) { return pht( - '%s added %s merchant member(s): %s.', + '%s added %s merchant manager(s): %s.', $actor, $add_count, $add_edges); @@ -26,7 +26,7 @@ final class PhortuneMerchantHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s removed %s merchant member(s): %s.', + '%s removed %s merchant manager(s): %s.', $actor, $rem_count, $rem_edges); @@ -41,7 +41,7 @@ final class PhortuneMerchantHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s edited %s merchant member(s), added %s: %s; removed %s: %s.', + '%s edited %s merchant manager(s), added %s: %s; removed %s: %s.', $actor, $total_count, $add_count, @@ -57,7 +57,7 @@ final class PhortuneMerchantHasMemberEdgeType extends PhabricatorEdgeType { $add_edges) { return pht( - '%s added %s merchant member(s) to %s: %s.', + '%s added %s merchant manager(s) to %s: %s.', $actor, $add_count, $object, @@ -71,7 +71,7 @@ final class PhortuneMerchantHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s removed %s merchant member(s) from %s: %s.', + '%s removed %s merchant manager(s) from %s: %s.', $actor, $rem_count, $object, @@ -88,7 +88,7 @@ final class PhortuneMerchantHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s edited %s merchant member(s) for %s, added %s: %s; removed %s: %s.', + '%s edited %s merchant manager(s) for %s, added %s: %s; removed %s: %s.', $actor, $total_count, $object, diff --git a/src/applications/phortune/editor/PhortuneAccountEditEngine.php b/src/applications/phortune/editor/PhortuneAccountEditEngine.php new file mode 100644 index 0000000000..ed0e4b01be --- /dev/null +++ b/src/applications/phortune/editor/PhortuneAccountEditEngine.php @@ -0,0 +1,108 @@ +getViewer()); + } + + protected function newObjectQuery() { + return new PhortuneAccountQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Payment Account'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Account: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getName(); + } + + protected function getObjectCreateShortText() { + return pht('Create Account'); + } + + protected function getObjectName() { + return pht('Account'); + } + + protected function getObjectCreateCancelURI($object) { + return $this->getApplication()->getApplicationURI('/'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('edit/'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + + if ($this->getIsCreate()) { + $member_phids = array($viewer->getPHID()); + } else { + $member_phids = $object->getMemberPHIDs(); + } + + $fields = array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Account name.')) + ->setConduitTypeDescription(pht('New account name.')) + ->setTransactionType( + PhortuneAccountNameTransaction::TRANSACTIONTYPE) + ->setValue($object->getName()) + ->setIsRequired(true), + + id(new PhabricatorUsersEditField()) + ->setKey('managers') + ->setAliases(array('memberPHIDs', 'managerPHIDs')) + ->setLabel(pht('Managers')) + ->setUseEdgeTransactions(true) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhortuneAccountHasMemberEdgeType::EDGECONST) + ->setDescription(pht('Initial account managers.')) + ->setConduitDescription(pht('Set account managers.')) + ->setConduitTypeDescription(pht('New list of managers.')) + ->setInitialValue($object->getMemberPHIDs()) + ->setValue($member_phids), + ); + + return $fields; + + } + +} diff --git a/src/applications/phortune/editor/PhortuneAccountEditor.php b/src/applications/phortune/editor/PhortuneAccountEditor.php index 556df99fbf..50a71c476f 100644 --- a/src/applications/phortune/editor/PhortuneAccountEditor.php +++ b/src/applications/phortune/editor/PhortuneAccountEditor.php @@ -1,6 +1,5 @@ getTransactionType()) { - case PhortuneAccountTransaction::TYPE_NAME: - return $object->getName(); - } - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhortuneAccountTransaction::TYPE_NAME: - return $xaction->getNewValue(); - } - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhortuneAccountTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - } - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhortuneAccountTransaction::TYPE_NAME: - return; - } - return parent::applyCustomExternalTransaction($object, $xaction); - } - protected function validateTransaction( PhabricatorLiskDAO $object, $type, @@ -70,48 +28,55 @@ final class PhortuneAccountEditor $errors = parent::validateTransaction($object, $type, $xactions); + $viewer = $this->requireActor(); + switch ($type) { - case PhortuneAccountTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Account name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; case PhabricatorTransactions::TYPE_EDGE: foreach ($xactions as $xaction) { switch ($xaction->getMetadataValue('edge:type')) { case PhortuneAccountHasMemberEdgeType::EDGECONST: - // TODO: This is a bit cumbersome, but validation happens before - // transaction normalization. Maybe provide a cleaner attack on - // this eventually? There's no way to generate "+" or "-" - // transactions right now. - $new = $xaction->getNewValue(); - $set = idx($new, '=', array()); + $old = $object->getMemberPHIDs(); + $new = $this->getPHIDTransactionNewValue($xaction, $old); - if (empty($set[$this->requireActor()->getPHID()])) { + $old = array_fuse($old); + $new = array_fuse($new); + + foreach ($new as $new_phid) { + if (isset($old[$new_phid])) { + continue; + } + + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($new_phid)) + ->executeOne(); + if (!$user) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'Account managers must be valid users, "%s" is not.', + $new_phid)); + $errors[] = $error; + continue; + } + } + + $actor_phid = $this->getActingAsPHID(); + if (!isset($new[$actor_phid])) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), - pht('You can not remove yourself as an account member.'), + pht('You can not remove yourself as an account manager.'), $xaction); $errors[] = $error; } - break; + break; } } break; } - return $errors; } + } diff --git a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php index bcc188f7e3..596f842574 100644 --- a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php +++ b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php @@ -58,7 +58,7 @@ final class PhortuneMerchantEditEngine } protected function getObjectViewURI($object) { - return $object->getViewURI(); + return $object->getURI(); } public function isEngineConfigurable() { @@ -81,21 +81,22 @@ final class PhortuneMerchantEditEngine ->setDescription(pht('Merchant name.')) ->setConduitTypeDescription(pht('New Merchant name.')) ->setIsRequired(true) - ->setTransactionType(PhortuneMerchantTransaction::TYPE_NAME) + ->setTransactionType( + PhortuneMerchantNameTransaction::TRANSACTIONTYPE) ->setValue($object->getName()), id(new PhabricatorUsersEditField()) ->setKey('members') - ->setAliases(array('memberPHIDs')) - ->setLabel(pht('Members')) + ->setAliases(array('memberPHIDs', 'managerPHIDs')) + ->setLabel(pht('Managers')) ->setUseEdgeTransactions(true) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue( 'edge:type', PhortuneMerchantHasMemberEdgeType::EDGECONST) - ->setDescription(pht('Initial merchant members.')) - ->setConduitDescription(pht('Set merchant members.')) - ->setConduitTypeDescription(pht('New list of members.')) + ->setDescription(pht('Initial merchant managers.')) + ->setConduitDescription(pht('Set merchant managers.')) + ->setConduitTypeDescription(pht('New list of managers.')) ->setInitialValue($object->getMemberPHIDs()) ->setValue($member_phids), @@ -104,7 +105,8 @@ final class PhortuneMerchantEditEngine ->setLabel(pht('Description')) ->setDescription(pht('Merchant description.')) ->setConduitTypeDescription(pht('New merchant description.')) - ->setTransactionType(PhortuneMerchantTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + PhortuneMerchantDescriptionTransaction::TRANSACTIONTYPE) ->setValue($object->getDescription()), id(new PhabricatorRemarkupEditField()) @@ -112,7 +114,8 @@ final class PhortuneMerchantEditEngine ->setLabel(pht('Contact Info')) ->setDescription(pht('Merchant contact information.')) ->setConduitTypeDescription(pht('Merchant contact information.')) - ->setTransactionType(PhortuneMerchantTransaction::TYPE_CONTACTINFO) + ->setTransactionType( + PhortuneMerchantContactInfoTransaction::TRANSACTIONTYPE) ->setValue($object->getContactInfo()), id(new PhabricatorTextEditField()) @@ -121,7 +124,8 @@ final class PhortuneMerchantEditEngine ->setDescription(pht('Email address invoices are sent from.')) ->setConduitTypeDescription( pht('Email address invoices are sent from.')) - ->setTransactionType(PhortuneMerchantTransaction::TYPE_INVOICEEMAIL) + ->setTransactionType( + PhortuneMerchantInvoiceEmailTransaction::TRANSACTIONTYPE) ->setValue($object->getInvoiceEmail()), id(new PhabricatorRemarkupEditField()) @@ -129,7 +133,8 @@ final class PhortuneMerchantEditEngine ->setLabel(pht('Invoice Footer')) ->setDescription(pht('Footer on invoice forms.')) ->setConduitTypeDescription(pht('Footer on invoice forms.')) - ->setTransactionType(PhortuneMerchantTransaction::TYPE_INVOICEFOOTER) + ->setTransactionType( + PhortuneMerchantInvoiceFooterTransaction::TRANSACTIONTYPE) ->setValue($object->getInvoiceFooter()), ); diff --git a/src/applications/phortune/editor/PhortuneMerchantEditor.php b/src/applications/phortune/editor/PhortuneMerchantEditor.php index 53fc7c94f2..954570be3f 100644 --- a/src/applications/phortune/editor/PhortuneMerchantEditor.php +++ b/src/applications/phortune/editor/PhortuneMerchantEditor.php @@ -11,104 +11,19 @@ final class PhortuneMerchantEditor return pht('Phortune Merchants'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this merchant.', $author); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhortuneMerchantTransaction::TYPE_NAME; - $types[] = PhortuneMerchantTransaction::TYPE_DESCRIPTION; - $types[] = PhortuneMerchantTransaction::TYPE_CONTACTINFO; - $types[] = PhortuneMerchantTransaction::TYPE_PICTURE; - $types[] = PhortuneMerchantTransaction::TYPE_INVOICEEMAIL; - $types[] = PhortuneMerchantTransaction::TYPE_INVOICEFOOTER; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDGE; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhortuneMerchantTransaction::TYPE_NAME: - return $object->getName(); - case PhortuneMerchantTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case PhortuneMerchantTransaction::TYPE_CONTACTINFO: - return $object->getContactInfo(); - case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: - return $object->getInvoiceEmail(); - case PhortuneMerchantTransaction::TYPE_INVOICEFOOTER: - return $object->getInvoiceFooter(); - case PhortuneMerchantTransaction::TYPE_PICTURE: - return $object->getProfileImagePHID(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhortuneMerchantTransaction::TYPE_NAME: - case PhortuneMerchantTransaction::TYPE_DESCRIPTION: - case PhortuneMerchantTransaction::TYPE_CONTACTINFO: - case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: - case PhortuneMerchantTransaction::TYPE_INVOICEFOOTER: - case PhortuneMerchantTransaction::TYPE_PICTURE: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhortuneMerchantTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - case PhortuneMerchantTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - return; - case PhortuneMerchantTransaction::TYPE_CONTACTINFO: - $object->setContactInfo($xaction->getNewValue()); - return; - case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: - $object->setInvoiceEmail($xaction->getNewValue()); - return; - case PhortuneMerchantTransaction::TYPE_INVOICEFOOTER: - $object->setInvoiceFooter($xaction->getNewValue()); - return; - case PhortuneMerchantTransaction::TYPE_PICTURE: - $object->setProfileImagePHID($xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhortuneMerchantTransaction::TYPE_NAME: - case PhortuneMerchantTransaction::TYPE_DESCRIPTION: - case PhortuneMerchantTransaction::TYPE_CONTACTINFO: - case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: - case PhortuneMerchantTransaction::TYPE_INVOICEFOOTER: - case PhortuneMerchantTransaction::TYPE_PICTURE: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - protected function validateTransaction( PhabricatorLiskDAO $object, $type, @@ -117,48 +32,28 @@ final class PhortuneMerchantEditor $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { - case PhortuneMerchantTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Merchant name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: - $new_email = null; + case PhabricatorTransactions::TYPE_EDGE: foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: - $new_email = $xaction->getNewValue(); - break; - } - } - if (strlen($new_email)) { - $email = new PhutilEmailAddress($new_email); - $domain = $email->getDomainName(); - - if (!$domain) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('%s is not a valid email.', $new_email), - nonempty(last($xactions), null)); - - $errors[] = $error; + switch ($xaction->getMetadataValue('edge:type')) { + case PhortuneMerchantHasMemberEdgeType::EDGECONST: + $new = $xaction->getNewValue(); + $set = idx($new, '-', array()); + $actor_phid = $this->requireActor()->getPHID(); + foreach ($set as $phid) { + if ($actor_phid == $phid) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('You can not remove yourself as an merchant manager.'), + $xaction); + $errors[] = $error; + } + } + break; } } break; } - return $errors; } diff --git a/src/applications/phortune/storage/PhortuneAccount.php b/src/applications/phortune/storage/PhortuneAccount.php index e86fd53df2..ff3b0d8a84 100644 --- a/src/applications/phortune/storage/PhortuneAccount.php +++ b/src/applications/phortune/storage/PhortuneAccount.php @@ -16,11 +16,8 @@ final class PhortuneAccount extends PhortuneDAO private $memberPHIDs = self::ATTACHABLE; public static function initializeNewAccount(PhabricatorUser $actor) { - $account = id(new PhortuneAccount()); - - $account->memberPHIDs = array(); - - return $account; + return id(new self()) + ->attachMemberPHIDs(array()); } public static function createNewAccount( @@ -31,7 +28,7 @@ final class PhortuneAccount extends PhortuneDAO $xactions = array(); $xactions[] = id(new PhortuneAccountTransaction()) - ->setTransactionType(PhortuneAccountTransaction::TYPE_NAME) + ->setTransactionType(PhortuneAccountNameTransaction::TRANSACTIONTYPE) ->setNewValue(pht('Default Account')); $xactions[] = id(new PhortuneAccountTransaction()) @@ -96,6 +93,10 @@ final class PhortuneAccount extends PhortuneDAO return $this; } + public function getURI() { + return '/phortune/'.$this->getID().'/'; + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/phortune/storage/PhortuneAccountTransaction.php b/src/applications/phortune/storage/PhortuneAccountTransaction.php index 5d4a746ad9..6733cbe879 100644 --- a/src/applications/phortune/storage/PhortuneAccountTransaction.php +++ b/src/applications/phortune/storage/PhortuneAccountTransaction.php @@ -1,9 +1,7 @@ getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this account.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this account from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'PhortuneAccountTransactionType'; } } diff --git a/src/applications/phortune/storage/PhortuneMerchant.php b/src/applications/phortune/storage/PhortuneMerchant.php index 69675c2ebc..81d0391370 100644 --- a/src/applications/phortune/storage/PhortuneMerchant.php +++ b/src/applications/phortune/storage/PhortuneMerchant.php @@ -53,7 +53,7 @@ final class PhortuneMerchant extends PhortuneDAO return $this; } - public function getViewURI() { + public function getURI() { return '/phortune/merchant/'.$this->getID().'/'; } @@ -70,6 +70,7 @@ final class PhortuneMerchant extends PhortuneDAO return $this->assertAttached($this->profileImageFile); } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/phortune/storage/PhortuneMerchantTransaction.php b/src/applications/phortune/storage/PhortuneMerchantTransaction.php index c5f8db6d43..3befb12212 100644 --- a/src/applications/phortune/storage/PhortuneMerchantTransaction.php +++ b/src/applications/phortune/storage/PhortuneMerchantTransaction.php @@ -1,14 +1,7 @@ getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this merchant.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this merchant from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for this merchant.', - $this->renderHandleLink($author_phid)); - case self::TYPE_CONTACTINFO: - return pht( - '%s updated the contact information for this merchant.', - $this->renderHandleLink($author_phid)); - case self::TYPE_INVOICEEMAIL: - return pht( - '%s updated the invoice email for this merchant.', - $this->renderHandleLink($author_phid)); - case self::TYPE_INVOICEFOOTER: - return pht( - '%s updated the invoice footer for this merchant.', - $this->renderHandleLink($author_phid)); - } - - return parent::getTitle(); - } - - public function shouldHide() { - $old = $this->getOldValue(); - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - case self::TYPE_CONTACTINFO: - case self::TYPE_INVOICEEMAIL: - case self::TYPE_INVOICEFOOTER: - return ($old === null); - } - return parent::shouldHide(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($this->getOldValue() !== null); - case self::TYPE_CONTACTINFO: - return ($this->getOldValue() !== null); - case self::TYPE_INVOICEEMAIL: - return ($this->getOldValue() !== null); - case self::TYPE_INVOICEFOOTER: - return ($this->getOldValue() !== null); - } - - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); + public function getBaseTransactionClass() { + return 'PhortuneMerchantTransactionType'; } } diff --git a/src/applications/phortune/view/PhortuneChargeTableView.php b/src/applications/phortune/view/PhortuneChargeTableView.php index 4e82404cc6..663c470a81 100644 --- a/src/applications/phortune/view/PhortuneChargeTableView.php +++ b/src/applications/phortune/view/PhortuneChargeTableView.php @@ -55,6 +55,7 @@ final class PhortuneChargeTableView extends AphrontView { } $table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('No charges found.')) ->setHeaders( array( pht('ID'), diff --git a/src/applications/phortune/xaction/PhortuneAccountNameTransaction.php b/src/applications/phortune/xaction/PhortuneAccountNameTransaction.php new file mode 100644 index 0000000000..08b8b69422 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneAccountNameTransaction.php @@ -0,0 +1,55 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($old) && strlen($new)) { + return pht( + '%s renamed this account from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } else { + return pht( + '%s created this account.', + $this->renderAuthor()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Accounts must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newRequiredError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneAccountTransactionType.php b/src/applications/phortune/xaction/PhortuneAccountTransactionType.php new file mode 100644 index 0000000000..910ed8d136 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneAccountTransactionType.php @@ -0,0 +1,4 @@ +getContactInfo(); + } + + public function applyInternalEffects($object, $value) { + $object->setContactInfo($value); + } + + public function getTitle() { + return pht( + '%s updated the merchant contact info.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the merchant contact info for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO MERCHANT CONTACT INFO'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneMerchantDescriptionTransaction.php b/src/applications/phortune/xaction/PhortuneMerchantDescriptionTransaction.php new file mode 100644 index 0000000000..34c94a6ea0 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneMerchantDescriptionTransaction.php @@ -0,0 +1,56 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + '%s updated the merchant description.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the merchant description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO MERCHANT DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneMerchantInvoiceEmailTransaction.php b/src/applications/phortune/xaction/PhortuneMerchantInvoiceEmailTransaction.php new file mode 100644 index 0000000000..d58167de25 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneMerchantInvoiceEmailTransaction.php @@ -0,0 +1,94 @@ +getInvoiceEmail(); + } + + public function applyInternalEffects($object, $value) { + $object->setInvoiceEmail($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($old) && strlen($new)) { + return pht( + '%s updated the invoice email from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } else if (strlen($old)) { + return pht( + '%s removed the invoice email.', + $this->renderAuthor()); + } else { + return pht( + '%s set the invoice email to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($old) && strlen($new)) { + return pht( + '%s updated %s invoice email from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } else if (strlen($old)) { + return pht( + '%s removed the invoice email for %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s set the invoice email for %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewValue()); + } + } + + public function getIcon() { + return 'fa-envelope'; + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('invoiceEmail'); + foreach ($xactions as $xaction) { + if (strlen($xaction->getNewValue())) { + $email = new PhutilEmailAddress($xaction->getNewValue()); + $domain = $email->getDomainName(); + if (!strlen($domain)) { + $errors[] = $this->newInvalidError( + pht('Invoice email "%s" must be a valid email.', + $xaction->getNewValue())); + } + + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The email can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + } + + return $errors; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneMerchantInvoiceFooterTransaction.php b/src/applications/phortune/xaction/PhortuneMerchantInvoiceFooterTransaction.php new file mode 100644 index 0000000000..6d8f584104 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneMerchantInvoiceFooterTransaction.php @@ -0,0 +1,56 @@ +getInvoiceFooter(); + } + + public function applyInternalEffects($object, $value) { + $object->setInvoiceFooter($value); + } + + public function getTitle() { + return pht( + '%s updated the merchant invoice footer.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the merchant invoice footer for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO MERCHANT INVOICE FOOTER'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneMerchantNameTransaction.php b/src/applications/phortune/xaction/PhortuneMerchantNameTransaction.php new file mode 100644 index 0000000000..b0115ace3e --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneMerchantNameTransaction.php @@ -0,0 +1,55 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this merchant from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s renamed %s merchant name from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Merchants must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneMerchantPictureTransaction.php b/src/applications/phortune/xaction/PhortuneMerchantPictureTransaction.php new file mode 100644 index 0000000000..7502592b3f --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneMerchantPictureTransaction.php @@ -0,0 +1,33 @@ +getProfileImagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setProfileImagePHID($value); + } + + public function getTitle() { + return pht( + '%s updated the picture.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the picture for merchant %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-camera-retro'; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneMerchantTransactionType.php b/src/applications/phortune/xaction/PhortuneMerchantTransactionType.php new file mode 100644 index 0000000000..e2c2eb85a5 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneMerchantTransactionType.php @@ -0,0 +1,4 @@ +writeOut(" %s\n", PhabricatorPolicy::getPolicyExplanation($viewer, $policy->getPHID())); $console->writeOut("\n"); - - $more = (array)$object->describeAutomaticCapability($capability); - if ($more) { - foreach ($more as $line) { - $console->writeOut(" %s\n", $line); - } - $console->writeOut("\n"); - } } + if ($object instanceof PhabricatorPolicyCodexInterface) { + $codex = PhabricatorPolicyCodex::newFromObject($object, $viewer); + + $rules = $codex->getPolicySpecialRuleDescriptions(); + foreach ($rules as $rule) { + echo tsprintf( + " - %s\n", + $rule->getDescription()); + } + + echo "\n"; + } } } diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index fec7c1f8af..767cfe5290 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -163,6 +163,8 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $author_phid, id(new PhabricatorDiffusionApplication())->getPHID()); + $acting_user = $this->loadActingUser($actor, $acting_as_phid); + $conn_w = id(new DifferentialRevision())->establishConnection('w'); // NOTE: The `differential_commit` table has a unique ID on `commitPHID`, @@ -263,7 +265,8 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $acting_as_phid, $repository, $commit, - $message); + $message, + $acting_user); } $data->save(); @@ -287,7 +290,22 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $acting_as, PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit, - $message) { + $message, + PhabricatorUser $acting_user = null) { + + // If we we were able to identify an author for the commit, we try to act + // as that user when loading tasks marked with "Fixes Txxx". This prevents + // mistakes where a user accidentally writes the wrong task IDs and affects + // tasks they can't see (and thus can't undo the status changes for). + + // This is just a guard rail, not a security measure. An attacker can still + // forge another user's identity trivially by forging author or committer + // emails. We also let commits with unrecognized authors act on any task to + // make behavior less confusing for new installs. + + if (!$acting_user) { + $acting_user = $actor; + } $maniphest = 'PhabricatorManiphestApplication'; if (!PhabricatorApplication::isClassInstalled($maniphest)) { @@ -321,9 +339,14 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker } $tasks = id(new ManiphestTaskQuery()) - ->setViewer($actor) + ->setViewer($acting_user) ->withIDs(array_keys($task_statuses)) ->needProjectPHIDs(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) ->execute(); foreach ($tasks as $task_id => $task) { @@ -369,4 +392,26 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker } } + private function loadActingUser(PhabricatorUser $viewer, $user_phid) { + if (!$user_phid) { + return null; + } + + $user_type = PhabricatorPeopleUserPHIDType::TYPECONST; + if (phid_get_type($user_phid) != $user_type) { + return null; + } + + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($user_phid)) + ->executeOne(); + if (!$user) { + return null; + } + + return $user; + } + + } diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 4329509b02..5b2f7827f9 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -978,9 +978,15 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { $objects = $query->executeWithCursorPager($pager); } + $this->didExecuteQuery($query); + return $objects; } + protected function didExecuteQuery(PhabricatorPolicyAwareQuery $query) { + return; + } + /* -( Rendering )---------------------------------------------------------- */ diff --git a/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php index 5e919258bd..588ccc3e5e 100644 --- a/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php @@ -101,4 +101,8 @@ abstract class PhabricatorFulltextStorageEngine extends Phobject { public function initIndex() {} + public function getFulltextTokens() { + return array(); + } + } diff --git a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php index 72c49576f0..5bb9943e59 100644 --- a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php @@ -3,6 +3,9 @@ final class PhabricatorMySQLFulltextStorageEngine extends PhabricatorFulltextStorageEngine { + private $fulltextTokens = array(); + private $engineLimits; + public function getEngineIdentifier() { return 'mysql'; } @@ -203,8 +206,64 @@ final class PhabricatorMySQLFulltextStorageEngine $title_field = PhabricatorSearchDocumentFieldType::FIELD_TITLE; $title_boost = 1024; + $stemmer = new PhutilSearchStemmer(); + $raw_query = $query->getParameter('query'); - $compiled_query = $this->compileQuery($raw_query); + $raw_query = trim($raw_query); + if (strlen($raw_query)) { + $compiler = PhabricatorSearchDocument::newQueryCompiler() + ->setStemmer($stemmer); + + $tokens = $compiler->newTokens($raw_query); + + list($min_length, $stopword_list) = $this->getEngineLimits($conn); + + // Process all the parts of the user's query so we can show them which + // parts we searched for and which ones we ignored. + $fulltext_tokens = array(); + foreach ($tokens as $key => $token) { + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($token); + + $fulltext_tokens[$key] = $fulltext_token; + + $value = $token->getValue(); + if (phutil_utf8_strlen($value) < $min_length) { + $fulltext_token->setIsShort(true); + continue; + } + + if (isset($stopword_list[phutil_utf8_strtolower($value)])) { + $fulltext_token->setIsStopword(true); + continue; + } + } + $this->fulltextTokens = $fulltext_tokens; + + // Remove tokens which aren't queryable from the query. This is mostly + // a workaround for the peculiar behaviors described in T12137. + foreach ($this->fulltextTokens as $key => $fulltext_token) { + if (!$fulltext_token->isQueryable()) { + unset($tokens[$key]); + } + } + + if (!$tokens) { + throw new PhutilSearchQueryCompilerSyntaxException( + pht( + 'All of your search terms are too short or too common to '. + 'appear in the search index. Search for longer or more '. + 'distinctive terms.')); + } + + $queries = array(); + $queries[] = $compiler->compileLiteralQuery($tokens); + $queries[] = $compiler->compileStemmedQuery($tokens); + $compiled_query = implode(' ', array_filter($queries)); + } else { + $compiled_query = null; + } + if (strlen($compiled_query)) { $select[] = qsprintf( $conn, @@ -394,20 +453,6 @@ final class PhabricatorMySQLFulltextStorageEngine return $sql; } - private function compileQuery($raw_query) { - $stemmer = new PhutilSearchStemmer(); - - $compiler = PhabricatorSearchDocument::newQueryCompiler() - ->setQuery($raw_query) - ->setStemmer($stemmer); - - $queries = array(); - $queries[] = $compiler->compileLiteralQuery(); - $queries[] = $compiler->compileStemmedQuery(); - - return implode(' ', array_filter($queries)); - } - public function indexExists() { return true; } @@ -416,4 +461,69 @@ final class PhabricatorMySQLFulltextStorageEngine return false; } + public function getFulltextTokens() { + return $this->fulltextTokens; + } + + private function getEngineLimits(AphrontDatabaseConnection $conn) { + if ($this->engineLimits === null) { + $this->engineLimits = $this->newEngineLimits($conn); + } + return $this->engineLimits; + } + + private function newEngineLimits(AphrontDatabaseConnection $conn) { + // First, try InnoDB. Some database may not have both table engines, so + // selecting variables from missing table engines can fail and throw. + + try { + $result = queryfx_one( + $conn, + 'SELECT @@innodb_ft_min_token_size innodb_max'); + } catch (AphrontQueryException $ex) { + $result = null; + } + + if ($result) { + $min_len = $result['innodb_max']; + $stopwords = queryfx_all( + $conn, + 'SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD'); + $stopwords = ipull($stopwords, 'value'); + $stopwords = array_fuse($stopwords); + + return array($min_len, $stopwords); + } + + // If InnoDB fails, try MyISAM. + $result = queryfx_one( + $conn, + 'SELECT + @@ft_min_word_len myisam_max, + @@ft_stopword_file myisam_stopwords'); + + $min_len = $result['myisam_max']; + + $file = $result['myisam_stopwords']; + if (preg_match('(/resources/sql/stopwords\.txt\z)', $file)) { + // If this is set to something that looks like the Phabricator + // stopword file, read that. + $file = 'stopwords.txt'; + } else { + // Otherwise, just use the default stopwords. This might be wrong + // but we can't read the actual value dynamically and reading + // whatever file the variable is set to could be a big headache + // to get right from a security perspective. + $file = 'stopwords_myisam.txt'; + } + + $root = dirname(phutil_get_library_root('phabricator')); + $data = Filesystem::readFile($root.'/resources/sql/'.$file); + $stopwords = explode("\n", $data); + $stopwords = array_filter($stopwords); + $stopwords = array_fuse($stopwords); + + return array($min_len, $stopwords); + } + } diff --git a/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php new file mode 100644 index 0000000000..6a91188c8f --- /dev/null +++ b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php @@ -0,0 +1,177 @@ +conpherence = $conpherence; + return $this; + } + + public function getConpherence() { + $conpherence = $this->conpherence; + + if (!$conpherence) { + return null; + } + + return $conpherence; + } + + public function willBuildNavigationItems(array $items) { + $viewer = $this->getViewer(); + $room_phids = array(); + foreach ($items as $item) { + $room_phids[] = $item->getMenuItemProperty('conpherence'); + } + + $rooms = id(new ConpherenceThreadQuery()) + ->setViewer($viewer) + ->withPHIDs($room_phids) + ->needProfileImage(true) + ->execute(); + + $rooms = mpull($rooms, null, 'getPHID'); + foreach ($items as $item) { + $room_phid = $item->getMenuItemProperty('conpherence'); + $room = idx($rooms, $room_phid, null); + $item->getMenuItem()->attachConpherence($room); + } + } + + public function getDisplayName( + PhabricatorProfileMenuItemConfiguration $config) { + $room = $this->getConpherence($config); + if (!$room) { + return pht('(Restricted/Invalid Conpherence)'); + } + + $name = $this->getName($config); + if (strlen($name)) { + return $name; + } + + return $room->getTitle(); + } + + public function buildEditEngineFields( + PhabricatorProfileMenuItemConfiguration $config) { + return array( + id(new PhabricatorDatasourceEditField()) + ->setKey(self::FIELD_CONPHERENCE) + ->setLabel(pht('Conpherence Room')) + ->setDatasource(new ConpherenceThreadDatasource()) + ->setIsRequired(true) + ->setSingleValue($config->getMenuItemProperty('conpherence')), + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setValue($this->getName($config)), + ); + } + + private function getName( + PhabricatorProfileMenuItemConfiguration $config) { + return $config->getMenuItemProperty('name'); + } + + protected function newNavigationMenuItems( + PhabricatorProfileMenuItemConfiguration $config) { + $viewer = $this->getViewer(); + $room = $this->getConpherence($config); + if (!$room) { + return array(); + } + + $participants = $room->getParticipants(); + $viewer_phid = $viewer->getPHID(); + $unread_count = null; + if (isset($participants[$viewer_phid])) { + $data = $room->getDisplayData($viewer); + $unread_count = $data['unread_count']; + } + + $count = null; + if ($unread_count) { + $count = phutil_tag( + 'span', + array( + 'class' => 'phui-list-item-count', + ), + $unread_count); + } + + $item = $this->newItem() + ->setHref('/'.$room->getMonogram()) + ->setName($this->getDisplayName($config)) + ->setIcon('fa-comments') + ->appendChild($count); + + return array( + $item, + ); + } + + public function validateTransactions( + PhabricatorProfileMenuItemConfiguration $config, + $field_key, + $value, + array $xactions) { + + $viewer = $this->getViewer(); + $errors = array(); + + if ($field_key == self::FIELD_CONPHERENCE) { + if ($this->isEmptyTransaction($value, $xactions)) { + $errors[] = $this->newRequiredError( + pht('You must choose a room.'), + $field_key); + } + + foreach ($xactions as $xaction) { + $new = $xaction['new']; + + if (!$new) { + continue; + } + + if ($new === $value) { + continue; + } + + $rooms = id(new ConpherenceThreadQuery()) + ->setViewer($viewer) + ->withPHIDs(array($new)) + ->execute(); + if (!$rooms) { + $errors[] = $this->newInvalidError( + pht( + 'Room "%s" is not a valid room which you have '. + 'permission to see.', + $new), + $xaction['xaction']); + } + } + } + + return $errors; + } + +} diff --git a/src/applications/search/query/PhabricatorFulltextResultSet.php b/src/applications/search/query/PhabricatorFulltextResultSet.php new file mode 100644 index 0000000000..decc863407 --- /dev/null +++ b/src/applications/search/query/PhabricatorFulltextResultSet.php @@ -0,0 +1,26 @@ +phids = $phids; + return $this; + } + + public function getPHIDs() { + return $this->phids; + } + + public function setFulltextTokens($fulltext_tokens) { + $this->fulltextTokens = $fulltext_tokens; + return $this; + } + + public function getFulltextTokens() { + return $this->fulltextTokens; + } + +} diff --git a/src/applications/search/query/PhabricatorFulltextToken.php b/src/applications/search/query/PhabricatorFulltextToken.php new file mode 100644 index 0000000000..d42c15e9e6 --- /dev/null +++ b/src/applications/search/query/PhabricatorFulltextToken.php @@ -0,0 +1,88 @@ +token = $token; + return $this; + } + + public function getToken() { + return $this->token; + } + + public function isQueryable() { + return !$this->getIsShort() && !$this->getIsStopword(); + } + + public function setIsShort($is_short) { + $this->isShort = $is_short; + return $this; + } + + public function getIsShort() { + return $this->isShort; + } + + public function setIsStopword($is_stopword) { + $this->isStopword = $is_stopword; + return $this; + } + + public function getIsStopword() { + return $this->isStopword; + } + + public function newTag() { + $token = $this->getToken(); + + $tip = null; + $icon = null; + + if ($this->getIsShort()) { + $shade = PHUITagView::COLOR_GREY; + $tip = pht('Ignored Short Word'); + } else if ($this->getIsStopword()) { + $shade = PHUITagView::COLOR_GREY; + $tip = pht('Ignored Common Word'); + } else { + $operator = $token->getOperator(); + switch ($operator) { + case PhutilSearchQueryCompiler::OPERATOR_NOT: + $shade = PHUITagView::COLOR_RED; + $icon = 'fa-minus'; + break; + default: + $shade = PHUITagView::COLOR_BLUE; + break; + } + } + + $tag = id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setShade($shade) + ->setName($token->getValue()); + + if ($tip !== null) { + Javelin::initBehavior('phabricator-tooltips'); + + $tag + ->addSigil('has-tooltip') + ->setMetadata( + array( + 'tip' => $tip, + )); + } + + if ($icon !== null) { + $tag->setIcon($icon); + } + + return $tag; + } + +} diff --git a/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php b/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php index f9bcb6804a..d944d61964 100644 --- a/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php +++ b/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php @@ -3,6 +3,8 @@ final class PhabricatorSearchApplicationSearchEngine extends PhabricatorApplicationSearchEngine { + private $resultSet; + public function getResultTypeDescription() { return pht('Fulltext Search Results'); } @@ -243,6 +245,9 @@ final class PhabricatorSearchApplicationSearchEngine PhabricatorSavedQuery $query, array $handles) { + $result_set = $this->resultSet; + $fulltext_tokens = $result_set->getFulltextTokens(); + $viewer = $this->requireViewer(); $list = new PHUIObjectItemListView(); $list->setNoDataString(pht('No results found.')); @@ -263,7 +268,28 @@ final class PhabricatorSearchApplicationSearchEngine } } + $fulltext_view = null; + if ($fulltext_tokens) { + require_celerity_resource('phabricator-search-results-css'); + + $fulltext_view = array(); + foreach ($fulltext_tokens as $token) { + $fulltext_view[] = $token->newTag(); + } + $fulltext_view = phutil_tag( + 'div', + array( + 'class' => 'phui-fulltext-tokens', + ), + array( + pht('Searched For:'), + ' ', + $fulltext_view, + )); + } + $result = new PhabricatorApplicationSearchResultView(); + $result->setContent($fulltext_view); $result->setObjectList($list); return $result; @@ -280,4 +306,8 @@ final class PhabricatorSearchApplicationSearchEngine return $owner_phids; } + protected function didExecuteQuery(PhabricatorPolicyAwareQuery $query) { + $this->resultSet = $query->getFulltextResultSet(); + } + } diff --git a/src/applications/search/query/PhabricatorSearchDocumentQuery.php b/src/applications/search/query/PhabricatorSearchDocumentQuery.php index d4700904c9..4aed4722bd 100644 --- a/src/applications/search/query/PhabricatorSearchDocumentQuery.php +++ b/src/applications/search/query/PhabricatorSearchDocumentQuery.php @@ -1,10 +1,12 @@ savedQuery = $query; @@ -20,11 +22,38 @@ final class PhabricatorSearchDocumentQuery if ($this->objectCapabilities) { return $this->objectCapabilities; } + return $this->getRequiredCapabilities(); } + public function getFulltextResultSet() { + if (!$this->fulltextResultSet) { + throw new PhutilInvalidStateException('execute'); + } + + return $this->fulltextResultSet; + } + + protected function willExecute() { + $this->unfilteredOffset = 0; + $this->fulltextResultSet = null; + } + protected function loadPage() { - $phids = $this->loadDocumentPHIDsWithoutPolicyChecks(); + // NOTE: The offset and limit information in the inherited properties of + // this object represent a policy-filtered offset and limit, but the + // underlying query engine needs an unfiltered offset and limit. We keep + // track of an unfiltered result offset internally. + + $query = id(clone($this->savedQuery)) + ->setParameter('offset', $this->unfilteredOffset) + ->setParameter('limit', $this->getRawResultLimit()); + + $result_set = PhabricatorSearchService::newResultSet($query, $this); + $phids = $result_set->getPHIDs(); + + $this->fulltextResultSet = $result_set; + $this->unfilteredOffset += count($phids); $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->getViewer()) @@ -69,25 +98,13 @@ final class PhabricatorSearchDocumentQuery return $handles; } - public function loadDocumentPHIDsWithoutPolicyChecks() { - $query = id(clone($this->savedQuery)) - ->setParameter('offset', $this->getOffset()) - ->setParameter('limit', $this->getRawResultLimit()); - return PhabricatorSearchService::executeSearch($query); - } - public function getQueryApplicationClass() { return 'PhabricatorSearchApplication'; } - protected function getResultCursor($result) { - throw new Exception( - pht( - 'This query does not support cursor paging; it must be offset paged.')); - } - protected function nextPage(array $page) { - $this->setOffset($this->getOffset() + count($page)); + // We already updated the internal offset in `loadPage()` after loading + // results, so we do not need to make any additional state updates here. return $this; } diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index 55ad678539..a315a5b9cf 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -234,22 +234,15 @@ abstract class PhabricatorModularTransactionType if ($all_day) { $display = phabricator_date($epoch, $viewer); - } else { - $display = phabricator_datetime($epoch, $viewer); - + } else if ($this->isRenderingTargetExternal()) { // When rendering to text, we explicitly render the offset from UTC to // provide context to the date: the mail may be generating with the // server's settings, or the user may later refer back to it after // changing timezones. - if ($this->isRenderingTargetExternal()) { - $offset = $viewer->getTimeZoneOffsetInHours(); - if ($offset >= 0) { - $display = pht('%s (UTC+%d)', $display, $offset); - } else { - $display = pht('%s (UTC-%d)', $display, abs($offset)); - } - } + $display = phabricator_datetimezone($epoch, $viewer); + } else { + $display = phabricator_datetime($epoch, $viewer); } return $this->renderValue($display); diff --git a/src/docs/contributor/developer_setup.diviner b/src/docs/contributor/developer_setup.diviner new file mode 100644 index 0000000000..95508ccd19 --- /dev/null +++ b/src/docs/contributor/developer_setup.diviner @@ -0,0 +1,112 @@ +@title Developer Setup +@group developer + +How to configure a Phabricator development environment. + +Overview +======== + +There are some options and workflows that may be useful if you are developing +or debugging Phabricator. + + +Configuration +============= + +To adjust Phabricator for development: + + - Enable `phabricator.developer-mode` to enable some options and show + more debugging information. + - Enable `phabricator.show-prototypes` to show all the incomplete + applications. + - See @{article: Using DarkConsole} for instructions on enabling the + debugging console. + + +Error Handling +============== + +Errors normally go to DarkConsole (if enabled) and the webserver error log, +which is often located somewhere like `/var/log/apache/error_log`. This file +often contains relevant information after you encounter an error. + +When debugging, you can print information to the error log with `phlog(...)`. +You can `phlog(new Exception(...))` to get a stack trace. + +You can print information to the UI with `throw new Exception(...)`, +`print_r(...)`, or `var_dump(...)`. + +You can abort execution with `die(...)` if you want to make sure execution +does not make it past some point. Normally `throw` does this too, but callers +can `catch` exceptions; they can not catch `die(...)`. + + +Utilities +========= + +After adding, renaming, or moving classes, run `arc liberate` to rebuild +the class map: + +``` +phabricator/ $ arc liberate +``` + +Until you do this, Phabricator won't recognize your new, moved, or renamed +classes. You do not need to run this after modifying an existing class. + +After any modifications to static resources (CSS / JS) but before sending +changes for review or pushing them to the remote, run `bin/celerity map`: + +``` +phabricator/ $ ./bin/celerity map +``` + +This rebuilds the static resource map. + +If you forget to run these commands you'll normally be warned by unit tests, +but knowing about them may prevent confusion before you hit the warnings. + + +Command Line +============ + +Almost every script supports a `--trace` flag, which prints out service +calls and more detailed error information. This is often the best way to get +started with debugging command-line scripts. + + +Performance +=========== + +Although it is more user-focused than developer-focused, the +@{article:Troubleshooting Performance Problems} guide has useful information +on the tools available for diagnosing and understanding performance problems. + + +Custom Domains +============== + +If you're working with applications that support custom domains (like Phurl or +Phame) you can normally test them by adding more entries to your webserver +configuration that look exactly like the primary entry (or expanding the +primary entry to match more domains). + +Phabricator routes all requests based on host headers, so alternate domains +do not normally need any kind of special configuration. + +You may also need to add `/etc/hosts` entries for the domains themselves. + + +Creating Test Data +================== + +You can create test objects with the "Lipsum" utility: + +``` +phabricator/ $ ./bin/lipsum help generate +phabricator/ $ ./bin/lipsum generate ... +``` + +Test data can make your local install feel a little more realistic. With +`--quickly`, you can generate a large amount of test data to help test issues +with performance or scale. diff --git a/src/docs/user/userguide/diffusion_hosting.diviner b/src/docs/user/userguide/diffusion_hosting.diviner index 1a9e7d5bdb..2443b475e8 100644 --- a/src/docs/user/userguide/diffusion_hosting.diviner +++ b/src/docs/user/userguide/diffusion_hosting.diviner @@ -175,15 +175,32 @@ If you're planning to use SSH, you should also edit `/etc/passwd` and account. The second field (which is the password field) must not be set to `!!`. This -value will prevent login. If it is set to `!!`, edit it and set it to `NP` ("no -password") instead. +value will prevent login. + +If you have `usermod` on your system, you can adjust this value with: + +``` +$ sudo usermod -p NP vcs-user +``` + +If you do not have `usermod`, carefully edit the file and set the field value +to `NP` ("no password") instead of `!!`. **`/etc/passwd`**: Open `/etc/passwd` and find the line for the `vcs-user` account. The last field (which is the login shell) must be set to a real shell. If it is set to something like `/bin/false`, then `sshd` will not be able to execute -commands. Instead, you should set it to a real shell, like `/bin/sh`. +commands. + +If you have `usermod` on your system, you can adjust this value with: + +``` +$ sudo usermod -s /bin/sh vcs-user +``` + +If you do not have `usermod`, carefully edit the file and change the field +to point at a real shell, usually `/bin/sh`. Configuring HTTP diff --git a/src/infrastructure/cluster/search/PhabricatorSearchService.php b/src/infrastructure/cluster/search/PhabricatorSearchService.php index a9ceb0e7e5..073f9ffab6 100644 --- a/src/infrastructure/cluster/search/PhabricatorSearchService.php +++ b/src/infrastructure/cluster/search/PhabricatorSearchService.php @@ -245,14 +245,29 @@ class PhabricatorSearchService * @throws PhutilAggregateException */ public static function executeSearch(PhabricatorSavedQuery $query) { + $result_set = self::newResultSet($query); + return $result_set->getPHIDs(); + } + + public static function newResultSet(PhabricatorSavedQuery $query) { $exceptions = array(); // try all services until one succeeds foreach (self::getAllServices() as $service) { + if (!$service->isReadable()) { + continue; + } + try { $engine = $service->getEngine(); - $res = $engine->executeSearch($query); - // return immediately if we get results - return $res; + $phids = $engine->executeSearch($query); + + return id(new PhabricatorFulltextResultSet()) + ->setPHIDs($phids) + ->setFulltextTokens($engine->getFulltextTokens()); + } catch (PhutilSearchQueryCompilerSyntaxException $ex) { + // If there's a query compilation error, return it directly to the + // user: they issued a query with bad syntax. + throw $ex; } catch (Exception $ex) { $exceptions[] = $ex; } diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index ee1b466dcb..95ba57f0c0 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1610,6 +1610,35 @@ final class PhabricatorUSEnglishTranslation '%s accepted this revision as %s reviewer(s): %s.' => '%s accepted this revision as: %3$s.', + + '%s added %s merchant manager(s): %s.' => array( + array( + '%s added a merchant manager: %3$s.', + '%s added merchant managers: %3$s.', + ), + ), + + '%s removed %s merchant manager(s): %s.' => array( + array( + '%s removed a merchant manager: %3$s.', + '%s removed merchant managers: %3$s.', + ), + ), + + '%s added %s account manager(s): %s.' => array( + array( + '%s added an account manager: %3$s.', + '%s added account managers: %3$s.', + ), + ), + + '%s removed %s account manager(s): %s.' => array( + array( + '%s removed an account manager: %3$s.', + '%s removed account managers: %3$s.', + ), + ), + ); } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementOptimizeWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementOptimizeWorkflow.php new file mode 100644 index 0000000000..5c1c49b0b2 --- /dev/null +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementOptimizeWorkflow.php @@ -0,0 +1,86 @@ +setName('optimize') + ->setExamples('**optimize**') + ->setSynopsis(pht('Run "OPTIMIZE TABLE" on tables to reclaim space.')); + } + + public function didExecute(PhutilArgumentParser $args) { + $api = $this->getSingleAPI(); + $conn = $api->getConn(null); + + $patches = $this->getPatches(); + $databases = $api->getDatabaseList($patches, true); + + $total_bytes = 0; + foreach ($databases as $database) { + queryfx($conn, 'USE %C', $database); + + $tables = queryfx_all($conn, 'SHOW TABLE STATUS'); + foreach ($tables as $table) { + $table_name = $table['Name']; + $old_bytes = + $table['Data_length'] + + $table['Index_length'] + + $table['Data_free']; + + $this->logInfo( + pht('OPTIMIZE'), + pht( + 'Optimizing table "%s"."%s"...', + $database, + $table_name)); + + $t_start = microtime(true); + queryfx( + $conn, + 'OPTIMIZE TABLE %T', + $table_name); + $t_end = microtime(true); + + $status = queryfx_one( + $conn, + 'SHOW TABLE STATUS LIKE %s', + $table_name); + + $new_bytes = + $status['Data_length'] + + $status['Index_length'] + + $status['Data_free']; + + $duration_ms = (int)(1000 * ($t_end - $t_start)); + + if ($old_bytes > $new_bytes) { + $this->logOkay( + pht('DONE'), + pht( + 'Compacted table by %s in %sms.', + phutil_format_bytes($old_bytes - $new_bytes), + new PhutilNumber($duration_ms))); + } else { + $this->logInfo( + pht('DONE'), + pht( + 'Optimized table (in %sms) but it had little effect.', + new PhutilNumber($duration_ms))); + } + + $total_bytes += ($old_bytes - $new_bytes); + } + } + + $this->logOkay( + pht('OPTIMIZED'), + pht( + 'Completed optimizations, reclaimed %s of disk space.', + phutil_format_bytes($total_bytes))); + + return 0; + } + +} diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php index 75054b9575..5d0cc079e7 100644 --- a/src/view/form/control/PhabricatorRemarkupControl.php +++ b/src/view/form/control/PhabricatorRemarkupControl.php @@ -5,6 +5,7 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { private $disableMacro = false; private $disableFullScreen = false; private $canPin; + private $sendOnEnter = false; public function setDisableMacros($disable) { $this->disableMacro = $disable; @@ -25,6 +26,15 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { return $this->canPin; } + public function setSendOnEnter($soe) { + $this->sendOnEnter = $soe; + return $this; + } + + public function getSendOnEnter() { + return $this->sendOnEnter; + } + protected function renderInput() { $id = $this->getID(); if (!$id) { @@ -78,6 +88,7 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { ), 'canPin' => $this->getCanPin(), 'disabled' => $this->getDisabled(), + 'sendOnEnter' => $this->getSendOnEnter(), 'rootID' => $root_id, 'autocompleteMap' => (object)array( 64 => array( // "@" diff --git a/src/view/phui/PHUIPagerView.php b/src/view/phui/PHUIPagerView.php index 232d4ab747..b78efcda96 100644 --- a/src/view/phui/PHUIPagerView.php +++ b/src/view/phui/PHUIPagerView.php @@ -55,7 +55,7 @@ final class PHUIPagerView extends AphrontView { } public function willShowPagingControls() { - return $this->hasMorePages; + return $this->hasMorePages || $this->getOffset(); } public function getHasMorePages() { diff --git a/src/view/viewutils.php b/src/view/viewutils.php index e0629800c7..7eee3e03fb 100644 --- a/src/view/viewutils.php +++ b/src/view/viewutils.php @@ -48,6 +48,24 @@ function phabricator_datetime($epoch, $user) { $user->getUserSetting($time_key))); } +function phabricator_datetimezone($epoch, $user) { + $datetime = phabricator_datetime($epoch, $user); + $timezone = phabricator_format_local_time($epoch, $user, 'T'); + + // Some obscure timezones just render as "+03" or "-09". Make these render + // as "UTC+3" instead. + if (preg_match('/^[+-]/', $timezone)) { + $timezone = (int)trim($timezone, '+'); + if ($timezone < 0) { + $timezone = pht('UTC-%s', $timezone); + } else { + $timezone = pht('UTC+%s', $timezone); + } + } + + return pht('%s (%s)', $datetime, $timezone); +} + /** * This function does not usually need to be called directly. Instead, call * @{function:phabricator_date}, @{function:phabricator_time}, or diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css index ebcf157ad4..b4386764ab 100644 --- a/webroot/rsrc/css/application/base/standard-page-view.css +++ b/webroot/rsrc/css/application/base/standard-page-view.css @@ -84,6 +84,10 @@ a.handle-status-closed:hover { color: {$orange}; } +.handle-availability-no-email .perfect-circle { + color: {$violet}; +} + .handle-availability-disabled .perfect-circle { color: {$greytext}; } @@ -97,6 +101,11 @@ a.handle-status-closed:hover { font-size: {$smallerfontsize}; } +.phui-handle.phui-link-person { + /* Prevent linebreaks between user availability markers and usernames. */ + white-space: nowrap; +} + .phui-handle .phui-icon-view { display: inline-block; margin: 2px 2px -2px 0; diff --git a/webroot/rsrc/css/application/conpherence/durable-column.css b/webroot/rsrc/css/application/conpherence/durable-column.css index 2b0ab85e44..fdbdbe848f 100644 --- a/webroot/rsrc/css/application/conpherence/durable-column.css +++ b/webroot/rsrc/css/application/conpherence/durable-column.css @@ -65,7 +65,7 @@ font-size: 10px; } -.conpherence-durable-column-header .phabricator-application-menu +.device-desktop .conpherence-durable-column-header .phabricator-application-menu .phui-list-item-view { margin: 0; width: 28px; @@ -73,7 +73,7 @@ min-width: 28px; } -.conpherence-durable-column-header .phabricator-application-menu +.device-desktop .conpherence-durable-column-header .phabricator-application-menu .phui-list-item-href { background: transparent; border: none; @@ -300,7 +300,7 @@ img { .minimize-column .conpherence-durable-column .conpherence-durable-column-header .phabricator-application-menu - .phui-list-item-icon.phui-font-fa:hover { + .phui-list-item-view:hover .phui-list-item-icon.phui-font-fa { color: {$darkbluetext}; } @@ -311,6 +311,6 @@ img { } .minimize-column .conpherence-durable-column .phabricator-application-menu - .conpherence-settings-icon { + .fa-gear { display: none; } diff --git a/webroot/rsrc/css/application/conpherence/notification.css b/webroot/rsrc/css/application/conpherence/notification.css index 6347b40ee3..21e73047a6 100644 --- a/webroot/rsrc/css/application/conpherence/notification.css +++ b/webroot/rsrc/css/application/conpherence/notification.css @@ -8,7 +8,7 @@ .phabricator-notification .conpherence-menu-item-view { display: block; - height: 46px; + height: 48px; overflow: hidden; position: relative; text-decoration: none; @@ -23,8 +23,8 @@ left: 8px; display: block; position: absolute; - width: 30px; - height: 30px; + width: 32px; + height: 32px; background-size: 100%; border-radius: 3px; } @@ -33,7 +33,7 @@ .conpherence-menu-item-title { display: block; margin-top: 8px; - margin-left: 46px; + margin-left: 48px; text-align: left; font-weight: bold; font-size: {$normalfontsize}; @@ -49,8 +49,7 @@ display: block; color: {$lightgreytext}; font-size: {$smallestfontsize}; - margin-top: 2px; - margin-left: 46px; + margin-left: 48px; width: 290px; text-overflow: ellipsis; white-space: nowrap; diff --git a/webroot/rsrc/css/application/search/search-results.css b/webroot/rsrc/css/application/search/search-results.css index 734d86c029..a1837fa391 100644 --- a/webroot/rsrc/css/application/search/search-results.css +++ b/webroot/rsrc/css/application/search/search-results.css @@ -16,3 +16,16 @@ font-weight: normal; color: #000; } + +.phui-fulltext-tokens { + margin: 16px 8px; + font-weight: bold; +} + +.phui-fulltext-tokens .phui-tag-view { + margin: 0 2px; +} + +.phui-fulltext-tokens .phui-tag-view.phui-tag-shade-grey { + opacity: 0.5; +} diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/phui-button.css index 21bf741225..f726487a2d 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/phui-button.css @@ -41,7 +41,7 @@ input[type="submit"] { font-weight: bold; font-size: {$normalfontsize}; display: inline-block; - padding: 3px 12px 4px; + padding: 4px 16px 5px; text-align: center; white-space: nowrap; border-radius: 3px; @@ -285,7 +285,7 @@ a.policy-control .phui-button-text { .dropdown .caret { margin-top: 7px; - margin-left: 6px; + margin-right: -4px; } .small.dropdown .caret { @@ -304,8 +304,8 @@ a.policy-control .phui-button-text { .button .phui-icon-view { display: inline-block; position: absolute; - top: 6px; - left: 10px; + top: 7px; + left: 12px; } .button.icon-last .phui-icon-view { diff --git a/webroot/rsrc/css/phui/phui-comment-form.css b/webroot/rsrc/css/phui/phui-comment-form.css index a49d033c3e..860dc9dcaf 100644 --- a/webroot/rsrc/css/phui/phui-comment-form.css +++ b/webroot/rsrc/css/phui/phui-comment-form.css @@ -155,8 +155,8 @@ body.device .phui-box.phui-object-box.phui-comment-form-view { .phui-comment-form-view .phui-form-view label.aphront-form-label, .phui-comment-form-view .phui-form-view .aphront-form-error { - height: 26px; - line-height: 26px; + height: 28x; + line-height: 28px; padding: 0; } diff --git a/webroot/rsrc/css/phui/phui-curtain-view.css b/webroot/rsrc/css/phui/phui-curtain-view.css index 7f42d53a3d..dbc706994a 100644 --- a/webroot/rsrc/css/phui/phui-curtain-view.css +++ b/webroot/rsrc/css/phui/phui-curtain-view.css @@ -24,6 +24,8 @@ .phui-curtain-panel-body { padding: 4px 0 0; + overflow: hidden; + text-overflow: ellipsis; } .device .phui-curtain-panel-body { diff --git a/webroot/rsrc/css/phui/phui-form.css b/webroot/rsrc/css/phui/phui-form.css index 810ad32c2c..8a6f705ec9 100644 --- a/webroot/rsrc/css/phui/phui-form.css +++ b/webroot/rsrc/css/phui/phui-form.css @@ -20,7 +20,7 @@ input[type="tel"], input[type="color"], div.jx-tokenizer-container { display: inline-block; - height: 28px; + height: 30px; line-height: 18px; color: #333; vertical-align: middle; @@ -49,10 +49,6 @@ div.jx-tokenizer-container { border: 1px solid {$greyborder}; border-radius: 3px; - -webkit-box-shadow: inset 0 1px 1px rgba({$alphablack}, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba({$alphablack}, 0.075); - box-shadow: inset 0 1px 1px rgba({$alphablack}, 0.075); - -webkit-transition: border linear .05s, box-shadow linear .05s; -moz-transition: border linear .05s, box-shadow linear .05s; -o-transition: border linear .05s, box-shadow linear .05s; @@ -114,7 +110,7 @@ select { border-radius: 3px; color: {$darkbluetext}; border: 1px solid {$greyborder}; - height: 28px; + height: 30px; padding: 0 24px 0 8px; margin: 0; min-width: 180px; diff --git a/webroot/rsrc/css/phui/phui-list.css b/webroot/rsrc/css/phui/phui-list.css index eadd1987ee..e571e228d8 100644 --- a/webroot/rsrc/css/phui/phui-list.css +++ b/webroot/rsrc/css/phui/phui-list.css @@ -228,3 +228,17 @@ .phui-list-item-action-icon { opacity: 1; } + +/* - Item Counts ----------------------------------------------------------- */ + +.phui-list-item-count { + position: absolute; + right: 7px; + top: 7px; + background: {$blue}; + border-radius: 2px; + color: #fff; + font-weight: bold; + padding: 0 5px 1px; + font-size: {$smallestfontsize}; +} diff --git a/webroot/rsrc/css/phui/phui-tag-view.css b/webroot/rsrc/css/phui/phui-tag-view.css index f14909bc06..d5b2710d7b 100644 --- a/webroot/rsrc/css/phui/phui-tag-view.css +++ b/webroot/rsrc/css/phui/phui-tag-view.css @@ -46,11 +46,11 @@ a.phui-tag-view:hover { .phui-tag-dot { position: relative; display: inline-block; - width: 6px; - height: 6px; + width: 5px; + height: 5px; margin-right: 4px; top: -1px; - border-radius: 6px; + border-radius: 5px; border: 1px solid transparent; } diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index fb1ddbd768..c4f568411a 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -256,7 +256,9 @@ color: {$lightgreytext}; } -.phui-timeline-title .phui-timeline-value { +.phui-timeline-title .phui-timeline-value, +.conpherence-transaction-content .phui-timeline-value, +.phui-feed-story-head .phui-timeline-value { font-style: italic; color: black; } diff --git a/webroot/rsrc/js/application/conpherence/behavior-pontificate.js b/webroot/rsrc/js/application/conpherence/behavior-pontificate.js index 5f9e24a915..537961ec71 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-pontificate.js +++ b/webroot/rsrc/js/application/conpherence/behavior-pontificate.js @@ -22,27 +22,4 @@ JX.behavior('conpherence-pontificate', function() { 'conpherence-pontificate', _sendMessage); - // Send on enter if the shift key is not held. - JX.Stratcom.listen( - 'keydown', - 'conpherence-pontificate', - function(e) { - if (e.getSpecialKey() != 'return') { - return; - } - - var raw = e.getRawEvent(); - if (raw.shiftKey) { - // If the shift key is pressed, let the browser write a newline into - // the textarea. - return; - } - - // From here on, interpret this as a "send" action, not a literal - // newline. - e.kill(); - - _sendMessage(e); - }); - }); diff --git a/webroot/rsrc/js/core/Prefab.js b/webroot/rsrc/js/core/Prefab.js index 2c3da10102..c5a8ef5e9d 100644 --- a/webroot/rsrc/js/core/Prefab.js +++ b/webroot/rsrc/js/core/Prefab.js @@ -101,7 +101,6 @@ JX.install('Prefab', { datasource.setSortHandler( JX.bind(datasource, JX.Prefab.sortHandler, config)); - datasource.setFilterHandler(JX.Prefab.filterClosedResults); datasource.setTransformer(JX.Prefab.transformDatasourceResults); var typeahead = new JX.Typeahead( @@ -256,37 +255,6 @@ JX.install('Prefab', { }, - /** - * Filter callback for tokenizers and typeaheads which filters out closed - * or disabled objects unless they are the only options. - */ - filterClosedResults: function(value, list) { - // Look for any open result. - var has_open = false; - var ii; - for (ii = 0; ii < list.length; ii++) { - if (!list[ii].closed) { - has_open = true; - break; - } - } - - if (!has_open) { - // Everything is closed, so just use it as-is. - return list; - } - - // Otherwise, only display the open results. - var results = []; - for (ii = 0; ii < list.length; ii++) { - if (!list[ii].closed) { - results.push(list[ii]); - } - } - - return results; - }, - /** * Transform results from a wire format into a usable format in a standard * way. diff --git a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js index f4cbc4f4fa..a53c567607 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js +++ b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js @@ -37,7 +37,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { // First, disable any active mode. if (edit_root) { - if (edit_mode == 'fa-arrows-alt') { + if (edit_mode == 'fullscreen') { JX.DOM.alterClass(edit_root, 'remarkup-control-fullscreen-mode', false); JX.DOM.alterClass(document.body, 'remarkup-fullscreen-mode', false); JX.Mask.hide('jx-light-mask'); @@ -58,7 +58,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { edit_mode = mode; // Now, apply the new mode. - if (mode == 'fa-arrows-alt') { + if (mode == 'fullscreen') { JX.DOM.alterClass(edit_root, 'remarkup-control-fullscreen-mode', true); JX.DOM.alterClass(document.body, 'remarkup-fullscreen-mode', true); JX.Mask.show('jx-light-mask'); @@ -118,7 +118,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { if (!edit_root) { return; } - if (edit_mode != 'fa-arrows-alt') { + if (edit_mode != 'fullscreen') { return; } @@ -140,7 +140,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { return; } - if (edit_mode != 'fa-arrows-alt') { + if (edit_mode != 'fullscreen') { return; } @@ -261,10 +261,10 @@ JX.behavior('phabricator-remarkup-assist', function(config) { break; case 'fa-arrows-alt': set_pinned_mode(root, false); - if (edit_mode == 'fa-arrows-alt') { + if (edit_mode == 'fullscreen') { set_edit_mode(root, 'normal'); } else { - set_edit_mode(root, 'fa-arrows-alt'); + set_edit_mode(root, 'fullscreen'); } break; case 'fa-eye': @@ -385,4 +385,36 @@ JX.behavior('phabricator-remarkup-assist', function(config) { .register(); } + if (config.sendOnEnter) { + // Send on enter if the shift key is not held. + JX.DOM.listen(area, 'keydown', null, + function(e) { + if (e.getSpecialKey() != 'return') { + return; + } + + var raw = e.getRawEvent(); + if (raw.shiftKey) { + // If the shift key is pressed, let the browser write a newline into + // the textarea. + return; + } + + if (edit_mode == 'fullscreen') { + // Don't send on enter in fullscreen + return; + } + + // From here on, interpret this as a "send" action, not a literal + // newline. + e.kill(); + + // This allows 'workflow' and similar actions to take effect. + // Such as pontificate in Conpherence + var form = e.getNode('tag:form'); + var r = JX.DOM.invoke(form, 'didSyntheticSubmit'); + + }); + } + }); diff --git a/webroot/rsrc/js/core/behavior-search-typeahead.js b/webroot/rsrc/js/core/behavior-search-typeahead.js index a981f3861e..864d45ff29 100644 --- a/webroot/rsrc/js/core/behavior-search-typeahead.js +++ b/webroot/rsrc/js/core/behavior-search-typeahead.js @@ -52,72 +52,55 @@ JX.behavior('phabricator-search-typeahead', function(config) { datasource.setTransformer(transform); - // Sort handler that orders results by type (e.g., applications, users) - // and then selects for good matches on the "priority" substrings if they - // exist (for instance, username matches are preferred over real name - // matches, and application name matches are preferred over application - // flavor text matches). - var sort_handler = function(value, list, cmp) { - var priority_hits = {}; - var type_priority = { - 'jump' : 1, - 'apps' : 2, - 'proj' : 3, - 'user' : 4, - 'repo' : 5, - 'symb' : 6 - }; - - var tokens = this.tokenize(value); + // First, sort all the results normally. + JX.bind(this, JX.Prefab.sortHandler, {}, value, list, cmp)(); + // Now we're going to apply some special rules to order results by type, + // so applications always appear near the top, then users, etc. var ii; + + var type_order = [ + 'jump', + 'apps', + 'proj', + 'user', + 'repo', + 'symb', + 'misc' + ]; + + var type_map = {}; + for (ii = 0; ii < type_order.length; ii++) { + type_map[type_order[ii]] = true; + } + + var buckets = {}; for (ii = 0; ii < list.length; ii++) { var item = list[ii]; - for (var jj = 0; jj < tokens.length; jj++) { - if (item.name.indexOf(tokens[jj]) === 0) { - priority_hits[item.id] = true; - } + var type = item.priorityType; + if (!type_map.hasOwnProperty(type)) { + type = 'misc'; } - if (!item.priority) { - continue; + if (!buckets.hasOwnProperty(type)) { + buckets[type] = []; } - for (var hh = 0; hh < tokens.length; hh++) { - if (item.priority.substr(0, tokens[hh].length) == tokens[hh]) { - priority_hits[item.id] = true; - } - } + buckets[type].push(item); } - list.sort(function(u, v) { - var u_type = type_priority[u.priorityType] || 999; - var v_type = type_priority[v.priorityType] || 999; - - if (u_type != v_type) { - return u_type - v_type; - } - - if (priority_hits[u.id] != priority_hits[v.id]) { - return priority_hits[v.id] ? 1 : -1; - } - - return cmp(u, v); - }); - // If we have more results than fit, limit each type of result to 3, so // we show 3 applications, then 3 users, etc. For jump items, we show only // one result. - var type_count = 0; - var current_type = null; - for (ii = 0; ii < list.length; ii++) { - if (list[ii].type != current_type) { - current_type = list[ii].type; - type_count = 1; - } else { - type_count++; + + var jj; + var results = []; + for (ii = 0; ii < type_order.length; ii++) { + var current_type = type_order[ii]; + var type_list = buckets[current_type] || []; + for (jj = 0; jj < type_list.length; jj++) { // Skip this item if: // - it's a jump nav item, and we already have at least one jump @@ -125,19 +108,21 @@ JX.behavior('phabricator-search-typeahead', function(config) { // - we have more items than will fit in the typeahead, and this // is the 4..Nth result of its type. - var skip = ((current_type == 'jump') && (type_count > 1)) || + var skip = ((current_type == 'jump') && (jj > 1)) || ((list.length > config.limit) && (type_count > 3)); if (skip) { - list.splice(ii, 1); - ii--; + continue; } + + results.push(type_list[jj]); } } + // Replace the list in place with the results. + list.splice.apply(list, [0, list.length].concat(results)); }; datasource.setSortHandler(JX.bind(datasource, sort_handler)); - datasource.setFilterHandler(JX.Prefab.filterClosedResults); datasource.setMaximumResultCount(config.limit); var typeahead = new JX.Typeahead(JX.$(config.id), JX.$(config.input)); diff --git a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js index a03c2adf70..16ed8b75f3 100644 --- a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js +++ b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js @@ -153,7 +153,6 @@ JX.install('PHUIXAutocomplete', { datasource.setTransformer(JX.bind(this, this._transformresult)); datasource.setSortHandler( JX.bind(datasource, JX.Prefab.sortHandler, {})); - datasource.setFilterHandler(JX.Prefab.filterClosedResults); this._datasources[code] = datasource; }