diff --git a/resources/celerity/map.php b/resources/celerity/map.php index badb3d06fb..8c4ba2ef05 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,15 +7,16 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => '4601645d', - 'conpherence.pkg.js' => '11f3e07e', - 'core.pkg.css' => 'de918edf', - 'core.pkg.js' => '30185d95', + 'conpherence.pkg.css' => 'cea72e09', + 'conpherence.pkg.js' => '6249a1cf', + 'core.pkg.css' => '46d588e4', + 'core.pkg.js' => '035325a7', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'e1d704ce', 'differential.pkg.js' => '634399e9', 'diffusion.pkg.css' => '91c5d3a6', 'diffusion.pkg.js' => '84c8f8fd', + 'favicon.ico' => '30672e08', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '949a7498', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', @@ -42,18 +43,17 @@ return array( 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '0ede4c9b', - 'rsrc/css/application/config/config-page.css' => '8798e14f', + 'rsrc/css/application/config/config-page.css' => 'b80124ae', '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' => '44bcaa19', - 'rsrc/css/application/conpherence/header-pane.css' => '20a7028c', + 'rsrc/css/application/conpherence/durable-column.css' => 'd82e130c', + 'rsrc/css/application/conpherence/header-pane.css' => '1c81cda6', 'rsrc/css/application/conpherence/menu.css' => '4f51db5a', - 'rsrc/css/application/conpherence/message-pane.css' => '0d7dff02', + 'rsrc/css/application/conpherence/message-pane.css' => '394ae8fa', 'rsrc/css/application/conpherence/notification.css' => '965db05b', - 'rsrc/css/application/conpherence/participant-pane.css' => '7bba0b56', - 'rsrc/css/application/conpherence/transaction.css' => '46253e19', - 'rsrc/css/application/conpherence/update.css' => '53bc527a', + 'rsrc/css/application/conpherence/participant-pane.css' => 'ac1baaa8', + 'rsrc/css/application/conpherence/transaction.css' => '85129c68', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => '16c52f5c', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', @@ -102,7 +102,7 @@ return array( 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', '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' => 'be6454ec', + 'rsrc/css/application/search/application-search-view.css' => '8452c849', 'rsrc/css/application/search/search-results.css' => '7dea472c', 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', @@ -110,7 +110,7 @@ return array( 'rsrc/css/core/core.css' => 'd0801452', 'rsrc/css/core/remarkup.css' => 'cd912f2c', 'rsrc/css/core/syntax.css' => '769d3498', - 'rsrc/css/core/z-index.css' => '0d4e5558', + 'rsrc/css/core/z-index.css' => 'd1270942', 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', 'rsrc/css/font/font-aleo.css' => '8bdb2835', 'rsrc/css/font/font-awesome.css' => '2b7ebbcc', @@ -136,7 +136,7 @@ return array( 'rsrc/css/phui/phui-document-pro.css' => 'ca1fed81', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'c32e8dec', - 'rsrc/css/phui/phui-feed-story.css' => 'aa49845d', + 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', 'rsrc/css/phui/phui-fontkit.css' => '9cda225e', 'rsrc/css/phui/phui-form-view.css' => '9e22b190', 'rsrc/css/phui/phui-form.css' => 'aac1d51d', @@ -144,7 +144,7 @@ return array( 'rsrc/css/phui/phui-header-view.css' => '06385974', 'rsrc/css/phui/phui-hovercard.css' => 'de1a2119', 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', - 'rsrc/css/phui/phui-icon.css' => '9bab6f02', + 'rsrc/css/phui/phui-icon.css' => '417f80fb', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => '28efab79', @@ -162,7 +162,7 @@ return array( 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => 'bc523970', - 'rsrc/css/phui/phui-two-column-view.css' => 'fcfbe347', + 'rsrc/css/phui/phui-two-column-view.css' => 'bbe32c23', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e09eb53a', 'rsrc/css/phui/workboards/phui-workcard.css' => '0c62d7c5', @@ -273,71 +273,20 @@ return array( 'rsrc/favicons/apple-touch-icon-60x60.png' => '8ff52925', 'rsrc/favicons/apple-touch-icon-72x72.png' => 'a2bb65d6', 'rsrc/favicons/apple-touch-icon-76x76.png' => '2d061a11', - 'rsrc/favicons/dark/apple-touch-icon-114x114.png' => 'd0c8978c', - 'rsrc/favicons/dark/apple-touch-icon-120x120.png' => '3a618bc0', - 'rsrc/favicons/dark/apple-touch-icon-144x144.png' => '92c1e188', - 'rsrc/favicons/dark/apple-touch-icon-152x152.png' => '7ce7e469', - 'rsrc/favicons/dark/apple-touch-icon-57x57.png' => 'e3f3f38b', - 'rsrc/favicons/dark/apple-touch-icon-60x60.png' => '1e0dcc72', - 'rsrc/favicons/dark/apple-touch-icon-72x72.png' => '7fb599b6', - 'rsrc/favicons/dark/apple-touch-icon-76x76.png' => '91146961', - 'rsrc/favicons/dark/favicon-128.png' => 'd6ac4346', - 'rsrc/favicons/dark/favicon-16x16.png' => '17434bb0', - 'rsrc/favicons/dark/favicon-196x196.png' => '5e06ee72', - 'rsrc/favicons/dark/favicon-32x32.png' => 'bdd7e16b', - 'rsrc/favicons/dark/favicon-96x96.png' => '0cf55978', - 'rsrc/favicons/dark/mstile-144x144.png' => '4dc9d42d', - 'rsrc/favicons/dark/mstile-150x150.png' => '2dc61c90', - 'rsrc/favicons/dark/mstile-310x150.png' => '4fe58ab2', - 'rsrc/favicons/dark/mstile-310x310.png' => 'e62c1677', - 'rsrc/favicons/dark/mstile-70x70.png' => '6d1f41b7', 'rsrc/favicons/favicon-128.png' => '72f7e812', 'rsrc/favicons/favicon-16x16.png' => 'fc6275ba', 'rsrc/favicons/favicon-196x196.png' => '95db275e', 'rsrc/favicons/favicon-32x32.png' => '5bd18b6c', 'rsrc/favicons/favicon-96x96.png' => '7242c8e9', + 'rsrc/favicons/favicon-mention.ico' => '1fdd0fa4', + 'rsrc/favicons/favicon-message.ico' => '115bc010', + 'rsrc/favicons/favicon.ico' => 'cdb11121', 'rsrc/favicons/mask-icon.svg' => 'e132a80f', 'rsrc/favicons/mstile-144x144.png' => '310c2ee5', 'rsrc/favicons/mstile-150x150.png' => '74bf5133', 'rsrc/favicons/mstile-310x150.png' => '4a49d3ee', 'rsrc/favicons/mstile-310x310.png' => 'a52ab264', 'rsrc/favicons/mstile-70x70.png' => '5edce7b8', - 'rsrc/favicons/red/apple-touch-icon-114x114.png' => '91e37d1d', - 'rsrc/favicons/red/apple-touch-icon-120x120.png' => '66687533', - 'rsrc/favicons/red/apple-touch-icon-144x144.png' => 'bc06002c', - 'rsrc/favicons/red/apple-touch-icon-152x152.png' => 'a713de42', - 'rsrc/favicons/red/apple-touch-icon-57x57.png' => '4729688b', - 'rsrc/favicons/red/apple-touch-icon-60x60.png' => '07b9b609', - 'rsrc/favicons/red/apple-touch-icon-72x72.png' => 'b20c3698', - 'rsrc/favicons/red/apple-touch-icon-76x76.png' => 'c6e7dd5c', - 'rsrc/favicons/red/favicon-128.png' => 'e2b2f8fe', - 'rsrc/favicons/red/favicon-16x16.png' => '929fbceb', - 'rsrc/favicons/red/favicon-196x196.png' => '94c089a5', - 'rsrc/favicons/red/favicon-32x32.png' => '5848673e', - 'rsrc/favicons/red/favicon-96x96.png' => '895d54e8', - 'rsrc/favicons/red/mstile-144x144.png' => '448639f5', - 'rsrc/favicons/red/mstile-150x150.png' => 'c2ba45d0', - 'rsrc/favicons/red/mstile-310x150.png' => 'b0e50799', - 'rsrc/favicons/red/mstile-310x310.png' => '2475c5a5', - 'rsrc/favicons/red/mstile-70x70.png' => '49b197e8', - 'rsrc/favicons/yellow/apple-touch-icon-114x114.png' => '5271fb3f', - 'rsrc/favicons/yellow/apple-touch-icon-120x120.png' => '6c3d9bf9', - 'rsrc/favicons/yellow/apple-touch-icon-144x144.png' => '6484472c', - 'rsrc/favicons/yellow/apple-touch-icon-152x152.png' => 'e305dda8', - 'rsrc/favicons/yellow/apple-touch-icon-57x57.png' => 'fa6c43d4', - 'rsrc/favicons/yellow/apple-touch-icon-60x60.png' => '2673f162', - 'rsrc/favicons/yellow/apple-touch-icon-72x72.png' => '3ad8020c', - 'rsrc/favicons/yellow/apple-touch-icon-76x76.png' => '58cffd81', - 'rsrc/favicons/yellow/favicon-128.png' => '3b2f8233', - 'rsrc/favicons/yellow/favicon-16x16.png' => 'f3a90518', - 'rsrc/favicons/yellow/favicon-196x196.png' => '932c7c65', - 'rsrc/favicons/yellow/favicon-32x32.png' => '005c9f92', - 'rsrc/favicons/yellow/favicon-96x96.png' => '3ad9bfa9', - 'rsrc/favicons/yellow/mstile-144x144.png' => 'fc52335c', - 'rsrc/favicons/yellow/mstile-150x150.png' => '9e556f80', - 'rsrc/favicons/yellow/mstile-310x150.png' => '2c915073', - 'rsrc/favicons/yellow/mstile-310x310.png' => 'ee49978d', - 'rsrc/favicons/yellow/mstile-70x70.png' => '85c7c939', 'rsrc/image/BFCFDA.png' => 'd5ec91f4', 'rsrc/image/actions/edit.png' => '2fc41442', 'rsrc/image/avatar.png' => 'e132bb6a', @@ -426,7 +375,7 @@ return array( 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 'rsrc/js/application/aphlict/Aphlict.js' => '5359e785', - 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '49e20786', + 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '2a171a9d', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'fb20ac8d', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'edd1ba66', @@ -436,14 +385,14 @@ return array( 'rsrc/js/application/calendar/behavior-month-view.js' => 'fe33e256', 'rsrc/js/application/calendar/behavior-recurring-edit.js' => '5f1c4d5f', 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', - 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', - 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'c5238acb', - 'rsrc/js/application/conpherence/behavior-menu.js' => '9eb55204', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '358c717b', + 'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '9bbf3762', + '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-quicksand-blacklist.js' => '7927a7d3', - 'rsrc/js/application/conpherence/behavior-toggle-widget.js' => '9bdbbab0', + 'rsrc/js/application/conpherence/behavior-toggle-widget.js' => '3dbf94d5', 'rsrc/js/application/countdown/timer.js' => 'e4cc26b3', 'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => 'edf8a145', 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e', @@ -531,6 +480,7 @@ return array( 'rsrc/js/core/Busy.js' => '59a7976a', 'rsrc/js/core/DragAndDropFileUpload.js' => '58dea2fa', 'rsrc/js/core/DraggableList.js' => '5a13c79f', + 'rsrc/js/core/Favicon.js' => '1fe2510c', 'rsrc/js/core/FileUpload.js' => '680ea2c8', 'rsrc/js/core/Hovercard.js' => '1bd28176', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', @@ -540,7 +490,7 @@ return array( 'rsrc/js/core/Prefab.js' => 'cfd23f37', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '320810c8', - 'rsrc/js/core/Title.js' => 'df5e11d2', + 'rsrc/js/core/Title.js' => '485aaa6c', 'rsrc/js/core/ToolTip.js' => '6323f942', 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', @@ -556,7 +506,7 @@ return array( 'rsrc/js/core/behavior-file-tree.js' => '88236f00', 'rsrc/js/core/behavior-form.js' => '5c54cbf3', 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', - 'rsrc/js/core/behavior-global-drag-and-drop.js' => 'c8e57404', + 'rsrc/js/core/behavior-global-drag-and-drop.js' => '960f6a39', 'rsrc/js/core/behavior-high-security-warning.js' => 'a464fe03', 'rsrc/js/core/behavior-history-install.js' => '7ee2b591', 'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64', @@ -610,22 +560,21 @@ return array( 'aphront-tokenizer-control-css' => '056da01b', 'aphront-tooltip-css' => '1a07aea8', 'aphront-typeahead-control-css' => 'd4f16145', - 'application-search-view-css' => 'be6454ec', + 'application-search-view-css' => '8452c849', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', 'changeset-view-manager' => 'a2828756', 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', - 'config-page-css' => '8798e14f', - 'conpherence-durable-column-view' => '44bcaa19', - 'conpherence-header-pane-css' => '20a7028c', + 'config-page-css' => 'b80124ae', + 'conpherence-durable-column-view' => 'd82e130c', + 'conpherence-header-pane-css' => '1c81cda6', 'conpherence-menu-css' => '4f51db5a', - 'conpherence-message-pane-css' => '0d7dff02', + 'conpherence-message-pane-css' => '394ae8fa', 'conpherence-notification-css' => '965db05b', - 'conpherence-participant-pane-css' => '7bba0b56', - 'conpherence-thread-manager' => '01774ab2', - 'conpherence-transaction-css' => '46253e19', - 'conpherence-update-css' => '53bc527a', + 'conpherence-participant-pane-css' => 'ac1baaa8', + 'conpherence-thread-manager' => '358c717b', + 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', 'differential-changeset-view-css' => '9ef7d354', 'differential-core-view-css' => '5b7b8ff4', @@ -650,7 +599,7 @@ return array( 'inline-comment-summary-css' => '51efda3a', 'javelin-aphlict' => '5359e785', 'javelin-behavior' => '61cbc29a', - 'javelin-behavior-aphlict-dropdown' => '49e20786', + 'javelin-behavior-aphlict-dropdown' => '2a171a9d', 'javelin-behavior-aphlict-listen' => 'fb20ac8d', 'javelin-behavior-aphlict-status' => '5e2634b9', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', @@ -665,10 +614,10 @@ return array( 'javelin-behavior-choose-control' => '327a00d1', 'javelin-behavior-comment-actions' => '0300eae6', 'javelin-behavior-config-reorder-fields' => 'b6993408', - 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', - 'javelin-behavior-conpherence-menu' => '9eb55204', + 'javelin-behavior-conpherence-menu' => '7524fcfa', 'javelin-behavior-conpherence-participant-pane' => '8604caa8', 'javelin-behavior-conpherence-pontificate' => 'f2e58483', + 'javelin-behavior-conpherence-search' => '9bbf3762', 'javelin-behavior-countdown-timer' => 'e4cc26b3', 'javelin-behavior-dark-console' => 'f411b6ae', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', @@ -697,13 +646,13 @@ return array( 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-drydock-live-operation-status' => '901935ef', - 'javelin-behavior-durable-column' => 'c5238acb', + 'javelin-behavior-durable-column' => 'aa3bd034', 'javelin-behavior-editengine-reorder-configs' => 'd7a74243', 'javelin-behavior-editengine-reorder-fields' => 'b59e1e96', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-event-all-day' => '937bb700', 'javelin-behavior-fancy-datepicker' => '568931f3', - 'javelin-behavior-global-drag-and-drop' => 'c8e57404', + 'javelin-behavior-global-drag-and-drop' => '960f6a39', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 'javelin-behavior-high-security-warning' => 'a464fe03', 'javelin-behavior-history-install' => '7ee2b591', @@ -774,7 +723,7 @@ return array( 'javelin-behavior-test-payment-form' => 'fc91ab6c', 'javelin-behavior-time-typeahead' => '522431f7', 'javelin-behavior-toggle-class' => '92b9ec77', - 'javelin-behavior-toggle-widget' => '9bdbbab0', + 'javelin-behavior-toggle-widget' => '3dbf94d5', 'javelin-behavior-typeahead-browse' => '635de1ec', 'javelin-behavior-typeahead-search' => '93d0c9e3', 'javelin-behavior-view-placeholder' => '47830651', @@ -848,6 +797,7 @@ return array( 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => '5a13c79f', 'phabricator-fatal-config-template-css' => '8f18fa41', + 'phabricator-favicon' => '1fe2510c', 'phabricator-feed-css' => 'ecd4ec57', 'phabricator-file-upload' => '680ea2c8', 'phabricator-filetree-view-css' => 'fccf9f82', @@ -869,7 +819,7 @@ return array( 'phabricator-source-code-view-css' => 'cbeef983', 'phabricator-standard-page-view' => '79176f5a', 'phabricator-textareautils' => '320810c8', - 'phabricator-title' => 'df5e11d2', + 'phabricator-title' => '485aaa6c', 'phabricator-tooltip' => '6323f942', 'phabricator-ui-example-css' => '528b19de', 'phabricator-uiexample-javelin-view' => 'd4a14807', @@ -882,7 +832,7 @@ return array( 'phabricator-uiexample-reactor-select' => 'a155550f', 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', - 'phabricator-zindex-css' => '0d4e5558', + 'phabricator-zindex-css' => 'd1270942', 'phame-css' => '8efb0729', 'pholio-css' => 'ca89d380', 'pholio-edit-css' => '07676f51', @@ -909,7 +859,7 @@ return array( 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'c32e8dec', 'phui-document-view-pro-css' => 'ca1fed81', - 'phui-feed-story-css' => 'aa49845d', + 'phui-feed-story-css' => '44a9c8e9', 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => 'aac1d51d', @@ -919,7 +869,7 @@ return array( 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'de1a2119', 'phui-icon-set-selector-css' => '1ab67aad', - 'phui-icon-view-css' => '9bab6f02', + 'phui-icon-view-css' => '417f80fb', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '28efab79', @@ -939,7 +889,7 @@ return array( 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '798c69b8', 'phui-timeline-view-css' => 'bc523970', - 'phui-two-column-view-css' => 'fcfbe347', + 'phui-two-column-view-css' => 'bbe32c23', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e09eb53a', 'phui-workcard-view-css' => '0c62d7c5', @@ -976,17 +926,6 @@ return array( 'javelin-request', 'javelin-typeahead-source', ), - '01774ab2' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-aphlict', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), '019f36c4' => array( 'javelin-behavior', 'javelin-dom', @@ -1152,6 +1091,10 @@ return array( 'javelin-uri', 'javelin-routable', ), + '1fe2510c' => array( + 'javelin-install', + 'javelin-dom', + ), '21df4ff5' => array( 'javelin-install', 'javelin-workboard-card', @@ -1171,6 +1114,17 @@ return array( 'javelin-install', 'javelin-util', ), + '2a171a9d' => array( + 'javelin-behavior', + 'javelin-request', + 'javelin-stratcom', + 'javelin-vector', + 'javelin-dom', + 'javelin-uri', + 'javelin-behavior-device', + 'phabricator-title', + 'phabricator-favicon', + ), '2b8de964' => array( 'javelin-install', 'javelin-util', @@ -1199,6 +1153,17 @@ return array( 'javelin-dom', 'javelin-workflow', ), + '358c717b' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-aphlict', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1215,6 +1180,13 @@ return array( 'javelin-util', 'javelin-uri', ), + '3dbf94d5' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-workflow', + 'javelin-stratcom', + ), '3f5d6dbf' => array( 'javelin-behavior', 'javelin-dom', @@ -1272,6 +1244,9 @@ return array( 'phabricator-drag-and-drop-file-upload', 'phabricator-textareautils', ), + '485aaa6c' => array( + 'javelin-install', + ), '491416b3' => array( 'javelin-behavior', 'javelin-uri', @@ -1282,16 +1257,6 @@ return array( 'javelin-dom', 'javelin-stratcom', ), - '49e20786' => array( - 'javelin-behavior', - 'javelin-request', - 'javelin-stratcom', - 'javelin-vector', - 'javelin-dom', - 'javelin-uri', - 'javelin-behavior-device', - 'phabricator-title', - ), '4a021c10' => array( 'javelin-install', 'javelin-util', @@ -1536,6 +1501,20 @@ return array( 'javelin-vector', 'javelin-dom', ), + '7524fcfa' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-behavior-device', + 'javelin-history', + 'javelin-vector', + 'javelin-scrollbar', + 'phabricator-title', + 'phabricator-shaped-request', + 'conpherence-thread-manager', + ), '769d3498' => array( 'syntax-default-css', ), @@ -1723,6 +1702,13 @@ return array( 'javelin-dom', 'phabricator-busy', ), + '960f6a39' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-uri', + 'javelin-mask', + 'phabricator-drag-and-drop-file-upload', + ), '988040b4' => array( 'javelin-install', 'javelin-dom', @@ -1740,27 +1726,13 @@ return array( 'phabricator-phtize', 'changeset-view-manager', ), - '9bdbbab0' => array( + '9bbf3762' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-workflow', 'javelin-stratcom', ), - '9eb55204' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-behavior-device', - 'javelin-history', - 'javelin-vector', - 'javelin-scrollbar', - 'phabricator-title', - 'phabricator-shaped-request', - 'conpherence-thread-manager', - ), '9ef7d354' => array( 'phui-inline-comment-view-css', ), @@ -1834,6 +1806,16 @@ return array( 'javelin-util', 'phabricator-prefab', ), + 'aa3bd034' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-behavior-device', + 'javelin-scrollbar', + 'javelin-quicksand', + 'phabricator-keyboard-shortcut', + 'conpherence-thread-manager', + ), 'ab2f381b' => array( 'javelin-request', 'javelin-behavior', @@ -1961,29 +1943,12 @@ return array( 'javelin-install', 'javelin-dom', ), - 'c5238acb' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-behavior-device', - 'javelin-scrollbar', - 'javelin-quicksand', - 'phabricator-keyboard-shortcut', - 'conpherence-thread-manager', - ), 'c587b80f' => array( 'javelin-install', ), 'c7ccd872' => array( 'phui-fontkit-css', ), - 'c8e57404' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-uri', - 'javelin-mask', - 'phabricator-drag-and-drop-file-upload', - ), 'c90a04fc' => array( 'javelin-dom', 'javelin-dynval', @@ -2010,12 +1975,6 @@ return array( 'javelin-util', 'phabricator-notification-css', ), - 'cf86d16a' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-workflow', - 'phabricator-drag-and-drop-file-upload', - ), 'cfd23f37' => array( 'javelin-install', 'javelin-util', @@ -2093,9 +2052,6 @@ return array( 'javelin-typeahead-ondemand-source', 'javelin-dom', ), - 'df5e11d2' => array( - 'javelin-install', - ), 'e0ec7f2f' => array( 'javelin-behavior', 'javelin-dom', @@ -2300,12 +2256,10 @@ return array( 'conpherence-message-pane-css', 'conpherence-notification-css', 'conpherence-transaction-css', - 'conpherence-update-css', 'conpherence-participant-pane-css', 'conpherence-header-pane-css', ), 'conpherence.pkg.js' => array( - 'javelin-behavior-conpherence-drag-and-drop-photo', 'javelin-behavior-conpherence-menu', 'javelin-behavior-conpherence-participant-pane', 'javelin-behavior-conpherence-pontificate', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index a3a2a6132a..8e7d339902 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -157,12 +157,10 @@ return array( 'conpherence-message-pane-css', 'conpherence-notification-css', 'conpherence-transaction-css', - 'conpherence-update-css', 'conpherence-participant-pane-css', 'conpherence-header-pane-css', ), 'conpherence.pkg.js' => array( - 'javelin-behavior-conpherence-drag-and-drop-photo', 'javelin-behavior-conpherence-menu', 'javelin-behavior-conpherence-participant-pane', 'javelin-behavior-conpherence-pontificate', diff --git a/resources/sql/autopatches/20161011.conpherence.ngrams.php b/resources/sql/autopatches/20161011.conpherence.ngrams.php new file mode 100644 index 0000000000..457143f6c7 --- /dev/null +++ b/resources/sql/autopatches/20161011.conpherence.ngrams.php @@ -0,0 +1,11 @@ +getPHID(), + array( + 'force' => true, + )); +} diff --git a/resources/sql/autopatches/20161011.conpherence.ngrams.sql b/resources/sql/autopatches/20161011.conpherence.ngrams.sql new file mode 100644 index 0000000000..e06c1489f7 --- /dev/null +++ b/resources/sql/autopatches/20161011.conpherence.ngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_conpherence.conpherence_threadtitle_ngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + KEY `key_object` (objectID), + KEY `key_ngram` (ngram, objectID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20161012.cal.01.import.sql b/resources/sql/autopatches/20161012.cal.01.import.sql new file mode 100644 index 0000000000..aff544f015 --- /dev/null +++ b/resources/sql/autopatches/20161012.cal.01.import.sql @@ -0,0 +1,15 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_import ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + authorPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + engineType VARCHAR(64) NOT NULL, + parameters LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + isDisabled BOOL NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + KEY `key_author` (authorPHID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20161012.cal.02.importxaction.sql b/resources/sql/autopatches/20161012.cal.02.importxaction.sql new file mode 100644 index 0000000000..e17474ac0b --- /dev/null +++ b/resources/sql/autopatches/20161012.cal.02.importxaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_importtransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20161012.cal.03.eventimport.sql b/resources/sql/autopatches/20161012.cal.03.eventimport.sql new file mode 100644 index 0000000000..968b40e00b --- /dev/null +++ b/resources/sql/autopatches/20161012.cal.03.eventimport.sql @@ -0,0 +1,11 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD importAuthorPHID VARBINARY(64); + +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD importSourcePHID VARBINARY(64); + +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD importUIDIndex BINARY(12); + +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD importUID LONGTEXT COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20161013.cal.01.importlog.sql b/resources/sql/autopatches/20161013.cal.01.importlog.sql new file mode 100644 index 0000000000..938f0c8d72 --- /dev/null +++ b/resources/sql/autopatches/20161013.cal.01.importlog.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_importlog ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + importPHID VARBINARY(64) NOT NULL, + parameters LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + KEY `key_import` (`importPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20161016.conpherence.imagephids.sql b/resources/sql/autopatches/20161016.conpherence.imagephids.sql new file mode 100644 index 0000000000..98ba1ef5ce --- /dev/null +++ b/resources/sql/autopatches/20161016.conpherence.imagephids.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_conpherence.conpherence_thread + DROP COLUMN imagePHIDs; diff --git a/scripts/ssh/ssh-auth.php b/scripts/ssh/ssh-auth.php index af6f7f7f43..b25905668b 100755 --- a/scripts/ssh/ssh-auth.php +++ b/scripts/ssh/ssh-auth.php @@ -4,74 +4,84 @@ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; -$keys = id(new PhabricatorAuthSSHKeyQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withIsActive(true) - ->execute(); +$cache = PhabricatorCaches::getMutableCache(); +$authfile_key = PhabricatorAuthSSHKeyQuery::AUTHFILE_CACHEKEY; +$authfile = $cache->getKey($authfile_key); -if (!$keys) { - echo pht('No keys found.')."\n"; - exit(1); -} +if ($authfile === null) { + $keys = id(new PhabricatorAuthSSHKeyQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIsActive(true) + ->execute(); -$bin = $root.'/bin/ssh-exec'; -foreach ($keys as $ssh_key) { - $key_argv = array(); - $object = $ssh_key->getObject(); - if ($object instanceof PhabricatorUser) { - $key_argv[] = '--phabricator-ssh-user'; - $key_argv[] = $object->getUsername(); - } else if ($object instanceof AlmanacDevice) { - if (!$ssh_key->getIsTrusted()) { - // If this key is not a trusted device key, don't allow SSH - // authentication. + if (!$keys) { + echo pht('No keys found.')."\n"; + exit(1); + } + + $bin = $root.'/bin/ssh-exec'; + foreach ($keys as $ssh_key) { + $key_argv = array(); + $object = $ssh_key->getObject(); + if ($object instanceof PhabricatorUser) { + $key_argv[] = '--phabricator-ssh-user'; + $key_argv[] = $object->getUsername(); + } else if ($object instanceof AlmanacDevice) { + if (!$ssh_key->getIsTrusted()) { + // If this key is not a trusted device key, don't allow SSH + // authentication. + continue; + } + $key_argv[] = '--phabricator-ssh-device'; + $key_argv[] = $object->getName(); + } else { + // We don't know what sort of key this is; don't permit SSH auth. continue; } - $key_argv[] = '--phabricator-ssh-device'; - $key_argv[] = $object->getName(); - } else { - // We don't know what sort of key this is; don't permit SSH auth. - continue; + + $key_argv[] = '--phabricator-ssh-key'; + $key_argv[] = $ssh_key->getID(); + + $cmd = csprintf('%s %Ls', $bin, $key_argv); + + $instance = PhabricatorEnv::getEnvConfig('cluster.instance'); + if (strlen($instance)) { + $cmd = csprintf('PHABRICATOR_INSTANCE=%s %C', $instance, $cmd); + } + + // This is additional escaping for the SSH 'command="..."' string. + $cmd = addcslashes($cmd, '"\\'); + + // Strip out newlines and other nonsense from the key type and key body. + + $type = $ssh_key->getKeyType(); + $type = preg_replace('@[\x00-\x20]+@', '', $type); + if (!strlen($type)) { + continue; + } + + $key = $ssh_key->getKeyBody(); + $key = preg_replace('@[\x00-\x20]+@', '', $key); + if (!strlen($key)) { + continue; + } + + $options = array( + 'command="'.$cmd.'"', + 'no-port-forwarding', + 'no-X11-forwarding', + 'no-agent-forwarding', + 'no-pty', + ); + $options = implode(',', $options); + + $lines[] = $options.' '.$type.' '.$key."\n"; } - $key_argv[] = '--phabricator-ssh-key'; - $key_argv[] = $ssh_key->getID(); - - $cmd = csprintf('%s %Ls', $bin, $key_argv); - - $instance = PhabricatorEnv::getEnvConfig('cluster.instance'); - if (strlen($instance)) { - $cmd = csprintf('PHABRICATOR_INSTANCE=%s %C', $instance, $cmd); - } - - // This is additional escaping for the SSH 'command="..."' string. - $cmd = addcslashes($cmd, '"\\'); - - // Strip out newlines and other nonsense from the key type and key body. - - $type = $ssh_key->getKeyType(); - $type = preg_replace('@[\x00-\x20]+@', '', $type); - if (!strlen($type)) { - continue; - } - - $key = $ssh_key->getKeyBody(); - $key = preg_replace('@[\x00-\x20]+@', '', $key); - if (!strlen($key)) { - continue; - } - - $options = array( - 'command="'.$cmd.'"', - 'no-port-forwarding', - 'no-X11-forwarding', - 'no-agent-forwarding', - 'no-pty', - ); - $options = implode(',', $options); - - $lines[] = $options.' '.$type.' '.$key."\n"; + $authfile = implode('', $lines); + $ttl = phutil_units('24 hours in seconds'); + $cache->setKey($authfile_key, $authfile, $ttl); } -echo implode('', $lines); +echo $authfile; exit(0); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 085e86c84b..eafb0acdd5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -140,6 +140,7 @@ phutil_register_library_map(array( 'AphrontDialogView' => 'view/AphrontDialogView.php', 'AphrontEpochHTTPParameterType' => 'aphront/httpparametertype/AphrontEpochHTTPParameterType.php', 'AphrontException' => 'aphront/exception/AphrontException.php', + 'AphrontFileHTTPParameterType' => 'aphront/httpparametertype/AphrontFileHTTPParameterType.php', 'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php', 'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php', 'AphrontFormControl' => 'view/form/control/AphrontFormControl.php', @@ -290,7 +291,6 @@ phutil_register_library_map(array( 'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php', 'ConpherenceDurableColumnView' => 'applications/conpherence/view/ConpherenceDurableColumnView.php', 'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php', - 'ConpherenceFormDragAndDropUploadControl' => 'applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php', 'ConpherenceFulltextQuery' => 'applications/conpherence/query/ConpherenceFulltextQuery.php', 'ConpherenceIndex' => 'applications/conpherence/storage/ConpherenceIndex.php', 'ConpherenceLayoutView' => 'applications/conpherence/view/ConpherenceLayoutView.php', @@ -313,13 +313,16 @@ phutil_register_library_map(array( 'ConpherenceSchemaSpec' => 'applications/conpherence/storage/ConpherenceSchemaSpec.php', 'ConpherenceTestCase' => 'applications/conpherence/__tests__/ConpherenceTestCase.php', 'ConpherenceThread' => 'applications/conpherence/storage/ConpherenceThread.php', + 'ConpherenceThreadDatasource' => 'applications/conpherence/typeahead/ConpherenceThreadDatasource.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', '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', 'ConpherenceTransaction' => 'applications/conpherence/storage/ConpherenceTransaction.php', 'ConpherenceTransactionComment' => 'applications/conpherence/storage/ConpherenceTransactionComment.php', 'ConpherenceTransactionQuery' => 'applications/conpherence/query/ConpherenceTransactionQuery.php', @@ -919,6 +922,7 @@ phutil_register_library_map(array( 'DrydockAuthorizationListView' => 'applications/drydock/view/DrydockAuthorizationListView.php', 'DrydockAuthorizationPHIDType' => 'applications/drydock/phid/DrydockAuthorizationPHIDType.php', 'DrydockAuthorizationQuery' => 'applications/drydock/query/DrydockAuthorizationQuery.php', + 'DrydockAuthorizationSearchConduitAPIMethod' => 'applications/drydock/conduit/DrydockAuthorizationSearchConduitAPIMethod.php', 'DrydockAuthorizationSearchEngine' => 'applications/drydock/query/DrydockAuthorizationSearchEngine.php', 'DrydockAuthorizationViewController' => 'applications/drydock/controller/DrydockAuthorizationViewController.php', 'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php', @@ -936,6 +940,7 @@ phutil_register_library_map(array( 'DrydockBlueprintNameNgrams' => 'applications/drydock/storage/DrydockBlueprintNameNgrams.php', 'DrydockBlueprintPHIDType' => 'applications/drydock/phid/DrydockBlueprintPHIDType.php', 'DrydockBlueprintQuery' => 'applications/drydock/query/DrydockBlueprintQuery.php', + 'DrydockBlueprintSearchConduitAPIMethod' => 'applications/drydock/conduit/DrydockBlueprintSearchConduitAPIMethod.php', 'DrydockBlueprintSearchEngine' => 'applications/drydock/query/DrydockBlueprintSearchEngine.php', 'DrydockBlueprintTransaction' => 'applications/drydock/storage/DrydockBlueprintTransaction.php', 'DrydockBlueprintTransactionQuery' => 'applications/drydock/query/DrydockBlueprintTransactionQuery.php', @@ -2095,8 +2100,49 @@ phutil_register_library_map(array( 'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.php', 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', + 'PhabricatorCalendarICSFileImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php', + 'PhabricatorCalendarICSImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSImportEngine.php', + 'PhabricatorCalendarICSURIImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSURIImportEngine.php', 'PhabricatorCalendarICSWriter' => 'applications/calendar/util/PhabricatorCalendarICSWriter.php', 'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php', + 'PhabricatorCalendarImport' => 'applications/calendar/storage/PhabricatorCalendarImport.php', + 'PhabricatorCalendarImportDefaultLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportDefaultLogType.php', + 'PhabricatorCalendarImportDeleteController' => 'applications/calendar/controller/PhabricatorCalendarImportDeleteController.php', + 'PhabricatorCalendarImportDeleteTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportDeleteTransaction.php', + 'PhabricatorCalendarImportDisableController' => 'applications/calendar/controller/PhabricatorCalendarImportDisableController.php', + 'PhabricatorCalendarImportDisableTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportDisableTransaction.php', + 'PhabricatorCalendarImportDropController' => 'applications/calendar/controller/PhabricatorCalendarImportDropController.php', + 'PhabricatorCalendarImportDuplicateLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportDuplicateLogType.php', + 'PhabricatorCalendarImportEditController' => 'applications/calendar/controller/PhabricatorCalendarImportEditController.php', + 'PhabricatorCalendarImportEditEngine' => 'applications/calendar/editor/PhabricatorCalendarImportEditEngine.php', + 'PhabricatorCalendarImportEditor' => 'applications/calendar/editor/PhabricatorCalendarImportEditor.php', + 'PhabricatorCalendarImportEmptyLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportEmptyLogType.php', + 'PhabricatorCalendarImportEngine' => 'applications/calendar/import/PhabricatorCalendarImportEngine.php', + 'PhabricatorCalendarImportEpochLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportEpochLogType.php', + 'PhabricatorCalendarImportFetchLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFetchLogType.php', + 'PhabricatorCalendarImportFrequencyLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFrequencyLogType.php', + 'PhabricatorCalendarImportICSFileTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php', + 'PhabricatorCalendarImportICSLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php', + 'PhabricatorCalendarImportICSURITransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php', + 'PhabricatorCalendarImportIgnoredNodeLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportIgnoredNodeLogType.php', + 'PhabricatorCalendarImportListController' => 'applications/calendar/controller/PhabricatorCalendarImportListController.php', + 'PhabricatorCalendarImportLog' => 'applications/calendar/storage/PhabricatorCalendarImportLog.php', + 'PhabricatorCalendarImportLogListController' => 'applications/calendar/controller/PhabricatorCalendarImportLogListController.php', + 'PhabricatorCalendarImportLogQuery' => 'applications/calendar/query/PhabricatorCalendarImportLogQuery.php', + 'PhabricatorCalendarImportLogSearchEngine' => 'applications/calendar/query/PhabricatorCalendarImportLogSearchEngine.php', + 'PhabricatorCalendarImportLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportLogType.php', + 'PhabricatorCalendarImportLogView' => 'applications/calendar/view/PhabricatorCalendarImportLogView.php', + 'PhabricatorCalendarImportNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportNameTransaction.php', + 'PhabricatorCalendarImportOriginalLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportOriginalLogType.php', + 'PhabricatorCalendarImportOrphanLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportOrphanLogType.php', + 'PhabricatorCalendarImportPHIDType' => 'applications/calendar/phid/PhabricatorCalendarImportPHIDType.php', + 'PhabricatorCalendarImportQuery' => 'applications/calendar/query/PhabricatorCalendarImportQuery.php', + 'PhabricatorCalendarImportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarImportSearchEngine.php', + 'PhabricatorCalendarImportTransaction' => 'applications/calendar/storage/PhabricatorCalendarImportTransaction.php', + 'PhabricatorCalendarImportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php', + 'PhabricatorCalendarImportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php', + 'PhabricatorCalendarImportUpdateLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportUpdateLogType.php', + 'PhabricatorCalendarImportViewController' => 'applications/calendar/controller/PhabricatorCalendarImportViewController.php', 'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php', 'PhabricatorCalendarReplyHandler' => 'applications/calendar/mail/PhabricatorCalendarReplyHandler.php', 'PhabricatorCalendarSchemaSpec' => 'applications/calendar/storage/PhabricatorCalendarSchemaSpec.php', @@ -2571,6 +2617,7 @@ phutil_register_library_map(array( 'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php', 'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php', 'PhabricatorFileEditController' => 'applications/files/controller/PhabricatorFileEditController.php', + 'PhabricatorFileEditField' => 'applications/transactions/editfield/PhabricatorFileEditField.php', 'PhabricatorFileEditor' => 'applications/files/editor/PhabricatorFileEditor.php', 'PhabricatorFileExternalRequest' => 'applications/files/storage/PhabricatorFileExternalRequest.php', 'PhabricatorFileExternalRequestGarbageCollector' => 'applications/files/garbagecollector/PhabricatorFileExternalRequestGarbageCollector.php', @@ -2993,20 +3040,30 @@ phutil_register_library_map(array( 'PhabricatorOwnersListController' => 'applications/owners/controller/PhabricatorOwnersListController.php', 'PhabricatorOwnersOwner' => 'applications/owners/storage/PhabricatorOwnersOwner.php', 'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php', + 'PhabricatorOwnersPackageAuditingTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageAuditingTransaction.php', + 'PhabricatorOwnersPackageAutoreviewTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageAutoreviewTransaction.php', 'PhabricatorOwnersPackageDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php', + 'PhabricatorOwnersPackageDescriptionTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageDescriptionTransaction.php', + 'PhabricatorOwnersPackageDominionTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageDominionTransaction.php', 'PhabricatorOwnersPackageEditEngine' => 'applications/owners/editor/PhabricatorOwnersPackageEditEngine.php', 'PhabricatorOwnersPackageFulltextEngine' => 'applications/owners/query/PhabricatorOwnersPackageFulltextEngine.php', 'PhabricatorOwnersPackageFunctionDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageFunctionDatasource.php', 'PhabricatorOwnersPackageNameNgrams' => 'applications/owners/storage/PhabricatorOwnersPackageNameNgrams.php', + 'PhabricatorOwnersPackageNameTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageNameTransaction.php', 'PhabricatorOwnersPackageOwnerDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php', + 'PhabricatorOwnersPackageOwnersTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageOwnersTransaction.php', 'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php', + 'PhabricatorOwnersPackagePathsTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackagePathsTransaction.php', + 'PhabricatorOwnersPackagePrimaryTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackagePrimaryTransaction.php', 'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php', 'PhabricatorOwnersPackageRemarkupRule' => 'applications/owners/remarkup/PhabricatorOwnersPackageRemarkupRule.php', 'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php', + 'PhabricatorOwnersPackageStatusTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageStatusTransaction.php', 'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php', 'PhabricatorOwnersPackageTransaction' => 'applications/owners/storage/PhabricatorOwnersPackageTransaction.php', 'PhabricatorOwnersPackageTransactionEditor' => 'applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php', 'PhabricatorOwnersPackageTransactionQuery' => 'applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php', + 'PhabricatorOwnersPackageTransactionType' => 'applications/owners/xaction/PhabricatorOwnersPackageTransactionType.php', 'PhabricatorOwnersPath' => 'applications/owners/storage/PhabricatorOwnersPath.php', 'PhabricatorOwnersPathsController' => 'applications/owners/controller/PhabricatorOwnersPathsController.php', 'PhabricatorOwnersPathsSearchEngineAttachment' => 'applications/owners/engineextension/PhabricatorOwnersPathsSearchEngineAttachment.php', @@ -3751,6 +3808,7 @@ phutil_register_library_map(array( 'PhabricatorSystemDAO' => 'applications/system/storage/PhabricatorSystemDAO.php', 'PhabricatorSystemDestructionGarbageCollector' => 'applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php', 'PhabricatorSystemDestructionLog' => 'applications/system/storage/PhabricatorSystemDestructionLog.php', + 'PhabricatorSystemFaviconController' => 'applications/system/controller/PhabricatorSystemFaviconController.php', 'PhabricatorSystemReadOnlyController' => 'applications/system/controller/PhabricatorSystemReadOnlyController.php', 'PhabricatorSystemRemoveDestroyWorkflow' => 'applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php', 'PhabricatorSystemRemoveLogWorkflow' => 'applications/system/management/PhabricatorSystemRemoveLogWorkflow.php', @@ -4627,6 +4685,7 @@ phutil_register_library_map(array( ), 'AphrontEpochHTTPParameterType' => 'AphrontHTTPParameterType', 'AphrontException' => 'Exception', + 'AphrontFileHTTPParameterType' => 'AphrontHTTPParameterType', 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', @@ -4785,7 +4844,6 @@ phutil_register_library_map(array( 'ConpherenceDAO' => 'PhabricatorLiskDAO', 'ConpherenceDurableColumnView' => 'AphrontTagView', 'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor', - 'ConpherenceFormDragAndDropUploadControl' => 'AphrontFormControl', 'ConpherenceFulltextQuery' => 'PhabricatorOffsetPagedQuery', 'ConpherenceIndex' => 'ConpherenceDAO', 'ConpherenceLayoutView' => 'AphrontTagView', @@ -4813,14 +4871,18 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorMentionableInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorNgramsInterface', ), + 'ConpherenceThreadDatasource' => 'PhabricatorTypeaheadDatasource', 'ConpherenceThreadIndexEngineExtension' => 'PhabricatorIndexEngineExtension', 'ConpherenceThreadListView' => 'AphrontView', 'ConpherenceThreadMailReceiver' => 'PhabricatorObjectMailReceiver', 'ConpherenceThreadMembersPolicyRule' => 'PhabricatorPolicyRule', 'ConpherenceThreadQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'ConpherenceThreadRemarkupRule' => 'PhabricatorObjectRemarkupRule', + 'ConpherenceThreadSearchController' => 'ConpherenceController', 'ConpherenceThreadSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'ConpherenceThreadTitleNgrams' => 'PhabricatorSearchNgrams', 'ConpherenceTransaction' => 'PhabricatorApplicationTransaction', 'ConpherenceTransactionComment' => 'PhabricatorApplicationTransactionComment', 'ConpherenceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -5468,12 +5530,14 @@ phutil_register_library_map(array( 'DrydockAuthorization' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', + 'PhabricatorConduitResultInterface', ), 'DrydockAuthorizationAuthorizeController' => 'DrydockController', 'DrydockAuthorizationListController' => 'DrydockController', 'DrydockAuthorizationListView' => 'AphrontView', 'DrydockAuthorizationPHIDType' => 'PhabricatorPHIDType', 'DrydockAuthorizationQuery' => 'DrydockQuery', + 'DrydockAuthorizationSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'DrydockAuthorizationSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockAuthorizationViewController' => 'DrydockController', 'DrydockBlueprint' => array( @@ -5483,6 +5547,7 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldInterface', 'PhabricatorNgramsInterface', 'PhabricatorProjectInterface', + 'PhabricatorConduitResultInterface', ), 'DrydockBlueprintController' => 'DrydockController', 'DrydockBlueprintCoreCustomField' => array( @@ -5501,6 +5566,7 @@ phutil_register_library_map(array( 'DrydockBlueprintNameNgrams' => 'PhabricatorSearchNgrams', 'DrydockBlueprintPHIDType' => 'PhabricatorPHIDType', 'DrydockBlueprintQuery' => 'DrydockQuery', + 'DrydockBlueprintSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'DrydockBlueprintSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockBlueprintTransaction' => 'PhabricatorApplicationTransaction', 'DrydockBlueprintTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -6781,6 +6847,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEvent' => array( 'PhabricatorCalendarDAO', 'PhabricatorPolicyInterface', + 'PhabricatorExtendedPolicyInterface', 'PhabricatorProjectInterface', 'PhabricatorMarkupInterface', 'PhabricatorApplicationTransactionInterface', @@ -6869,8 +6936,58 @@ phutil_register_library_map(array( 'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController', 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', + 'PhabricatorCalendarICSFileImportEngine' => 'PhabricatorCalendarICSImportEngine', + 'PhabricatorCalendarICSImportEngine' => 'PhabricatorCalendarImportEngine', + 'PhabricatorCalendarICSURIImportEngine' => 'PhabricatorCalendarICSImportEngine', 'PhabricatorCalendarICSWriter' => 'Phobject', 'PhabricatorCalendarIconSet' => 'PhabricatorIconSet', + 'PhabricatorCalendarImport' => array( + 'PhabricatorCalendarDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorDestructibleInterface', + ), + 'PhabricatorCalendarImportDefaultLogType' => 'PhabricatorCalendarImportLogType', + 'PhabricatorCalendarImportDeleteController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarImportDeleteTransaction' => 'PhabricatorCalendarImportTransactionType', + 'PhabricatorCalendarImportDisableController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarImportDisableTransaction' => 'PhabricatorCalendarImportTransactionType', + 'PhabricatorCalendarImportDropController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarImportDuplicateLogType' => 'PhabricatorCalendarImportLogType', + 'PhabricatorCalendarImportEditController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarImportEditEngine' => 'PhabricatorEditEngine', + 'PhabricatorCalendarImportEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorCalendarImportEmptyLogType' => 'PhabricatorCalendarImportLogType', + 'PhabricatorCalendarImportEngine' => 'Phobject', + 'PhabricatorCalendarImportEpochLogType' => 'PhabricatorCalendarImportLogType', + 'PhabricatorCalendarImportFetchLogType' => 'PhabricatorCalendarImportLogType', + 'PhabricatorCalendarImportFrequencyLogType' => 'PhabricatorCalendarImportLogType', + 'PhabricatorCalendarImportICSFileTransaction' => 'PhabricatorCalendarImportTransactionType', + 'PhabricatorCalendarImportICSLogType' => 'PhabricatorCalendarImportLogType', + 'PhabricatorCalendarImportICSURITransaction' => 'PhabricatorCalendarImportTransactionType', + 'PhabricatorCalendarImportIgnoredNodeLogType' => 'PhabricatorCalendarImportLogType', + 'PhabricatorCalendarImportListController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarImportLog' => array( + 'PhabricatorCalendarDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorDestructibleInterface', + ), + 'PhabricatorCalendarImportLogListController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarImportLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorCalendarImportLogSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorCalendarImportLogType' => 'Phobject', + 'PhabricatorCalendarImportLogView' => 'AphrontView', + 'PhabricatorCalendarImportNameTransaction' => 'PhabricatorCalendarImportTransactionType', + 'PhabricatorCalendarImportOriginalLogType' => 'PhabricatorCalendarImportLogType', + 'PhabricatorCalendarImportOrphanLogType' => 'PhabricatorCalendarImportLogType', + 'PhabricatorCalendarImportPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorCalendarImportQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorCalendarImportSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorCalendarImportTransaction' => 'PhabricatorModularTransaction', + 'PhabricatorCalendarImportTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorCalendarImportTransactionType' => 'PhabricatorModularTransactionType', + 'PhabricatorCalendarImportUpdateLogType' => 'PhabricatorCalendarImportLogType', + 'PhabricatorCalendarImportViewController' => 'PhabricatorCalendarController', 'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorCalendarReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorCalendarSchemaSpec' => 'PhabricatorConfigSchemaSpec', @@ -7418,6 +7535,7 @@ phutil_register_library_map(array( 'PhabricatorFileDeleteController' => 'PhabricatorFileController', 'PhabricatorFileDropUploadController' => 'PhabricatorFileController', 'PhabricatorFileEditController' => 'PhabricatorFileController', + 'PhabricatorFileEditField' => 'PhabricatorEditField', 'PhabricatorFileEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorFileExternalRequest' => array( 'PhabricatorFileDAO', @@ -7892,20 +8010,30 @@ phutil_register_library_map(array( 'PhabricatorFulltextInterface', 'PhabricatorNgramsInterface', ), + 'PhabricatorOwnersPackageAuditingTransaction' => 'PhabricatorOwnersPackageTransactionType', + 'PhabricatorOwnersPackageAutoreviewTransaction' => 'PhabricatorOwnersPackageTransactionType', 'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorOwnersPackageDescriptionTransaction' => 'PhabricatorOwnersPackageTransactionType', + 'PhabricatorOwnersPackageDominionTransaction' => 'PhabricatorOwnersPackageTransactionType', 'PhabricatorOwnersPackageEditEngine' => 'PhabricatorEditEngine', 'PhabricatorOwnersPackageFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorOwnersPackageFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorOwnersPackageNameNgrams' => 'PhabricatorSearchNgrams', + 'PhabricatorOwnersPackageNameTransaction' => 'PhabricatorOwnersPackageTransactionType', 'PhabricatorOwnersPackageOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource', + 'PhabricatorOwnersPackageOwnersTransaction' => 'PhabricatorOwnersPackageTransactionType', 'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType', + 'PhabricatorOwnersPackagePathsTransaction' => 'PhabricatorOwnersPackageTransactionType', + 'PhabricatorOwnersPackagePrimaryTransaction' => 'PhabricatorOwnersPackageTransactionType', 'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorOwnersPackageRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorOwnersPackageStatusTransaction' => 'PhabricatorOwnersPackageTransactionType', 'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase', - 'PhabricatorOwnersPackageTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorOwnersPackageTransaction' => 'PhabricatorModularTransaction', 'PhabricatorOwnersPackageTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorOwnersPackageTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorOwnersPackageTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO', 'PhabricatorOwnersPathsController' => 'PhabricatorOwnersController', 'PhabricatorOwnersPathsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', @@ -8815,6 +8943,7 @@ phutil_register_library_map(array( 'PhabricatorSystemDAO' => 'PhabricatorLiskDAO', 'PhabricatorSystemDestructionGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorSystemDestructionLog' => 'PhabricatorSystemDAO', + 'PhabricatorSystemFaviconController' => 'PhabricatorController', 'PhabricatorSystemReadOnlyController' => 'PhabricatorController', 'PhabricatorSystemRemoveDestroyWorkflow' => 'PhabricatorSystemRemoveWorkflow', 'PhabricatorSystemRemoveLogWorkflow' => 'PhabricatorSystemRemoveWorkflow', diff --git a/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php b/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php new file mode 100644 index 0000000000..ceff8c1ddb --- /dev/null +++ b/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php @@ -0,0 +1,60 @@ +getFileKey($key); + return $request->getExists($key) || + $request->getFileExists($file_key); + } + + protected function getParameterValue(AphrontRequest $request, $key) { + $value = $request->getStrList($key); + if ($value) { + return head($value); + } + + // NOTE: At least for now, we'll attempt to read a direct upload if we + // miss on a PHID. Currently, PHUIFormFileControl does a client-side + // upload on workflow forms (which is good) but doesn't have a hook for + // non-workflow forms (which isn't as good). Giving it a hook is desirable, + // but complicated. Even if we do hook it, it may be reasonable to keep + // this code around as a fallback if the client-side JS goes awry. + + $file_key = $this->getFileKey($key); + if (!$request->getFileExists($file_key)) { + return null; + } + + $viewer = $this->getViewer(); + $file = PhabricatorFile::newFromPHPUpload( + idx($_FILES, $file_key), + array( + 'authorPHID' => $viewer->getPHID(), + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + )); + return $file->getPHID(); + } + + protected function getParameterTypeName() { + return 'file'; + } + + protected function getParameterFormatDescriptions() { + return array( + pht('A file PHID.'), + ); + } + + protected function getParameterExamples() { + return array( + 'v=PHID-FILE-wxyz', + ); + } + +} diff --git a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php index 1fc61ffb2c..4d04707598 100644 --- a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php +++ b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php @@ -191,6 +191,20 @@ final class PhabricatorAuthSSHKeyEditor return 'ssh-key-'.$object->getPHID(); } + protected function applyFinalEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + // 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); + + return $xactions; + } + + protected function getMailTo(PhabricatorLiskDAO $object) { return $object->getObject()->getSSHKeyNotifyPHIDs(); } diff --git a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php index 4592d794fa..6ba047d100 100644 --- a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php +++ b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php @@ -3,6 +3,8 @@ final class PhabricatorAuthSSHKeyQuery extends PhabricatorCursorPagedPolicyAwareQuery { + const AUTHFILE_CACHEKEY = 'ssh.authfile'; + private $ids; private $phids; private $objectPHIDs; diff --git a/src/applications/cache/PhabricatorCaches.php b/src/applications/cache/PhabricatorCaches.php index c9bd304ccf..a725238150 100644 --- a/src/applications/cache/PhabricatorCaches.php +++ b/src/applications/cache/PhabricatorCaches.php @@ -99,6 +99,23 @@ final class PhabricatorCaches extends Phobject { return $caches; } + public static function getMutableCache() { + static $cache; + if (!$cache) { + $caches = self::buildMutableCaches(); + $cache = self::newStackFromCaches($caches); + } + return $cache; + } + + private static function buildMutableCaches() { + $caches = array(); + + $caches[] = new PhabricatorKeyValueDatabaseCache(); + + return $caches; + } + /* -( Repository Graph Cache )--------------------------------------------- */ diff --git a/src/applications/cache/PhabricatorKeyValueDatabaseCache.php b/src/applications/cache/PhabricatorKeyValueDatabaseCache.php index 99060478c0..c6a52024fe 100644 --- a/src/applications/cache/PhabricatorKeyValueDatabaseCache.php +++ b/src/applications/cache/PhabricatorKeyValueDatabaseCache.php @@ -98,7 +98,7 @@ final class PhabricatorKeyValueDatabaseCache $this->establishConnection('w'), 'DELETE FROM %T WHERE cacheKeyHash IN (%Ls)', $this->getTableName(), - $keys); + $map); } return $this; diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index 208fc83ae9..fa95f6951b 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -73,7 +73,24 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { => 'PhabricatorCalendarExportICSController', 'disable/(?P[1-9]\d*)/' => 'PhabricatorCalendarExportDisableController', - + ), + 'import/' => array( + $this->getQueryRoutePattern() + => 'PhabricatorCalendarImportListController', + $this->getEditRoutePattern('edit/') + => 'PhabricatorCalendarImportEditController', + '(?P[1-9]\d*)/' + => 'PhabricatorCalendarImportViewController', + 'disable/(?P[1-9]\d*)/' + => 'PhabricatorCalendarImportDisableController', + 'delete/(?P[1-9]\d*)/' + => 'PhabricatorCalendarImportDeleteController', + 'drop/' + => 'PhabricatorCalendarImportDropController', + 'log/' => array( + $this->getQueryRoutePattern() + => 'PhabricatorCalendarImportLogListController', + ), ), ), ); diff --git a/src/applications/calendar/controller/PhabricatorCalendarController.php b/src/applications/calendar/controller/PhabricatorCalendarController.php index 36a9bfbbe9..88ffba9380 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarController.php @@ -18,4 +18,23 @@ abstract class PhabricatorCalendarController extends PhabricatorController { ->setContent($ics_data); } + protected function newImportedEventResponse(PhabricatorCalendarEvent $event) { + if (!$event->isImportedEvent()) { + return null; + } + + // Give the user a specific, detailed message if they try to edit an + // imported event via common web paths. Other edits (including those via + // the API) are blocked by the normal policy system, but this makes it more + // clear exactly why the event can't be edited. + + return $this->newDialog() + ->setTitle(pht('Can Not Edit Imported Event')) + ->appendParagraph( + pht( + 'This event has been imported from an external source and '. + 'can not be edited.')) + ->addCancelButton($event->getURI(), pht('Done')); + } + } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php index 63ce289970..fbf7f9d45e 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php @@ -7,19 +7,27 @@ final class PhabricatorCalendarEventCancelController $viewer = $request->getViewer(); $id = $request->getURIData('id'); + // Just check CAN_VIEW first. Then we'll check if this is an import so + // we can raise a better error. $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($viewer) ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) ->executeOne(); if (!$event) { return new Aphront404Response(); } + $response = $this->newImportedEventResponse($event); + if ($response) { + return $response; + } + + // Now that we've done the import check, check for CAN_EDIT. + PhabricatorPolicyFilter::requireCapability( + $viewer, + $event, + PhabricatorPolicyCapability::CAN_EDIT); + $cancel_uri = $event->getURI(); $is_parent = $event->isParentEvent(); diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index b5beea9019..7c01e795b7 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -4,6 +4,20 @@ final class PhabricatorCalendarEventEditController extends PhabricatorCalendarController { public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + + $id = $request->getURIData('id'); + if ($id) { + $event = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + $response = $this->newImportedEventResponse($event); + if ($response) { + return $response; + } + } + return id(new PhabricatorCalendarEventEditEngine()) ->setController($this) ->buildResponse(); diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php index 9b58d39dec..52ad8cb133 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php @@ -15,6 +15,11 @@ final class PhabricatorCalendarEventJoinController return new Aphront404Response(); } + $response = $this->newImportedEventResponse($event); + if ($response) { + return $response; + } + $cancel_uri = $event->getURI(); $action = $request->getURIData('action'); diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php index ac9015fbcf..711b2eeb3e 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php @@ -7,6 +7,10 @@ final class PhabricatorCalendarEventListController return true; } + public function isGlobalDragAndDropUploadEnabled() { + return true; + } + public function handleRequest(AphrontRequest $request) { $year = $request->getURIData('year'); $month = $request->getURIData('month'); @@ -43,6 +47,10 @@ final class PhabricatorCalendarEventListController ->setType(PHUIListItemView::TYPE_LABEL) ->setName(pht('Import/Export')); + $items[] = id(new PHUIListItemView()) + ->setName('Imports') + ->setHref('/calendar/import/'); + $items[] = id(new PHUIListItemView()) ->setName('Exports') ->setHref('/calendar/export/'); diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 89e8109a4c..c8bc9b5593 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -63,6 +63,19 @@ final class PhabricatorCalendarEventViewController ->setHeader(pht('Details')); $recurring_header = $this->buildRecurringHeader($event); + // NOTE: This is a bit hacky: for imported events, we're just hiding the + // comment form without actually preventing comments. Users could still + // submit a request to add comments to these events. This isn't really a + // major problem since they can't do anything truly bad and there isn't an + // easy way to selectively disable this or some other similar behaviors + // today, but it would probably be nice to fully disable these + // "pseudo-edits" (like commenting and probably subscribing and awarding + // tokens) at some point. + if ($event->isImportedEvent()) { + $comment_view = null; + $timeline->setShouldTerminate(true); + } + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) @@ -105,6 +118,16 @@ final class PhabricatorCalendarEventViewController ->setPolicyObject($event) ->setHeaderIcon($event->getIcon()); + if ($event->isImportedEvent()) { + $header->addTag( + id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setName(pht('Imported')) + ->setIcon('fa-download') + ->setHref($event->getImportSource()->getURI()) + ->setShade('orange')); + } + foreach ($this->buildRSVPActions($event) as $action) { $header->addActionLink($action); } @@ -141,12 +164,15 @@ final class PhabricatorCalendarEventViewController ->setWorkflow(!$can_edit)); } + $can_attend = !$event->isImportedEvent(); + if ($is_attending) { $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Decline Event')) ->setIcon('fa-user-times') ->setHref($this->getApplicationURI("event/join/{$id}/")) + ->setDisabled(!$can_attend) ->setWorkflow(true)); } else { $curtain->addAction( @@ -154,6 +180,7 @@ final class PhabricatorCalendarEventViewController ->setName(pht('Join Event')) ->setIcon('fa-user-plus') ->setHref($this->getApplicationURI("event/join/{$id}/")) + ->setDisabled(!$can_attend) ->setWorkflow(true)); } @@ -261,6 +288,15 @@ final class PhabricatorCalendarEventViewController pht('None')); } + if ($event->isImportedEvent()) { + $properties->addProperty( + pht('Imported By'), + pht( + '%s from %s', + $viewer->renderHandle($event->getImportAuthorPHID()), + $viewer->renderHandle($event->getImportSourcePHID()))); + } + $properties->addProperty( pht('Invitees'), $invitee_list); diff --git a/src/applications/calendar/controller/PhabricatorCalendarImportDeleteController.php b/src/applications/calendar/controller/PhabricatorCalendarImportDeleteController.php new file mode 100644 index 0000000000..fd65777515 --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarImportDeleteController.php @@ -0,0 +1,64 @@ +getViewer(); + + $import = id(new PhabricatorCalendarImportQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$import) { + return new Aphront404Response(); + } + + $import_uri = $import->getURI(); + + $engine = $import->getEngine(); + if (!$engine->canDeleteAnyEvents($viewer, $import)) { + return $this->newDialog() + ->setTitle(pht('No Imported Events')) + ->appendParagraph( + pht( + 'No events from this source currently exist. They may have '. + 'failed to import, have been updated by another source, or '. + 'already have been deleted.')) + ->addCancelButton($import_uri, pht('Done')); + } + + if ($request->isFormPost()) { + $xactions = array(); + $xactions[] = id(new PhabricatorCalendarImportTransaction()) + ->setTransactionType( + PhabricatorCalendarImportDeleteTransaction::TRANSACTIONTYPE) + ->setNewValue(true); + + $editor = id(new PhabricatorCalendarImportEditor()) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->setContentSourceFromRequest($request); + + $editor->applyTransactions($import, $xactions); + + return id(new AphrontRedirectResponse())->setURI($import_uri); + } + + return $this->newDialog() + ->setTitle(pht('Delete Imported Events')) + ->appendParagraph( + pht( + 'Delete all the events that were imported from this source? '. + 'This action can not be undone.')) + ->addCancelButton($import_uri) + ->addSubmitButton(pht('Delete Events')); + } + +} diff --git a/src/applications/calendar/controller/PhabricatorCalendarImportDisableController.php b/src/applications/calendar/controller/PhabricatorCalendarImportDisableController.php new file mode 100644 index 0000000000..5a9b720be4 --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarImportDisableController.php @@ -0,0 +1,71 @@ +getViewer(); + + $import = id(new PhabricatorCalendarImportQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$import) { + return new Aphront404Response(); + } + + $import_uri = $import->getURI(); + $is_disable = !$import->getIsDisabled(); + + if (!$import->getEngine()->canDisable($viewer, $import)) { + $reason = $import->getEngine()->explainCanDisable($viewer, $import); + return $this->newDialog() + ->setTitle(pht('Unable to Disable')) + ->appendParagraph($reason) + ->addCancelButton($import_uri); + } + + if ($request->isFormPost()) { + $xactions = array(); + $xactions[] = id(new PhabricatorCalendarImportTransaction()) + ->setTransactionType( + PhabricatorCalendarImportDisableTransaction::TRANSACTIONTYPE) + ->setNewValue($is_disable ? 1 : 0); + + $editor = id(new PhabricatorCalendarImportEditor()) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->setContentSourceFromRequest($request); + + $editor->applyTransactions($import, $xactions); + + return id(new AphrontRedirectResponse())->setURI($import_uri); + } + + if ($is_disable) { + $title = pht('Disable Import'); + $body = pht( + 'Disable this import? Events from this source will no longer be '. + 'updated.'); + $button = pht('Disable Import'); + } else { + $title = pht('Enable Import'); + $body = pht( + 'Enable this import? Events from this source will be updated again.'); + $button = pht('Enable Import'); + } + + return $this->newDialog() + ->setTitle($title) + ->appendParagraph($body) + ->addCancelButton($import_uri) + ->addSubmitButton($button); + } + +} diff --git a/src/applications/calendar/controller/PhabricatorCalendarImportDropController.php b/src/applications/calendar/controller/PhabricatorCalendarImportDropController.php new file mode 100644 index 0000000000..03f515087b --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarImportDropController.php @@ -0,0 +1,86 @@ +getViewer(); + + if (!$request->validateCSRF()) { + return new Aphront400Response(); + } + + $cancel_uri = $this->getApplicationURI(); + + $ids = $request->getStrList('h'); + if ($ids) { + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withIDs($ids) + ->setRaisePolicyExceptions(true) + ->execute(); + } else { + $files = array(); + } + + if (!$files) { + return $this->newDialog() + ->setTitle(pht('Nothing Uploaded')) + ->appendParagraph( + pht( + 'Drag and drop .ics files to upload them and import them into '. + 'Calendar.')) + ->addCancelButton($cancel_uri, pht('Done')); + } + + $engine = new PhabricatorCalendarICSImportEngine(); + $imports = array(); + foreach ($files as $file) { + $import = PhabricatorCalendarImport::initializeNewCalendarImport( + $viewer, + clone $engine); + + $xactions = array(); + $xactions[] = id(new PhabricatorCalendarImportTransaction()) + ->setTransactionType( + PhabricatorCalendarImportICSFileTransaction::TRANSACTIONTYPE) + ->setNewValue($file->getPHID()); + + $editor = id(new PhabricatorCalendarImportEditor()) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->setContentSourceFromRequest($request); + + $editor->applyTransactions($import, $xactions); + + $imports[] = $import; + } + + $import_phids = mpull($imports, 'getPHID'); + $events = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withImportSourcePHIDs($import_phids) + ->execute(); + + if (count($events) == 1) { + // The user imported exactly one event. This is consistent with dropping + // a .ics file from an email; just take them to the event. + $event = head($events); + $next_uri = $event->getURI(); + } else if (count($imports) > 1) { + // The user imported multiple different files. Take them to a summary + // list of generated import activity. + $source_phids = implode(',', $import_phids); + $next_uri = '/calendar/import/log/?importSourcePHIDs='.$source_phids; + } else { + // The user imported one file, which had zero or more than one event. + // Take them to the import detail page. + $import = head($imports); + $next_uri = $import->getURI(); + } + + return id(new AphrontRedirectResponse())->setURI($next_uri); + } + +} diff --git a/src/applications/calendar/controller/PhabricatorCalendarImportEditController.php b/src/applications/calendar/controller/PhabricatorCalendarImportEditController.php new file mode 100644 index 0000000000..0af9fa1540 --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarImportEditController.php @@ -0,0 +1,92 @@ +setController($this); + + $id = $request->getURIData('id'); + if (!$id) { + $list_uri = $this->getApplicationURI('import/'); + + $import_type = $request->getStr('importType'); + $import_engines = PhabricatorCalendarImportEngine::getAllImportEngines(); + if (empty($import_engines[$import_type])) { + return $this->buildEngineTypeResponse($list_uri); + } + + $import_engine = $import_engines[$import_type]; + + $engine + ->addContextParameter('importType', $import_type) + ->setImportEngine($import_engine); + } + + return $engine->buildResponse(); + } + + private function buildEngineTypeResponse($cancel_uri) { + $import_engines = PhabricatorCalendarImportEngine::getAllImportEngines(); + + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $e_import = null; + $errors = array(); + if ($request->isFormPost()) { + $e_import = pht('Required'); + $errors[] = pht( + 'To import events, you must select a source to import from.'); + } + + $type_control = id(new AphrontFormRadioButtonControl()) + ->setLabel(pht('Import Type')) + ->setName('importType') + ->setError($e_import); + + foreach ($import_engines as $import_engine) { + $type_control->addButton( + $import_engine->getImportEngineType(), + $import_engine->getImportEngineName(), + $import_engine->getImportEngineHint()); + } + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('New Import')); + $crumbs->setBorder(true); + + $title = pht('Choose Import Type'); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('New Import')) + ->setHeaderIcon('fa-upload'); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild($type_control) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Continue')) + ->addCancelButton($cancel_uri)); + + $box = id(new PHUIObjectBoxView()) + ->setFormErrors($errors) + ->setHeaderText(pht('Import')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter( + array( + $box, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } + +} diff --git a/src/applications/calendar/controller/PhabricatorCalendarImportListController.php b/src/applications/calendar/controller/PhabricatorCalendarImportListController.php new file mode 100644 index 0000000000..df78ea2445 --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarImportListController.php @@ -0,0 +1,25 @@ +setController($this) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Import Events')) + ->setHref($this->getApplicationURI('import/edit/')) + ->setIcon('fa-upload')); + + return $crumbs; + } + + +} diff --git a/src/applications/calendar/controller/PhabricatorCalendarImportLogListController.php b/src/applications/calendar/controller/PhabricatorCalendarImportLogListController.php new file mode 100644 index 0000000000..91bdef4fdf --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarImportLogListController.php @@ -0,0 +1,12 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/calendar/controller/PhabricatorCalendarImportViewController.php b/src/applications/calendar/controller/PhabricatorCalendarImportViewController.php new file mode 100644 index 0000000000..59b0f86915 --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarImportViewController.php @@ -0,0 +1,241 @@ +getViewer(); + + $import = id(new PhabricatorCalendarImportQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); + if (!$import) { + return new Aphront404Response(); + } + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb( + pht('Imports'), + '/calendar/import/'); + $crumbs->addTextCrumb(pht('Import %d', $import->getID())); + $crumbs->setBorder(true); + + $timeline = $this->buildTransactionTimeline( + $import, + new PhabricatorCalendarImportTransactionQuery()); + $timeline->setShouldTerminate(true); + + $header = $this->buildHeaderView($import); + $curtain = $this->buildCurtain($import); + $details = $this->buildPropertySection($import); + + $log_messages = $this->buildLogMessages($import); + $imported_events = $this->buildImportedEvents($import); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn( + array( + $log_messages, + $imported_events, + $timeline, + )) + ->setCurtain($curtain) + ->addPropertySection(pht('Details'), $details); + + $page_title = pht( + 'Import %d %s', + $import->getID(), + $import->getDisplayName()); + + return $this->newPage() + ->setTitle($page_title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($import->getPHID())) + ->appendChild($view); + } + + private function buildHeaderView( + PhabricatorCalendarImport $import) { + $viewer = $this->getViewer(); + $id = $import->getID(); + + if ($import->getIsDisabled()) { + $icon = 'fa-ban'; + $color = 'red'; + $status = pht('Disabled'); + } else { + $icon = 'fa-check'; + $color = 'bluegrey'; + $status = pht('Active'); + } + + $header = id(new PHUIHeaderView()) + ->setViewer($viewer) + ->setHeader($import->getDisplayName()) + ->setStatus($icon, $color, $status) + ->setPolicyObject($import); + + return $header; + } + + private function buildCurtain(PhabricatorCalendarImport $import) { + $viewer = $this->getViewer(); + $id = $import->getID(); + + $curtain = $this->newCurtainView($import); + $engine = $import->getEngine(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $import, + PhabricatorPolicyCapability::CAN_EDIT); + + $edit_uri = "import/edit/{$id}/"; + $edit_uri = $this->getApplicationURI($edit_uri); + + $can_disable = ($can_edit && $engine->canDisable($viewer, $import)); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Import')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($edit_uri)); + + $disable_uri = "import/disable/{$id}/"; + $disable_uri = $this->getApplicationURI($disable_uri); + if ($import->getIsDisabled()) { + $disable_name = pht('Enable Import'); + $disable_icon = 'fa-check'; + } else { + $disable_name = pht('Disable Import'); + $disable_icon = 'fa-ban'; + } + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName($disable_name) + ->setIcon($disable_icon) + ->setDisabled(!$can_disable) + ->setWorkflow(true) + ->setHref($disable_uri)); + + + if ($can_edit) { + $can_delete = $engine->canDeleteAnyEvents($viewer, $import); + } else { + $can_delete = false; + } + + $delete_uri = "import/delete/{$id}/"; + $delete_uri = $this->getApplicationURI($delete_uri); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Delete Imported Events')) + ->setIcon('fa-times') + ->setDisabled(!$can_delete) + ->setWorkflow(true) + ->setHref($delete_uri)); + + return $curtain; + } + + private function buildPropertySection( + PhabricatorCalendarImport $import) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setViewer($viewer); + + $engine = $import->getEngine(); + + $properties->addProperty( + pht('Source Type'), + $engine->getImportEngineTypeName()); + + $engine->appendImportProperties( + $viewer, + $import, + $properties); + + return $properties; + } + + private function buildLogMessages(PhabricatorCalendarImport $import) { + $viewer = $this->getViewer(); + + $logs = id(new PhabricatorCalendarImportLogQuery()) + ->setViewer($viewer) + ->withImportPHIDs(array($import->getPHID())) + ->setLimit(25) + ->execute(); + + $logs_view = id(new PhabricatorCalendarImportLogView()) + ->setViewer($viewer) + ->setLogs($logs); + + $all_uri = $this->getApplicationURI('import/log/'); + $all_uri = (string)id(new PhutilURI($all_uri)) + ->setQueryParam('importSourcePHID', $import->getPHID()); + + $all_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('View All')) + ->setIcon('fa-search') + ->setHref($all_uri); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Log Messages')) + ->addActionLink($all_button); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($logs_view); + } + + private function buildImportedEvents(PhabricatorCalendarImport $import) { + $viewer = $this->getViewer(); + + $engine = id(new PhabricatorCalendarEventSearchEngine()) + ->setViewer($viewer); + + $saved = $engine->newSavedQuery() + ->setParameter('importSourcePHIDs', array($import->getPHID())); + + $pager = $engine->newPagerForSavedQuery($saved); + $pager->setPageSize(25); + + $query = $engine->buildQueryFromSavedQuery($saved); + + $results = $engine->executeQuery($query, $pager); + $view = $engine->renderResults($results, $saved); + $list = $view->getObjectList(); + $list->setNoDataString(pht('No imported events.')); + + $all_uri = $this->getApplicationURI(); + $all_uri = (string)id(new PhutilURI($all_uri)) + ->setQueryParam('importSourcePHID', $import->getPHID()) + ->setQueryParam('display', 'list'); + + $all_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('View All')) + ->setIcon('fa-search') + ->setHref($all_uri); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Imported Events')) + ->addActionLink($all_button); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + } + +} diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index 33442b6702..655037c140 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -200,6 +200,11 @@ final class PhabricatorCalendarEventEditor protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { + + if ($object->isImportedEvent()) { + return false; + } + return true; } @@ -210,6 +215,11 @@ final class PhabricatorCalendarEventEditor protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { + + if ($object->isImportedEvent()) { + return false; + } + return true; } diff --git a/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php new file mode 100644 index 0000000000..031a890d59 --- /dev/null +++ b/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php @@ -0,0 +1,115 @@ +importEngine = $engine; + return $this; + } + + public function getImportEngine() { + return $this->importEngine; + } + + public function getEngineName() { + return pht('Calendar Imports'); + } + + public function isEngineConfigurable() { + return false; + } + + public function getSummaryHeader() { + return pht('Configure Calendar Import Forms'); + } + + public function getSummaryText() { + return pht('Configure how users create and edit imports.'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorCalendarApplication'; + } + + protected function newEditableObject() { + $viewer = $this->getViewer(); + $engine = $this->getImportEngine(); + + return PhabricatorCalendarImport::initializeNewCalendarImport( + $viewer, + $engine); + } + + protected function newObjectQuery() { + return new PhabricatorCalendarImportQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Import'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Import: %s', $object->getDisplayName()); + } + + protected function getObjectEditShortText($object) { + return pht('Import %d', $object->getID()); + } + + protected function getObjectCreateShortText() { + return pht('Create Import'); + } + + protected function getObjectName() { + return pht('Import'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('import/edit/'); + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + + $fields = array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the import.')) + ->setTransactionType( + PhabricatorCalendarImportNameTransaction::TRANSACTIONTYPE) + ->setConduitDescription(pht('Rename the import.')) + ->setConduitTypeDescription(pht('New import name.')) + ->setValue($object->getName()), + id(new PhabricatorBoolEditField()) + ->setKey('disabled') + ->setOptions(pht('Active'), pht('Disabled')) + ->setLabel(pht('Disabled')) + ->setDescription(pht('Disable the import.')) + ->setTransactionType( + PhabricatorCalendarImportDisableTransaction::TRANSACTIONTYPE) + ->setIsConduitOnly(true) + ->setConduitDescription(pht('Disable or restore the import.')) + ->setConduitTypeDescription(pht('True to cancel the import.')) + ->setValue($object->getIsDisabled()), + ); + + $import_engine = $object->getEngine(); + foreach ($import_engine->newEditEngineFields($this, $object) as $field) { + $fields[] = $field; + } + + return $fields; + } + + +} diff --git a/src/applications/calendar/editor/PhabricatorCalendarImportEditor.php b/src/applications/calendar/editor/PhabricatorCalendarImportEditor.php new file mode 100644 index 0000000000..fe22a9f8c9 --- /dev/null +++ b/src/applications/calendar/editor/PhabricatorCalendarImportEditor.php @@ -0,0 +1,42 @@ +getIsNewObject()) { + $actor = $this->getActor(); + + $import_engine = $object->getEngine(); + $import_engine->didCreateImport($actor, $object); + } + + return $xactions; + } + + +} diff --git a/src/applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php b/src/applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php new file mode 100644 index 0000000000..7284776da6 --- /dev/null +++ b/src/applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php @@ -0,0 +1,102 @@ +getParameter($phid_key); + + $properties->addProperty( + pht('Source File'), + $viewer->renderHandle($file_phid)); + } + + public function newEditEngineFields( + PhabricatorEditEngine $engine, + PhabricatorCalendarImport $import) { + $fields = array(); + + if ($engine->getIsCreate()) { + $fields[] = id(new PhabricatorFileEditField()) + ->setKey('icsFilePHID') + ->setLabel(pht('ICS File')) + ->setDescription(pht('ICS file to import.')) + ->setTransactionType( + PhabricatorCalendarImportICSFileTransaction::TRANSACTIONTYPE) + ->setConduitDescription(pht('File PHID to import.')) + ->setConduitTypeDescription(pht('File PHID.')); + } + + return $fields; + } + + public function getDisplayName(PhabricatorCalendarImport $import) { + $filename_key = PhabricatorCalendarImportICSFileTransaction::PARAMKEY_NAME; + $filename = $import->getParameter($filename_key); + if (strlen($filename)) { + return pht('ICS File "%s"', $filename); + } else { + return pht('ICS File'); + } + } + + public function didCreateImport( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import) { + + $phid_key = PhabricatorCalendarImportICSFileTransaction::PARAMKEY_FILE; + $file_phid = $import->getParameter($phid_key); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + if (!$file) { + throw new Exception( + pht( + 'Unable to load file ("%s") for import.', + $file_phid)); + } + + $data = $file->loadFileData(); + + return $this->importICSData($viewer, $import, $data); + } + + + public function canDisable( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import) { + return false; + } + + public function explainCanDisable( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import) { + return pht( + 'You can not disable import of an ICS file because the entire import '. + 'occurs immediately when you upload the file. There is no further '. + 'activity to disable.'); + } + + +} diff --git a/src/applications/calendar/import/PhabricatorCalendarICSImportEngine.php b/src/applications/calendar/import/PhabricatorCalendarICSImportEngine.php new file mode 100644 index 0000000000..3d94085469 --- /dev/null +++ b/src/applications/calendar/import/PhabricatorCalendarICSImportEngine.php @@ -0,0 +1,34 @@ +parseICSData($data); + } catch (PhutilICSParserException $ex) { + // TODO: In theory, it would be nice to store these in a fully abstract + // form so they can be translated at display time. As-is, we'll store the + // error messages in whatever language we were using when the parser + // failure occurred. + + $import->newLogMessage( + PhabricatorCalendarImportICSLogType::LOGTYPE, + array( + 'ics.code' => $ex->getParserFailureCode(), + 'ics.message' => $ex->getMessage(), + )); + + $document = null; + } + + return $this->importEventDocument($viewer, $import, $document); + } + +} diff --git a/src/applications/calendar/import/PhabricatorCalendarICSURIImportEngine.php b/src/applications/calendar/import/PhabricatorCalendarICSURIImportEngine.php new file mode 100644 index 0000000000..7b1754a911 --- /dev/null +++ b/src/applications/calendar/import/PhabricatorCalendarICSURIImportEngine.php @@ -0,0 +1,111 @@ +getParameter($uri_key); + + // Since the URI may contain a secret hash, don't show it to users who + // can not edit the import. + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $import, + PhabricatorPolicyCapability::CAN_EDIT); + if (!$can_edit) { + $uri_display = phutil_tag('em', array(), pht('Restricted')); + } else if (!PhabricatorEnv::isValidRemoteURIForLink($uri)) { + $uri_display = $uri; + } else { + $uri_display = phutil_tag( + 'a', + array( + 'href' => $uri, + 'target' => '_blank', + ), + $uri); + } + + $properties->addProperty(pht('Source URI'), $uri_display); + } + + public function newEditEngineFields( + PhabricatorEditEngine $engine, + PhabricatorCalendarImport $import) { + $fields = array(); + + if ($engine->getIsCreate()) { + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('uri') + ->setLabel(pht('URI')) + ->setDescription(pht('URI to import.')) + ->setTransactionType( + PhabricatorCalendarImportICSURITransaction::TRANSACTIONTYPE) + ->setConduitDescription(pht('URI to import.')) + ->setConduitTypeDescription(pht('New URI.')); + } + + return $fields; + } + + public function getDisplayName(PhabricatorCalendarImport $import) { + return pht('ICS URI'); + } + + public function didCreateImport( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import) { + + $uri_key = PhabricatorCalendarImportICSURITransaction::PARAMKEY_URI; + $uri = $import->getParameter($uri_key); + + PhabricatorSystemActionEngine::willTakeAction( + array($viewer->getPHID()), + new PhabricatorFilesOutboundRequestAction(), + 1); + + $file = PhabricatorFile::newFromFileDownload( + $uri, + array( + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + 'authorPHID' => $import->getAuthorPHID(), + 'canCDN' => true, + )); + + $import->newLogMessage( + PhabricatorCalendarImportFetchLogType::LOGTYPE, + array( + 'file.phid' => $file->getPHID(), + )); + + $data = $file->loadFileData(); + + return $this->importICSData($viewer, $import, $data); + } + + public function canDisable( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import) { + return true; + } + +} diff --git a/src/applications/calendar/import/PhabricatorCalendarImportEngine.php b/src/applications/calendar/import/PhabricatorCalendarImportEngine.php new file mode 100644 index 0000000000..2fc9399fe3 --- /dev/null +++ b/src/applications/calendar/import/PhabricatorCalendarImportEngine.php @@ -0,0 +1,424 @@ +getPhobjectClassConstant('ENGINETYPE', 64); + } + + + abstract public function getImportEngineName(); + abstract public function getImportEngineTypeName(); + abstract public function getImportEngineHint(); + + public function appendImportProperties( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import, + PHUIPropertyListView $properties) { + return; + } + + abstract public function newEditEngineFields( + PhabricatorEditEngine $engine, + PhabricatorCalendarImport $import); + + abstract public function getDisplayName(PhabricatorCalendarImport $import); + + abstract public function didCreateImport( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import); + + abstract public function canDisable( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import); + + public function explainCanDisable( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import) { + throw new PhutilMethodNotImplementedException(); + } + + final public static function getAllImportEngines() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getImportEngineType') + ->setSortMethod('getImportEngineName') + ->execute(); + } + + final protected function importEventDocument( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import, + PhutilCalendarRootNode $root = null) { + + $event_type = PhutilCalendarEventNode::NODETYPE; + + $nodes = array(); + if ($root) { + foreach ($root->getChildren() as $document) { + foreach ($document->getChildren() as $node) { + $node_type = $node->getNodeType(); + if ($node_type != $event_type) { + $import->newLogMessage( + PhabricatorCalendarImportIgnoredNodeLogType::LOGTYPE, + array( + 'node.type' => $node_type, + )); + continue; + } + + $nodes[] = $node; + } + } + } + + // Reject events which have dates outside of the range of a signed + // 32-bit integer. We'll need to accommodate a wider range of events + // eventually, but have about 20 years until it's an issue and we'll + // all be dead by then. + foreach ($nodes as $key => $node) { + $dates = array(); + $dates[] = $node->getStartDateTime(); + $dates[] = $node->getEndDateTime(); + $dates[] = $node->getCreatedDateTime(); + $dates[] = $node->getModifiedDateTime(); + $rrule = $node->getRecurrenceRule(); + if ($rrule) { + $dates[] = $rrule->getUntil(); + } + + $bad_date = false; + foreach ($dates as $date) { + if ($date === null) { + continue; + } + + $year = $date->getYear(); + if ($year < 1970 || $year > 2037) { + $bad_date = true; + break; + } + } + + if ($bad_date) { + $import->newLogMessage( + PhabricatorCalendarImportEpochLogType::LOGTYPE, + array()); + unset($nodes[$key]); + } + } + + // Reject events which occur too frequently. Users do not normally define + // these events and the UI and application make many assumptions which are + // incompatible with events recurring once per second. + foreach ($nodes as $key => $node) { + $rrule = $node->getRecurrenceRule(); + if (!$rrule) { + // This is not a recurring event, so we don't need to check the + // frequency. + continue; + } + $scale = $rrule->getFrequencyScale(); + if ($scale >= PhutilCalendarRecurrenceRule::SCALE_DAILY) { + // This is a daily, weekly, monthly, or yearly event. These are + // supported. + } else { + // This is an hourly, minutely, or secondly event. + $import->newLogMessage( + PhabricatorCalendarImportFrequencyLogType::LOGTYPE, + array( + 'frequency' => $rrule->getFrequency(), + )); + unset($nodes[$key]); + } + } + + $node_map = array(); + foreach ($nodes as $node) { + $full_uid = $this->getFullNodeUID($node); + if (isset($node_map[$full_uid])) { + $import->newLogMessage( + PhabricatorCalendarImportDuplicateLogType::LOGTYPE, + array( + 'uid.full' => $full_uid, + )); + continue; + } + $node_map[$full_uid] = $node; + } + + // If we already know about some of these events and they were created + // here, we're not going to import it again. This can happen if a user + // exports an event and then tries to import it again. This is probably + // not what they meant to do and this pathway generally leads to madness. + $likely_phids = array(); + foreach ($node_map as $full_uid => $node) { + $uid = $node->getUID(); + $matches = null; + if (preg_match('/^(PHID-.*)@(.*)\z/', $uid, $matches)) { + $likely_phids[$full_uid] = $matches[1]; + } + } + + if ($likely_phids) { + // NOTE: We're using the omnipotent viewer here because we don't want + // to collide with events that already exist, even if you can't see + // them. + $events = id(new PhabricatorCalendarEventQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($likely_phids) + ->execute(); + $events = mpull($events, null, 'getPHID'); + foreach ($node_map as $full_uid => $node) { + $phid = idx($likely_phids, $full_uid); + if (!$phid) { + continue; + } + + $event = idx($events, $phid); + if (!$event) { + continue; + } + + $import->newLogMessage( + PhabricatorCalendarImportOriginalLogType::LOGTYPE, + array( + 'phid' => $event->getPHID(), + )); + + unset($node_map[$full_uid]); + } + } + + if ($node_map) { + $events = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withImportAuthorPHIDs(array($viewer->getPHID())) + ->withImportUIDs(array_keys($node_map)) + ->execute(); + $events = mpull($events, null, 'getImportUID'); + } else { + $events = null; + } + + $xactions = array(); + $update_map = array(); + foreach ($node_map as $full_uid => $node) { + $event = idx($events, $full_uid); + if (!$event) { + $event = PhabricatorCalendarEvent::initializeNewCalendarEvent($viewer); + } + + $event + ->setImportAuthorPHID($viewer->getPHID()) + ->setImportSourcePHID($import->getPHID()) + ->setImportUID($full_uid) + ->attachImportSource($import); + + $this->updateEventFromNode($viewer, $event, $node); + $xactions[$full_uid] = $this->newUpdateTransactions($event, $node); + $update_map[$full_uid] = $event; + } + + // Reorder events so we create parents first. This allows us to populate + // "instanceOfEventPHID" correctly. + $insert_order = array(); + foreach ($update_map as $full_uid => $event) { + $parent_uid = $this->getParentNodeUID($node_map[$full_uid]); + if ($parent_uid === null) { + $insert_order[$full_uid] = $full_uid; + continue; + } + + if (empty($update_map[$parent_uid])) { + // The parent was not present in this import, which means it either + // does not exist or we're going to delete it anyway. We just drop + // this node. + + $import->newLogMessage( + PhabricatorCalendarImportOrphanLogType::LOGTYPE, + array( + 'uid.full' => $full_uid, + 'uid.parent' => $parent_uid, + )); + + continue; + } + + // Otherwise, we're going to insert the parent first, then insert + // the child. + $insert_order[$parent_uid] = $parent_uid; + $insert_order[$full_uid] = $full_uid; + } + + // TODO: Define per-engine content sources so this can say "via Upload" or + // whatever. + $content_source = PhabricatorContentSource::newForSource( + PhabricatorWebContentSource::SOURCECONST); + + // NOTE: We're using the omnipotent user here because imported events are + // otherwise immutable. + $edit_actor = PhabricatorUser::getOmnipotentUser(); + + $update_map = array_select_keys($update_map, $insert_order); + foreach ($update_map as $full_uid => $event) { + $parent_uid = $this->getParentNodeUID($node_map[$full_uid]); + if ($parent_uid) { + $parent_phid = $update_map[$full_uid]->getPHID(); + } else { + $parent_phid = null; + } + + $event->setInstanceOfEventPHID($parent_phid); + + $event_xactions = $xactions[$full_uid]; + + $editor = id(new PhabricatorCalendarEventEditor()) + ->setActor($edit_actor) + ->setActingAsPHID($import->getPHID()) + ->setContentSource($content_source) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $is_new = !$event->getID(); + + $editor->applyTransactions($event, $event_xactions); + + $import->newLogMessage( + PhabricatorCalendarImportUpdateLogType::LOGTYPE, + array( + 'new' => $is_new, + 'phid' => $event->getPHID(), + )); + } + + if (!$update_map) { + $import->newLogMessage( + PhabricatorCalendarImportEmptyLogType::LOGTYPE, + array()); + } + + // TODO: When the source is a subscription-based ICS file or some other + // similar source, we should load all events from the source here and + // destroy the ones we didn't update. These are events that have been + // deleted. + } + + private function getFullNodeUID(PhutilCalendarEventNode $node) { + $uid = $node->getUID(); + $instance_epoch = $this->getNodeInstanceEpoch($node); + $full_uid = $uid.'/'.$instance_epoch; + + return $full_uid; + } + + private function getParentNodeUID(PhutilCalendarEventNode $node) { + $recurrence_id = $node->getRecurrenceID(); + + if (!strlen($recurrence_id)) { + return null; + } + + return $node->getUID().'/'; + } + + private function getNodeInstanceEpoch(PhutilCalendarEventNode $node) { + $instance_iso = $node->getRecurrenceID(); + if (strlen($instance_iso)) { + $instance_datetime = PhutilCalendarAbsoluteDateTime::newFromISO8601( + $instance_iso); + $instance_epoch = $instance_datetime->getEpoch(); + } else { + $instance_epoch = null; + } + + return $instance_epoch; + } + + private function newUpdateTransactions( + PhabricatorCalendarEvent $event, + PhutilCalendarEventNode $node) { + + $xactions = array(); + $uid = $node->getUID(); + + $name = $node->getName(); + if (!strlen($name)) { + if (strlen($uid)) { + $name = pht('Unnamed Event "%s"', $uid); + } else { + $name = pht('Unnamed Imported Event'); + } + } + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventNameTransaction::TRANSACTIONTYPE) + ->setNewValue($name); + + $description = $node->getDescription(); + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventDescriptionTransaction::TRANSACTIONTYPE) + ->setNewValue((string)$description); + + $is_recurring = (bool)$node->getRecurrenceRule(); + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventRecurringTransaction::TRANSACTIONTYPE) + ->setNewValue($is_recurring); + + return $xactions; + } + + private function updateEventFromNode( + PhabricatorUser $actor, + PhabricatorCalendarEvent $event, + PhutilCalendarEventNode $node) { + + $instance_epoch = $this->getNodeInstanceEpoch($node); + $event->setUTCInstanceEpoch($instance_epoch); + + $timezone = $actor->getTimezoneIdentifier(); + + // TODO: These should be transactional, but the transaction only accepts + // epoch timestamps right now. + $start_datetime = $node->getStartDateTime() + ->setViewerTimezone($timezone); + $end_datetime = $node->getEndDateTime() + ->setViewerTimezone($timezone); + + $event + ->setStartDateTime($start_datetime) + ->setEndDateTime($end_datetime); + + // TODO: This should be transactional, but the transaction only accepts + // simple frequency rules right now. + $rrule = $node->getRecurrenceRule(); + if ($rrule) { + $event->setRecurrenceRule($rrule); + + $until_datetime = $rrule->getUntil(); + if ($until_datetime) { + $until_datetime->setViewerTimezone($timezone); + $event->setUntilDateTime($until_datetime); + } + } + + return $event; + } + + public function canDeleteAnyEvents( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import) { + + $any_event = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withImportSourcePHIDs(array($import->getPHID())) + ->setLimit(1) + ->execute(); + + return (bool)$any_event; + } + +} diff --git a/src/applications/calendar/importlog/PhabricatorCalendarImportDefaultLogType.php b/src/applications/calendar/importlog/PhabricatorCalendarImportDefaultLogType.php new file mode 100644 index 0000000000..4e61e3f94f --- /dev/null +++ b/src/applications/calendar/importlog/PhabricatorCalendarImportDefaultLogType.php @@ -0,0 +1,20 @@ +getParameter('type'); + if (strlen($type)) { + return pht('Unknown Message "%s"', $type); + } else { + return pht('Unknown Message'); + } + } + +} diff --git a/src/applications/calendar/importlog/PhabricatorCalendarImportDuplicateLogType.php b/src/applications/calendar/importlog/PhabricatorCalendarImportDuplicateLogType.php new file mode 100644 index 0000000000..0f72932404 --- /dev/null +++ b/src/applications/calendar/importlog/PhabricatorCalendarImportDuplicateLogType.php @@ -0,0 +1,23 @@ +getParameter('uid.full'); + return pht( + 'Ignored duplicate event "%s" present in source.', + $duplicate_uid); + } + +} diff --git a/src/applications/calendar/importlog/PhabricatorCalendarImportEmptyLogType.php b/src/applications/calendar/importlog/PhabricatorCalendarImportEmptyLogType.php new file mode 100644 index 0000000000..5a540645ba --- /dev/null +++ b/src/applications/calendar/importlog/PhabricatorCalendarImportEmptyLogType.php @@ -0,0 +1,32 @@ +renderHandle($log->getParameter('file.phid')); + } + + public function getDisplayIcon( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + return 'fa-download'; + } + + public function getDisplayColor( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + return 'green'; + } + +} diff --git a/src/applications/calendar/importlog/PhabricatorCalendarImportFrequencyLogType.php b/src/applications/calendar/importlog/PhabricatorCalendarImportFrequencyLogType.php new file mode 100644 index 0000000000..b7d6177939 --- /dev/null +++ b/src/applications/calendar/importlog/PhabricatorCalendarImportFrequencyLogType.php @@ -0,0 +1,38 @@ +getParameter('frequency'); + + return pht( + 'Ignored an event with an unsupported frequency rule ("%s"). Events '. + 'which repeat more frequently than daily are not supported.', + $frequency); + } + + public function getDisplayIcon( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + return 'fa-clock-o'; + } + + public function getDisplayColor( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + return 'red'; + } + +} diff --git a/src/applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php b/src/applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php new file mode 100644 index 0000000000..b3032f073e --- /dev/null +++ b/src/applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php @@ -0,0 +1,36 @@ +getParameter('ics.code'), + $log->getParameter('ics.message')); + } + + + public function getDisplayIcon( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + return 'fa-file'; + } + + public function getDisplayColor( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + return 'red'; + } + +} diff --git a/src/applications/calendar/importlog/PhabricatorCalendarImportIgnoredNodeLogType.php b/src/applications/calendar/importlog/PhabricatorCalendarImportIgnoredNodeLogType.php new file mode 100644 index 0000000000..5f7d45db78 --- /dev/null +++ b/src/applications/calendar/importlog/PhabricatorCalendarImportIgnoredNodeLogType.php @@ -0,0 +1,23 @@ +getParameter('node.type'); + return pht( + 'Ignored unsupported "%s" node present in source.', + $node_type); + } + +} diff --git a/src/applications/calendar/importlog/PhabricatorCalendarImportLogType.php b/src/applications/calendar/importlog/PhabricatorCalendarImportLogType.php new file mode 100644 index 0000000000..a6c4ca4ec3 --- /dev/null +++ b/src/applications/calendar/importlog/PhabricatorCalendarImportLogType.php @@ -0,0 +1,39 @@ +getPhobjectClassConstant('LOGTYPE', 64); + } + + final public static function getAllLogTypes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getLogTypeConstant') + ->execute(); + } + + abstract public function getDisplayType( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log); + + public function getDisplayIcon( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + return 'fa-warning'; + } + + public function getDisplayColor( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + return 'yellow'; + } + + public function getDisplayDescription( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + return null; + } + +} diff --git a/src/applications/calendar/importlog/PhabricatorCalendarImportOriginalLogType.php b/src/applications/calendar/importlog/PhabricatorCalendarImportOriginalLogType.php new file mode 100644 index 0000000000..567cc5f68e --- /dev/null +++ b/src/applications/calendar/importlog/PhabricatorCalendarImportOriginalLogType.php @@ -0,0 +1,26 @@ +getParameter('phid'); + + return pht( + 'Ignored an event (%s) because the original version of this event '. + 'was created here.', + $viewer->renderHandle($phid)); + } + +} diff --git a/src/applications/calendar/importlog/PhabricatorCalendarImportOrphanLogType.php b/src/applications/calendar/importlog/PhabricatorCalendarImportOrphanLogType.php new file mode 100644 index 0000000000..236b0e7f65 --- /dev/null +++ b/src/applications/calendar/importlog/PhabricatorCalendarImportOrphanLogType.php @@ -0,0 +1,25 @@ +getParameter('uid.full'); + $parent_uid = $log->getParameter('uid.parent'); + return pht( + 'Found orphaned child event ("%s") without a parent event ("%s").', + $child_uid, + $parent_uid); + } + +} diff --git a/src/applications/calendar/importlog/PhabricatorCalendarImportUpdateLogType.php b/src/applications/calendar/importlog/PhabricatorCalendarImportUpdateLogType.php new file mode 100644 index 0000000000..9b20c54615 --- /dev/null +++ b/src/applications/calendar/importlog/PhabricatorCalendarImportUpdateLogType.php @@ -0,0 +1,38 @@ +getParameter('new'); + if ($is_new) { + return pht('Imported Event'); + } else { + return pht('Updated Event'); + } + } + + public function getDisplayDescription( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + $event_phid = $log->getParameter('phid'); + return $viewer->renderHandle($event_phid); + } + + public function getDisplayIcon( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + return 'fa-upload'; + } + + public function getDisplayColor( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + return 'green'; + } + +} diff --git a/src/applications/calendar/phid/PhabricatorCalendarImportPHIDType.php b/src/applications/calendar/phid/PhabricatorCalendarImportPHIDType.php new file mode 100644 index 0000000000..876ec7acf7 --- /dev/null +++ b/src/applications/calendar/phid/PhabricatorCalendarImportPHIDType.php @@ -0,0 +1,50 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $import = $objects[$phid]; + + $id = $import->getID(); + $name = $import->getDisplayName(); + $uri = $import->getURI(); + + $handle + ->setName($name) + ->setFullName(pht('Calendar Import %s: %s', $id, $name)) + ->setURI($uri); + + if ($import->getIsDisabled()) { + $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); + } + } + } + +} diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 9b928c9d1a..9949ee2f5a 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -14,6 +14,9 @@ final class PhabricatorCalendarEventQuery private $instanceSequencePairs; private $isStub; private $parentEventPHIDs; + private $importSourcePHIDs; + private $importAuthorPHIDs; + private $importUIDs; private $generateGhosts = false; @@ -77,6 +80,21 @@ final class PhabricatorCalendarEventQuery return $this; } + public function withImportSourcePHIDs(array $import_phids) { + $this->importSourcePHIDs = $import_phids; + return $this; + } + + public function withImportAuthorPHIDs(array $author_phids) { + $this->importAuthorPHIDs = $author_phids; + return $this; + } + + public function withImportUIDs(array $uids) { + $this->importUIDs = $uids; + return $this; + } + protected function getDefaultOrderVector() { return array('start', 'id'); } @@ -411,6 +429,27 @@ final class PhabricatorCalendarEventQuery $this->parentEventPHIDs); } + if ($this->importSourcePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'event.importSourcePHID IN (%Ls)', + $this->importSourcePHIDs); + } + + if ($this->importAuthorPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'event.importAuthorPHID IN (%Ls)', + $this->importAuthorPHIDs); + } + + if ($this->importUIDs !== null) { + $where[] = qsprintf( + $conn, + 'event.importUID IN (%Ls)', + $this->importUIDs); + } + return $where; } @@ -441,6 +480,42 @@ final class PhabricatorCalendarEventQuery $events = $this->getEventsInRange($events); + $import_phids = array(); + foreach ($events as $event) { + $import_phid = $event->getImportSourcePHID(); + if ($import_phid !== null) { + $import_phids[$import_phid] = $import_phid; + } + } + + if ($import_phids) { + $imports = id(new PhabricatorCalendarImportQuery()) + ->setParentQuery($this) + ->setViewer($viewer) + ->withPHIDs($import_phids) + ->execute(); + $imports = mpull($imports, null, 'getPHID'); + } else { + $imports = array(); + } + + foreach ($events as $key => $event) { + $import_phid = $event->getImportSourcePHID(); + if ($import_phid === null) { + $event->attachImportSource(null); + continue; + } + + $import = idx($imports, $import_phid); + if (!$import) { + unset($events[$key]); + $this->didRejectResult($event); + continue; + } + + $event->attachImportSource($import); + } + $phids = array(); foreach ($events as $event) { @@ -561,5 +636,4 @@ final class PhabricatorCalendarEventQuery return $raw_limit; } - } diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index a29b7ea37a..26c3dc938b 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -51,6 +51,10 @@ final class PhabricatorCalendarEventSearchEngine ->setKey('isCancelled') ->setOptions($this->getCancelledOptions()) ->setDefault('active'), + id(new PhabricatorPHIDsSearchField()) + ->setLabel(pht('Import Sources')) + ->setKey('importSourcePHIDs') + ->setAliases(array('importSourcePHID')), id(new PhabricatorSearchSelectField()) ->setLabel(pht('Display Options')) ->setKey('display') @@ -114,6 +118,10 @@ final class PhabricatorCalendarEventSearchEngine break; } + if ($map['importSourcePHIDs']) { + $query->withImportSourcePHIDs($map['importSourcePHIDs']); + } + // Generate ghosts (and ignore stub events) if we aren't querying for // specific events or exporting. if (!empty($map['export'])) { @@ -313,11 +321,9 @@ final class PhabricatorCalendarEventSearchEngine $list->addItem($item); } - $result = new PhabricatorApplicationSearchResultView(); - $result->setObjectList($list); - $result->setNoDataString(pht('No events found.')); - - return $result; + return $this->newResultView() + ->setObjectList($list) + ->setNoDataString(pht('No events found.')); } private function buildCalendarMonthView( @@ -385,10 +391,9 @@ final class PhabricatorCalendarEventSearchEngine ->setProfileHeader(true) ->setHeader($from->format('F Y')); - return id(new PhabricatorApplicationSearchResultView()) + return $this->newResultView($month_view) ->setCrumbs($crumbs) - ->setHeader($header) - ->setContent($month_view); + ->setHeader($header); } private function buildCalendarDayView( @@ -459,10 +464,9 @@ final class PhabricatorCalendarEventSearchEngine ->setProfileHeader(true) ->setHeader($from->format('D, F jS')); - return id(new PhabricatorApplicationSearchResultView()) + return $this->newResultView($day_view) ->setCrumbs($crumbs) - ->setHeader($header) - ->setContent($day_view); + ->setHeader($header); } private function getDisplayYearAndMonthAndDay( @@ -588,4 +592,26 @@ final class PhabricatorCalendarEventSearchEngine ); } + + private function newResultView($content = null) { + // If we aren't rendering a dashboard panel, activate global drag-and-drop + // so you can import ".ics" files by dropping them directly onto the + // calendar. + if (!$this->isPanelContext()) { + $drop_upload = id(new PhabricatorGlobalUploadTargetView()) + ->setViewer($this->requireViewer()) + ->setHintText("\xE2\x87\xAA ".pht('Drop .ics Files to Import')) + ->setSubmitURI('/calendar/import/drop/') + ->setViewPolicy(PhabricatorPolicies::POLICY_NOONE); + + $content = array( + $drop_upload, + $content, + ); + } + + return id(new PhabricatorApplicationSearchResultView()) + ->setContent($content); + } + } diff --git a/src/applications/calendar/query/PhabricatorCalendarImportLogQuery.php b/src/applications/calendar/query/PhabricatorCalendarImportLogQuery.php new file mode 100644 index 0000000000..81f309bdca --- /dev/null +++ b/src/applications/calendar/query/PhabricatorCalendarImportLogQuery.php @@ -0,0 +1,111 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withImportPHIDs(array $phids) { + $this->importPHIDs = $phids; + return $this; + } + + public function newResultObject() { + return new PhabricatorCalendarImportLog(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'log.id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'log.phid IN (%Ls)', + $this->phids); + } + + if ($this->importPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'log.importPHID IN (%Ls)', + $this->importPHIDs); + } + + + return $where; + } + + protected function willFilterPage(array $page) { + $viewer = $this->getViewer(); + + $type_map = PhabricatorCalendarImportLogType::getAllLogTypes(); + foreach ($page as $log) { + $type_constant = $log->getParameter('type'); + + $type_object = idx($type_map, $type_constant); + if (!$type_object) { + $type_object = new PhabricatorCalendarImportDefaultLogType(); + } + + $type_object = clone $type_object; + $log->attachLogType($type_object); + } + + $import_phids = mpull($page, 'getImportPHID'); + + if ($import_phids) { + $imports = id(new PhabricatorCalendarImportQuery()) + ->setViewer($viewer) + ->withPHIDs($import_phids) + ->execute(); + $imports = mpull($imports, null, 'getPHID'); + } else { + $imports = array(); + } + + foreach ($page as $key => $log) { + $import = idx($imports, $log->getImportPHID()); + if (!$import) { + $this->didRejectResult($import); + unset($page[$key]); + continue; + } + + $log->attachImport($import); + } + + return $page; + } + + protected function getPrimaryTableAlias() { + return 'log'; + } + + public function getQueryApplicationClass() { + return 'PhabricatorCalendarApplication'; + } + +} diff --git a/src/applications/calendar/query/PhabricatorCalendarImportLogSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarImportLogSearchEngine.php new file mode 100644 index 0000000000..81a1256fca --- /dev/null +++ b/src/applications/calendar/query/PhabricatorCalendarImportLogSearchEngine.php @@ -0,0 +1,77 @@ +setLabel(pht('Import Sources')) + ->setKey('importSourcePHIDs') + ->setAliases(array('importSourcePHID')), + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['importSourcePHIDs']) { + $query->withImportPHIDs($map['importSourcePHIDs']); + } + + return $query; + } + + protected function getURI($path) { + return '/calendar/import/log/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Logs'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $logs, + PhabricatorSavedQuery $query, + array $handles) { + + assert_instances_of($logs, 'PhabricatorCalendarImportLog'); + $viewer = $this->requireViewer(); + + $view = id(new PhabricatorCalendarImportLogView()) + ->setShowImportSources(true) + ->setViewer($viewer) + ->setLogs($logs); + + return id(new PhabricatorApplicationSearchResultView()) + ->setTable($view->newTable()); + } +} diff --git a/src/applications/calendar/query/PhabricatorCalendarImportQuery.php b/src/applications/calendar/query/PhabricatorCalendarImportQuery.php new file mode 100644 index 0000000000..5d44657994 --- /dev/null +++ b/src/applications/calendar/query/PhabricatorCalendarImportQuery.php @@ -0,0 +1,99 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withAuthorPHIDs(array $phids) { + $this->authorPHIDs = $phids; + return $this; + } + + public function withIsDisabled($is_disabled) { + $this->isDisabled = $is_disabled; + return $this; + } + + public function newResultObject() { + return new PhabricatorCalendarImport(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'import.id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'import.phid IN (%Ls)', + $this->phids); + } + + if ($this->authorPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'import.authorPHID IN (%Ls)', + $this->authorPHIDs); + } + + if ($this->isDisabled !== null) { + $where[] = qsprintf( + $conn, + 'import.isDisabled = %d', + (int)$this->isDisabled); + } + + return $where; + } + + protected function willFilterPage(array $page) { + $engines = PhabricatorCalendarImportEngine::getAllImportEngines(); + foreach ($page as $key => $import) { + $engine_type = $import->getEngineType(); + $engine = idx($engines, $engine_type); + + if (!$engine) { + unset($page[$key]); + $this->didRejectResult($import); + continue; + } + + $import->attachEngine(clone $engine); + } + + return $page; + } + + protected function getPrimaryTableAlias() { + return 'import'; + } + + public function getQueryApplicationClass() { + return 'PhabricatorCalendarApplication'; + } + +} diff --git a/src/applications/calendar/query/PhabricatorCalendarImportSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarImportSearchEngine.php new file mode 100644 index 0000000000..75252b6dac --- /dev/null +++ b/src/applications/calendar/query/PhabricatorCalendarImportSearchEngine.php @@ -0,0 +1,81 @@ +newQuery(); + + return $query; + } + + protected function getURI($path) { + return '/calendar/import/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Imports'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $imports, + PhabricatorSavedQuery $query, + array $handles) { + + assert_instances_of($imports, 'PhabricatorCalendarImport'); + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + foreach ($imports as $import) { + $item = id(new PHUIObjectItemView()) + ->setViewer($viewer) + ->setObjectName(pht('Import %d', $import->getID())) + ->setHeader($import->getDisplayName()) + ->setHref($import->getURI()); + + if ($import->getIsDisabled()) { + $item->setDisabled(true); + } + + $list->addItem($item); + } + + $result = new PhabricatorApplicationSearchResultView(); + $result->setObjectList($list); + $result->setNoDataString(pht('No imports found.')); + + return $result; + } +} diff --git a/src/applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php b/src/applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php new file mode 100644 index 0000000000..123ec9b3c4 --- /dev/null +++ b/src/applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php @@ -0,0 +1,10 @@ +newRelativeDateTime('PT1H'); return id(new PhabricatorCalendarEvent()) + ->setDescription('') ->setHostPHID($actor->getPHID()) ->setIsCancelled(0) ->setIsAllDay(0) @@ -90,6 +98,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setAllDayDateTo(0) ->setStartDateTime($datetime_start) ->setEndDateTime($datetime_end) + ->attachImportSource(null) ->applyViewerTimezone($actor); } @@ -170,11 +179,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setName($parent->getName()) ->setDescription($parent->getDescription()); - $sequence = $this->getSequenceIndex(); - if ($start) { $start_datetime = $start; } else { + $sequence = $this->getSequenceIndex(); $start_datetime = $parent->newSequenceIndexDateTime($sequence); if (!$start_datetime) { @@ -192,6 +200,19 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setStartDateTime($start_datetime) ->setEndDateTime($end_datetime); + if ($parent->isImportedEvent()) { + $full_uid = $parent->getImportUID().'/'.$start_datetime->getEpoch(); + + // NOTE: We don't attach the import source because this gets called + // from CalendarEventQuery while building ghosts, before we've loaded + // and attached sources. Possibly this sequence should be flipped. + + $this + ->setImportAuthorPHID($parent->getImportAuthorPHID()) + ->setImportSourcePHID($parent->getImportSourcePHID()) + ->setImportUID($full_uid); + } + return $this; } @@ -306,6 +327,14 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $this->mailKey = Filesystem::readRandomCharacters(20); } + $import_uid = $this->getImportUID(); + if ($import_uid !== null) { + $index = PhabricatorHash::digestForIndex($import_uid); + } else { + $index = null; + } + $this->setImportUIDIndex($index); + $this->updateUTCEpochs(); return parent::save(); @@ -344,6 +373,11 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO 'utcUntilEpoch' => 'epoch?', 'utcInstanceEpoch' => 'epoch?', + 'importAuthorPHID' => 'phid?', + 'importSourcePHID' => 'phid?', + 'importUIDIndex' => 'bytes12?', + 'importUID' => 'text?', + // TODO: DEPRECATED. 'allDayDateFrom' => 'epoch', 'allDayDateTo' => 'epoch', @@ -554,6 +588,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } } + if ($this->isImportedEvent()) { + return 'fa-download'; + } + return $this->getIcon(); } @@ -562,6 +600,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return 'red'; } + if ($this->isImportedEvent()) { + return 'orange'; + } + if ($viewer->isLoggedIn()) { $status = $this->getUserInviteStatus($viewer->getPHID()); switch ($status) { @@ -885,6 +927,21 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $set; } + public function isImportedEvent() { + return (bool)$this->getImportSourcePHID(); + } + + public function getImportSource() { + return $this->assertAttached($this->importSource); + } + + public function attachImportSource( + PhabricatorCalendarImport $import = null) { + $this->importSource = $import; + return $this; + } + + /* -( Markup Interface )--------------------------------------------------- */ @@ -947,11 +1004,19 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: - return $this->getEditPolicy(); + if ($this->getImportSource()) { + return PhabricatorPolicies::POLICY_NOONE; + } else { + return $this->getEditPolicy(); + } } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + if ($this->getImportSource()) { + return false; + } + // The host of an event can always view and edit it. $user_phid = $this->getHostPHID(); if ($user_phid) { @@ -974,12 +1039,40 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } public function describeAutomaticCapability($capability) { + if ($this->getImportSource()) { + return pht( + 'Events imported from external sources can not be edited in '. + 'Phabricator.'); + } + return pht( 'The host of an event can always view and edit it. Users who are '. 'invited to an event can always view it.'); } +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ + + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + $extended = array(); + + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + $import_source = $this->getImportSource(); + if ($import_source) { + $extended[] = array( + $import_source, + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + break; + } + + return $extended; + } + + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -1056,13 +1149,31 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setKey('description') ->setType('string') ->setDescription(pht('The event description.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('isAllDay') + ->setType('bool') + ->setDescription(pht('True if the event is an all day event.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('startDateTime') + ->setType('datetime') + ->setDescription(pht('Start date and time of the event.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('endDateTime') + ->setType('datetime') + ->setDescription(pht('End date and time of the event.')), ); } public function getFieldValuesForConduit() { + $start_datetime = $this->newStartDateTime(); + $end_datetime = $this->newEndDateTime(); + return array( 'name' => $this->getName(), 'description' => $this->getDescription(), + 'isAllDay' => (bool)$this->getIsAllDay(), + 'startDateTime' => $this->getConduitDateTime($start_datetime), + 'endDateTime' => $this->getConduitDateTime($end_datetime), ); } @@ -1070,4 +1181,26 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return array(); } + private function getConduitDateTime($datetime) { + if (!$datetime) { + return null; + } + + $epoch = $datetime->getEpoch(); + + // TODO: Possibly pass the actual viewer in from the Conduit stuff, or + // retain it when setting the viewer timezone? + $viewer = id(new PhabricatorUser()) + ->overrideTimezoneIdentifier($this->viewerTimezone); + + return array( + 'epoch' => (int)$epoch, + 'display' => array( + 'default' => phabricator_datetime($epoch, $viewer), + ), + 'iso8601' => $datetime->getISO8601(), + 'timezone' => $this->viewerTimezone, + ); + } + } diff --git a/src/applications/calendar/storage/PhabricatorCalendarImport.php b/src/applications/calendar/storage/PhabricatorCalendarImport.php new file mode 100644 index 0000000000..93b0897ee5 --- /dev/null +++ b/src/applications/calendar/storage/PhabricatorCalendarImport.php @@ -0,0 +1,178 @@ +setName('') + ->setAuthorPHID($actor->getPHID()) + ->setViewPolicy($actor->getPHID()) + ->setEditPolicy($actor->getPHID()) + ->setIsDisabled(0) + ->setEngineType($engine->getImportEngineType()) + ->attachEngine($engine); + } + + protected function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_SERIALIZATION => array( + 'parameters' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text', + 'engineType' => 'text64', + 'isDisabled' => 'bool', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_author' => array( + 'columns' => array('authorPHID'), + ), + ), + ) + parent::getConfiguration(); + } + + public function getPHIDType() { + return PhabricatorCalendarImportPHIDType::TYPECONST; + } + + public function getURI() { + $id = $this->getID(); + return "/calendar/import/{$id}/"; + } + + public function attachEngine(PhabricatorCalendarImportEngine $engine) { + $this->engine = $engine; + return $this; + } + + public function getEngine() { + return $this->assertAttached($this->engine); + } + + public function getParameter($key, $default = null) { + return idx($this->parameters, $key, $default); + } + + public function setParameter($key, $value) { + $this->parameters[$key] = $value; + return $this; + } + + public function getDisplayName() { + $name = $this->getName(); + if (strlen($name)) { + return $name; + } + + return $this->getEngine()->getDisplayName($this); + } + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorCalendarImportEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorCalendarImportTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + + public function newLogMessage($type, array $parameters) { + $parameters = array( + 'type' => $type, + ) + $parameters; + + return id(new PhabricatorCalendarImportLog()) + ->setImportPHID($this->getPHID()) + ->setParameters($parameters) + ->save(); + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $viewer = $engine->getViewer(); + + $this->openTransaction(); + + $events = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withImportSourcePHIDs(array($this->getPHID())) + ->execute(); + foreach ($events as $event) { + $engine->destroyObject($event); + } + + $logs = id(new PhabricatorCalendarImportLogQuery()) + ->setViewer($viewer) + ->withImportPHIDs(array($this->getPHID())) + ->execute(); + foreach ($logs as $log) { + $engine->destroyObject($log); + } + + $this->delete(); + $this->saveTransaction(); + } + +} diff --git a/src/applications/calendar/storage/PhabricatorCalendarImportLog.php b/src/applications/calendar/storage/PhabricatorCalendarImportLog.php new file mode 100644 index 0000000000..e25a3090e0 --- /dev/null +++ b/src/applications/calendar/storage/PhabricatorCalendarImportLog.php @@ -0,0 +1,103 @@ + array( + 'parameters' => self::SERIALIZATION_JSON, + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_import' => array( + 'columns' => array('importPHID'), + ), + ), + ) + parent::getConfiguration(); + } + + public function getParameter($key, $default = null) { + return idx($this->parameters, $key, $default); + } + + public function setParameter($key, $value) { + $this->parameters[$key] = $value; + return $this; + } + + public function getImport() { + return $this->assertAttached($this->import); + } + + public function attachImport(PhabricatorCalendarImport $import) { + $this->import = $import; + return $this; + } + + public function getDisplayIcon(PhabricatorUser $viewer) { + return $this->getLogType()->getDisplayIcon($viewer, $this); + } + + public function getDisplayColor(PhabricatorUser $viewer) { + return $this->getLogType()->getDisplayColor($viewer, $this); + } + + public function getDisplayType(PhabricatorUser $viewer) { + return $this->getLogType()->getDisplayType($viewer, $this); + } + + public function getDisplayDescription(PhabricatorUser $viewer) { + return $this->getLogType()->getDisplayDescription($viewer, $this); + } + + public function getLogType() { + return $this->assertAttached($this->logType); + } + + public function attachLogType(PhabricatorCalendarImportLogType $type) { + $this->logType = $type; + return $this; + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return PhabricatorPolicies::getMostOpenPolicy(); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $viewer = $engine->getViewer(); + $this->delete(); + } + +} diff --git a/src/applications/calendar/storage/PhabricatorCalendarImportTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarImportTransaction.php new file mode 100644 index 0000000000..2102e94576 --- /dev/null +++ b/src/applications/calendar/storage/PhabricatorCalendarImportTransaction.php @@ -0,0 +1,18 @@ +logs = $logs; + return $this; + } + + public function getLogs() { + return $this->logs; + } + + public function setShowImportSources($show_import_sources) { + $this->showImportSources = $show_import_sources; + return $this; + } + + public function getShowImportSources() { + return $this->showImportSources; + } + + public function render() { + return $this->newTable(); + } + + public function newTable() { + $viewer = $this->getViewer(); + $logs = $this->getLogs(); + + $show_sources = $this->getShowImportSources(); + + $rows = array(); + foreach ($logs as $log) { + $icon = $log->getDisplayIcon($viewer); + $color = $log->getDisplayColor($viewer); + $name = $log->getDisplayType($viewer); + $description = $log->getDisplayDescription($viewer); + + $rows[] = array( + $log->getID(), + ($show_sources + ? $viewer->renderHandle($log->getImport()->getPHID()) + : null), + id(new PHUIIconView())->setIcon($icon, $color), + $name, + phutil_escape_html_newlines($description), + phabricator_datetime($log->getDateCreated(), $viewer), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('ID'), + pht('Source'), + null, + pht('Type'), + pht('Message'), + pht('Date'), + )) + ->setColumnVisibility( + array( + true, + $show_sources, + )) + ->setColumnClasses( + array( + 'top', + 'top', + 'top', + 'top pri', + 'top wide', + 'top', + )); + + return $table; + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarImportDeleteTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarImportDeleteTransaction.php new file mode 100644 index 0000000000..83bf78169f --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarImportDeleteTransaction.php @@ -0,0 +1,30 @@ +setViewer($this->getActor()) + ->withImportSourcePHIDs(array($object->getPHID())) + ->execute(); + + $engine = new PhabricatorDestructionEngine(); + foreach ($events as $event) { + $engine->destroyObject($event); + } + } + + public function getTitle() { + return pht( + '%s deleted imported events from this source.', + $this->renderAuthor()); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarImportDisableTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarImportDisableTransaction.php new file mode 100644 index 0000000000..9cc23ab33f --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarImportDisableTransaction.php @@ -0,0 +1,28 @@ +getIsDisabled(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsDisabled((int)$value); + } + + public function getTitle() { + if ($this->getNewValue()) { + return pht( + '%s disabled this import.', + $this->renderAuthor()); + } else { + return pht( + '%s enabled this import.', + $this->renderAuthor()); + } + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php new file mode 100644 index 0000000000..26a968d13d --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php @@ -0,0 +1,80 @@ +getParameter(self::PARAMKEY_FILE); + } + + public function applyInternalEffects($object, $value) { + $object->setParameter(self::PARAMKEY_FILE, $value); + + $viewer = $this->getActor(); + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($value)) + ->executeOne(); + if ($file) { + $object->setParameter(self::PARAMKEY_NAME, $file->getName()); + } + } + + public function getTitle() { + return pht( + '%s imported an ICS file.', + $this->renderAuthor()); + } + + public function validateTransactions($object, array $xactions) { + $viewer = $this->getActor(); + $errors = array(); + + $ics_type = PhabricatorCalendarICSFileImportEngine::ENGINETYPE; + $import_type = $object->getEngine()->getImportEngineType(); + if ($import_type != $ics_type) { + if (!$xactions) { + return $errors; + } + + $errors[] = $this->newInvalidError( + pht( + 'You can not attach an ICS file to an import type other than '. + 'an ICS import (type is "%s").', + $import_type)); + + return $errors; + } + + $new_value = $object->getParameter(self::PARAMKEY_FILE); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + if (!strlen($new_value)) { + continue; + } + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($new_value)) + ->executeOne(); + if (!$file) { + $errors[] = $this->newInvalidError( + pht( + 'File PHID "%s" is not valid or not visible.', + $new_value), + $xaction); + } + } + + if (!$new_value) { + $errors[] = $this->newRequiredError( + pht('You must select an ".ics" file to import.')); + } + + return $errors; + } +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php new file mode 100644 index 0000000000..13fdbd232b --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php @@ -0,0 +1,73 @@ +getParameter(self::PARAMKEY_URI); + } + + public function applyInternalEffects($object, $value) { + $object->setParameter(self::PARAMKEY_URI, $value); + } + + public function getTitle() { + // NOTE: This transaction intentionally does not disclose the actual + // URI. + return pht( + '%s updated the import URI.', + $this->renderAuthor()); + } + + public function validateTransactions($object, array $xactions) { + $viewer = $this->getActor(); + $errors = array(); + + $ics_type = PhabricatorCalendarICSURIImportEngine::ENGINETYPE; + $import_type = $object->getEngine()->getImportEngineType(); + if ($import_type != $ics_type) { + if (!$xactions) { + return $errors; + } + + $errors[] = $this->newInvalidError( + pht( + 'You can not attach an ICS URI to an import type other than '. + 'an ICS URI import (type is "%s").', + $import_type)); + + return $errors; + } + + $new_value = $object->getParameter(self::PARAMKEY_URI); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + if (!strlen($new_value)) { + continue; + } + + try { + PhabricatorEnv::requireValidRemoteURIForFetch( + $new_value, + array( + 'http', + 'https', + )); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError( + $ex->getMessage(), + $xaction); + } + } + + if (!strlen($new_value)) { + $errors[] = $this->newRequiredError( + pht('You must select an ".ics" URI to import.')); + } + + return $errors; + } +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarImportNameTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarImportNameTransaction.php new file mode 100644 index 0000000000..49705af009 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarImportNameTransaction.php @@ -0,0 +1,39 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (!strlen($old)) { + return pht( + '%s named this import %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else if (!strlen($new)) { + return pht( + '%s removed the name of this import (was: %s).', + $this->renderAuthor(), + $this->renderOldValue()); + } else { + return pht( + '%s renamed this import from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php b/src/applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php new file mode 100644 index 0000000000..9659ce7759 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php @@ -0,0 +1,4 @@ + 'font/eot', 'ttf' => 'font/ttf', 'mp3' => 'audio/mpeg', + 'ico' => 'image/x-icon', ); } diff --git a/src/applications/celerity/resources/CelerityResourcesOnDisk.php b/src/applications/celerity/resources/CelerityResourcesOnDisk.php index 3187c5b9ad..1f9e614da9 100644 --- a/src/applications/celerity/resources/CelerityResourcesOnDisk.php +++ b/src/applications/celerity/resources/CelerityResourcesOnDisk.php @@ -39,6 +39,7 @@ abstract class CelerityResourcesOnDisk extends CelerityPhysicalResources { 'ttf', 'eot', 'mp3', + 'ico', ); } diff --git a/src/applications/conduit/call/ConduitCall.php b/src/applications/conduit/call/ConduitCall.php index 017d96ae8b..6be49daef0 100644 --- a/src/applications/conduit/call/ConduitCall.php +++ b/src/applications/conduit/call/ConduitCall.php @@ -15,7 +15,7 @@ final class ConduitCall extends Phobject { private $request; private $user; - public function __construct($method, array $params) { + public function __construct($method, array $params, $strictly_typed = true) { $this->method = $method; $this->handler = $this->buildMethodHandler($method); @@ -41,7 +41,7 @@ final class ConduitCall extends Phobject { "'".implode("', '", array_keys($invalid_params))."'")); } - $this->request = new ConduitAPIRequest($params); + $this->request = new ConduitAPIRequest($params, $strictly_typed); } public function getAPIRequest() { diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php index b9e8b1b15e..991865b564 100644 --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -25,9 +25,11 @@ final class PhabricatorConduitAPIController try { - list($metadata, $params) = $this->decodeConduitParams($request, $method); + list($metadata, $params, $strictly_typed) = $this->decodeConduitParams( + $request, + $method); - $call = new ConduitCall($method, $params); + $call = new ConduitCall($method, $params, $strictly_typed); $method_implementation = $call->getMethodImplementation(); $result = null; @@ -638,7 +640,7 @@ final class PhabricatorConduitAPIController $metadata = idx($params, '__conduit__', array()); unset($params['__conduit__']); - return array($metadata, $params); + return array($metadata, $params, true); } // Otherwise, look for a single parameter called 'params' which has the @@ -659,7 +661,7 @@ final class PhabricatorConduitAPIController $metadata = idx($params, '__conduit__', array()); unset($params['__conduit__']); - return array($metadata, $params); + return array($metadata, $params, true); } // If we do not have `params`, assume this is a simple HTTP request with @@ -675,7 +677,7 @@ final class PhabricatorConduitAPIController } } - return array($metadata, $params); + return array($metadata, $params, false); } private function authorizeOAuthMethodAccess( diff --git a/src/applications/conduit/parametertype/ConduitBoolParameterType.php b/src/applications/conduit/parametertype/ConduitBoolParameterType.php index fe1564350f..7ad9dd13e5 100644 --- a/src/applications/conduit/parametertype/ConduitBoolParameterType.php +++ b/src/applications/conduit/parametertype/ConduitBoolParameterType.php @@ -3,17 +3,9 @@ final class ConduitBoolParameterType extends ConduitParameterType { - protected function getParameterValue(array $request, $key) { - $value = parent::getParameterValue($request, $key); - - if (!is_bool($value)) { - $this->raiseValidationException( - $request, - $key, - pht('Expected boolean (true or false), got something else.')); - } - - return $value; + protected function getParameterValue(array $request, $key, $strict) { + $value = parent::getParameterValue($request, $key, $strict); + return $this->parseBoolValue($request, $key, $value, $strict); } protected function getParameterTypeName() { diff --git a/src/applications/conduit/parametertype/ConduitColumnsParameterType.php b/src/applications/conduit/parametertype/ConduitColumnsParameterType.php index c6669fae06..1892747892 100644 --- a/src/applications/conduit/parametertype/ConduitColumnsParameterType.php +++ b/src/applications/conduit/parametertype/ConduitColumnsParameterType.php @@ -3,10 +3,10 @@ final class ConduitColumnsParameterType extends ConduitParameterType { - protected function getParameterValue(array $request, $key) { + protected function getParameterValue(array $request, $key, $strict) { // We don't do any meaningful validation here because the transaction // itself validates everything and the input format is flexible. - return parent::getParameterValue($request, $key); + return parent::getParameterValue($request, $key, $strict); } protected function getParameterTypeName() { diff --git a/src/applications/conduit/parametertype/ConduitEpochParameterType.php b/src/applications/conduit/parametertype/ConduitEpochParameterType.php index 1594186e5c..e8fe095c50 100644 --- a/src/applications/conduit/parametertype/ConduitEpochParameterType.php +++ b/src/applications/conduit/parametertype/ConduitEpochParameterType.php @@ -3,15 +3,9 @@ final class ConduitEpochParameterType extends ConduitParameterType { - protected function getParameterValue(array $request, $key) { - $value = parent::getParameterValue($request, $key); - - if (!is_int($value)) { - $this->raiseValidationException( - $request, - $key, - pht('Expected epoch timestamp as integer, got something else.')); - } + protected function getParameterValue(array $request, $key, $strict) { + $value = parent::getParameterValue($request, $key, $strict); + $value = $this->parseIntValue($request, $key, $value, $strict); if ($value <= 0) { $this->raiseValidationException( diff --git a/src/applications/conduit/parametertype/ConduitIntListParameterType.php b/src/applications/conduit/parametertype/ConduitIntListParameterType.php index 07c87dcd8a..27e0ff9843 100644 --- a/src/applications/conduit/parametertype/ConduitIntListParameterType.php +++ b/src/applications/conduit/parametertype/ConduitIntListParameterType.php @@ -3,19 +3,15 @@ final class ConduitIntListParameterType extends ConduitListParameterType { - protected function getParameterValue(array $request, $key) { - $list = parent::getParameterValue($request, $key); + protected function getParameterValue(array $request, $key, $strict) { + $list = parent::getParameterValue($request, $key, $strict); foreach ($list as $idx => $item) { - if (!is_int($item)) { - $this->raiseValidationException( - $request, - $key, - pht( - 'Expected a list of integers, but item with index "%s" is '. - 'not an integer.', - $idx)); - } + $list[$idx] = $this->parseIntValue( + $request, + $key.'['.$idx.']', + $item, + $strict); } return $list; diff --git a/src/applications/conduit/parametertype/ConduitIntParameterType.php b/src/applications/conduit/parametertype/ConduitIntParameterType.php index 54f66fdf6c..e0d91e5d93 100644 --- a/src/applications/conduit/parametertype/ConduitIntParameterType.php +++ b/src/applications/conduit/parametertype/ConduitIntParameterType.php @@ -3,17 +3,9 @@ final class ConduitIntParameterType extends ConduitParameterType { - protected function getParameterValue(array $request, $key) { - $value = parent::getParameterValue($request, $key); - - if (!is_int($value)) { - $this->raiseValidationException( - $request, - $key, - pht('Expected integer, got something else.')); - } - - return $value; + protected function getParameterValue(array $request, $key, $strict) { + $value = parent::getParameterValue($request, $key, $strict); + return $this->parseIntValue($request, $key, $value, $strict); } protected function getParameterTypeName() { diff --git a/src/applications/conduit/parametertype/ConduitListParameterType.php b/src/applications/conduit/parametertype/ConduitListParameterType.php index 6ec3898ac2..aebadeb175 100644 --- a/src/applications/conduit/parametertype/ConduitListParameterType.php +++ b/src/applications/conduit/parametertype/ConduitListParameterType.php @@ -14,8 +14,8 @@ abstract class ConduitListParameterType return $this->allowEmptyList; } - protected function getParameterValue(array $request, $key) { - $value = parent::getParameterValue($request, $key); + protected function getParameterValue(array $request, $key, $strict) { + $value = parent::getParameterValue($request, $key, $strict); if (!is_array($value)) { $this->raiseValidationException( @@ -48,17 +48,18 @@ abstract class ConduitListParameterType return $value; } - protected function validateStringList(array $request, $key, array $list) { + protected function parseStringList( + array $request, + $key, + array $list, + $strict) { + foreach ($list as $idx => $item) { - if (!is_string($item)) { - $this->raiseValidationException( - $request, - $key, - pht( - 'Expected a list of strings, but item with index "%s" is '. - 'not a string.', - $idx)); - } + $list[$idx] = $this->parseStringValue( + $request, + $key.'['.$idx.']', + $item, + $strict); } return $list; diff --git a/src/applications/conduit/parametertype/ConduitPHIDListParameterType.php b/src/applications/conduit/parametertype/ConduitPHIDListParameterType.php index 60199dbe45..bbe89b6d43 100644 --- a/src/applications/conduit/parametertype/ConduitPHIDListParameterType.php +++ b/src/applications/conduit/parametertype/ConduitPHIDListParameterType.php @@ -3,9 +3,9 @@ final class ConduitPHIDListParameterType extends ConduitListParameterType { - protected function getParameterValue(array $request, $key) { - $list = parent::getParameterValue($request, $key); - return $this->validateStringList($request, $key, $list); + protected function getParameterValue(array $request, $key, $strict) { + $list = parent::getParameterValue($request, $key, $strict); + return $this->parseStringList($request, $key, $list, $strict); } protected function getParameterTypeName() { diff --git a/src/applications/conduit/parametertype/ConduitPHIDParameterType.php b/src/applications/conduit/parametertype/ConduitPHIDParameterType.php index f182758071..3bb45697dc 100644 --- a/src/applications/conduit/parametertype/ConduitPHIDParameterType.php +++ b/src/applications/conduit/parametertype/ConduitPHIDParameterType.php @@ -3,8 +3,8 @@ final class ConduitPHIDParameterType extends ConduitParameterType { - protected function getParameterValue(array $request, $key) { - $value = parent::getParameterValue($request, $key); + protected function getParameterValue(array $request, $key, $strict) { + $value = parent::getParameterValue($request, $key, $strict); if (!is_string($value)) { $this->raiseValidationException( diff --git a/src/applications/conduit/parametertype/ConduitParameterType.php b/src/applications/conduit/parametertype/ConduitParameterType.php index 011401433e..4eca31d96c 100644 --- a/src/applications/conduit/parametertype/ConduitParameterType.php +++ b/src/applications/conduit/parametertype/ConduitParameterType.php @@ -30,12 +30,12 @@ abstract class ConduitParameterType extends Phobject { } - final public function getValue(array $request, $key) { + final public function getValue(array $request, $key, $strict = true) { if (!$this->getExists($request, $key)) { return $this->getParameterDefault(); } - return $this->getParameterValue($request, $key); + return $this->getParameterValue($request, $key, $strict); } final public function getKeys($key) { @@ -85,7 +85,7 @@ abstract class ConduitParameterType extends Phobject { return array_key_exists($key, $request); } - protected function getParameterValue(array $request, $key) { + protected function getParameterValue(array $request, $key, $strict) { return $request[$key]; } @@ -93,6 +93,53 @@ abstract class ConduitParameterType extends Phobject { return array($key); } + protected function parseStringValue(array $request, $key, $value, $strict) { + if (!is_string($value)) { + $this->raiseValidationException( + $request, + $key, + pht('Expected string, got something else.')); + } + return $value; + } + + protected function parseIntValue(array $request, $key, $value, $strict) { + if (!$strict && is_string($value) && ctype_digit($value)) { + $value = $value + 0; + if (!is_int($value)) { + $this->raiseValidationException( + $request, + $key, + pht('Integer overflow.')); + } + } else if (!is_int($value)) { + $this->raiseValidationException( + $request, + $key, + pht('Expected integer, got something else.')); + } + return $value; + } + + protected function parseBoolValue(array $request, $key, $value, $strict) { + $bool_strings = array( + '0' => false, + '1' => true, + 'false' => false, + 'true' => true, + ); + + if (!$strict && is_string($value) && isset($bool_strings[$value])) { + $value = $bool_strings[$value]; + } else if (!is_bool($value)) { + $this->raiseValidationException( + $request, + $key, + pht('Expected boolean (true or false), got something else.')); + } + return $value; + } + abstract protected function getParameterTypeName(); diff --git a/src/applications/conduit/parametertype/ConduitPointsParameterType.php b/src/applications/conduit/parametertype/ConduitPointsParameterType.php index 5b330aabd1..9e5be819f2 100644 --- a/src/applications/conduit/parametertype/ConduitPointsParameterType.php +++ b/src/applications/conduit/parametertype/ConduitPointsParameterType.php @@ -3,8 +3,8 @@ final class ConduitPointsParameterType extends ConduitParameterType { - protected function getParameterValue(array $request, $key) { - $value = parent::getParameterValue($request, $key); + protected function getParameterValue(array $request, $key, $strict) { + $value = parent::getParameterValue($request, $key, $strict); if (($value !== null) && !is_numeric($value)) { $this->raiseValidationException( diff --git a/src/applications/conduit/parametertype/ConduitProjectListParameterType.php b/src/applications/conduit/parametertype/ConduitProjectListParameterType.php index c26db7febf..bd504c7eb4 100644 --- a/src/applications/conduit/parametertype/ConduitProjectListParameterType.php +++ b/src/applications/conduit/parametertype/ConduitProjectListParameterType.php @@ -3,9 +3,9 @@ final class ConduitProjectListParameterType extends ConduitListParameterType { - protected function getParameterValue(array $request, $key) { - $list = parent::getParameterValue($request, $key); - $list = $this->validateStringList($request, $key, $list); + protected function getParameterValue(array $request, $key, $strict) { + $list = parent::getParameterValue($request, $key, $strict); + $list = $this->parseStringList($request, $key, $list, $strict); return id(new PhabricatorProjectPHIDResolver()) ->setViewer($this->getViewer()) ->resolvePHIDs($list); diff --git a/src/applications/conduit/parametertype/ConduitStringListParameterType.php b/src/applications/conduit/parametertype/ConduitStringListParameterType.php index 664a1ded99..20c9389f81 100644 --- a/src/applications/conduit/parametertype/ConduitStringListParameterType.php +++ b/src/applications/conduit/parametertype/ConduitStringListParameterType.php @@ -3,9 +3,9 @@ final class ConduitStringListParameterType extends ConduitListParameterType { - protected function getParameterValue(array $request, $key) { - $list = parent::getParameterValue($request, $key); - return $this->validateStringList($request, $key, $list); + protected function getParameterValue(array $request, $key, $strict) { + $list = parent::getParameterValue($request, $key, $strict); + return $this->parseStringList($request, $key, $list, $strict); } protected function getParameterTypeName() { diff --git a/src/applications/conduit/parametertype/ConduitStringParameterType.php b/src/applications/conduit/parametertype/ConduitStringParameterType.php index b93490fc73..f10f3731e2 100644 --- a/src/applications/conduit/parametertype/ConduitStringParameterType.php +++ b/src/applications/conduit/parametertype/ConduitStringParameterType.php @@ -3,17 +3,9 @@ final class ConduitStringParameterType extends ConduitParameterType { - protected function getParameterValue(array $request, $key) { - $value = parent::getParameterValue($request, $key); - - if (!is_string($value)) { - $this->raiseValidationException( - $request, - $key, - pht('Expected string, got something else.')); - } - - return $value; + protected function getParameterValue(array $request, $key, $strict) { + $value = parent::getParameterValue($request, $key, $strict); + return $this->parseStringValue($request, $key, $value, $strict); } protected function getParameterTypeName() { diff --git a/src/applications/conduit/parametertype/ConduitUserListParameterType.php b/src/applications/conduit/parametertype/ConduitUserListParameterType.php index ad6555146d..85a9095ac5 100644 --- a/src/applications/conduit/parametertype/ConduitUserListParameterType.php +++ b/src/applications/conduit/parametertype/ConduitUserListParameterType.php @@ -3,9 +3,9 @@ final class ConduitUserListParameterType extends ConduitListParameterType { - protected function getParameterValue(array $request, $key) { - $list = parent::getParameterValue($request, $key); - $list = $this->validateStringList($request, $key, $list); + protected function getParameterValue(array $request, $key, $strict) { + $list = parent::getParameterValue($request, $key, $strict); + $list = $this->parseStringList($request, $key, $list, $strict); return id(new PhabricatorUserPHIDResolver()) ->setViewer($this->getViewer()) ->resolvePHIDs($list); diff --git a/src/applications/conduit/parametertype/ConduitUserParameterType.php b/src/applications/conduit/parametertype/ConduitUserParameterType.php index 3590d1a405..ede7f1f466 100644 --- a/src/applications/conduit/parametertype/ConduitUserParameterType.php +++ b/src/applications/conduit/parametertype/ConduitUserParameterType.php @@ -3,8 +3,8 @@ final class ConduitUserParameterType extends ConduitParameterType { - protected function getParameterValue(array $request, $key) { - $value = parent::getParameterValue($request, $key); + protected function getParameterValue(array $request, $key, $strict) { + $value = parent::getParameterValue($request, $key, $strict); if ($value === null) { return null; diff --git a/src/applications/conduit/protocol/ConduitAPIRequest.php b/src/applications/conduit/protocol/ConduitAPIRequest.php index 47cc31fba0..3a2818a47a 100644 --- a/src/applications/conduit/protocol/ConduitAPIRequest.php +++ b/src/applications/conduit/protocol/ConduitAPIRequest.php @@ -6,9 +6,11 @@ final class ConduitAPIRequest extends Phobject { private $user; private $isClusterRequest = false; private $oauthToken; + private $isStrictlyTyped = true; - public function __construct(array $params) { + public function __construct(array $params, $strictly_typed) { $this->params = $params; + $this->isStrictlyTyped = $strictly_typed; } public function getValue($key, $default = null) { @@ -68,6 +70,10 @@ final class ConduitAPIRequest extends Phobject { return $this->isClusterRequest; } + public function getIsStrictlyTyped() { + return $this->isStrictlyTyped; + } + public function newContentSource() { return PhabricatorContentSource::newForSource( PhabricatorConduitContentSource::SOURCECONST); diff --git a/src/applications/conpherence/ConpherenceTransactionRenderer.php b/src/applications/conpherence/ConpherenceTransactionRenderer.php index 341d287e8b..187247e063 100644 --- a/src/applications/conpherence/ConpherenceTransactionRenderer.php +++ b/src/applications/conpherence/ConpherenceTransactionRenderer.php @@ -5,7 +5,6 @@ final class ConpherenceTransactionRenderer extends Phobject { public static function renderTransactions( PhabricatorUser $user, ConpherenceThread $conpherence, - $full_display = true, $marker_type = 'older') { $transactions = $conpherence->getTransactions(); @@ -74,8 +73,7 @@ final class ConpherenceTransactionRenderer extends Phobject { ->setUser($user) ->setConpherenceThread($conpherence) ->setHandles($handles) - ->setMarkupEngine($engine) - ->setFullDisplay($full_display); + ->setMarkupEngine($engine); foreach ($transactions as $transaction) { $collapsed = false; diff --git a/src/applications/conpherence/application/PhabricatorConpherenceApplication.php b/src/applications/conpherence/application/PhabricatorConpherenceApplication.php index 3081919503..5faaf40b9c 100644 --- a/src/applications/conpherence/application/PhabricatorConpherenceApplication.php +++ b/src/applications/conpherence/application/PhabricatorConpherenceApplication.php @@ -37,6 +37,8 @@ final class PhabricatorConpherenceApplication extends PhabricatorApplication { => 'ConpherenceListController', 'thread/(?P[1-9]\d*)/' => 'ConpherenceListController', + 'threadsearch/(?P[1-9]\d*)/' + => 'ConpherenceThreadSearchController', '(?P[1-9]\d*)/' => 'ConpherenceViewController', '(?P[1-9]\d*)/(?P[1-9]\d*)/' diff --git a/src/applications/conpherence/controller/ConpherenceColumnViewController.php b/src/applications/conpherence/controller/ConpherenceColumnViewController.php index 9705c1f755..de83379aab 100644 --- a/src/applications/conpherence/controller/ConpherenceColumnViewController.php +++ b/src/applications/conpherence/controller/ConpherenceColumnViewController.php @@ -9,7 +9,7 @@ final class ConpherenceColumnViewController extends $latest_conpherences = array(); $latest_participant = id(new ConpherenceParticipantQuery()) ->withParticipantPHIDs(array($user->getPHID())) - ->setLimit(6) + ->setLimit(8) ->execute(); if ($latest_participant) { $conpherence_phids = mpull($latest_participant, 'getConpherencePHID'); diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index 389a8ce9b7..e6690a55d1 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -18,6 +18,12 @@ abstract class ConpherenceController extends PhabricatorController { // Local Links if ($conpherence) { + $nav->addMenuItem( + id(new PHUIListItemView()) + ->setName(pht('Joined Rooms')) + ->setType(PHUIListItemView::TYPE_LINK) + ->setHref($this->getApplicationURI())); + $nav->addMenuItem( id(new PHUIListItemView()) ->setName(pht('Edit Room')) @@ -113,6 +119,16 @@ abstract class ConpherenceController extends PhabricatorController { ->setHref('#') ->addClass('conpherence-participant-toggle')); + Javelin::initBehavior('conpherence-search'); + + $header->addActionItem( + id(new PHUIIconCircleView()) + ->addSigil('conpherence-search-toggle') + ->setIcon('fa-search') + ->setHref('#') + ->setColor('green') + ->addClass('conpherence-search-toggle')); + if ($can_join && !$participating) { $action = ConpherenceUpdateActions::JOIN_ROOM; $uri = $this->getApplicationURI('update/'.$conpherence->getID().'/'); @@ -149,4 +165,61 @@ abstract class ConpherenceController extends PhabricatorController { return $header; } + public function buildSearchForm() { + $viewer = $this->getViewer(); + $conpherence = $this->conpherence; + $name = $conpherence->getTitle(); + + $bar = javelin_tag( + 'input', + array( + 'type' => 'text', + 'id' => 'conpherence-search-input', + 'name' => 'fulltext', + 'class' => 'conpherence-search-input', + 'sigil' => 'conpherence-search-input', + 'placeholder' => pht('Search %s...', $name), + )); + + $id = $conpherence->getID(); + $form = phabricator_form( + $viewer, + array( + 'method' => 'POST', + 'action' => '/conpherence/threadsearch/'.$id.'/', + 'sigil' => 'conpherence-search-form', + 'class' => 'conpherence-search-form', + 'id' => 'conpherence-search-form', + ), + array( + $bar, + )); + + $form_view = phutil_tag( + 'div', + array( + 'class' => 'conpherence-search-form-view', + ), + $form); + + $results = phutil_tag( + 'div', + array( + 'id' => 'conpherence-search-results', + 'class' => 'conpherence-search-results', + )); + + $view = phutil_tag( + 'div', + array( + 'class' => 'conpherence-search-window', + ), + array( + $form_view, + $results, + )); + + return $view; + } + } diff --git a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php index c72490f60f..03f9c926ca 100644 --- a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php +++ b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php @@ -20,7 +20,7 @@ final class ConpherenceNotificationPanelController ->withPHIDs(array_keys($participant_data)) ->needProfileImage(true) ->needTransactions(true) - ->setTransactionLimit(3 * 5) + ->setTransactionLimit(50) ->needParticipantCache(true) ->execute(); } diff --git a/src/applications/conpherence/controller/ConpherenceThreadSearchController.php b/src/applications/conpherence/controller/ConpherenceThreadSearchController.php new file mode 100644 index 0000000000..155a61c234 --- /dev/null +++ b/src/applications/conpherence/controller/ConpherenceThreadSearchController.php @@ -0,0 +1,41 @@ +getViewer(); + $conpherence_id = $request->getURIData('id'); + $fulltext = $request->getStr('fulltext'); + + $conpherence = id(new ConpherenceThreadQuery()) + ->setViewer($viewer) + ->withIDs(array($conpherence_id)) + ->executeOne(); + + if (!$conpherence) { + return new Aphront404Response(); + } + + $engine = new ConpherenceThreadSearchEngine(); + $engine->setViewer($viewer); + $saved = $engine->buildSavedQueryFromBuiltin('all') + ->setParameter('phids', array($conpherence->getPHID())) + ->setParameter('fulltext', $fulltext); + + $pager = $engine->newPagerForSavedQuery($saved); + $pager->setPageSize(15); + + $query = $engine->buildQueryFromSavedQuery($saved); + + $results = $engine->executeQuery($query, $pager); + $view = $engine->renderResults($results, $saved); + + return id(new AphrontAjaxResponse()) + ->setContent($view->getContent()); + } +} diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index db21b3d02f..724b802566 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -327,7 +327,6 @@ final class ConpherenceUpdateController ->setUser($user) ->setDatasource(new PhabricatorPeopleDatasource())); - require_celerity_resource('conpherence-update-css'); $view = id(new AphrontDialogView()) ->setTitle(pht('Add Participants')) ->addHiddenInput('action', 'add_person') @@ -336,9 +335,6 @@ final class ConpherenceUpdateController $request->getInt('latest_transaction_id')) ->appendForm($form); - if ($request->getExists('minimal_display')) { - $view->addHiddenInput('minimal_display', true); - } return $view; } @@ -407,8 +403,6 @@ final class ConpherenceUpdateController } } - require_celerity_resource('conpherence-update-css'); - $dialog = id(new AphrontDialogView()) ->setTitle($title) ->addHiddenInput('action', 'remove_person') @@ -471,7 +465,6 @@ final class ConpherenceUpdateController ->setCapability(PhabricatorPolicyCapability::CAN_JOIN) ->setPolicies($policies)); - require_celerity_resource('conpherence-update-css'); $view = id(new AphrontDialogView()) ->setTitle($title) ->addHiddenInput('action', 'metadata') @@ -481,9 +474,6 @@ final class ConpherenceUpdateController ->addHiddenInput('__continue__', true) ->appendChild($form); - if ($request->getExists('minimal_display')) { - $view->addHiddenInput('minimal_display', true); - } if ($request->getExists('force_ajax')) { $view->addHiddenInput('force_ajax', true); } @@ -496,7 +486,6 @@ final class ConpherenceUpdateController $conpherence_id, $latest_transaction_id) { - $minimal_display = $this->getRequest()->getExists('minimal_display'); $need_transactions = false; $need_participant_cache = true; switch ($action) { @@ -529,8 +518,7 @@ final class ConpherenceUpdateController if ($need_transactions && $conpherence->getTransactions()) { $data = ConpherenceTransactionRenderer::renderTransactions( $user, - $conpherence, - !$minimal_display); + $conpherence); $key = PhabricatorConpherenceColumnMinimizeSetting::SETTINGKEY; $minimized = $user->getUserSetting($key); if (!$minimized) { @@ -551,35 +539,33 @@ final class ConpherenceUpdateController $nav_item = null; $header = null; $people_widget = null; - if (!$minimal_display) { - switch ($action) { - case ConpherenceUpdateActions::METADATA: - $policy_objects = id(new PhabricatorPolicyQuery()) - ->setViewer($user) - ->setObject($conpherence) - ->execute(); - $header = $this->buildHeaderPaneContent( - $conpherence, - $policy_objects); - $header = hsprintf('%s', $header); - $nav_item = id(new ConpherenceThreadListView()) - ->setUser($user) - ->setBaseURI($this->getApplicationURI()) - ->renderSingleThread($conpherence, $policy_objects); - $nav_item = hsprintf('%s', $nav_item); - break; - case ConpherenceUpdateActions::ADD_PERSON: - $people_widget = id(new ConpherenceParticipantView()) - ->setUser($user) - ->setConpherence($conpherence) - ->setUpdateURI($update_uri); - $people_widget = hsprintf('%s', $people_widget->render()); - break; - case ConpherenceUpdateActions::REMOVE_PERSON: - case ConpherenceUpdateActions::NOTIFICATIONS: - default: - break; - } + switch ($action) { + case ConpherenceUpdateActions::METADATA: + $policy_objects = id(new PhabricatorPolicyQuery()) + ->setViewer($user) + ->setObject($conpherence) + ->execute(); + $header = $this->buildHeaderPaneContent( + $conpherence, + $policy_objects); + $header = hsprintf('%s', $header); + $nav_item = id(new ConpherenceThreadListView()) + ->setUser($user) + ->setBaseURI($this->getApplicationURI()) + ->renderSingleThread($conpherence, $policy_objects); + $nav_item = hsprintf('%s', $nav_item); + break; + case ConpherenceUpdateActions::ADD_PERSON: + $people_widget = id(new ConpherenceParticipantView()) + ->setUser($user) + ->setConpherence($conpherence) + ->setUpdateURI($update_uri); + $people_widget = hsprintf('%s', $people_widget->render()); + break; + case ConpherenceUpdateActions::REMOVE_PERSON: + case ConpherenceUpdateActions::NOTIFICATIONS: + default: + break; } $data = $conpherence->getDisplayData($user); $dropdown_query = id(new AphlictDropdownDataQuery()) diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 029a6115a2..8d93f03b7d 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -73,7 +73,6 @@ final class ConpherenceViewController extends $data = ConpherenceTransactionRenderer::renderTransactions( $user, $conpherence, - $full_display = true, $marker_type); $messages = ConpherenceTransactionRenderer::renderMessagePaneContent( $data['transactions'], @@ -89,9 +88,11 @@ final class ConpherenceViewController extends ->setObject($conpherence) ->execute(); $header = $this->buildHeaderPaneContent($conpherence, $policy_objects); + $search = $this->buildSearchForm(); $form = $this->renderFormContent(); $content = array( 'header' => $header, + 'search' => $search, 'transactions' => $messages, 'form' => $form, ); @@ -128,6 +129,7 @@ final class ConpherenceViewController extends ->setBaseURI($this->getApplicationURI()) ->setThread($conpherence) ->setHeader($header) + ->setSearch($search) ->setMessages($messages) ->setReplyForm($form) ->setLatestTransactionID($data['latest_transaction_id']) diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index 640a19a612..48dc3323e0 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -76,6 +76,12 @@ final class ConpherenceThreadQuery return $this; } + public function withTitleNgrams($ngrams) { + return $this->withNgramsConstraint( + id(new ConpherenceThreadTitleNgrams()), + $ngrams); + } + protected function loadPage() { $table = new ConpherenceThread(); $conn_r = $table->establishConnection('r'); @@ -145,61 +151,100 @@ final class ConpherenceThreadQuery } } - protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { - $joins = array(); + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); if ($this->participantPHIDs !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T p ON p.conpherencePHID = thread.phid', id(new ConpherenceParticipant())->getTableName()); } if (strlen($this->fulltext)) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T idx ON idx.threadPHID = thread.phid', id(new ConpherenceIndex())->getTableName()); } - $joins[] = $this->buildApplicationSearchJoinClause($conn_r); - return implode(' ', $joins); + // See note in buildWhereClauseParts() about this optimization. + $viewer = $this->getViewer(); + if (!$viewer->isOmnipotent() && $viewer->isLoggedIn()) { + $joins[] = qsprintf( + $conn, + 'LEFT JOIN %T vp ON vp.conpherencePHID = thread.phid + AND vp.participantPHID = %s', + id(new ConpherenceParticipant())->getTableName(), + $viewer->getPHID()); + } + + return $joins; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - $where[] = $this->buildPagingClause($conn_r); + // Optimize policy filtering of private rooms. If we are not looking for + // particular rooms by ID or PHID, we can just skip over any rooms with + // "View Policy: Room Participants" if the viewer isn't a participant: we + // know they won't be able to see the room. + // This avoids overheating browse/search queries, since it's common for + // a large number of rooms to be private and have this view policy. + $viewer = $this->getViewer(); + + $can_optimize = + !$viewer->isOmnipotent() && + ($this->ids === null) && + ($this->phids === null); + + if ($can_optimize) { + $members_policy = id(new ConpherenceThreadMembersPolicyRule()) + ->getObjectPolicyFullKey(); + + if ($viewer->isLoggedIn()) { + $where[] = qsprintf( + $conn, + 'thread.viewPolicy != %s OR vp.participantPHID = %s', + $members_policy, + $viewer->getPHID()); + } else { + $where[] = qsprintf( + $conn, + 'thread.viewPolicy != %s', + $members_policy); + } + } if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'thread.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'thread.phid IN (%Ls)', $this->phids); } if ($this->participantPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'p.participantPHID IN (%Ls)', $this->participantPHIDs); } if (strlen($this->fulltext)) { $where[] = qsprintf( - $conn_r, + $conn, 'MATCH(idx.corpus) AGAINST (%s IN BOOLEAN MODE)', $this->fulltext); } - return $this->formatWhereClause($where); + return $where; } private function loadParticipantsAndInitHandles(array $conpherences) { diff --git a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php index f2c172b0f2..3129028c2e 100644 --- a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php +++ b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php @@ -13,7 +13,8 @@ final class ConpherenceThreadSearchEngine public function newQuery() { return id(new ConpherenceThreadQuery()) - ->needParticipantCache(true); + ->needParticipantCache(true) + ->needProfileImage(true); } protected function buildCustomSearchFields() { @@ -22,8 +23,13 @@ final class ConpherenceThreadSearchEngine ->setLabel(pht('Participants')) ->setKey('participants') ->setAliases(array('participant')), + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Rooms')) + ->setKey('phids') + ->setDescription(pht('Search by room titles.')) + ->setDatasource(id(new ConpherenceThreadDatasource())), id(new PhabricatorSearchTextField()) - ->setLabel(pht('Contains Words')) + ->setLabel(pht('Room Contains Words')) ->setKey('fulltext'), ); } @@ -47,7 +53,9 @@ final class ConpherenceThreadSearchEngine if ($map['fulltext']) { $query->withFulltext($map['fulltext']); } - + if ($map['phids']) { + $query->withPHIDs($map['phids']); + } return $query; } @@ -140,6 +148,7 @@ final class ConpherenceThreadSearchEngine $context = array(); } + $content = array(); $list = new PHUIObjectItemListView(); $list->setUser($viewer); foreach ($conpherences as $conpherence_phid => $conpherence) { @@ -150,63 +159,82 @@ final class ConpherenceThreadSearchEngine $icon_name = $conpherence->getPolicyIconName($policy_objects); $icon = id(new PHUIIconView()) ->setIcon($icon_name); - $item = id(new PHUIObjectItemView()) - ->setObjectName($conpherence->getMonogram()) - ->setHeader($title) - ->setHref('/'.$conpherence->getMonogram()) - ->setObject($conpherence) - ->addIcon('none', $created) - ->addIcon( - 'none', - pht('Messages: %d', $conpherence->getMessageCount())) - ->addAttribute( - array( - $icon, - ' ', - pht( - 'Last updated %s', - phabricator_datetime($conpherence->getDateModified(), $viewer)), - )); - $messages = idx($context, $conpherence_phid); - if ($messages) { - foreach ($messages as $group) { - $rows = array(); - foreach ($group as $message) { - $xaction = $message['xaction']; - if (!$xaction) { - continue; + if (!strlen($fulltext)) { + $item = id(new PHUIObjectItemView()) + ->setObjectName($conpherence->getMonogram()) + ->setHeader($title) + ->setHref('/'.$conpherence->getMonogram()) + ->setObject($conpherence) + ->setImageURI($conpherence->getProfileImageURI()) + ->addIcon('none', $created) + ->addIcon( + 'none', + pht('Messages: %d', $conpherence->getMessageCount())) + ->addAttribute( + array( + $icon, + ' ', + pht( + 'Last updated %s', + phabricator_datetime($conpherence->getDateModified(), $viewer)), + )); + $list->addItem($item); + } else { + $messages = idx($context, $conpherence_phid); + $box = array(); + $list = null; + if ($messages) { + foreach ($messages as $group) { + $rows = array(); + foreach ($group as $message) { + $xaction = $message['xaction']; + if (!$xaction) { + continue; + } + + $view = id(new ConpherenceTransactionView()) + ->setUser($viewer) + ->setHandles($handles) + ->setMarkupEngine($engines[$conpherence_phid]) + ->setConpherenceThread($conpherence) + ->setConpherenceTransaction($xaction) + ->setSearchResult(true) + ->addClass('conpherence-fulltext-result'); + + if ($message['match']) { + $view->addClass('conpherence-fulltext-match'); + } + + $rows[] = $view; } - - $view = id(new ConpherenceTransactionView()) - ->setUser($viewer) - ->setHandles($handles) - ->setMarkupEngine($engines[$conpherence_phid]) - ->setConpherenceThread($conpherence) - ->setConpherenceTransaction($xaction) - ->setFullDisplay(false) - ->addClass('conpherence-fulltext-result'); - - if ($message['match']) { - $view->addClass('conpherence-fulltext-match'); - } - - $rows[] = $view; + $box[] = id(new PHUIBoxView()) + ->appendChild($rows) + ->addClass('conpherence-fulltext-results'); } - - $box = id(new PHUIBoxView()) - ->appendChild($rows) - ->addClass('conpherence-fulltext-results'); - $item->appendChild($box); } - } + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($icon_name) + ->setHref('/'.$monogram); - $list->addItem($item); + $content[] = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($box); + } + } + + if ($list) { + $content = $list; + } else { + $content = id(new PHUIBoxView()) + ->addClass('conpherence-search-room-results') + ->appendChild($content); } $result = new PhabricatorApplicationSearchResultView(); - $result->setObjectList($list); - $result->setNoDataString(pht('No threads found.')); + $result->setContent($content); + $result->setNoDataString(pht('No results found.')); return $result; } diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index 36930f6151..e912f52591 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -5,11 +5,11 @@ final class ConpherenceThread extends ConpherenceDAO PhabricatorPolicyInterface, PhabricatorApplicationTransactionInterface, PhabricatorMentionableInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorNgramsInterface { protected $title; protected $topic; - protected $imagePHIDs = array(); // TODO; nuke after migrations protected $profileImagePHID; protected $messageCount; protected $recentParticipantPHIDs = array(); @@ -41,7 +41,6 @@ final class ConpherenceThread extends ConpherenceDAO self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'recentParticipantPHIDs' => self::SERIALIZATION_JSON, - 'imagePHIDs' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'title' => 'text255?', @@ -427,6 +426,16 @@ final class ConpherenceThread extends ConpherenceDAO return $timeline; } +/* -( PhabricatorNgramInterface )------------------------------------------ */ + + + public function newNgrams() { + return array( + id(new ConpherenceThreadTitleNgrams()) + ->setValue($this->getTitle()), + ); + } + /* -( PhabricatorDestructibleInterface )----------------------------------- */ diff --git a/src/applications/conpherence/storage/ConpherenceThreadTitleNgrams.php b/src/applications/conpherence/storage/ConpherenceThreadTitleNgrams.php new file mode 100644 index 0000000000..7f8ec99d02 --- /dev/null +++ b/src/applications/conpherence/storage/ConpherenceThreadTitleNgrams.php @@ -0,0 +1,17 @@ +getViewer(); + $raw_query = $this->getRawQuery(); + + $rooms = id(new ConpherenceThreadQuery()) + ->setViewer($viewer) + ->withTitleNgrams($raw_query) + ->needParticipants(true) + ->execute(); + + $results = array(); + foreach ($rooms as $room) { + if (strlen($room->getTopic())) { + $topic = $room->getTopic(); + } else { + $topic = phutil_tag('em', array(), pht('No topic set')); + } + + $token = id(new PhabricatorTypeaheadResult()) + ->setName($room->getTitle()) + ->setPHID($room->getPHID()) + ->addAttribute($topic); + + $results[] = $token; + } + + return $results; + } + +} diff --git a/src/applications/conpherence/view/ConpherenceDurableColumnView.php b/src/applications/conpherence/view/ConpherenceDurableColumnView.php index 786cbea174..e3240bc2e4 100644 --- a/src/applications/conpherence/view/ConpherenceDurableColumnView.php +++ b/src/applications/conpherence/view/ConpherenceDurableColumnView.php @@ -413,8 +413,7 @@ final class ConpherenceDurableColumnView extends AphrontTagView { $data = ConpherenceTransactionRenderer::renderTransactions( $this->getUser(), - $conpherence, - $full_display = false); + $conpherence); $messages = ConpherenceTransactionRenderer::renderMessagePaneContent( $data['transactions'], $data['oldest_transaction_id'], diff --git a/src/applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php b/src/applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php deleted file mode 100644 index 3d9ea57cf9..0000000000 --- a/src/applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php +++ /dev/null @@ -1,40 +0,0 @@ -dropID = $drop_id; - return $this; - } - public function getDropID() { - return $this->dropID; - } - - protected function getCustomControlClass() { - return null; - } - - protected function renderInput() { - - $drop_id = celerity_generate_unique_node_id(); - Javelin::initBehavior('conpherence-drag-and-drop-photo', - array( - 'target' => $drop_id, - 'form_pane' => 'conpherence-form', - 'upload_uri' => '/file/dropupload/', - 'activated_class' => 'conpherence-dialogue-upload-photo', - )); - require_celerity_resource('conpherence-update-css'); - - return phutil_tag( - 'div', - array( - 'id' => $drop_id, - 'class' => 'conpherence-dialogue-drag-photo', - ), - pht('Drag and drop an image here to upload it.')); - } - -} diff --git a/src/applications/conpherence/view/ConpherenceLayoutView.php b/src/applications/conpherence/view/ConpherenceLayoutView.php index f0d94d9a06..6699d61994 100644 --- a/src/applications/conpherence/view/ConpherenceLayoutView.php +++ b/src/applications/conpherence/view/ConpherenceLayoutView.php @@ -7,6 +7,7 @@ final class ConpherenceLayoutView extends AphrontTagView { private $threadView; private $role; private $header; + private $search; private $messages; private $replyForm; private $latestTransactionID; @@ -26,6 +27,11 @@ final class ConpherenceLayoutView extends AphrontTagView { return $this; } + public function setSearch($search) { + $this->search = $search; + return $this; + } + public function setRole($role) { $this->role = $role; return $this; @@ -55,26 +61,17 @@ final class ConpherenceLayoutView extends AphrontTagView { return $this; } - public function getWidgetColumnVisible() { - $widget_key = PhabricatorConpherenceWidgetVisibleSetting::SETTINGKEY; - $user = $this->getUser(); - return (bool)$user->getUserSetting($widget_key, false); - } - protected function getTagAttributes() { $classes = array(); - if (!$this->getWidgetColumnVisible()) { - $classes[] = 'hide-widgets'; - } + $classes[] = 'conpherence-layout'; + $classes[] = 'hide-widgets'; + $classes[] = 'conpherence-role-'.$this->role; return array( - 'id' => 'conpherence-main-layout', - 'sigil' => 'conpherence-layout', - 'class' => 'conpherence-layout '. - implode(' ', $classes). - ' conpherence-role-'.$this->role, - ); - + 'id' => 'conpherence-main-layout', + 'sigil' => 'conpherence-layout', + 'class' => implode(' ', $classes), + ); } protected function getTagContent() { @@ -131,6 +128,12 @@ final class ConpherenceLayoutView extends AphrontTagView { 'class' => 'conpherence-content-pane', ), array( + phutil_tag( + 'div', + array( + 'class' => 'conpherence-loading-mask', + ), + ''), javelin_tag( 'div', array( @@ -184,6 +187,14 @@ final class ConpherenceLayoutView extends AphrontTagView { 'sigil' => 'conpherence-messages', ), nonempty($this->messages, '')), + javelin_tag( + 'div', + array( + 'class' => 'conpherence-search-main', + 'id' => 'conpherence-search-main', + 'sigil' => 'conpherence-search-main', + ), + nonempty($this->search, '')), phutil_tag( 'div', array( @@ -230,13 +241,7 @@ final class ConpherenceLayoutView extends AphrontTagView { $box = id(new PHUIObjectBoxView()) ->setHeader($header) - ->setObjectList($view->getObjectList()); - if ($viewer->isLoggedIn()) { - $info = id(new PHUIInfoView()) - ->appendChild(pht('You have not joined any rooms yet.')) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE); - $box->setInfoView($info); - } + ->setObjectList($view->getContent()); return $box; } else { diff --git a/src/applications/conpherence/view/ConpherenceTransactionView.php b/src/applications/conpherence/view/ConpherenceTransactionView.php index 12123aafcd..c18a672ca2 100644 --- a/src/applications/conpherence/view/ConpherenceTransactionView.php +++ b/src/applications/conpherence/view/ConpherenceTransactionView.php @@ -6,8 +6,8 @@ final class ConpherenceTransactionView extends AphrontView { private $conpherenceTransaction; private $handles; private $markupEngine; - private $fullDisplay; private $classes = array(); + private $searchResult; private $timeOnly; public function setConpherenceThread(ConpherenceThread $t) { @@ -47,17 +47,13 @@ final class ConpherenceTransactionView extends AphrontView { return $this->markupEngine; } - public function setFullDisplay($bool) { - $this->fullDisplay = $bool; + public function addClass($class) { + $this->classes[] = $class; return $this; } - private function getFullDisplay() { - return $this->fullDisplay; - } - - public function addClass($class) { - $this->classes[] = $class; + public function setSearchResult($result) { + $this->searchResult = $result; return $this; } @@ -100,11 +96,7 @@ final class ConpherenceTransactionView extends AphrontView { $image = $this->renderTransactionImage(); $content = $this->renderTransactionContent(); $classes = implode(' ', $this->classes); - - $transaction_dom_id = null; - if ($this->getFullDisplay()) { - $transaction_dom_id = 'anchor-'.$transaction->getID(); - } + $transaction_dom_id = 'anchor-'.$transaction->getID(); $header = phutil_tag_div( 'conpherence-transaction-header grouped', @@ -137,12 +129,25 @@ final class ConpherenceTransactionView extends AphrontView { $tip = phabricator_datetime($transaction->getDateCreated(), $viewer); $label = phabricator_time($transaction->getDateCreated(), $viewer); $width = 360; - if ($this->getFullDisplay()) { - Javelin::initBehavior('phabricator-watch-anchor'); - $anchor = id(new PhabricatorAnchorView()) - ->setAnchorName($transaction->getID()) - ->render(); + Javelin::initBehavior('phabricator-watch-anchor'); + $anchor = id(new PhabricatorAnchorView()) + ->setAnchorName($transaction->getID()) + ->render(); + + if ($this->searchResult) { + $uri = $thread->getMonogram(); + $info[] = hsprintf( + '%s', + javelin_tag( + 'a', + array( + 'href' => '/'.$uri.'#'.$transaction->getID(), + 'class' => 'transaction-date', + 'sigil' => 'conpherence-search-result-jump', + ), + $tip)); + } else { $info[] = hsprintf( '%s%s', $anchor, @@ -150,7 +155,7 @@ final class ConpherenceTransactionView extends AphrontView { 'a', array( 'href' => '#'.$transaction->getID(), - 'class' => 'anchor-link', + 'class' => 'transaction-date anchor-link', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $tip, @@ -158,20 +163,6 @@ final class ConpherenceTransactionView extends AphrontView { ), ), $label)); - } else { - $href = '/'.$thread->getMonogram().'#'.$transaction->getID(); - $info[] = javelin_tag( - 'a', - array( - 'href' => $href, - 'class' => 'epoch-link', - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $tip, - 'size' => $width, - ), - ), - $label); } return phutil_tag( @@ -247,17 +238,14 @@ final class ConpherenceTransactionView extends AphrontView { break; } - $this->appendChild( - phutil_tag( - 'div', - array( - 'class' => $content_class, - ), - $content)); + $view = phutil_tag( + 'div', + array( + 'class' => $content_class, + ), + $content); - return phutil_tag_div( - 'conpherence-transaction-content', - $this->renderChildren()); + return phutil_tag_div('conpherence-transaction-content', $view); } } diff --git a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php index 1d7b646270..3eb1568a76 100644 --- a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php +++ b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php @@ -70,6 +70,19 @@ final class PhabricatorDifferentialConfigOptions ); } + $inline_description = $this->deformat( + pht(<<newOption( 'differential.fields', @@ -254,13 +267,7 @@ final class PhabricatorDifferentialConfigOptions 'int', 0) ->setSummary(pht('Inline patches in email, as body text.')) - ->setDescription( - pht( - "To include patches inline in email bodies, set this to a ". - "positive integer. Patches will be inlined if they are at most ". - "that many lines. For instance, a value of 100 means 'inline ". - "patches if they are no longer than 100 lines'. By default, ". - "patches are not inlined.")), + ->setDescription($inline_description), $this->newOption( 'metamta.differential.patch-format', 'enum', diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 3e9fe2b99f..b4828494c0 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1264,11 +1264,30 @@ final class DifferentialTransactionEditor $config_attach = PhabricatorEnv::getEnvConfig($config_key_attach); if ($config_inline || $config_attach) { - $patch = $this->buildPatchForMail($diff); - $lines = substr_count($patch, "\n"); + $body_limit = PhabricatorEnv::getEnvConfig('metamta.email-body-limit'); - if ($config_inline && ($lines <= $config_inline)) { - $this->appendChangeDetailsForMail($object, $diff, $patch, $body); + $patch = $this->buildPatchForMail($diff); + if ($config_inline) { + $lines = substr_count($patch, "\n"); + $bytes = strlen($patch); + + // Limit the patch size to the smaller of 256 bytes per line or + // the mail body limit. This prevents degenerate behavior for patches + // with one line that is 10MB long. See T11748. + $byte_limits = array(); + $byte_limits[] = (256 * $config_inline); + $byte_limits[] = $body_limit; + $byte_limit = min($byte_limits); + + $lines_ok = ($lines <= $config_inline); + $bytes_ok = ($bytes <= $byte_limit); + + if ($lines_ok && $bytes_ok) { + $this->appendChangeDetailsForMail($object, $diff, $patch, $body); + } else { + // TODO: Provide a helpful message about the patch being too + // large or lengthy here. + } } if ($config_attach) { diff --git a/src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php index 33b21b2ffe..754bafd850 100644 --- a/src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php @@ -19,8 +19,9 @@ final class DiffusionGitUploadPackSSHWorkflow extends DiffusionGitSSHWorkflow { $device = AlmanacKeys::getLiveDevice(); $skip_sync = $this->shouldSkipReadSynchronization(); + $is_proxy = $this->shouldProxy(); - if ($this->shouldProxy()) { + if ($is_proxy) { $command = $this->getProxyCommand(); if ($device) { @@ -48,6 +49,8 @@ final class DiffusionGitUploadPackSSHWorkflow extends DiffusionGitSSHWorkflow { } $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); + $pull_event = $this->newPullEvent(); + $future = id(new ExecFuture('%C', $command)) ->setEnv($this->getEnvironment()); @@ -56,6 +59,26 @@ final class DiffusionGitUploadPackSSHWorkflow extends DiffusionGitSSHWorkflow { ->setCommandChannelFromExecFuture($future) ->execute(); + if ($err) { + $pull_event + ->setResultType('error') + ->setResultCode($err); + } else { + $pull_event + ->setResultType('pull') + ->setResultCode(0); + } + + // TODO: Currently, when proxying, we do not write a log on the proxy. + // Perhaps we should write a "proxy log". This is not very useful for + // statistics or auditing, but could be useful for diagnostics. Marking + // the proxy logs as proxied (and recording devicePHID on all logs) would + // make differentiating between these use cases easier. + + if (!$is_proxy) { + $pull_event->save(); + } + if (!$err) { $this->waitForGitClient(); } diff --git a/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php index 78252fb8d9..20a6a251a7 100644 --- a/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php @@ -30,10 +30,8 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow { DiffusionCommitHookEngine::ENV_REMOTE_PROTOCOL => 'ssh', ); - $ssh_client = getenv('SSH_CLIENT'); - if ($ssh_client) { - // This has the format " ". Grab the IP. - $remote_address = head(explode(' ', $ssh_client)); + $remote_address = $this->getSSHRemoteAddress(); + if ($remote_address !== null) { $env[DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS] = $remote_address; } @@ -259,5 +257,17 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow { return false; } + protected function newPullEvent() { + $viewer = $this->getViewer(); + $repository = $this->getRepository(); + $remote_address = $this->getSSHRemoteAddress(); + + return id(new PhabricatorRepositoryPullEvent()) + ->setEpoch(PhabricatorTime::getNow()) + ->setRemoteAddress($remote_address) + ->setRemoteProtocol('ssh') + ->setPullerPHID($viewer->getPHID()) + ->setRepositoryPHID($repository->getPHID()); + } } diff --git a/src/applications/drydock/conduit/DrydockAuthorizationSearchConduitAPIMethod.php b/src/applications/drydock/conduit/DrydockAuthorizationSearchConduitAPIMethod.php new file mode 100644 index 0000000000..7c90639bcc --- /dev/null +++ b/src/applications/drydock/conduit/DrydockAuthorizationSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ +getBlueprint(); - $query->withBlueprintPHIDs(array($blueprint->getPHID())); + if ($blueprint) { + $query->withBlueprintPHIDs(array($blueprint->getPHID())); + } return $query; } @@ -38,15 +40,46 @@ final class DrydockAuthorizationSearchEngine protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); + if ($map['blueprintPHIDs']) { + $query->withBlueprintPHIDs($map['blueprintPHIDs']); + } + + if ($map['objectPHIDs']) { + $query->withObjectPHIDs($map['objectPHIDs']); + } + return $query; } protected function buildCustomSearchFields() { - return array(); + return array( + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Blueprints')) + ->setKey('blueprintPHIDs') + ->setConduitParameterType(new ConduitPHIDListParameterType()) + ->setDescription(pht('Search authorizations for specific blueprints.')) + ->setAliases(array('blueprint', 'blueprints')) + ->setDatasource(new DrydockBlueprintDatasource()), + id(new PhabricatorPHIDsSearchField()) + ->setLabel(pht('Objects')) + ->setKey('objectPHIDs') + ->setDescription(pht('Search authorizations from specific objects.')) + ->setAliases(array('object', 'objects')), + ); + } + + protected function getHiddenFields() { + return array( + 'blueprintPHIDs', + 'objectPHIDs', + ); } protected function getURI($path) { $blueprint = $this->getBlueprint(); + if (!$blueprint) { + throw new PhutilInvalidStateException('setBlueprint'); + } $id = $blueprint->getID(); return "/drydock/blueprint/{$id}/authorizations/".$path; } diff --git a/src/applications/drydock/storage/DrydockAuthorization.php b/src/applications/drydock/storage/DrydockAuthorization.php index 9503a03767..cfd186c9d3 100644 --- a/src/applications/drydock/storage/DrydockAuthorization.php +++ b/src/applications/drydock/storage/DrydockAuthorization.php @@ -2,7 +2,8 @@ final class DrydockAuthorization extends DrydockDAO implements - PhabricatorPolicyInterface { + PhabricatorPolicyInterface, + PhabricatorConduitResultInterface { const OBJECTAUTH_ACTIVE = 'active'; const OBJECTAUTH_INACTIVE = 'inactive'; @@ -204,4 +205,52 @@ final class DrydockAuthorization extends DrydockDAO } +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('blueprintPHID') + ->setType('phid') + ->setDescription(pht( + 'PHID of the blueprint this request was made for.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('blueprintAuthorizationState') + ->setType('map') + ->setDescription(pht('Authorization state of this request.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('objectPHID') + ->setType('phid') + ->setDescription(pht( + 'PHID of the object which requested authorization.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('objectAuthorizationState') + ->setType('map') + ->setDescription(pht('Authorization state of the requesting object.')), + ); + } + + public function getFieldValuesForConduit() { + $blueprint_state = $this->getBlueprintAuthorizationState(); + $object_state = $this->getObjectAuthorizationState(); + return array( + 'blueprintPHID' => $this->getBlueprintPHID(), + 'blueprintAuthorizationState' => array( + 'value' => $blueprint_state, + 'name' => self::getBlueprintStateName($blueprint_state), + ), + 'objectPHID' => $this->getObjectPHID(), + 'objectAuthorizationState' => array( + 'value' => $object_state, + 'name' => self::getObjectStateName($object_state), + ), + ); + } + + public function getConduitSearchAttachments() { + return array( + ); + } + } diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index 87ea777f72..afd3b771ca 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -10,7 +10,8 @@ final class DrydockBlueprint extends DrydockDAO PhabricatorPolicyInterface, PhabricatorCustomFieldInterface, PhabricatorNgramsInterface, - PhabricatorProjectInterface { + PhabricatorProjectInterface, + PhabricatorConduitResultInterface { protected $className; protected $blueprintName; @@ -360,4 +361,33 @@ final class DrydockBlueprint extends DrydockDAO ); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of this blueprint.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('type') + ->setType('string') + ->setDescription(pht('The type of resource this blueprint provides.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getBlueprintName(), + 'type' => $this->getImplementation()->getType(), + ); + } + + public function getConduitSearchAttachments() { + return array( + ); + } + } diff --git a/src/applications/files/view/PhabricatorGlobalUploadTargetView.php b/src/applications/files/view/PhabricatorGlobalUploadTargetView.php index 3e0690815c..76aacbe36f 100644 --- a/src/applications/files/view/PhabricatorGlobalUploadTargetView.php +++ b/src/applications/files/view/PhabricatorGlobalUploadTargetView.php @@ -14,6 +14,9 @@ final class PhabricatorGlobalUploadTargetView extends AphrontView { private $showIfSupportedID; + private $hintText; + private $viewPolicy; + private $submitURI; public function setShowIfSupportedID($show_if_supported_id) { $this->showIfSupportedID = $show_if_supported_id; @@ -24,8 +27,37 @@ final class PhabricatorGlobalUploadTargetView extends AphrontView { return $this->showIfSupportedID; } + public function setHintText($hint_text) { + $this->hintText = $hint_text; + return $this; + } + + public function getHintText() { + return $this->hintText; + } + + public function setViewPolicy($view_policy) { + $this->viewPolicy = $view_policy; + return $this; + } + + public function getViewPolicy() { + return $this->viewPolicy; + } + + public function setSubmitURI($submit_uri) { + $this->submitURI = $submit_uri; + return $this; + } + + public function getSubmitURI() { + return $this->submitURI; + } + + + public function render() { - $viewer = $this->getUser(); + $viewer = $this->getViewer(); if (!$viewer->isLoggedIn()) { return null; } @@ -34,18 +66,30 @@ final class PhabricatorGlobalUploadTargetView extends AphrontView { require_celerity_resource('global-drag-and-drop-css'); + $hint_text = $this->getHintText(); + if (!strlen($hint_text)) { + $hint_text = "\xE2\x87\xAA ".pht('Drop Files to Upload'); + } + // Use the configured default view policy. Drag and drop uploads use // a more restrictive view policy if we don't specify a policy explicitly, // as the more restrictive policy is correct for most drop targets (like // Pholio uploads and Remarkup text areas). - $view_policy = PhabricatorFile::initializeNewFile()->getViewPolicy(); + $view_policy = $this->getViewPolicy(); + if ($view_policy === null) { + $view_policy = PhabricatorFile::initializeNewFile()->getViewPolicy(); + } + + $submit_uri = $this->getSubmitURI(); + $done_uri = '/file/query/authored/'; Javelin::initBehavior('global-drag-and-drop', array( 'ifSupported' => $this->showIfSupportedID, 'instructions' => $instructions_id, 'uploadURI' => '/file/dropupload/', - 'browseURI' => '/file/query/authored/', + 'submitURI' => $submit_uri, + 'browseURI' => $done_uri, 'viewPolicy' => $view_policy, 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(), )); @@ -57,6 +101,6 @@ final class PhabricatorGlobalUploadTargetView extends AphrontView { 'class' => 'phabricator-global-upload-instructions', 'style' => 'display: none;', ), - "\xE2\x87\xAA ".pht('Drop Files to Upload')); + $hint_text); } } diff --git a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php index 54068e1e71..709558eaa2 100644 --- a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php +++ b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php @@ -53,7 +53,6 @@ final class PhabricatorApplicationDetailViewController $panel = $config->buildConfigurationPagePanel(); $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $panels[] = $panel; - } $view = id(new PHUITwoColumnView()) @@ -126,8 +125,7 @@ final class PhabricatorApplicationDetailViewController $properties = id(new PHUIPropertyListView()); $header = id(new PHUIHeaderView()) - ->setHeader(pht('POLICIES')) - ->setHeaderIcon('fa-lock'); + ->setHeader(pht('Policies')); $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, diff --git a/src/applications/owners/controller/PhabricatorOwnersArchiveController.php b/src/applications/owners/controller/PhabricatorOwnersArchiveController.php index 2ef618a2d5..47f9d87d23 100644 --- a/src/applications/owners/controller/PhabricatorOwnersArchiveController.php +++ b/src/applications/owners/controller/PhabricatorOwnersArchiveController.php @@ -31,8 +31,9 @@ final class PhabricatorOwnersArchiveController $xactions = array(); + $type = PhabricatorOwnersPackageStatusTransaction::TRANSACTIONTYPE; $xactions[] = id(new PhabricatorOwnersPackageTransaction()) - ->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_STATUS) + ->setTransactionType($type) ->setNewValue($new_status); id(new PhabricatorOwnersPackageTransactionEditor()) diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php index 7f911d29d9..b69faf5648 100644 --- a/src/applications/owners/controller/PhabricatorOwnersPathsController.php +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -48,7 +48,7 @@ final class PhabricatorOwnersPathsController ); } - $type_paths = PhabricatorOwnersPackageTransaction::TYPE_PATHS; + $type_paths = PhabricatorOwnersPackagePathsTransaction::TRANSACTIONTYPE; $xactions = array(); $xactions[] = id(new PhabricatorOwnersPackageTransaction()) diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php index b20613c92e..4adaebc582 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php @@ -95,14 +95,16 @@ EOTEXT ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the package.')) - ->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_NAME) + ->setTransactionType( + PhabricatorOwnersPackageNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getName()), id(new PhabricatorDatasourceEditField()) ->setKey('owners') ->setLabel(pht('Owners')) ->setDescription(pht('Users and projects which own the package.')) - ->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_OWNERS) + ->setTransactionType( + PhabricatorOwnersPackageOwnersTransaction::TRANSACTIONTYPE) ->setDatasource(new PhabricatorProjectOrUserDatasource()) ->setIsCopyable(true) ->setValue($object->getOwnerPHIDs()), @@ -112,7 +114,7 @@ EOTEXT ->setDescription( pht('Change package dominion rules.')) ->setTransactionType( - PhabricatorOwnersPackageTransaction::TYPE_DOMINION) + PhabricatorOwnersPackageDominionTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setValue($object->getDominion()) ->setOptions($dominion_map), @@ -124,7 +126,7 @@ EOTEXT 'Automatically trigger reviews for commits affecting files in '. 'this package.')) ->setTransactionType( - PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW) + PhabricatorOwnersPackageAutoreviewTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setValue($object->getAutoReview()) ->setOptions($autoreview_map), @@ -135,7 +137,8 @@ EOTEXT pht( 'Automatically trigger audits for commits affecting files in '. 'this package.')) - ->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_AUDITING) + ->setTransactionType( + PhabricatorOwnersPackageAuditingTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setValue($object->getAuditingEnabled()) ->setOptions( @@ -148,13 +151,14 @@ EOTEXT ->setLabel(pht('Description')) ->setDescription(pht('Human-readable description of the package.')) ->setTransactionType( - PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION) + PhabricatorOwnersPackageDescriptionTransaction::TRANSACTIONTYPE) ->setValue($object->getDescription()), id(new PhabricatorSelectEditField()) ->setKey('status') ->setLabel(pht('Status')) ->setDescription(pht('Archive or enable the package.')) - ->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_STATUS) + ->setTransactionType( + PhabricatorOwnersPackageStatusTransaction::TRANSACTIONTYPE) ->setIsConduitOnly(true) ->setValue($object->getStatus()) ->setOptions($object->getStatusNameMap()), @@ -162,7 +166,8 @@ EOTEXT ->setKey('paths.set') ->setLabel(pht('Paths')) ->setIsConduitOnly(true) - ->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_PATHS) + ->setTransactionType( + PhabricatorOwnersPackagePathsTransaction::TRANSACTIONTYPE) ->setConduitDescription( pht('Overwrite existing package paths with new paths.')) ->setConduitTypeDescription( diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php index 5fb4a9af8a..40657abd57 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php @@ -14,354 +14,12 @@ final class PhabricatorOwnersPackageTransactionEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorOwnersPackageTransaction::TYPE_NAME; - $types[] = PhabricatorOwnersPackageTransaction::TYPE_OWNERS; - $types[] = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; - $types[] = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; - $types[] = PhabricatorOwnersPackageTransaction::TYPE_PATHS; - $types[] = PhabricatorOwnersPackageTransaction::TYPE_STATUS; - $types[] = PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW; - $types[] = PhabricatorOwnersPackageTransaction::TYPE_DOMINION; - $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorOwnersPackageTransaction::TYPE_NAME: - return $object->getName(); - case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: - $phids = mpull($object->getOwners(), 'getUserPHID'); - $phids = array_values($phids); - return $phids; - case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: - return (int)$object->getAuditingEnabled(); - case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case PhabricatorOwnersPackageTransaction::TYPE_PATHS: - $paths = $object->getPaths(); - return mpull($paths, 'getRef'); - case PhabricatorOwnersPackageTransaction::TYPE_STATUS: - return $object->getStatus(); - case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW: - return $object->getAutoReview(); - case PhabricatorOwnersPackageTransaction::TYPE_DOMINION: - return $object->getDominion(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorOwnersPackageTransaction::TYPE_NAME: - case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: - case PhabricatorOwnersPackageTransaction::TYPE_STATUS: - case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW: - case PhabricatorOwnersPackageTransaction::TYPE_DOMINION: - return $xaction->getNewValue(); - case PhabricatorOwnersPackageTransaction::TYPE_PATHS: - $new = $xaction->getNewValue(); - foreach ($new as $key => $info) { - $new[$key]['excluded'] = (int)idx($info, 'excluded'); - } - return $new; - case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: - return (int)$xaction->getNewValue(); - case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: - $phids = $xaction->getNewValue(); - $phids = array_unique($phids); - $phids = array_values($phids); - return $phids; - } - } - - protected function transactionHasEffect( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorOwnersPackageTransaction::TYPE_PATHS: - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); - list($rem, $add) = $diffs; - - return ($rem || $add); - } - - return parent::transactionHasEffect($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorOwnersPackageTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - return; - case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: - $object->setAuditingEnabled($xaction->getNewValue()); - return; - case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: - case PhabricatorOwnersPackageTransaction::TYPE_PATHS: - return; - case PhabricatorOwnersPackageTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - return; - case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW: - $object->setAutoReview($xaction->getNewValue()); - return; - case PhabricatorOwnersPackageTransaction::TYPE_DOMINION: - $object->setDominion($xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorOwnersPackageTransaction::TYPE_NAME: - case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: - case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: - case PhabricatorOwnersPackageTransaction::TYPE_STATUS: - case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW: - case PhabricatorOwnersPackageTransaction::TYPE_DOMINION: - return; - case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - $owners = $object->getOwners(); - $owners = mpull($owners, null, 'getUserPHID'); - - $rem = array_diff($old, $new); - foreach ($rem as $phid) { - if (isset($owners[$phid])) { - $owners[$phid]->delete(); - unset($owners[$phid]); - } - } - - $add = array_diff($new, $old); - foreach ($add as $phid) { - $owners[$phid] = id(new PhabricatorOwnersOwner()) - ->setPackageID($object->getID()) - ->setUserPHID($phid) - ->save(); - } - - // TODO: Attach owners here - return; - case PhabricatorOwnersPackageTransaction::TYPE_PATHS: - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - $paths = $object->getPaths(); - - $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); - list($rem, $add) = $diffs; - - $set = PhabricatorOwnersPath::getSetFromTransactionValue($rem); - foreach ($paths as $path) { - $ref = $path->getRef(); - if (PhabricatorOwnersPath::isRefInSet($ref, $set)) { - $path->delete(); - } - } - - foreach ($add as $ref) { - $path = PhabricatorOwnersPath::newFromRef($ref) - ->setPackageID($object->getID()) - ->save(); - } - - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhabricatorOwnersPackageTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Package name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - - foreach ($xactions as $xaction) { - $new = $xaction->getNewValue(); - if (preg_match('([,!])', $new)) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Package names may not contain commas (",") or exclamation '. - 'marks ("!"). These characters are ambiguous when package '. - 'names are parsed from the command line.'), - $xaction); - } - } - - break; - case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW: - $map = PhabricatorOwnersPackage::getAutoreviewOptionsMap(); - foreach ($xactions as $xaction) { - $new = $xaction->getNewValue(); - - if (empty($map[$new])) { - $valid = array_keys($map); - - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Autoreview setting "%s" is not valid. '. - 'Valid settings are: %s.', - $new, - implode(', ', $valid)), - $xaction); - } - } - break; - case PhabricatorOwnersPackageTransaction::TYPE_DOMINION: - $map = PhabricatorOwnersPackage::getDominionOptionsMap(); - foreach ($xactions as $xaction) { - $new = $xaction->getNewValue(); - - if (empty($map[$new])) { - $valid = array_keys($map); - - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Dominion setting "%s" is not valid. '. - 'Valid settings are: %s.', - $new, - implode(', ', $valid)), - $xaction); - } - } - break; - case PhabricatorOwnersPackageTransaction::TYPE_PATHS: - if (!$xactions) { - continue; - } - - $old = mpull($object->getPaths(), 'getRef'); - foreach ($xactions as $xaction) { - $new = $xaction->getNewValue(); - - // Check that we have a list of paths. - if (!is_array($new)) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('Path specification must be a list of paths.'), - $xaction); - continue; - } - - // Check that each item in the list is formatted properly. - $type_exception = null; - foreach ($new as $key => $value) { - try { - PhutilTypeSpec::checkMap( - $value, - array( - 'repositoryPHID' => 'string', - 'path' => 'string', - 'excluded' => 'optional wild', - )); - } catch (PhutilTypeCheckException $ex) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Path specification list contains invalid value '. - 'in key "%s": %s.', - $key, - $ex->getMessage()), - $xaction); - $type_exception = $ex; - } - } - - if ($type_exception) { - continue; - } - - // Check that any new paths reference legitimate repositories which - // the viewer has permission to see. - list($rem, $add) = PhabricatorOwnersPath::getTransactionValueChanges( - $old, - $new); - - if ($add) { - $repository_phids = ipull($add, 'repositoryPHID'); - - $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($this->getActor()) - ->withPHIDs($repository_phids) - ->execute(); - $repositories = mpull($repositories, null, 'getPHID'); - - foreach ($add as $ref) { - $repository_phid = $ref['repositoryPHID']; - if (isset($repositories[$repository_phid])) { - continue; - } - - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Path specification list references repository PHID "%s", '. - 'but that is not a valid, visible repository.', - $repository_phid)); - } - } - } - break; - } - - return $errors; - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php index 359a1fcb8a..66e15b634a 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php @@ -1,17 +1,7 @@ getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_OWNERS: - if (!is_array($old)) { - $old = array(); - } - - if (!is_array($new)) { - $new = array(); - } - - $add = array_diff($new, $old); - foreach ($add as $phid) { - $phids[] = $phid; - } - $rem = array_diff($old, $new); - foreach ($rem as $phid) { - $phids[] = $phid; - } - break; - } - - return $phids; - } - - public function shouldHide() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - if ($old === null) { - return true; - } - break; - case self::TYPE_PRIMARY: - // TODO: Eventually, remove these transactions entirely. - return true; - } - - return parent::shouldHide(); - } - - public function getTitle() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $author_phid = $this->getAuthorPHID(); - - switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_CREATE: - return pht( - '%s created this package.', - $this->renderHandleLink($author_phid)); - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this package.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this package from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - case self::TYPE_OWNERS: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - if ($add && !$rem) { - return pht( - '%s added %s owner(s): %s.', - $this->renderHandleLink($author_phid), - count($add), - $this->renderHandleList($add)); - } else if ($rem && !$add) { - return pht( - '%s removed %s owner(s): %s.', - $this->renderHandleLink($author_phid), - count($rem), - $this->renderHandleList($rem)); - } else { - return pht( - '%s changed %s package owner(s), added %s: %s; removed %s: %s.', - $this->renderHandleLink($author_phid), - count($add) + count($rem), - count($add), - $this->renderHandleList($add), - count($rem), - $this->renderHandleList($rem)); - } - case self::TYPE_AUDITING: - if ($new) { - return pht( - '%s enabled auditing for this package.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s disabled auditing for this package.', - $this->renderHandleLink($author_phid)); - } - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for this package.', - $this->renderHandleLink($author_phid)); - case self::TYPE_PATHS: - // TODO: Flesh this out. - return pht( - '%s updated paths for this package.', - $this->renderHandleLink($author_phid)); - case self::TYPE_STATUS: - if ($new == PhabricatorOwnersPackage::STATUS_ACTIVE) { - return pht( - '%s activated this package.', - $this->renderHandleLink($author_phid)); - } else if ($new == PhabricatorOwnersPackage::STATUS_ARCHIVED) { - return pht( - '%s archived this package.', - $this->renderHandleLink($author_phid)); - } - case self::TYPE_AUTOREVIEW: - $map = PhabricatorOwnersPackage::getAutoreviewOptionsMap(); - $map = ipull($map, 'name'); - - $old = idx($map, $old, $old); - $new = idx($map, $new, $new); - - return pht( - '%s adjusted autoreview from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - case self::TYPE_DOMINION: - $map = PhabricatorOwnersPackage::getDominionOptionsMap(); - $map = ipull($map, 'short'); - - $old = idx($map, $old, $old); - $new = idx($map, $new, $new); - - return pht( - '%s adjusted package dominion rules from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - - return parent::getTitle(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($this->getOldValue() !== null); - case self::TYPE_PATHS: - return true; - } - - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - return $this->renderTextCorpusChangeDetails( - $viewer, - $old, - $new); - case self::TYPE_PATHS: - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); - list($rem, $add) = $diffs; - - $rows = array(); - foreach ($rem as $ref) { - $rows[] = array( - 'class' => 'diff-removed', - 'change' => '-', - ) + $ref; - } - - foreach ($add as $ref) { - $rows[] = array( - 'class' => 'diff-added', - 'change' => '+', - ) + $ref; - } - - $rowc = array(); - foreach ($rows as $key => $row) { - $rowc[] = $row['class']; - $rows[$key] = array( - $row['change'], - $row['excluded'] ? pht('Exclude') : pht('Include'), - $viewer->renderHandle($row['repositoryPHID']), - $row['path'], - ); - } - - $table = id(new AphrontTableView($rows)) - ->setRowClasses($rowc) - ->setHeaders( - array( - null, - pht('Type'), - pht('Repository'), - pht('Path'), - )) - ->setColumnClasses( - array( - null, - null, - null, - 'wide', - )); - - return $table; - } - - return parent::renderChangeDetails($viewer); - } - - public function getRemarkupBlocks() { - $blocks = parent::getRemarkupBlocks(); - - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - $blocks[] = $this->getNewValue(); - break; - } - - return $blocks; + public function getBaseTransactionClass() { + return 'PhabricatorOwnersPackageTransactionType'; } } diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackageAuditingTransaction.php b/src/applications/owners/xaction/PhabricatorOwnersPackageAuditingTransaction.php new file mode 100644 index 0000000000..df4f0feb01 --- /dev/null +++ b/src/applications/owners/xaction/PhabricatorOwnersPackageAuditingTransaction.php @@ -0,0 +1,32 @@ +getAuditingEnabled(); + } + + public function generateNewValue($object, $value) { + return (int)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setAuditingEnabled($value); + } + + public function getTitle() { + if ($this->getNewValue()) { + return pht( + '%s enabled auditing for this package.', + $this->renderAuthor()); + } else { + return pht( + '%s disabled auditing for this package.', + $this->renderAuthor()); + } + } + +} diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackageAutoreviewTransaction.php b/src/applications/owners/xaction/PhabricatorOwnersPackageAutoreviewTransaction.php new file mode 100644 index 0000000000..03ec4f7af1 --- /dev/null +++ b/src/applications/owners/xaction/PhabricatorOwnersPackageAutoreviewTransaction.php @@ -0,0 +1,56 @@ +getAutoReview(); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $map = PhabricatorOwnersPackage::getAutoreviewOptionsMap(); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + + if (empty($map[$new])) { + $valid = array_keys($map); + + $errors[] = $this->newInvalidError( + pht( + 'Autoreview setting "%s" is not valid. '. + 'Valid settings are: %s.', + $new, + implode(', ', $valid)), + $xaction); + } + } + + return $errors; + } + + public function applyInternalEffects($object, $value) { + $object->setAutoReview($value); + } + + public function getTitle() { + $map = PhabricatorOwnersPackage::getAutoreviewOptionsMap(); + $map = ipull($map, 'name'); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old = idx($map, $old, $old); + $new = idx($map, $new, $new); + + return pht( + '%s adjusted autoreview from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old), + $this->renderValue($new)); + } + +} diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackageDescriptionTransaction.php b/src/applications/owners/xaction/PhabricatorOwnersPackageDescriptionTransaction.php new file mode 100644 index 0000000000..8b2effe80c --- /dev/null +++ b/src/applications/owners/xaction/PhabricatorOwnersPackageDescriptionTransaction.php @@ -0,0 +1,29 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + '%s updated the description for this package.', + $this->renderAuthor()); + } + + public function newChangeDetailView() { + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($this->getViewer()) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + +} diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackageDominionTransaction.php b/src/applications/owners/xaction/PhabricatorOwnersPackageDominionTransaction.php new file mode 100644 index 0000000000..0c1ff0a916 --- /dev/null +++ b/src/applications/owners/xaction/PhabricatorOwnersPackageDominionTransaction.php @@ -0,0 +1,56 @@ +getDominion(); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $map = PhabricatorOwnersPackage::getDominionOptionsMap(); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + + if (empty($map[$new])) { + $valid = array_keys($map); + + $errors[] = $this->newInvalidError( + pht( + 'Dominion setting "%s" is not valid. '. + 'Valid settings are: %s.', + $new, + implode(', ', $valid)), + $xaction); + } + } + + return $errors; + } + + public function applyInternalEffects($object, $value) { + $object->setDominion($value); + } + + public function getTitle() { + $map = PhabricatorOwnersPackage::getDominionOptionsMap(); + $map = ipull($map, 'short'); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old = idx($map, $old, $old); + $new = idx($map, $new, $new); + + return pht( + '%s adjusted package dominion rules from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old), + $this->renderValue($new)); + } + +} diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackageNameTransaction.php b/src/applications/owners/xaction/PhabricatorOwnersPackageNameTransaction.php new file mode 100644 index 0000000000..b4e50daec2 --- /dev/null +++ b/src/applications/owners/xaction/PhabricatorOwnersPackageNameTransaction.php @@ -0,0 +1,52 @@ +getName(); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $missing = $this->isEmptyTextTransaction( + $object->getName(), + $xactions); + + if ($missing) { + $errors[] = $this->newRequiredError( + pht('Package name is required.'), + nonempty(last($xactions), null)); + } + + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (preg_match('([,!])', $new)) { + $errors[] = $this->newInvalidError( + pht( + 'Package names may not contain commas (",") or exclamation '. + 'marks ("!"). These characters are ambiguous when package '. + 'names are parsed from the command line.'), + $xaction); + } + } + + return $errors; + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this package from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + +} diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackageOwnersTransaction.php b/src/applications/owners/xaction/PhabricatorOwnersPackageOwnersTransaction.php new file mode 100644 index 0000000000..7fef0d5e6c --- /dev/null +++ b/src/applications/owners/xaction/PhabricatorOwnersPackageOwnersTransaction.php @@ -0,0 +1,76 @@ +getOwners(), 'getUserPHID'); + $phids = array_values($phids); + return $phids; + } + + public function generateNewValue($object, $value) { + $phids = array_unique($value); + $phids = array_values($phids); + return $phids; + } + + public function applyExternalEffects($object, $value) { + $old = $this->generateOldValue($object); + $new = $value; + + $owners = $object->getOwners(); + $owners = mpull($owners, null, 'getUserPHID'); + + $rem = array_diff($old, $new); + foreach ($rem as $phid) { + if (isset($owners[$phid])) { + $owners[$phid]->delete(); + unset($owners[$phid]); + } + } + + $add = array_diff($new, $old); + foreach ($add as $phid) { + $owners[$phid] = id(new PhabricatorOwnersOwner()) + ->setPackageID($object->getID()) + ->setUserPHID($phid) + ->save(); + } + + // TODO: Attach owners here + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + if ($add && !$rem) { + return pht( + '%s added %s owner(s): %s.', + $this->renderAuthor(), + count($add), + $this->renderHandleList($add)); + } else if ($rem && !$add) { + return pht( + '%s removed %s owner(s): %s.', + $this->renderAuthor(), + count($rem), + $this->renderHandleList($rem)); + } else { + return pht( + '%s changed %s package owner(s), added %s: %s; removed %s: %s.', + $this->renderAuthor(), + count($add) + count($rem), + count($add), + $this->renderHandleList($add), + count($rem), + $this->renderHandleList($rem)); + } + } + +} diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackagePathsTransaction.php b/src/applications/owners/xaction/PhabricatorOwnersPackagePathsTransaction.php new file mode 100644 index 0000000000..5952b78aed --- /dev/null +++ b/src/applications/owners/xaction/PhabricatorOwnersPackagePathsTransaction.php @@ -0,0 +1,189 @@ +getPaths(); + return mpull($paths, 'getRef'); + } + + public function generateNewValue($object, $value) { + $new = $value; + foreach ($new as $key => $info) { + $new[$key]['excluded'] = (int)idx($info, 'excluded'); + } + return $new; + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$xactions) { + return $errors; + } + + $old = mpull($object->getPaths(), 'getRef'); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + + // Check that we have a list of paths. + if (!is_array($new)) { + $errors[] = $this->newInvalidError( + pht('Path specification must be a list of paths.'), + $xaction); + continue; + } + + // Check that each item in the list is formatted properly. + $type_exception = null; + foreach ($new as $key => $value) { + try { + PhutilTypeSpec::checkMap( + $value, + array( + 'repositoryPHID' => 'string', + 'path' => 'string', + 'excluded' => 'optional wild', + )); + } catch (PhutilTypeCheckException $ex) { + $errors[] = $this->newInvalidError( + pht( + 'Path specification list contains invalid value '. + 'in key "%s": %s.', + $key, + $ex->getMessage()), + $xaction); + $type_exception = $ex; + } + } + + if ($type_exception) { + continue; + } + + // Check that any new paths reference legitimate repositories which + // the viewer has permission to see. + list($rem, $add) = PhabricatorOwnersPath::getTransactionValueChanges( + $old, + $new); + + if ($add) { + $repository_phids = ipull($add, 'repositoryPHID'); + + $repositories = id(new PhabricatorRepositoryQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($repository_phids) + ->execute(); + $repositories = mpull($repositories, null, 'getPHID'); + + foreach ($add as $ref) { + $repository_phid = $ref['repositoryPHID']; + if (isset($repositories[$repository_phid])) { + continue; + } + + $errors[] = $this->newInvalidError( + pht( + 'Path specification list references repository PHID "%s", '. + 'but that is not a valid, visible repository.', + $repository_phid)); + } + } + } + + return $errors; + } + + public function applyExternalEffects($object, $value) { + $old = $this->generateOldValue($object); + $new = $value; + + $paths = $object->getPaths(); + + $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); + list($rem, $add) = $diffs; + + $set = PhabricatorOwnersPath::getSetFromTransactionValue($rem); + foreach ($paths as $path) { + $ref = $path->getRef(); + if (PhabricatorOwnersPath::isRefInSet($ref, $set)) { + $path->delete(); + } + } + + foreach ($add as $ref) { + $path = PhabricatorOwnersPath::newFromRef($ref) + ->setPackageID($object->getID()) + ->save(); + } + } + + public function getTitle() { + // TODO: Flesh this out. + return pht( + '%s updated paths for this package.', + $this->renderAuthor()); + } + + public function hasChangeDetailView() { + return true; + } + + public function newChangeDetailView() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); + list($rem, $add) = $diffs; + + $rows = array(); + foreach ($rem as $ref) { + $rows[] = array( + 'class' => 'diff-removed', + 'change' => '-', + ) + $ref; + } + + foreach ($add as $ref) { + $rows[] = array( + 'class' => 'diff-added', + 'change' => '+', + ) + $ref; + } + + $rowc = array(); + foreach ($rows as $key => $row) { + $rowc[] = $row['class']; + $rows[$key] = array( + $row['change'], + $row['excluded'] ? pht('Exclude') : pht('Include'), + $this->renderHandle($row['repositoryPHID']), + $row['path'], + ); + } + + $table = id(new AphrontTableView($rows)) + ->setViewer($this->getViewer()) + ->setRowClasses($rowc) + ->setHeaders( + array( + null, + pht('Type'), + pht('Repository'), + pht('Path'), + )) + ->setColumnClasses( + array( + null, + null, + null, + 'wide', + )); + + return $table; + } + +} diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackagePrimaryTransaction.php b/src/applications/owners/xaction/PhabricatorOwnersPackagePrimaryTransaction.php new file mode 100644 index 0000000000..76d3c2f10f --- /dev/null +++ b/src/applications/owners/xaction/PhabricatorOwnersPackagePrimaryTransaction.php @@ -0,0 +1,15 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new == PhabricatorOwnersPackage::STATUS_ACTIVE) { + return pht( + '%s activated this package.', + $this->renderAuthor()); + } else if ($new == PhabricatorOwnersPackage::STATUS_ARCHIVED) { + return pht( + '%s archived this package.', + $this->renderAuthor()); + } + } + +} diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackageTransactionType.php b/src/applications/owners/xaction/PhabricatorOwnersPackageTransactionType.php new file mode 100644 index 0000000000..5faaa79a17 --- /dev/null +++ b/src/applications/owners/xaction/PhabricatorOwnersPackageTransactionType.php @@ -0,0 +1,4 @@ +isFormPost()) { $v_note = $request->getStr('description'); $v_slug = $request->getStr('slug'); + $normal_slug = PhabricatorSlug::normalize($v_slug); // If what the user typed isn't what we're actually using, warn them // about it. if (strlen($v_slug)) { - $normal_slug = PhabricatorSlug::normalize($v_slug); $no_slash_slug = rtrim($normal_slug, '/'); if ($normal_slug !== $v_slug && $no_slash_slug !== $v_slug) { return $this->newDialog() @@ -66,9 +66,21 @@ final class PhrictionMoveController extends PhrictionController { $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionTransaction::TYPE_MOVE_TO) ->setNewValue($document); - $target_document = PhrictionDocument::initializeNewDocument( - $viewer, - $v_slug); + $target_document = id(new PhrictionDocumentQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withSlugs(array($normal_slug)) + ->needContent(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$target_document) { + $target_document = PhrictionDocument::initializeNewDocument( + $viewer, + $v_slug); + } try { $editor->applyTransactions($target_document, $xactions); $redir_uri = PhrictionDocument::getSlugURI( diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index f0606c65a4..af238dfcf9 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -588,6 +588,7 @@ final class PhrictionTransactionEditor // Prevent overwrites and no-op moves. $exists = PhrictionDocumentStatus::STATUS_EXISTS; if ($target_document) { + $message = null; if ($target_document->getSlug() == $source_document->getSlug()) { $message = pht( 'You can not move a document to its existing location. '. @@ -598,13 +599,14 @@ final class PhrictionTransactionEditor 'overwrite an existing document which is already at that '. 'location. Move or delete the existing document first.'); } - - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - $message, - $xaction); - $errors[] = $error; + if ($message !== null) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + $message, + $xaction); + $errors[] = $error; + } } break; diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index 6350df5e02..b7bef9a623 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -111,6 +111,7 @@ final class PhabricatorApplicationSearchController 'before' => true, 'after' => true, 'nux' => true, + 'overheated' => true, ); foreach ($pt_data as $pt_key => $pt_value) { @@ -238,6 +239,9 @@ final class PhabricatorApplicationSearchController $nux_view = null; } + $force_overheated = $request->getBool('overheated'); + $is_overheated = $query->getIsOverheated() || $force_overheated; + if ($nux_view) { $box->appendChild($nux_view); } else { @@ -265,19 +269,33 @@ final class PhabricatorApplicationSearchController $box->appendChild($list->getContent()); } + if ($is_overheated) { + $box->appendChild($this->newOverheatedView($objects)); + } + $result_header = $list->getHeader(); if ($result_header) { $box->setHeader($result_header); $header = $result_header; } - if ($list->getActions()) { - foreach ($list->getActions() as $action) { + $actions = $list->getActions(); + if ($actions) { + foreach ($actions as $action) { $header->addActionLink($action); } } $use_actions = $engine->newUseResultsActions($saved_query); + + // TODO: Eventually, modularize all this stuff. + $builtin_use_actions = $this->newBuiltinUseActions(); + if ($builtin_use_actions) { + foreach ($builtin_use_actions as $builtin_use_action) { + $use_actions[] = $builtin_use_action; + } + } + if ($use_actions) { $use_dropdown = $this->newUseResultsDropdown( $saved_query, @@ -329,7 +347,6 @@ final class PhabricatorApplicationSearchController $crumbs->addTextCrumb($title); } - $nav->addClass('application-search-view'); require_celerity_resource('application-search-view-css'); return $this->newPage() @@ -337,6 +354,7 @@ final class PhabricatorApplicationSearchController ->setTitle(pht('Query: %s', $title)) ->setCrumbs($crumbs) ->setNavigation($nav) + ->addClass('application-search-view') ->appendChild($body); } @@ -525,4 +543,57 @@ final class PhabricatorApplicationSearchController ->setDropdownMenu($action_list); } + private function newOverheatedView(array $results) { + if ($results) { + $message = pht( + 'Most objects matching your query are not visible to you, so '. + 'filtering results is taking a long time. Only some results are '. + 'shown. Refine your query to find results more quickly.'); + } else { + $message = pht( + 'Most objects matching your query are not visible to you, so '. + 'filtering results is taking a long time. Refine your query to '. + 'find results more quickly.'); + } + + return id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setFlush(true) + ->setTitle(pht('Query Overheated')) + ->setErrors( + array( + $message, + )); + } + + private function newBuiltinUseActions() { + $actions = array(); + + $is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); + + if ($is_dev) { + $engine = $this->getSearchEngine(); + $nux_uri = $engine->getQueryBaseURI(); + $nux_uri = id(new PhutilURI($nux_uri)) + ->setQueryParam('nux', true); + + $actions[] = id(new PhabricatorActionView()) + ->setIcon('fa-bug') + ->setName(pht('Developer: Show New User State')) + ->setHref($nux_uri); + } + + if ($is_dev) { + $overheated_uri = $this->getRequest()->getRequestURI() + ->setQueryParam('overheated', true); + + $actions[] = id(new PhabricatorActionView()) + ->setIcon('fa-bug') + ->setName(pht('Developer: Show Overheated State')) + ->setHref($overheated_uri); + } + + return $actions; + } + } diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index a1279ad8ae..26a9e0f8e4 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -406,6 +406,10 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return $this->getURI('query/edit/'); } + public function getQueryBaseURI() { + return $this->getURI(''); + } + /** * Return the URI to a path within the application. Used to construct default @@ -1115,7 +1119,9 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { continue; } - $value = $field->readValueFromConduitRequest($constraints); + $value = $field->readValueFromConduitRequest( + $constraints, + $request->getIsStrictlyTyped()); $saved_query->setParameter($field->getKey(), $value); } diff --git a/src/applications/search/field/PhabricatorSearchField.php b/src/applications/search/field/PhabricatorSearchField.php index d31aeb1950..f45befead6 100644 --- a/src/applications/search/field/PhabricatorSearchField.php +++ b/src/applications/search/field/PhabricatorSearchField.php @@ -323,10 +323,14 @@ abstract class PhabricatorSearchField extends Phobject { $this->getConduitKey()); } - public function readValueFromConduitRequest(array $constraints) { + public function readValueFromConduitRequest( + array $constraints, + $strict = true) { + return $this->getConduitParameterType()->getValue( $constraints, - $this->getConduitKey()); + $this->getConduitKey(), + $strict); } public function getValidConstraintKeys() { diff --git a/src/applications/search/view/PhabricatorApplicationSearchResultView.php b/src/applications/search/view/PhabricatorApplicationSearchResultView.php index 1c3f4aad65..3576d5238e 100644 --- a/src/applications/search/view/PhabricatorApplicationSearchResultView.php +++ b/src/applications/search/view/PhabricatorApplicationSearchResultView.php @@ -94,6 +94,4 @@ final class PhabricatorApplicationSearchResultView extends Phobject { return $this->header; } - - } diff --git a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php index 6896f7ba2f..78c21f5d65 100644 --- a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php +++ b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php @@ -187,7 +187,7 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO return array($this->getAuthorPHID()); } -/* -( PhabricatorDestructableInterface )----------------------------------- */ +/* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { diff --git a/src/applications/system/application/PhabricatorSystemApplication.php b/src/applications/system/application/PhabricatorSystemApplication.php index 0ec8f6a7a4..7ce2b4dbe0 100644 --- a/src/applications/system/application/PhabricatorSystemApplication.php +++ b/src/applications/system/application/PhabricatorSystemApplication.php @@ -26,6 +26,7 @@ final class PhabricatorSystemApplication extends PhabricatorApplication { '/readonly/' => array( '(?P[^/]+)/' => 'PhabricatorSystemReadOnlyController', ), + '/favicon.ico' => 'PhabricatorSystemFaviconController', ); } diff --git a/src/applications/system/controller/PhabricatorSystemFaviconController.php b/src/applications/system/controller/PhabricatorSystemFaviconController.php new file mode 100644 index 0000000000..d9a31cbcb6 --- /dev/null +++ b/src/applications/system/controller/PhabricatorSystemFaviconController.php @@ -0,0 +1,19 @@ +setContent($content) + ->setMimeType('image/x-icon') + ->setCacheDurationInSeconds(phutil_units('24 hours in seconds')) + ->setCanCDN(true); + } +} diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 2805162336..8a71509c99 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -1903,7 +1903,10 @@ abstract class PhabricatorEditEngine $parameter_type->setViewer($viewer); try { - $xaction['value'] = $parameter_type->getValue($xaction, 'value'); + $xaction['value'] = $parameter_type->getValue( + $xaction, + 'value', + $request->getIsStrictlyTyped()); } catch (Exception $ex) { throw new PhutilProxyException( pht( diff --git a/src/applications/transactions/editfield/PhabricatorFileEditField.php b/src/applications/transactions/editfield/PhabricatorFileEditField.php new file mode 100644 index 0000000000..48f5e6b215 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorFileEditField.php @@ -0,0 +1,23 @@ +setEncType('multipart/form-data'); + return parent::appendToForm($form); + } + +} diff --git a/src/docs/user/configuration/configuration_guide.diviner b/src/docs/user/configuration/configuration_guide.diviner index b420996848..b4c494449b 100644 --- a/src/docs/user/configuration/configuration_guide.diviner +++ b/src/docs/user/configuration/configuration_guide.diviner @@ -48,8 +48,6 @@ this: DocumentRoot /path/to/phabricator/webroot RewriteEngine on - RewriteRule ^/rsrc/(.*) - [L,QSA] - RewriteRule ^/favicon.ico - [L,QSA] RewriteRule ^(.*)$ /index.php?__path__=$1 [B,L,QSA] @@ -91,10 +89,6 @@ For nginx, use a configuration like this: rewrite ^/(.*)$ /index.php?__path__=/$1 last; } - location = /favicon.ico { - try_files $uri =204; - } - location /index.php { fastcgi_pass localhost:9000; fastcgi_index index.php; @@ -130,8 +124,6 @@ For lighttpd, add a section like this to your lighttpd.conf: $HTTP["host"] =~ "phabricator(\.example\.com)?" { server.document-root = "/path/to/phabricator/webroot" url.rewrite-once = ( - "^(/rsrc/.*)$" => "$1", - "^(/favicon.ico)$" => "$1", # This simulates QSA ("query string append") mode in apache "^(/[^?]*)\?(.*)" => "/index.php?__path__=$1&$2", "^(/.*)$" => "/index.php?__path__=$1", diff --git a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php index 3e5b70cc05..f5960e9504 100644 --- a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php +++ b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php @@ -98,7 +98,27 @@ abstract class PhabricatorGarbageCollector extends Phobject { } } - return $this->collectGarbage(); + // Hold a lock while performing collection to avoid racing other daemons + // running the same collectors. + $lock_name = 'gc:'.$this->getCollectorConstant(); + $lock = PhabricatorGlobalLock::newLock($lock_name); + + try { + $lock->lock(5); + } catch (PhutilLockException $ex) { + return false; + } + + try { + $result = $this->collectGarbage(); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + + return $result; } diff --git a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php index 42d8001ee2..7aa0f28dfe 100644 --- a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php @@ -44,6 +44,7 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { * and `null` (inherit from parent query, with no exceptions by default). */ private $raisePolicyExceptions; + private $isOverheated; /* -( Query Configuration )------------------------------------------------ */ @@ -215,6 +216,14 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { $this->willExecute(); + // If we examine and filter significantly more objects than the query + // limit, we stop early. This prevents us from looping through a huge + // number of records when the viewer can see few or none of them. See + // T11773 for some discussion. + $this->isOverheated = false; + $overheat_limit = $limit * 10; + $total_seen = 0; + do { if ($need) { $this->rawResultLimit = min($need - $count, 1024); @@ -232,6 +241,8 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { $page = array(); } + $total_seen += count($page); + if ($page) { $maybe_visible = $this->willFilterPage($page); if ($maybe_visible) { @@ -302,6 +313,11 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { } $this->nextPage($page); + + if ($overheat_limit && ($total_seen >= $overheat_limit)) { + $this->isOverheated = true; + break; + } } while (true); $results = $this->didLoadResults($results); @@ -369,6 +385,15 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { return $this; } + + public function getIsOverheated() { + if ($this->isOverheated === null) { + throw new PhutilInvalidStateException('execute'); + } + return $this->isOverheated; + } + + /** * Return a map of all object PHIDs which were loaded in the query but * filtered out by policy constraints. This allows a caller to distinguish diff --git a/src/infrastructure/ssh/PhabricatorSSHWorkflow.php b/src/infrastructure/ssh/PhabricatorSSHWorkflow.php index b5ac17b7bf..beee356951 100644 --- a/src/infrastructure/ssh/PhabricatorSSHWorkflow.php +++ b/src/infrastructure/ssh/PhabricatorSSHWorkflow.php @@ -83,4 +83,19 @@ abstract class PhabricatorSSHWorkflow extends PhabricatorManagementWorkflow { return $this->originalArguments; } + public function getSSHRemoteAddress() { + $ssh_client = getenv('SSH_CLIENT'); + if (!strlen($ssh_client)) { + return null; + } + + // TODO: When commands are proxied, the original remote address should + // also be proxied. + + // This has the format " ". Grab the IP. + $remote_address = head(explode(' ', $ssh_client)); + + return $remote_address; + } + } diff --git a/src/view/page/PhabricatorBarePageView.php b/src/view/page/PhabricatorBarePageView.php index 82b4d43faf..ca9d432286 100644 --- a/src/view/page/PhabricatorBarePageView.php +++ b/src/view/page/PhabricatorBarePageView.php @@ -111,11 +111,13 @@ class PhabricatorBarePageView extends AphrontPageView { '/rsrc/favicons/apple-touch-icon-152x152.png'), )); - $apple_tag = phutil_tag( - 'meta', + $favicon_tag = phutil_tag( + 'link', array( - 'name' => 'apple-mobile-web-app-status-bar-style', - 'content' => 'black-translucent', + 'id' => 'favicon', + 'rel' => 'shortcut icon', + 'href' => celerity_get_resource_uri( + '/rsrc/favicons/favicon.ico'), )); $referrer_tag = phutil_tag( @@ -146,7 +148,7 @@ class PhabricatorBarePageView extends AphrontPageView { $icon_tag_76, $icon_tag_120, $icon_tag_152, - $apple_tag, + $favicon_tag, $referrer_tag, CelerityStaticResourceResponse::renderInlineScript( $framebust.jsprintf('window.__DEV__=%d;', ($developer ? 1 : 0))), diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 7d9fccde5b..4bd09992e4 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -23,6 +23,17 @@ final class PhabricatorMainMenuView extends AphrontView { return $this->controller; } + private function getFaviconURI($type = null) { + switch ($type) { + case 'message': + return celerity_get_resource_uri('/rsrc/favicons/favicon-message.ico'); + case 'mention': + return celerity_get_resource_uri('/rsrc/favicons/favicon-mention.ico'); + default: + return celerity_get_resource_uri('/rsrc/favicons/favicon.ico'); + } + } + public function render() { $viewer = $this->getViewer(); @@ -440,6 +451,9 @@ final class PhabricatorMainMenuView extends AphrontView { 'countType' => $conpherence_data['countType'], 'countNumber' => $message_count_number, 'unreadClass' => 'message-unread', + 'favicon' => $this->getFaviconURI('default'), + 'message_favicon' => $this->getFaviconURI('message'), + 'mention_favicon' => $this->getFaviconURI('mention'), )); $message_notification_dropdown = javelin_tag( @@ -518,6 +532,9 @@ final class PhabricatorMainMenuView extends AphrontView { 'countType' => $notification_data['countType'], 'countNumber' => $count_number, 'unreadClass' => 'alert-unread', + 'favicon' => $this->getFaviconURI('default'), + 'message_favicon' => $this->getFaviconURI('message'), + 'mention_favicon' => $this->getFaviconURI('mention'), )); $notification_dropdown = javelin_tag( @@ -600,6 +617,9 @@ final class PhabricatorMainMenuView extends AphrontView { 'countType' => null, 'countNumber' => null, 'unreadClass' => 'setup-unread', + 'favicon' => $this->getFaviconURI('default'), + 'message_favicon' => $this->getFaviconURI('message'), + 'mention_favicon' => $this->getFaviconURI('mention'), )); $setup_notification_dropdown = javelin_tag( diff --git a/webroot/rsrc/css/application/config/config-page.css b/webroot/rsrc/css/application/config/config-page.css index a46ead7a2c..5fe15aadb8 100644 --- a/webroot/rsrc/css/application/config/config-page.css +++ b/webroot/rsrc/css/application/config/config-page.css @@ -8,19 +8,37 @@ border-bottom: 1px solid {$thinblueborder}; } +.device-phone .config-page-header { + margin: 4px 12px 0; + padding-bottom: 4px; +} + .config-page-header .phui-profile-header { padding: 0; } +.device-phone .config-page-header .phui-profile-header { + padding-left: 4px; + padding-right: 4px; +} + .config-page-header .phui-profile-header.phui-header-shell .phui-header-header { font-size: 20px; } +.device-phone .config-page-header .phui-profile-header.phui-header-shell + .phui-header-header { + font-size: 16px; +} .config-page-content { margin: 0 24px; } +.device-phone .config-page-content { + margin: 0 4px; +} + .device-desktop .config-page-content .phui-object-item-list-view { padding-left: 0; padding-right: 0; diff --git a/webroot/rsrc/css/application/conpherence/durable-column.css b/webroot/rsrc/css/application/conpherence/durable-column.css index f94fa1f6b8..d141115b73 100644 --- a/webroot/rsrc/css/application/conpherence/durable-column.css +++ b/webroot/rsrc/css/application/conpherence/durable-column.css @@ -180,19 +180,6 @@ padding: 8px 12px 0; } -.conpherence-durable-column-transactions - .conpherence-transaction-view.conpherence-edited { - color: {$lightgreytext}; - margin: 0; - padding: 0; - font-style: italic; -} - -.conpherence-durable-column-transactions .conpherence-edited - .conpherence-transaction-header { - display: none; -} - .conpherence-durable-column-transactions .conpherence-transaction-view { background: none; margin: 0; @@ -205,48 +192,29 @@ word-wrap: break-word; } -.conpherence-durable-column-transactions .conpherence-transaction-detail { - border: 0; - margin: 0 0 0 32px; +.conpherence-durable-column-transactions .conpherence-transaction-view + .conpherence-transaction-detail { + border: 0; + margin: 0 0 0 32px; } -.conpherence-durable-column-transactions .conpherence-transaction-detail - .conpherence-transaction-header { +.conpherence-durable-column-transactions .conpherence-transaction-view + .conpherence-transaction-detail .conpherence-transaction-header { background: none; padding: 0 0 2px 0; } .conpherence-durable-column-transactions -.conpherence-transaction-view.date-marker { - margin: 12px 0 0; + .conpherence-transaction-view.date-marker { + margin: 12px 0 0; } .conpherence-durable-column-transactions -.conpherence-transaction-view.date-marker .date { - left: 0; - font-size: {$normalfontsize}; - top: -14px; - padding: 0 6px 0 0; -} - -.conpherence-durable-column-transactions .conpherence-transaction-detail -.conpherence-transaction-header .conpherence-transaction-info { - color: {$lightbluetext}; - font-size: {$smallerfontsize}; -} - -.conpherence-transaction-header .epoch-link { - color: {$lightgreytext}; -} - -.conpherence-durable-column-transactions .conpherence-transaction-detail -.conpherence-transaction-header .phui-link-person { - margin: 0 8px 0 0; -} - -.conpherence-durable-column-transactions .conpherence-transaction-detail -.conpherence-transaction-content .phui-link-person { - color: #000; + .conpherence-transaction-view.date-marker .date { + left: 0; + font-size: {$normalfontsize}; + top: -10px; + padding: 0 6px 0 0; } .conpherence-durable-column-transactions @@ -267,9 +235,8 @@ img { } .conpherence-durable-column-transactions .conpherence-transaction-detail -.conpherence-transaction-content { - background: #fff; - padding: 0 0 8px 0; + .conpherence-transaction-content { + padding: 0 0 8px 0; } .conpherence-durable-column-textarea { diff --git a/webroot/rsrc/css/application/conpherence/header-pane.css b/webroot/rsrc/css/application/conpherence/header-pane.css index 522771cc15..f181a1adb1 100644 --- a/webroot/rsrc/css/application/conpherence/header-pane.css +++ b/webroot/rsrc/css/application/conpherence/header-pane.css @@ -18,6 +18,9 @@ padding: 0; font-size: 12px; margin: 0; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } .conpherence-header-pane .phui-header-col1 { @@ -40,6 +43,11 @@ .conpherence-header-pane .phui-header-col2 { height: 40px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 60%; + max-width: 0; } .conpherence-header-pane .phui-header-action-list .phui-header-action-item @@ -66,7 +74,17 @@ } .conpherence-participant-toggle.phui-icon-circle .phui-icon-view { - color: {$sky}; + color: {$sky}; +} + +.show-searchbar .conpherence-search-toggle.phui-icon-circle { + text-decoration: none; + border-color: {$green}; + cursor: pointer; +} + +.show-searchbar .conpherence-search-toggle.phui-icon-circle .phui-icon-view { + color: {$green}; } .hide-widgets .conpherence-participant-toggle.phui-icon-circle { @@ -76,5 +94,5 @@ } .hide-widgets .conpherence-participant-toggle.phui-icon-circle .phui-icon-view { - color: {$lightblueborder}; + color: {$lightblueborder}; } diff --git a/webroot/rsrc/css/application/conpherence/message-pane.css b/webroot/rsrc/css/application/conpherence/message-pane.css index 6df4cb8828..6336ac0cc5 100644 --- a/webroot/rsrc/css/application/conpherence/message-pane.css +++ b/webroot/rsrc/css/application/conpherence/message-pane.css @@ -82,6 +82,12 @@ } .device .conpherence-message-pane .conpherence-messages { + left: 0; + bottom: 44px; + box-shadow: none; +} + +.device-phone .conpherence-message-pane .conpherence-messages { left: 0; right: 0; bottom: 44px; @@ -186,6 +192,12 @@ } .device .conpherence-message-pane .phui-form-view { + left: 0; + height: 34px; + width: auto; +} + +.device-phone .conpherence-message-pane .phui-form-view { left: 0; right: 0; height: 34px; @@ -198,18 +210,18 @@ width: 100%; } -.conpherence-message-pane .conpherence-transaction-view { +.conpherence-transaction-view { padding: 2px 0px; margin: 4px 20px; background-size: 100%; min-height: auto; } -.device-phone .conpherence-message-pane .conpherence-transaction-view { +.device-phone .conpherence-transaction-view { margin: 0 8px; } -.conpherence-message-pane .conpherence-transaction-image { +.conpherence-transaction-image { float: left; border-radius: 3px; height: 35px; @@ -219,88 +231,85 @@ top: 5px; } -.device-phone .conpherence-message-pane .conpherence-transaction-image { +.device-phone .conpherence-transaction-image { height: 25px; width: 25px; background-size: 25px; } -.conpherence-message-pane .conpherence-comment.anchor-target, -.conpherence-message-pane .conpherence-edited.anchor-target { +.conpherence-transaction-view.conpherence-comment.anchor-target, +.conpherence-transaction-view.conpherence-edited.anchor-target { background: {$lightyellow}; } -.conpherence-message-pane .conpherence-comment.anchor-target { +.cconpherence-transaction-view.conpherence-comment.anchor-target { margin: 4px 8px 4px 8px; padding: 2px 4px 2px 4px; } -.conpherence-message-pane .conpherence-edited.anchor-target { +.conpherence-transaction-view.conpherence-edited.anchor-target { margin: 0px 8px 0px 8px; padding: 0px 4px 0px 4px; } -.conpherence-message-pane .conpherence-transaction-detail { +.conpherence-transaction-view .conpherence-transaction-detail { border-width: 0; margin-left: 45px; } -.device-phone .conpherence-message-pane .conpherence-transaction-detail { +.device-phone .conpherence-transaction-view .conpherence-transaction-detail { margin-left: 32px; } -.conpherence-message-pane .conpherence-transaction-view.date-marker { +.conpherence-transaction-view.date-marker { padding: 0; margin: 20px 20px 4px; min-height: auto; } -.device-phone .conpherence-message-pane -.conpherence-transaction-view.date-marker { +.device-phone .conpherence-transaction-view.date-marker { margin: 12px 0 4px; } -.device-tablet .conpherence-message-pane - .conpherence-transaction-view.date-marker { - padding-left: 37px; +.device-tablet .conpherence-transaction-view.date-marker { + padding-left: 37px; } -.conpherence-message-pane .conpherence-transaction-view.date-marker - .date { - left: 40px; - font-size: {$normalfontsize}; - padding: 0px 4px; +.conpherence-transaction-view.date-marker .date { + left: 40px; + font-size: {$normalfontsize}; + padding: 0px 4px; } -.device .conpherence-message-pane .conpherence-transaction-view.date-marker - .date { - left: 4px; +.device .conpherence-transaction-view.date-marker .date { + left: 4px; } -.device-phone .conpherence-message-pane .conpherence-edited { +.device-phone .conpherence-transaction-view.conpherence-edited { min-height: none; color: {$lightgreytext}; margin: 0 8px; } -.conpherence-message-pane .conpherence-edited .conpherence-transaction-content { - color: {$lightgreytext}; - font-size: {$biggerfontsize}; - font-style: italic; - margin: 0; - padding: 0; - float: left; - line-height: 20px; +.conpherence-transaction-view.conpherence-edited + .conpherence-transaction-content { + color: {$lightgreytext}; + font-size: {$biggerfontsize}; + font-style: italic; + margin: 0; + padding: 0; + float: left; + line-height: 20px; } -.conpherence-message-pane .conpherence-edited { +.conpherence-transaction-view.conpherence-edited { padding: 0; margin-top: 0; margin-bottom: 0; min-height: inherit; } -.conpherence-message-pane .conpherence-edited + .conpherence-comment { +.conpherence-transaction-view.conpherence-edited + .conpherence-comment { margin-top: 16px; } @@ -309,28 +318,29 @@ margin-top: 24px; } -.conpherence-message-pane .conpherence-edited .conpherence-transaction-header { - float: right; +.conpherence-transaction-view.conpherence-edited + .conpherence-transaction-header { + float: right; } -.conpherence-message-pane .conpherence-edited +.conpherence-transaction-view.conpherence-edited .conpherence-transaction-content a { color: {$darkbluetext}; } -.conpherence-message-pane .conpherence-transaction-info { +.conpherence-transaction-view .conpherence-transaction-info { margin: 0 8px; } -.conpherence-message-pane .conpherence-transaction-info, -.conpherence-message-pane .anchor-link, -.conpherence-message-pane .phabricator-content-source-view { +.conpherence-transaction-view .conpherence-transaction-info, +.conpherence-transaction-view .transaction-date, +.conpherence-transaction-view .phabricator-content-source-view { color: {$lightgreytext}; line-height: 16px; font-size: {$smallerfontsize}; } -.conpherence-message-pane .conpherence-transaction-content { +.conpherence-transaction-view .conpherence-transaction-content { padding: 2px 0 8px 0; } @@ -400,3 +410,77 @@ margin-top: 0; margin-bottom: 0; } + +/* this causes scrolling issues on iDevices */ +.device .conpherence-layout + .phabricator-standard-page-footer { + display: none; +} + +/***** Thread Loading *********************************************************/ + +.conpherence-layout .conpherence-loading-mask { + height: 0; + opacity: 0; + top: 0; + right: 0; + left: 240px; + bottom: 0; + transition: all 0.3s; + position: fixed; + background-color: #fff; +} + +.conpherence-layout.loading .conpherence-loading-mask { + opacity: 1; + height: auto; +} + + +/***** Thread Search **********************************************************/ + +.conpherence-search-main { + opacity: 0; + transition: all 0.2s; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + height: 0; +} + +.show-searchbar .conpherence-search-main { + opacity: 1; + height: auto; +} + +.conpherence-search-form-view { + display: none; +} + +.show-searchbar .conpherence-search-form-view { + display: block; + height: 54px; + background: {$lightbluebackground}; + position: absolute; + top: 1px; + left: 0; + right: 0; +} + +.conpherence-search-form-view input.conpherence-search-input { + padding-left: 8px; + width: calc(100% - 24px); + border-radius: 20px; + margin: 12px; +} + +.conpherence-search-results { + position: absolute; + background: #fff; + top: 54px; + left: 0; + right: 0; + bottom: 0; + overflow-y: auto; +} diff --git a/webroot/rsrc/css/application/conpherence/participant-pane.css b/webroot/rsrc/css/application/conpherence/participant-pane.css index 94bedce84d..c72d098226 100644 --- a/webroot/rsrc/css/application/conpherence/participant-pane.css +++ b/webroot/rsrc/css/application/conpherence/participant-pane.css @@ -15,7 +15,7 @@ -webkit-overflow-scrolling: touch; } -.device .conpherence-participant-pane { +.device-phone .conpherence-participant-pane { background-color: {$page.background}; } diff --git a/webroot/rsrc/css/application/conpherence/transaction.css b/webroot/rsrc/css/application/conpherence/transaction.css index 62454c448e..6e52a8c30c 100644 --- a/webroot/rsrc/css/application/conpherence/transaction.css +++ b/webroot/rsrc/css/application/conpherence/transaction.css @@ -28,10 +28,15 @@ font-weight: bold; } +/***** Thread Search **********************************************************/ + .conpherence-fulltext-results { - margin: 0 8px 8px; - background: {$lightgreybackground}; - border: 1px solid {$lightgreyborder}; + padding: 8px 0; +} + +.conpherence-fulltext-results + .conpherence-fulltext-results { + border-top: 2px solid {$thinblueborder}; + margin-top: -8px; } .conpherence-fulltext-result { @@ -46,3 +51,19 @@ .conpherence-fulltext-results .epoch-link { float: right; } + +.conpherence-message-pane .conpherence-fulltext-results + .conpherence-transaction-view.conpherence-fulltext-result { + margin-left: 0; + margin-right: 0; +} + +.conpherence-message-pane .conpherence-search-room-results .phui-object-box { + border: none; + margin: 0; +} + +.conpherence-message-pane .conpherence-search-room-results + .phui-object-box .phui-header-shell { + display: none; +} diff --git a/webroot/rsrc/css/application/conpherence/update.css b/webroot/rsrc/css/application/conpherence/update.css deleted file mode 100644 index 8b29c085ed..0000000000 --- a/webroot/rsrc/css/application/conpherence/update.css +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @provides conpherence-update-css - */ - -.aphront-dialog-view .conpherence-dialogue-drag-photo { - border: 1px dashed #bfbfbf; - padding: 10px 0px 10px 10px; -} - -.aphront-dialog-view .conpherence-dialogue-upload-photo { - background: {$lightgreen}; - border-color: {$green}; -} diff --git a/webroot/rsrc/css/application/search/application-search-view.css b/webroot/rsrc/css/application/search/application-search-view.css index 4a573f5111..1320df9c0f 100644 --- a/webroot/rsrc/css/application/search/application-search-view.css +++ b/webroot/rsrc/css/application/search/application-search-view.css @@ -11,6 +11,11 @@ padding: 0 16px 24px; } +.device-phone .application-search-view + .application-search-results.phui-object-box { + padding: 0 12px 24px; +} + .application-search-view .application-search-results .phui-profile-header { padding: 16px 8px; border-bottom: 1px solid {$thinblueborder}; @@ -31,6 +36,11 @@ padding: 12px 0; } +.device-phone .application-search-results + .phui-profile-header.phui-header-shell { + padding: 12px 0 12px 4px; +} + .device-phone .application-search-results .phui-profile-header.phui-header-shell .phui-header-header { font-size: 16px; diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css index 3447a18de4..3bfff11070 100644 --- a/webroot/rsrc/css/core/z-index.css +++ b/webroot/rsrc/css/core/z-index.css @@ -68,9 +68,8 @@ div.phui-calendar-day-event { z-index: 4; } -.loading .messages-loading-mask, -.loading .widgets-loading-mask { - z-index: 5; +.conpherence-message-pane .conpherence-search-main { + z-index: 4; } .dark-console { @@ -89,6 +88,11 @@ div.phui-calendar-day-event { z-index: 6; } +.loading .messages-loading-mask, +.loading .widgets-loading-mask { + z-index: 6; +} + .conpherence-durable-column { z-index: 7; } @@ -154,6 +158,10 @@ div.jx-typeahead-results { z-index: 25; } +.conpherence-layout .conpherence-loading-mask { + z-index: 30; +} + .phuix-dropdown-menu { z-index: 32; } diff --git a/webroot/rsrc/css/phui/phui-feed-story.css b/webroot/rsrc/css/phui/phui-feed-story.css index d971202ebe..3fdfeb5a57 100644 --- a/webroot/rsrc/css/phui/phui-feed-story.css +++ b/webroot/rsrc/css/phui/phui-feed-story.css @@ -16,7 +16,6 @@ float: left; margin-right: 8px; border-radius: 3px; - box-shadow: {$borderinset}; } .phui-feed-story-head .phui-feed-story-actor-image { diff --git a/webroot/rsrc/css/phui/phui-icon.css b/webroot/rsrc/css/phui/phui-icon.css index 338ff4e4c3..1ceb4c3855 100644 --- a/webroot/rsrc/css/phui/phui-icon.css +++ b/webroot/rsrc/css/phui/phui-icon.css @@ -101,6 +101,16 @@ a.phui-icon-circle.hover-pink:hover .phui-icon-view { color: {$pink}; } +a.phui-icon-circle.hover-green:hover { + text-decoration: none; + border-color: {$green}; + cursor: pointer; +} + +a.phui-icon-circle.hover-green:hover .phui-icon-view { + color: {$green}; +} + /* - Icon in a Square ------------------------------------------------------- */ .phui-icon-view.phui-icon-square { diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 57f8d81934..e8f1ce0de5 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -23,7 +23,7 @@ } .device-phone .phui-two-column-header .phui-header-header { - font-size: 18px; + font-size: 16px; } .phui-two-column-view .phui-two-column-header .phui-header-shell { diff --git a/webroot/rsrc/favicons/dark/apple-touch-icon-114x114.png b/webroot/rsrc/favicons/dark/apple-touch-icon-114x114.png deleted file mode 100644 index ddb29bcde2..0000000000 Binary files a/webroot/rsrc/favicons/dark/apple-touch-icon-114x114.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/apple-touch-icon-120x120.png b/webroot/rsrc/favicons/dark/apple-touch-icon-120x120.png deleted file mode 100644 index b641cd6558..0000000000 Binary files a/webroot/rsrc/favicons/dark/apple-touch-icon-120x120.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/apple-touch-icon-144x144.png b/webroot/rsrc/favicons/dark/apple-touch-icon-144x144.png deleted file mode 100644 index d2cf49d293..0000000000 Binary files a/webroot/rsrc/favicons/dark/apple-touch-icon-144x144.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/apple-touch-icon-152x152.png b/webroot/rsrc/favicons/dark/apple-touch-icon-152x152.png deleted file mode 100644 index 41a146e9f3..0000000000 Binary files a/webroot/rsrc/favicons/dark/apple-touch-icon-152x152.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/apple-touch-icon-57x57.png b/webroot/rsrc/favicons/dark/apple-touch-icon-57x57.png deleted file mode 100644 index a5141404ce..0000000000 Binary files a/webroot/rsrc/favicons/dark/apple-touch-icon-57x57.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/apple-touch-icon-60x60.png b/webroot/rsrc/favicons/dark/apple-touch-icon-60x60.png deleted file mode 100644 index c8dc8def04..0000000000 Binary files a/webroot/rsrc/favicons/dark/apple-touch-icon-60x60.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/apple-touch-icon-72x72.png b/webroot/rsrc/favicons/dark/apple-touch-icon-72x72.png deleted file mode 100644 index 78ae78918b..0000000000 Binary files a/webroot/rsrc/favicons/dark/apple-touch-icon-72x72.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/apple-touch-icon-76x76.png b/webroot/rsrc/favicons/dark/apple-touch-icon-76x76.png deleted file mode 100644 index 060cfb4838..0000000000 Binary files a/webroot/rsrc/favicons/dark/apple-touch-icon-76x76.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/favicon-128.png b/webroot/rsrc/favicons/dark/favicon-128.png deleted file mode 100644 index 01165d4f83..0000000000 Binary files a/webroot/rsrc/favicons/dark/favicon-128.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/favicon-16x16.png b/webroot/rsrc/favicons/dark/favicon-16x16.png deleted file mode 100644 index e98657cc57..0000000000 Binary files a/webroot/rsrc/favicons/dark/favicon-16x16.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/favicon-196x196.png b/webroot/rsrc/favicons/dark/favicon-196x196.png deleted file mode 100644 index 78cbe52e78..0000000000 Binary files a/webroot/rsrc/favicons/dark/favicon-196x196.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/favicon-32x32.png b/webroot/rsrc/favicons/dark/favicon-32x32.png deleted file mode 100644 index c2955ad83a..0000000000 Binary files a/webroot/rsrc/favicons/dark/favicon-32x32.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/favicon-96x96.png b/webroot/rsrc/favicons/dark/favicon-96x96.png deleted file mode 100644 index 9cc09dd833..0000000000 Binary files a/webroot/rsrc/favicons/dark/favicon-96x96.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/favicon.ico b/webroot/rsrc/favicons/dark/favicon.ico deleted file mode 100644 index ce3d048bd4..0000000000 Binary files a/webroot/rsrc/favicons/dark/favicon.ico and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/mstile-144x144.png b/webroot/rsrc/favicons/dark/mstile-144x144.png deleted file mode 100644 index d2cf49d293..0000000000 Binary files a/webroot/rsrc/favicons/dark/mstile-144x144.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/mstile-150x150.png b/webroot/rsrc/favicons/dark/mstile-150x150.png deleted file mode 100644 index b56a239e02..0000000000 Binary files a/webroot/rsrc/favicons/dark/mstile-150x150.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/mstile-310x150.png b/webroot/rsrc/favicons/dark/mstile-310x150.png deleted file mode 100644 index 99163350dd..0000000000 Binary files a/webroot/rsrc/favicons/dark/mstile-310x150.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/mstile-310x310.png b/webroot/rsrc/favicons/dark/mstile-310x310.png deleted file mode 100644 index 136a8e4034..0000000000 Binary files a/webroot/rsrc/favicons/dark/mstile-310x310.png and /dev/null differ diff --git a/webroot/rsrc/favicons/dark/mstile-70x70.png b/webroot/rsrc/favicons/dark/mstile-70x70.png deleted file mode 100644 index 01165d4f83..0000000000 Binary files a/webroot/rsrc/favicons/dark/mstile-70x70.png and /dev/null differ diff --git a/webroot/rsrc/favicons/favicon-mention.ico b/webroot/rsrc/favicons/favicon-mention.ico new file mode 100644 index 0000000000..6e27ce8339 Binary files /dev/null and b/webroot/rsrc/favicons/favicon-mention.ico differ diff --git a/webroot/rsrc/favicons/favicon-message.ico b/webroot/rsrc/favicons/favicon-message.ico new file mode 100644 index 0000000000..07408f3182 Binary files /dev/null and b/webroot/rsrc/favicons/favicon-message.ico differ diff --git a/webroot/rsrc/favicons/red/apple-touch-icon-114x114.png b/webroot/rsrc/favicons/red/apple-touch-icon-114x114.png deleted file mode 100644 index 9d8293ec39..0000000000 Binary files a/webroot/rsrc/favicons/red/apple-touch-icon-114x114.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/apple-touch-icon-120x120.png b/webroot/rsrc/favicons/red/apple-touch-icon-120x120.png deleted file mode 100644 index 89f65a0e2e..0000000000 Binary files a/webroot/rsrc/favicons/red/apple-touch-icon-120x120.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/apple-touch-icon-144x144.png b/webroot/rsrc/favicons/red/apple-touch-icon-144x144.png deleted file mode 100644 index b514bdc033..0000000000 Binary files a/webroot/rsrc/favicons/red/apple-touch-icon-144x144.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/apple-touch-icon-152x152.png b/webroot/rsrc/favicons/red/apple-touch-icon-152x152.png deleted file mode 100644 index df893974a5..0000000000 Binary files a/webroot/rsrc/favicons/red/apple-touch-icon-152x152.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/apple-touch-icon-57x57.png b/webroot/rsrc/favicons/red/apple-touch-icon-57x57.png deleted file mode 100644 index c8b423e6c0..0000000000 Binary files a/webroot/rsrc/favicons/red/apple-touch-icon-57x57.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/apple-touch-icon-60x60.png b/webroot/rsrc/favicons/red/apple-touch-icon-60x60.png deleted file mode 100644 index 4ef5b42ccf..0000000000 Binary files a/webroot/rsrc/favicons/red/apple-touch-icon-60x60.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/apple-touch-icon-72x72.png b/webroot/rsrc/favicons/red/apple-touch-icon-72x72.png deleted file mode 100644 index 75d9ba8407..0000000000 Binary files a/webroot/rsrc/favicons/red/apple-touch-icon-72x72.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/apple-touch-icon-76x76.png b/webroot/rsrc/favicons/red/apple-touch-icon-76x76.png deleted file mode 100644 index bd7593a772..0000000000 Binary files a/webroot/rsrc/favicons/red/apple-touch-icon-76x76.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/favicon-128.png b/webroot/rsrc/favicons/red/favicon-128.png deleted file mode 100644 index c9a9df6314..0000000000 Binary files a/webroot/rsrc/favicons/red/favicon-128.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/favicon-16x16.png b/webroot/rsrc/favicons/red/favicon-16x16.png deleted file mode 100644 index a98536fb93..0000000000 Binary files a/webroot/rsrc/favicons/red/favicon-16x16.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/favicon-196x196.png b/webroot/rsrc/favicons/red/favicon-196x196.png deleted file mode 100644 index f4a9c2a69f..0000000000 Binary files a/webroot/rsrc/favicons/red/favicon-196x196.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/favicon-32x32.png b/webroot/rsrc/favicons/red/favicon-32x32.png deleted file mode 100644 index 7bc7c159db..0000000000 Binary files a/webroot/rsrc/favicons/red/favicon-32x32.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/favicon-96x96.png b/webroot/rsrc/favicons/red/favicon-96x96.png deleted file mode 100644 index 4a874510f6..0000000000 Binary files a/webroot/rsrc/favicons/red/favicon-96x96.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/favicon.ico b/webroot/rsrc/favicons/red/favicon.ico deleted file mode 100644 index 1d9437706e..0000000000 Binary files a/webroot/rsrc/favicons/red/favicon.ico and /dev/null differ diff --git a/webroot/rsrc/favicons/red/mstile-144x144.png b/webroot/rsrc/favicons/red/mstile-144x144.png deleted file mode 100644 index b514bdc033..0000000000 Binary files a/webroot/rsrc/favicons/red/mstile-144x144.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/mstile-150x150.png b/webroot/rsrc/favicons/red/mstile-150x150.png deleted file mode 100644 index 63cba86e5a..0000000000 Binary files a/webroot/rsrc/favicons/red/mstile-150x150.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/mstile-310x150.png b/webroot/rsrc/favicons/red/mstile-310x150.png deleted file mode 100644 index ca5a1cb9a3..0000000000 Binary files a/webroot/rsrc/favicons/red/mstile-310x150.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/mstile-310x310.png b/webroot/rsrc/favicons/red/mstile-310x310.png deleted file mode 100644 index abb890254f..0000000000 Binary files a/webroot/rsrc/favicons/red/mstile-310x310.png and /dev/null differ diff --git a/webroot/rsrc/favicons/red/mstile-70x70.png b/webroot/rsrc/favicons/red/mstile-70x70.png deleted file mode 100644 index c9a9df6314..0000000000 Binary files a/webroot/rsrc/favicons/red/mstile-70x70.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/apple-touch-icon-114x114.png b/webroot/rsrc/favicons/yellow/apple-touch-icon-114x114.png deleted file mode 100644 index 81ca69890a..0000000000 Binary files a/webroot/rsrc/favicons/yellow/apple-touch-icon-114x114.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/apple-touch-icon-120x120.png b/webroot/rsrc/favicons/yellow/apple-touch-icon-120x120.png deleted file mode 100644 index 8ca5ddb2d2..0000000000 Binary files a/webroot/rsrc/favicons/yellow/apple-touch-icon-120x120.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/apple-touch-icon-144x144.png b/webroot/rsrc/favicons/yellow/apple-touch-icon-144x144.png deleted file mode 100644 index 73fcc43063..0000000000 Binary files a/webroot/rsrc/favicons/yellow/apple-touch-icon-144x144.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/apple-touch-icon-152x152.png b/webroot/rsrc/favicons/yellow/apple-touch-icon-152x152.png deleted file mode 100644 index 3de0817a02..0000000000 Binary files a/webroot/rsrc/favicons/yellow/apple-touch-icon-152x152.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/apple-touch-icon-57x57.png b/webroot/rsrc/favicons/yellow/apple-touch-icon-57x57.png deleted file mode 100644 index a41af6d514..0000000000 Binary files a/webroot/rsrc/favicons/yellow/apple-touch-icon-57x57.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/apple-touch-icon-60x60.png b/webroot/rsrc/favicons/yellow/apple-touch-icon-60x60.png deleted file mode 100644 index 45a8e345f6..0000000000 Binary files a/webroot/rsrc/favicons/yellow/apple-touch-icon-60x60.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/apple-touch-icon-72x72.png b/webroot/rsrc/favicons/yellow/apple-touch-icon-72x72.png deleted file mode 100644 index 8f4f1b35c3..0000000000 Binary files a/webroot/rsrc/favicons/yellow/apple-touch-icon-72x72.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/apple-touch-icon-76x76.png b/webroot/rsrc/favicons/yellow/apple-touch-icon-76x76.png deleted file mode 100644 index 1f386e2377..0000000000 Binary files a/webroot/rsrc/favicons/yellow/apple-touch-icon-76x76.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/favicon-128.png b/webroot/rsrc/favicons/yellow/favicon-128.png deleted file mode 100644 index a7411cc7db..0000000000 Binary files a/webroot/rsrc/favicons/yellow/favicon-128.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/favicon-16x16.png b/webroot/rsrc/favicons/yellow/favicon-16x16.png deleted file mode 100644 index 50847ee4c5..0000000000 Binary files a/webroot/rsrc/favicons/yellow/favicon-16x16.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/favicon-196x196.png b/webroot/rsrc/favicons/yellow/favicon-196x196.png deleted file mode 100644 index 577a45d64a..0000000000 Binary files a/webroot/rsrc/favicons/yellow/favicon-196x196.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/favicon-32x32.png b/webroot/rsrc/favicons/yellow/favicon-32x32.png deleted file mode 100644 index 9b92b3ff80..0000000000 Binary files a/webroot/rsrc/favicons/yellow/favicon-32x32.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/favicon-96x96.png b/webroot/rsrc/favicons/yellow/favicon-96x96.png deleted file mode 100644 index 4070e92109..0000000000 Binary files a/webroot/rsrc/favicons/yellow/favicon-96x96.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/favicon.ico b/webroot/rsrc/favicons/yellow/favicon.ico deleted file mode 100644 index 7728c042e3..0000000000 Binary files a/webroot/rsrc/favicons/yellow/favicon.ico and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/mstile-144x144.png b/webroot/rsrc/favicons/yellow/mstile-144x144.png deleted file mode 100644 index 73fcc43063..0000000000 Binary files a/webroot/rsrc/favicons/yellow/mstile-144x144.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/mstile-150x150.png b/webroot/rsrc/favicons/yellow/mstile-150x150.png deleted file mode 100644 index 3d090a694a..0000000000 Binary files a/webroot/rsrc/favicons/yellow/mstile-150x150.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/mstile-310x150.png b/webroot/rsrc/favicons/yellow/mstile-310x150.png deleted file mode 100644 index 08d4855c67..0000000000 Binary files a/webroot/rsrc/favicons/yellow/mstile-310x150.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/mstile-310x310.png b/webroot/rsrc/favicons/yellow/mstile-310x310.png deleted file mode 100644 index 9b95754168..0000000000 Binary files a/webroot/rsrc/favicons/yellow/mstile-310x310.png and /dev/null differ diff --git a/webroot/rsrc/favicons/yellow/mstile-70x70.png b/webroot/rsrc/favicons/yellow/mstile-70x70.png deleted file mode 100644 index a7411cc7db..0000000000 Binary files a/webroot/rsrc/favicons/yellow/mstile-70x70.png and /dev/null differ diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js index b564350ff2..f12be7a22b 100644 --- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js +++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js @@ -8,6 +8,7 @@ * javelin-uri * javelin-behavior-device * phabricator-title + * phabricator-favicon */ JX.behavior('aphlict-dropdown', function(config, statics) { @@ -17,6 +18,8 @@ JX.behavior('aphlict-dropdown', function(config, statics) { var dropdown = JX.$(config.dropdownID); var bubble = JX.$(config.bubbleID); var icon = JX.DOM.scry(bubble, 'span', 'menu-icon')[0]; + var favicon = config.favicon; + var message_favicon = config.message_favicon; var count; if (config.countID) { @@ -26,13 +29,23 @@ JX.behavior('aphlict-dropdown', function(config, statics) { var request = null; var dirty = config.local ? false : true; + function _updateFavicon(new_count) { + if ((config.countType == 'messages') && (new_count)) { + JX.Favicon.setFavicon(message_favicon); + } else if (config.countType == 'messages') { + JX.Favicon.setFavicon(favicon); + } + } + if (config.countType) { JX.Title.setCount(config.countType, config.countNumber); + _updateFavicon(config.countNumber); } function _updateCount(number) { if (config.countType) { JX.Title.setCount(config.countType, number); + _updateFavicon(number); } else { return; } diff --git a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js index 0a39863c74..831cceab59 100644 --- a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js +++ b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js @@ -31,7 +31,6 @@ JX.install('ConpherenceThreadManager', { _transactionCache: null, _canEditLoadedThread: null, _updating: null, - _minimalDisplay: false, _messagesRootCallback: JX.bag, _willLoadThreadCallback: JX.bag, _didLoadThreadCallback: JX.bag, @@ -150,11 +149,6 @@ JX.install('ConpherenceThreadManager', { return this._canEditLoadedThread; }, - setMinimalDisplay: function(bool) { - this._minimalDisplay = bool; - return this; - }, - setMessagesRootCallback: function(callback) { this._messagesRootCallback = callback; return this; @@ -196,9 +190,6 @@ JX.install('ConpherenceThreadManager', { }, _getParams: function(base_params) { - if (this._minimalDisplay) { - base_params.minimal_display = true; - } if (this._latestTransactionID) { base_params.latest_transaction_id = this._latestTransactionID; } diff --git a/webroot/rsrc/js/application/conpherence/behavior-conpherence-search.js b/webroot/rsrc/js/application/conpherence/behavior-conpherence-search.js new file mode 100644 index 0000000000..39ddd29f65 --- /dev/null +++ b/webroot/rsrc/js/application/conpherence/behavior-conpherence-search.js @@ -0,0 +1,82 @@ +/** + * @provides javelin-behavior-conpherence-search + * @requires javelin-behavior + * javelin-dom + * javelin-util + * javelin-workflow + * javelin-stratcom + */ + +JX.behavior('conpherence-search', function() { + + var shown = true; + var request = null; + + function _toggleSearch(e) { + e.kill(); + var node = JX.$('conpherence-main-layout'); + + shown = !shown; + JX.DOM.alterClass(node, 'show-searchbar', !shown); + if (!shown) { + JX.$('conpherence-search-input').focus(); + } else { + var form_root = JX.DOM.find(document, 'div', 'conpherence-form'); + var textarea = JX.DOM.find(form_root, 'textarea'); + textarea.focus(); + } + JX.Stratcom.invoke('resize'); + } + + function _doSearch(e) { + e.kill(); + var search_text = JX.$('conpherence-search-input').value; + var search_uri = JX.$('conpherence-search-form').action; + var search_node = JX.$('conpherence-search-results'); + + if (request || !search_text) { + return; + } + + request = new JX.Request(search_uri, function(response) { + JX.DOM.setContent(search_node, JX.$H(response)); + request = null; + }); + request.setData({fulltext: search_text}); + request.send(); + } + + function _viewResult(e) { + e.kill(); + var uri = e.getNode('tag:a'); + _toggleSearch(e); + JX.$U(uri).go(); + } + + JX.Stratcom.listen( + ['submit', 'didSyntheticSubmit'], + 'conpherence-search-input', + _doSearch); + + JX.Stratcom.listen( + 'keydown', + 'conpherence-search-input', + function(e) { + if (e.getSpecialKey() != 'return') { + return; + } + e.kill(); + _doSearch(e); + }); + + JX.Stratcom.listen( + 'click', + 'conpherence-search-result-jump', + _viewResult); + + JX.Stratcom.listen( + 'click', + 'conpherence-search-toggle', + _toggleSearch); + +}); diff --git a/webroot/rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js b/webroot/rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js deleted file mode 100644 index 7980bf3871..0000000000 --- a/webroot/rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @provides javelin-behavior-conpherence-drag-and-drop-photo - * @requires javelin-behavior - * javelin-dom - * javelin-workflow - * phabricator-drag-and-drop-file-upload - */ - -JX.behavior('conpherence-drag-and-drop-photo', function(config) { - - var target = JX.$(config.target); - var form_pane = JX.$(config.form_pane); - - function onupload(f) { - var data = { - 'file_id' : f.getID(), - 'action' : 'metadata' - }; - - var form = JX.DOM.find(form_pane, 'form'); - var workflow = JX.Workflow.newFromForm(form, data); - workflow.start(); - } - - if (JX.PhabricatorDragAndDropFileUpload.isSupported()) { - var drop = new JX.PhabricatorDragAndDropFileUpload(target) - .setURI(config.upload_uri); - drop.listen('didBeginDrag', function() { - JX.DOM.alterClass(target, config.activated_class, true); - }); - drop.listen('didEndDrag', function() { - JX.DOM.alterClass(target, config.activated_class, false); - }); - drop.listen('didUpload', onupload); - drop.start(); - } - -}); diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js index a7fe3fbef7..c6c82bc3be 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js +++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js @@ -114,7 +114,6 @@ JX.behavior('durable-column', function(config, statics) { */ var threadManager = new JX.ConpherenceThreadManager(); - threadManager.setMinimalDisplay(true); threadManager.setMessagesRootCallback(function() { return _getColumnMessagesNode(); }); @@ -282,7 +281,7 @@ JX.behavior('durable-column', function(config, statics) { function _sendMessage(e) { e.kill(); var form = _getColumnFormNode(); - threadManager.sendMessage(form, { minimal_display: true }); + threadManager.sendMessage(form, {}); } JX.Stratcom.listen( diff --git a/webroot/rsrc/js/application/conpherence/behavior-menu.js b/webroot/rsrc/js/application/conpherence/behavior-menu.js index 50add23158..1621126a5e 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-menu.js +++ b/webroot/rsrc/js/application/conpherence/behavior-menu.js @@ -32,20 +32,23 @@ JX.behavior('conpherence-menu', function(config) { return scrollbar.getContentNode(); }); threadManager.setWillLoadThreadCallback(function() { - markThreadLoading(true); + markThreadsLoading(true); }); threadManager.setDidLoadThreadCallback(function(r) { var header = JX.$H(r.header); + var search = JX.$H(r.search); var messages = JX.$H(r.transactions); var form = JX.$H(r.form); var root = JX.DOM.find(document, 'div', 'conpherence-layout'); var header_root = JX.DOM.find(root, 'div', 'conpherence-header-pane'); + var search_root = JX.DOM.find(root, 'div', 'conpherence-search-main'); var form_root = JX.DOM.find(root, 'div', 'conpherence-form'); JX.DOM.setContent(header_root, header); + JX.DOM.setContent(search_root, search); JX.DOM.setContent(scrollbar.getContentNode(), messages); JX.DOM.setContent(form_root, form); - markThreadLoading(false); + markThreadsLoading(false); didRedrawThread(true); }); @@ -188,37 +191,17 @@ JX.behavior('conpherence-menu', function(config) { if (_thread.visible !== null || !config.hasWidgets) { reloadWidget(data); - } else { - JX.Stratcom.invoke( - 'conpherence-update-widgets', - null, - { - widget : getDefaultWidget(), - buildSelectors : false, - toggleWidget : true, - threadID : _thread.selected - }); } _thread.visible = _thread.selected; } function markThreadsLoading(loading) { - var root = JX.DOM.find(document, 'div', 'conpherence-layout'); - var menu = JX.DOM.find(root, 'div', 'conpherence-menu-pane'); - JX.DOM.alterClass(menu, 'loading', loading); + var root = JX.$('conpherence-main-layout'); + JX.DOM.alterClass(root, 'loading', loading); } function markThreadLoading(loading) { - var root = JX.DOM.find(document, 'div', 'conpherence-layout'); - var header_root = JX.DOM.find(root, 'div', 'conpherence-header-pane'); - var messages_root = JX.DOM.find(root, 'div', 'conpherence-message-pane'); - var form_root = JX.DOM.find(root, 'div', 'conpherence-form'); - - JX.DOM.alterClass(header_root, 'loading', loading); - JX.DOM.alterClass(messages_root, 'loading', loading); - JX.DOM.alterClass(form_root, 'loading', loading); - try { var textarea = JX.DOM.find(form, 'textarea'); textarea.disabled = loading; @@ -266,32 +249,10 @@ JX.behavior('conpherence-menu', function(config) { var root = JX.DOM.find(document, 'div', 'conpherence-layout'); var widgets_root = JX.DOM.find(root, 'div', 'conpherence-widgets-holder'); JX.DOM.setContent(widgets_root, JX.$H(response.widgets)); - - JX.Stratcom.invoke( - 'conpherence-update-widgets', - null, - { - widget : widget, - buildSelectors : true, - toggleWidget : true, - threadID : _thread.selected - }); - - markWidgetLoading(false); } function getDefaultWidget() { - var device = JX.Device.getDevice(); - var widget = 'conpherence-message-pane'; - if (device == 'desktop') { - widget = 'widgets-people'; - var uri = JX.$U(location.href); - var params = uri.getQueryParams(); - if ('settings' in params) { - widget = 'widgets-settings'; - } - } - return widget; + return 'widgets-people'; } /** @@ -378,38 +339,6 @@ JX.behavior('conpherence-menu', function(config) { selectThread(e.getNode('conpherence-menu-click'), true); }); - JX.Stratcom.listen('click', 'conpherence-edit-metadata', function (e) { - e.kill(); - var root = e.getNode('conpherence-layout'); - var form = JX.DOM.find(root, 'form', 'conpherence-pontificate'); - var data = e.getNodeData('conpherence-edit-metadata'); - var header = JX.DOM.find(root, 'div', 'conpherence-header-pane'); - var messages = scrollbar.getContentNode(); - - new JX.Workflow.newFromForm(form, data) - .setHandler(JX.bind(this, function(r) { - JX.DOM.appendContent(messages, JX.$H(r.transactions)); - _scrollMessageWindow(); - - JX.DOM.setContent( - header, - JX.$H(r.header) - ); - - try { - // update the menu entry - JX.DOM.replace( - JX.$(r.conpherence_phid + '-nav-item'), - JX.$H(r.nav_item) - ); - selectThreadByID(r.conpherence_phid + '-nav-item'); - } catch (ex) { - // Ignore; this view may not have a menu. - } - })) - .start(); - }); - /** * On devices, we just show a thread list, so we don't want to automatically * select or load any threads. On desktop, we automatically select the first diff --git a/webroot/rsrc/js/application/conpherence/behavior-toggle-widget.js b/webroot/rsrc/js/application/conpherence/behavior-toggle-widget.js index 1b36f8ff79..0022273c0e 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-toggle-widget.js +++ b/webroot/rsrc/js/application/conpherence/behavior-toggle-widget.js @@ -9,6 +9,20 @@ JX.behavior('toggle-widget', function(config) { + var device; + + function init() { + device = JX.Device.getDevice(); + if (device != 'phone') { + var node = JX.$('conpherence-main-layout'); + JX.DOM.alterClass(node, 'hide-widgets', !config.show); + JX.Stratcom.invoke('resize'); + } else { + config.show = 0; + } + } + init(); + function _toggleColumn(e) { e.kill(); var node = JX.$('conpherence-main-layout'); @@ -16,9 +30,11 @@ JX.behavior('toggle-widget', function(config) { JX.DOM.alterClass(node, 'hide-widgets', !config.show); JX.Stratcom.invoke('resize'); - new JX.Request(config.settingsURI) - .setData({value: (config.show ? 1 : 0)}) - .send(); + if (device != 'phone') { + new JX.Request(config.settingsURI) + .setData({value: (config.show ? 1 : 0)}) + .send(); + } } JX.Stratcom.listen( diff --git a/webroot/rsrc/js/core/Favicon.js b/webroot/rsrc/js/core/Favicon.js new file mode 100644 index 0000000000..fb494dfbbd --- /dev/null +++ b/webroot/rsrc/js/core/Favicon.js @@ -0,0 +1,35 @@ +/** + * @provides phabricator-favicon + * @requires javelin-install + * javelin-dom + */ +JX.install('Favicon', { + statics: { + _favicon: null, + + setFavicon: function(favicon) { + var self = JX.Favicon; + self._favicon = favicon; + self._update(); + }, + + _update: function() { + var self = JX.Favicon; + var cur_favicon = JX.$('favicon'); + + if (self._favicon === null) { + self._favicon = cur_favicon.href; + } + + var new_favicon = JX.$N( + 'link', + {href: self._favicon, + id: 'favicon', + rel: 'shortcut icon', + }); + + JX.DOM.replace(cur_favicon, new_favicon); + + } + } +}); diff --git a/webroot/rsrc/js/core/Title.js b/webroot/rsrc/js/core/Title.js index 08ca5e2f25..f99f948e39 100644 --- a/webroot/rsrc/js/core/Title.js +++ b/webroot/rsrc/js/core/Title.js @@ -1,7 +1,6 @@ /** - * @requires javelin-install * @provides phabricator-title - * @javelin + * @requires javelin-install */ /** @@ -44,6 +43,7 @@ JX.install('Title', { } document.title = title; + } } }); diff --git a/webroot/rsrc/js/core/behavior-global-drag-and-drop.js b/webroot/rsrc/js/core/behavior-global-drag-and-drop.js index a291772b6e..08b0fd1226 100644 --- a/webroot/rsrc/js/core/behavior-global-drag-and-drop.js +++ b/webroot/rsrc/js/core/behavior-global-drag-and-drop.js @@ -70,7 +70,14 @@ JX.behavior('global-drag-and-drop', function(config, statics) { // If whatever the user dropped in has finished uploading, send them to // their uploads. var uri; - uri = JX.$U(config.browseURI); + var is_submit = !!config.submitURI; + + if (is_submit) { + uri = JX.$U(config.submitURI); + } else { + uri = JX.$U(config.browseURI); + } + var ids = []; for (var ii = 0; ii < statics.files.length; ii++) { ids.push(statics.files[ii].getID()); @@ -79,7 +86,12 @@ JX.behavior('global-drag-and-drop', function(config, statics) { statics.files = []; - uri.go(); + if (is_submit) { + new JX.Workflow(uri) + .start(); + } else { + uri.go(); + } } });