1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-28 01:32:42 +01:00

(stable) Promote 2016 Week 43

This commit is contained in:
epriestley 2016-10-21 16:10:42 -07:00
commit 5efbf4d74a
229 changed files with 5747 additions and 1488 deletions

View file

@ -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',

View file

@ -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',

View file

@ -0,0 +1,11 @@
<?php
$table = new ConpherenceThread();
foreach (new LiskMigrationIterator($table) as $thread) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$thread->getPHID(),
array(
'force' => true,
));
}

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_conpherence.conpherence_thread
DROP COLUMN imagePHIDs;

View file

@ -4,6 +4,11 @@
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
$cache = PhabricatorCaches::getMutableCache();
$authfile_key = PhabricatorAuthSSHKeyQuery::AUTHFILE_CACHEKEY;
$authfile = $cache->getKey($authfile_key);
if ($authfile === null) {
$keys = id(new PhabricatorAuthSSHKeyQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIsActive(true)
@ -73,5 +78,10 @@ foreach ($keys as $ssh_key) {
$lines[] = $options.' '.$type.' '.$key."\n";
}
echo implode('', $lines);
$authfile = implode('', $lines);
$ttl = phutil_units('24 hours in seconds');
$cache->setKey($authfile_key, $authfile, $ttl);
}
echo $authfile;
exit(0);

View file

@ -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',

View file

@ -0,0 +1,60 @@
<?php
final class AphrontFileHTTPParameterType
extends AphrontHTTPParameterType {
private function getFileKey($key) {
return $key.'_raw';
}
protected function getParameterExists(AphrontRequest $request, $key) {
$file_key = $this->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',
);
}
}

View file

@ -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();
}

View file

@ -3,6 +3,8 @@
final class PhabricatorAuthSSHKeyQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
const AUTHFILE_CACHEKEY = 'ssh.authfile';
private $ids;
private $phids;
private $objectPHIDs;

View file

@ -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 )--------------------------------------------- */

View file

@ -98,7 +98,7 @@ final class PhabricatorKeyValueDatabaseCache
$this->establishConnection('w'),
'DELETE FROM %T WHERE cacheKeyHash IN (%Ls)',
$this->getTableName(),
$keys);
$map);
}
return $this;

View file

@ -73,7 +73,24 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication {
=> 'PhabricatorCalendarExportICSController',
'disable/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarExportDisableController',
),
'import/' => array(
$this->getQueryRoutePattern()
=> 'PhabricatorCalendarImportListController',
$this->getEditRoutePattern('edit/')
=> 'PhabricatorCalendarImportEditController',
'(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarImportViewController',
'disable/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarImportDisableController',
'delete/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarImportDeleteController',
'drop/'
=> 'PhabricatorCalendarImportDropController',
'log/' => array(
$this->getQueryRoutePattern()
=> 'PhabricatorCalendarImportLogListController',
),
),
),
);

View file

@ -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'));
}
}

View file

@ -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();

View file

@ -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();

View file

@ -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');

View file

@ -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/');

View file

@ -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);

View file

@ -0,0 +1,64 @@
<?php
final class PhabricatorCalendarImportDeleteController
extends PhabricatorCalendarController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->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'));
}
}

View file

@ -0,0 +1,71 @@
<?php
final class PhabricatorCalendarImportDisableController
extends PhabricatorCalendarController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->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);
}
}

View file

@ -0,0 +1,86 @@
<?php
final class PhabricatorCalendarImportDropController
extends PhabricatorCalendarController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->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);
}
}

View file

@ -0,0 +1,92 @@
<?php
final class PhabricatorCalendarImportEditController
extends PhabricatorCalendarController {
public function handleRequest(AphrontRequest $request) {
$engine = id(new PhabricatorCalendarImportEditEngine())
->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);
}
}

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorCalendarImportListController
extends PhabricatorCalendarController {
public function handleRequest(AphrontRequest $request) {
return id(new PhabricatorCalendarImportSearchEngine())
->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;
}
}

View file

@ -0,0 +1,12 @@
<?php
final class PhabricatorCalendarImportLogListController
extends PhabricatorCalendarController {
public function handleRequest(AphrontRequest $request) {
return id(new PhabricatorCalendarImportLogSearchEngine())
->setController($this)
->buildResponse();
}
}

View file

@ -0,0 +1,241 @@
<?php
final class PhabricatorCalendarImportViewController
extends PhabricatorCalendarController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->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);
}
}

View file

@ -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;
}

View file

@ -0,0 +1,115 @@
<?php
final class PhabricatorCalendarImportEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'calendar.import';
private $importEngine;
public function setImportEngine(PhabricatorCalendarImportEngine $engine) {
$this->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;
}
}

View file

@ -0,0 +1,42 @@
<?php
final class PhabricatorCalendarImportEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorCalendarApplication';
}
public function getEditorObjectsDescription() {
return pht('Calendar Imports');
}
public function getCreateObjectTitle($author, $object) {
return pht('%s created this import.', $author);
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
protected function applyFinalEffects(
PhabricatorLiskDAO $object,
array $xactions) {
if ($this->getIsNewObject()) {
$actor = $this->getActor();
$import_engine = $object->getEngine();
$import_engine->didCreateImport($actor, $object);
}
return $xactions;
}
}

View file

@ -0,0 +1,102 @@
<?php
final class PhabricatorCalendarICSFileImportEngine
extends PhabricatorCalendarICSImportEngine {
const ENGINETYPE = 'icsfile';
public function getImportEngineName() {
return pht('Import .ics File');
}
public function getImportEngineTypeName() {
return pht('.ics File');
}
public function getImportEngineHint() {
return pht('Import an event in ".ics" (iCalendar) format.');
}
public function appendImportProperties(
PhabricatorUser $viewer,
PhabricatorCalendarImport $import,
PHUIPropertyListView $properties) {
$phid_key = PhabricatorCalendarImportICSFileTransaction::PARAMKEY_FILE;
$file_phid = $import->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.');
}
}

View file

@ -0,0 +1,34 @@
<?php
abstract class PhabricatorCalendarICSImportEngine
extends PhabricatorCalendarImportEngine {
final protected function importICSData(
PhabricatorUser $viewer,
PhabricatorCalendarImport $import,
$data) {
$parser = new PhutilICSParser();
try {
$document = $parser->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);
}
}

View file

@ -0,0 +1,111 @@
<?php
final class PhabricatorCalendarICSURIImportEngine
extends PhabricatorCalendarICSImportEngine {
const ENGINETYPE = 'icsuri';
public function getImportEngineName() {
return pht('Import .ics URI');
}
public function getImportEngineTypeName() {
return pht('.ics URI');
}
public function getImportEngineHint() {
return pht('Import or subscribe to a calendar in .ics format by URI.');
}
public function appendImportProperties(
PhabricatorUser $viewer,
PhabricatorCalendarImport $import,
PHUIPropertyListView $properties) {
$uri_key = PhabricatorCalendarImportICSURITransaction::PARAMKEY_URI;
$uri = $import->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;
}
}

View file

@ -0,0 +1,424 @@
<?php
abstract class PhabricatorCalendarImportEngine
extends Phobject {
final public function getImportEngineType() {
return $this->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;
}
}

View file

@ -0,0 +1,20 @@
<?php
final class PhabricatorCalendarImportDefaultLogType
extends PhabricatorCalendarImportLogType {
const LOGTYPE = 'default';
public function getDisplayType(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
$type = $log->getParameter('type');
if (strlen($type)) {
return pht('Unknown Message "%s"', $type);
} else {
return pht('Unknown Message');
}
}
}

View file

@ -0,0 +1,23 @@
<?php
final class PhabricatorCalendarImportDuplicateLogType
extends PhabricatorCalendarImportLogType {
const LOGTYPE = 'duplicate';
public function getDisplayType(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('Duplicate Event');
}
public function getDisplayDescription(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
$duplicate_uid = $log->getParameter('uid.full');
return pht(
'Ignored duplicate event "%s" present in source.',
$duplicate_uid);
}
}

View file

@ -0,0 +1,32 @@
<?php
final class PhabricatorCalendarImportEmptyLogType
extends PhabricatorCalendarImportLogType {
const LOGTYPE = 'empty';
public function getDisplayType(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('No Events Imported');
}
public function getDisplayDescription(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('Found no valid events to import.');
}
public function getDisplayIcon(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return 'fa-ban';
}
public function getDisplayColor(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return 'red';
}
}

View file

@ -0,0 +1,34 @@
<?php
final class PhabricatorCalendarImportEpochLogType
extends PhabricatorCalendarImportLogType {
const LOGTYPE = 'epoch';
public function getDisplayType(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('Out of Range');
}
public function getDisplayDescription(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht(
'Ignored an event with an out-of-range date. Only dates between '.
'1970 and 2037 are supported.');
}
public function getDisplayIcon(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return 'fa-clock-o';
}
public function getDisplayColor(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return 'red';
}
}

View file

@ -0,0 +1,33 @@
<?php
final class PhabricatorCalendarImportFetchLogType
extends PhabricatorCalendarImportLogType {
const LOGTYPE = 'fetch';
public function getDisplayType(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('Fetched Calendar');
}
public function getDisplayDescription(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return $viewer->renderHandle($log->getParameter('file.phid'));
}
public function getDisplayIcon(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return 'fa-download';
}
public function getDisplayColor(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return 'green';
}
}

View file

@ -0,0 +1,38 @@
<?php
final class PhabricatorCalendarImportFrequencyLogType
extends PhabricatorCalendarImportLogType {
const LOGTYPE = 'frequency';
public function getDisplayType(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('Too Frequent');
}
public function getDisplayDescription(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
$frequency = $log->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';
}
}

View file

@ -0,0 +1,36 @@
<?php
final class PhabricatorCalendarImportICSLogType
extends PhabricatorCalendarImportLogType {
const LOGTYPE = 'ics';
public function getDisplayType(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('ICS Parse Error');
}
public function getDisplayDescription(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht(
'Failed to parse ICS data ("%s"): %s',
$log->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';
}
}

View file

@ -0,0 +1,23 @@
<?php
final class PhabricatorCalendarImportIgnoredNodeLogType
extends PhabricatorCalendarImportLogType {
const LOGTYPE = 'nodetype';
public function getDisplayType(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('Ignored Node');
}
public function getDisplayDescription(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
$node_type = $log->getParameter('node.type');
return pht(
'Ignored unsupported "%s" node present in source.',
$node_type);
}
}

View file

@ -0,0 +1,39 @@
<?php
abstract class PhabricatorCalendarImportLogType
extends Phobject {
final public function getLogTypeConstant() {
return $this->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;
}
}

View file

@ -0,0 +1,26 @@
<?php
final class PhabricatorCalendarImportOriginalLogType
extends PhabricatorCalendarImportLogType {
const LOGTYPE = 'original';
public function getDisplayType(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('Original Event');
}
public function getDisplayDescription(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
$phid = $log->getParameter('phid');
return pht(
'Ignored an event (%s) because the original version of this event '.
'was created here.',
$viewer->renderHandle($phid));
}
}

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorCalendarImportOrphanLogType
extends PhabricatorCalendarImportLogType {
const LOGTYPE = 'orphan';
public function getDisplayType(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('Orphan');
}
public function getDisplayDescription(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
$child_uid = $log->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);
}
}

View file

@ -0,0 +1,38 @@
<?php
final class PhabricatorCalendarImportUpdateLogType
extends PhabricatorCalendarImportLogType {
const LOGTYPE = 'update';
public function getDisplayType(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
$is_new = $log->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';
}
}

View file

@ -0,0 +1,50 @@
<?php
final class PhabricatorCalendarImportPHIDType extends PhabricatorPHIDType {
const TYPECONST = 'CIMP';
public function getTypeName() {
return pht('Calendar Import');
}
public function newObject() {
return new PhabricatorCalendarImport();
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorCalendarApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new PhabricatorCalendarImportQuery())
->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);
}
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,111 @@
<?php
final class PhabricatorCalendarImportLogQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $importPHIDs;
public function withIDs(array $ids) {
$this->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';
}
}

View file

@ -0,0 +1,77 @@
<?php
final class PhabricatorCalendarImportLogSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Calendar Import Logs');
}
public function getApplicationClassName() {
return 'PhabricatorCalendarApplication';
}
public function newQuery() {
return new PhabricatorCalendarImportLogQuery();
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorPHIDsSearchField())
->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());
}
}

View file

@ -0,0 +1,99 @@
<?php
final class PhabricatorCalendarImportQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $isDisabled;
public function withIDs(array $ids) {
$this->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';
}
}

View file

@ -0,0 +1,81 @@
<?php
final class PhabricatorCalendarImportSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Calendar Imports');
}
public function getApplicationClassName() {
return 'PhabricatorCalendarApplication';
}
public function newQuery() {
return new PhabricatorCalendarImportQuery();
}
protected function buildCustomSearchFields() {
return array();
}
protected function buildQueryFromParameters(array $map) {
$query = $this->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;
}
}

View file

@ -0,0 +1,10 @@
<?php
final class PhabricatorCalendarImportTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new PhabricatorCalendarImportTransaction();
}
}

View file

@ -3,6 +3,7 @@
final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
implements
PhabricatorPolicyInterface,
PhabricatorExtendedPolicyInterface,
PhabricatorProjectInterface,
PhabricatorMarkupInterface,
PhabricatorApplicationTransactionInterface,
@ -40,8 +41,14 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
protected $utcInstanceEpoch;
protected $parameters = array();
protected $importAuthorPHID;
protected $importSourcePHID;
protected $importUIDIndex;
protected $importUID;
private $parentEvent = self::ATTACHABLE;
private $invitees = self::ATTACHABLE;
private $importSource = self::ATTACHABLE;
private $viewerTimezone;
@ -74,6 +81,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
$datetime_end = $datetime_start->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:
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,
);
}
}

View file

@ -0,0 +1,178 @@
<?php
final class PhabricatorCalendarImport
extends PhabricatorCalendarDAO
implements
PhabricatorPolicyInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorDestructibleInterface {
protected $name;
protected $authorPHID;
protected $viewPolicy;
protected $editPolicy;
protected $engineType;
protected $parameters = array();
protected $isDisabled = 0;
private $engine = self::ATTACHABLE;
public static function initializeNewCalendarImport(
PhabricatorUser $actor,
PhabricatorCalendarImportEngine $engine) {
return id(new self())
->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();
}
}

View file

@ -0,0 +1,103 @@
<?php
final class PhabricatorCalendarImportLog
extends PhabricatorCalendarDAO
implements
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
protected $importPHID;
protected $parameters = array();
private $import = self::ATTACHABLE;
private $logType = self::ATTACHABLE;
protected function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => 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();
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorCalendarImportTransaction
extends PhabricatorModularTransaction {
public function getApplicationName() {
return 'calendar';
}
public function getApplicationTransactionType() {
return PhabricatorCalendarImportPHIDType::TYPECONST;
}
public function getBaseTransactionClass() {
return 'PhabricatorCalendarImportTransactionType';
}
}

View file

@ -0,0 +1,84 @@
<?php
final class PhabricatorCalendarImportLogView extends AphrontView {
private $logs = array();
private $showImportSources = false;
public function setLogs(array $logs) {
assert_instances_of($logs, 'PhabricatorCalendarImportLog');
$this->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;
}
}

View file

@ -0,0 +1,30 @@
<?php
final class PhabricatorCalendarImportDeleteTransaction
extends PhabricatorCalendarImportTransactionType {
const TRANSACTIONTYPE = 'calendar.import.delete';
public function generateOldValue($object) {
return false;
}
public function applyExternalEffects($object, $value) {
$events = id(new PhabricatorCalendarEventQuery())
->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());
}
}

View file

@ -0,0 +1,28 @@
<?php
final class PhabricatorCalendarImportDisableTransaction
extends PhabricatorCalendarImportTransactionType {
const TRANSACTIONTYPE = 'calendar.import.disable';
public function generateOldValue($object) {
return (int)$object->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());
}
}
}

View file

@ -0,0 +1,80 @@
<?php
final class PhabricatorCalendarImportICSFileTransaction
extends PhabricatorCalendarImportTransactionType {
const TRANSACTIONTYPE = 'calendar.import.ics.file';
const PARAMKEY_FILE = 'ics.filePHID';
const PARAMKEY_NAME = 'ics.fileName';
public function generateOldValue($object) {
return $object->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;
}
}

View file

@ -0,0 +1,73 @@
<?php
final class PhabricatorCalendarImportICSURITransaction
extends PhabricatorCalendarImportTransactionType {
const TRANSACTIONTYPE = 'calendar.import.ics.uri';
const PARAMKEY_URI = 'ics.uri';
public function generateOldValue($object) {
return $object->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;
}
}

View file

@ -0,0 +1,39 @@
<?php
final class PhabricatorCalendarImportNameTransaction
extends PhabricatorCalendarImportTransactionType {
const TRANSACTIONTYPE = 'calendar.import.name';
public function generateOldValue($object) {
return $object->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());
}
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class PhabricatorCalendarImportTransactionType
extends PhabricatorModularTransactionType {}

View file

@ -145,6 +145,7 @@ abstract class CelerityResourceController extends PhabricatorController {
'eot' => 'font/eot',
'ttf' => 'font/ttf',
'mp3' => 'audio/mpeg',
'ico' => 'image/x-icon',
);
}

View file

@ -39,6 +39,7 @@ abstract class CelerityResourcesOnDisk extends CelerityPhysicalResources {
'ttf',
'eot',
'mp3',
'ico',
);
}

View file

@ -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() {

View file

@ -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(

View file

@ -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() {

View file

@ -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() {

View file

@ -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(

View file

@ -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(
$list[$idx] = $this->parseIntValue(
$request,
$key,
pht(
'Expected a list of integers, but item with index "%s" is '.
'not an integer.',
$idx));
}
$key.'['.$idx.']',
$item,
$strict);
}
return $list;

View file

@ -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() {

View file

@ -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) {
foreach ($list as $idx => $item) {
if (!is_string($item)) {
$this->raiseValidationException(
$request,
protected function parseStringList(
array $request,
$key,
pht(
'Expected a list of strings, but item with index "%s" is '.
'not a string.',
$idx));
}
array $list,
$strict) {
foreach ($list as $idx => $item) {
$list[$idx] = $this->parseStringValue(
$request,
$key.'['.$idx.']',
$item,
$strict);
}
return $list;

View file

@ -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() {

View file

@ -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(

View file

@ -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();

View file

@ -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(

View file

@ -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);

View file

@ -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() {

View file

@ -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() {

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -37,6 +37,8 @@ final class PhabricatorConpherenceApplication extends PhabricatorApplication {
=> 'ConpherenceListController',
'thread/(?P<id>[1-9]\d*)/'
=> 'ConpherenceListController',
'threadsearch/(?P<id>[1-9]\d*)/'
=> 'ConpherenceThreadSearchController',
'(?P<id>[1-9]\d*)/'
=> 'ConpherenceViewController',
'(?P<id>[1-9]\d*)/(?P<messageID>[1-9]\d*)/'

View file

@ -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');

View file

@ -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;
}
}

View file

@ -20,7 +20,7 @@ final class ConpherenceNotificationPanelController
->withPHIDs(array_keys($participant_data))
->needProfileImage(true)
->needTransactions(true)
->setTransactionLimit(3 * 5)
->setTransactionLimit(50)
->needParticipantCache(true)
->execute();
}

View file

@ -0,0 +1,41 @@
<?php
final class ConpherenceThreadSearchController
extends ConpherenceController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->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());
}
}

View file

@ -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,7 +539,6 @@ final class ConpherenceUpdateController
$nav_item = null;
$header = null;
$people_widget = null;
if (!$minimal_display) {
switch ($action) {
case ConpherenceUpdateActions::METADATA:
$policy_objects = id(new PhabricatorPolicyQuery())
@ -580,7 +567,6 @@ final class ConpherenceUpdateController
default:
break;
}
}
$data = $conpherence->getDisplayData($user);
$dropdown_query = id(new AphlictDropdownDataQuery())
->setViewer($user);

View file

@ -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'])

View file

@ -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());
}
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
return $joins;
}
$where[] = $this->buildPagingClause($conn_r);
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
// 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) {

View file

@ -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,11 +159,14 @@ final class ConpherenceThreadSearchEngine
$icon_name = $conpherence->getPolicyIconName($policy_objects);
$icon = id(new PHUIIconView())
->setIcon($icon_name);
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',
@ -167,8 +179,11 @@ final class ConpherenceThreadSearchEngine
'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();
@ -184,7 +199,7 @@ final class ConpherenceThreadSearchEngine
->setMarkupEngine($engines[$conpherence_phid])
->setConpherenceThread($conpherence)
->setConpherenceTransaction($xaction)
->setFullDisplay(false)
->setSearchResult(true)
->addClass('conpherence-fulltext-result');
if ($message['match']) {
@ -193,20 +208,33 @@ final class ConpherenceThreadSearchEngine
$rows[] = $view;
}
$box = id(new PHUIBoxView())
$box[] = id(new PHUIBoxView())
->appendChild($rows)
->addClass('conpherence-fulltext-results');
$item->appendChild($box);
}
}
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon($icon_name)
->setHref('/'.$monogram);
$content[] = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($box);
}
}
$list->addItem($item);
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;
}

View file

@ -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 )----------------------------------- */

View file

@ -0,0 +1,17 @@
<?php
final class ConpherenceThreadTitleNgrams
extends PhabricatorSearchNgrams {
public function getNgramKey() {
return 'threadtitle';
}
public function getColumnName() {
return 'title';
}
public function getApplicationName() {
return 'conpherence';
}
}

Some files were not shown because too many files have changed in this diff Show more