diff --git a/resources/builtin/image-100x100.png b/resources/builtin/image-100x100.png
new file mode 100644
index 0000000000..c56a9aa083
Binary files /dev/null and b/resources/builtin/image-100x100.png differ
diff --git a/resources/builtin/image-220x220.png b/resources/builtin/image-220x220.png
new file mode 100644
index 0000000000..928b5b05eb
Binary files /dev/null and b/resources/builtin/image-220x220.png differ
diff --git a/resources/builtin/image-280x210.png b/resources/builtin/image-280x210.png
new file mode 100644
index 0000000000..48237b045e
Binary files /dev/null and b/resources/builtin/image-280x210.png differ
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
index 0a6ce7d67e..2fdb459dcc 100644
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -7,11 +7,11 @@
*/
return array(
'names' => array(
- 'core.pkg.css' => '8aed144e',
- 'core.pkg.js' => '60924527',
+ 'core.pkg.css' => '50250d4f',
+ 'core.pkg.js' => 'f3e08b38',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => 'bb338e4b',
- 'differential.pkg.js' => '3cfa26f9',
+ 'differential.pkg.js' => '895b8d62',
'diffusion.pkg.css' => '591664fa',
'diffusion.pkg.js' => '0115b37c',
'maniphest.pkg.css' => '68d4dd3d',
@@ -33,22 +33,23 @@ return array(
'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c',
'rsrc/css/aphront/typeahead.css' => '0e403212',
'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af',
- 'rsrc/css/application/auth/auth.css' => '1e655982',
+ 'rsrc/css/application/auth/auth.css' => '44975d4b',
'rsrc/css/application/base/main-menu-view.css' => '1766b04d',
'rsrc/css/application/base/notification-menu.css' => '3c9d8aa1',
'rsrc/css/application/base/phabricator-application-launch-view.css' => '132f9d14',
- 'rsrc/css/application/base/standard-page-view.css' => 'dc14c671',
+ 'rsrc/css/application/base/standard-page-view.css' => '062f0f54',
'rsrc/css/application/chatlog/chatlog.css' => '852140ff',
+ 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4',
'rsrc/css/application/config/config-options.css' => '7fedf08b',
'rsrc/css/application/config/config-template.css' => '8e6c6fcd',
'rsrc/css/application/config/config-welcome.css' => '6abd79be',
'rsrc/css/application/config/setup-issue.css' => '22270af2',
'rsrc/css/application/config/unhandled-exception.css' => '37d4f9a2',
- 'rsrc/css/application/conpherence/durable-column.css' => '2e68a92f',
+ 'rsrc/css/application/conpherence/durable-column.css' => '8c43d6ac',
'rsrc/css/application/conpherence/menu.css' => 'f9f1d143',
- 'rsrc/css/application/conpherence/message-pane.css' => '73631823',
- 'rsrc/css/application/conpherence/notification.css' => 'd208f806',
- 'rsrc/css/application/conpherence/transaction.css' => '25138b7f',
+ 'rsrc/css/application/conpherence/message-pane.css' => '7cbf4cbb',
+ 'rsrc/css/application/conpherence/notification.css' => '919974b6',
+ 'rsrc/css/application/conpherence/transaction.css' => '42a457f6',
'rsrc/css/application/conpherence/update.css' => '1099a660',
'rsrc/css/application/conpherence/widget-pane.css' => '2af42ebe',
'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4',
@@ -106,10 +107,10 @@ return array(
'rsrc/css/application/slowvote/slowvote.css' => '266df6a1',
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
'rsrc/css/application/uiexample/example.css' => '528b19de',
- 'rsrc/css/core/core.css' => 'cee2aadb',
+ 'rsrc/css/core/core.css' => '6230ff55',
'rsrc/css/core/remarkup.css' => '0037bdbf',
'rsrc/css/core/syntax.css' => '6b7b24d9',
- 'rsrc/css/core/z-index.css' => 'ef044fae',
+ 'rsrc/css/core/z-index.css' => '8c8c40aa',
'rsrc/css/diviner/diviner-shared.css' => '38813222',
'rsrc/css/font/font-awesome.css' => 'e2e712fe',
'rsrc/css/font/font-source-sans-pro.css' => '8906c07b',
@@ -118,9 +119,9 @@ return array(
'rsrc/css/layout/phabricator-hovercard-view.css' => '44394670',
'rsrc/css/layout/phabricator-side-menu-view.css' => 'a440478a',
'rsrc/css/layout/phabricator-source-code-view.css' => '2ceee894',
- 'rsrc/css/phui/calendar/phui-calendar-day.css' => '75b8cc4a',
+ 'rsrc/css/phui/calendar/phui-calendar-day.css' => '38891735',
'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59',
- 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'a92e47d2',
+ 'rsrc/css/phui/calendar/phui-calendar-month.css' => '75e6a2ee',
'rsrc/css/phui/calendar/phui-calendar.css' => '8675968e',
'rsrc/css/phui/phui-action-header-view.css' => 'e4471f43',
'rsrc/css/phui/phui-action-list.css' => '4f4d09f2',
@@ -131,16 +132,16 @@ return array(
'rsrc/css/phui/phui-document.css' => '7b564cf6',
'rsrc/css/phui/phui-feed-story.css' => 'c9f3a0b5',
'rsrc/css/phui/phui-fontkit.css' => '1e71371a',
- 'rsrc/css/phui/phui-form-view.css' => 'ddec8479',
+ 'rsrc/css/phui/phui-form-view.css' => 'e1abbe8e',
'rsrc/css/phui/phui-form.css' => 'f535f938',
- 'rsrc/css/phui/phui-header-view.css' => 'a1d2905a',
+ 'rsrc/css/phui/phui-header-view.css' => '5c0c1c39',
'rsrc/css/phui/phui-icon.css' => 'bc766998',
'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8',
'rsrc/css/phui/phui-info-panel.css' => '27ea50a1',
'rsrc/css/phui/phui-info-view.css' => '33595731',
'rsrc/css/phui/phui-list.css' => '2e25ebfb',
'rsrc/css/phui/phui-object-box.css' => '3a601bc5',
- 'rsrc/css/phui/phui-object-item-list-view.css' => '172ea456',
+ 'rsrc/css/phui/phui-object-item-list-view.css' => 'c259c94f',
'rsrc/css/phui/phui-pinboard-view.css' => 'eaab2b1b',
'rsrc/css/phui/phui-property-list-view.css' => 'd2d143ea',
'rsrc/css/phui/phui-remarkup-preview.css' => '19ad512b',
@@ -148,7 +149,7 @@ return array(
'rsrc/css/phui/phui-status.css' => '888cedb8',
'rsrc/css/phui/phui-tag-view.css' => '402691cc',
'rsrc/css/phui/phui-text.css' => 'cf019f54',
- 'rsrc/css/phui/phui-timeline-view.css' => 'b0fbc4d7',
+ 'rsrc/css/phui/phui-timeline-view.css' => 'a85542c8',
'rsrc/css/phui/phui-workboard-view.css' => '3279cbbf',
'rsrc/css/phui/phui-workpanel-view.css' => 'e495a5cc',
'rsrc/css/sprite-gradient.css' => '4bdb98a7',
@@ -209,7 +210,7 @@ return array(
'rsrc/externals/javelin/lib/Resource.js' => '44959b73',
'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692',
'rsrc/externals/javelin/lib/Router.js' => '29274e2b',
- 'rsrc/externals/javelin/lib/Scrollbar.js' => 'eaa5b321',
+ 'rsrc/externals/javelin/lib/Scrollbar.js' => '087e919c',
'rsrc/externals/javelin/lib/Sound.js' => '949c0fe5',
'rsrc/externals/javelin/lib/URI.js' => '6eff08aa',
'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8',
@@ -279,22 +280,6 @@ return array(
'rsrc/image/icon/fatcow/source/mobile.png' => 'f1321264',
'rsrc/image/icon/fatcow/source/tablet.png' => '49396799',
'rsrc/image/icon/fatcow/source/web.png' => '136ccb5d',
- 'rsrc/image/icon/fatcow/thumbnails/default.p100.png' => '7d490b01',
- 'rsrc/image/icon/fatcow/thumbnails/default160x120.png' => 'f2e8a2eb',
- 'rsrc/image/icon/fatcow/thumbnails/default280x210.png' => '43e8926a',
- 'rsrc/image/icon/fatcow/thumbnails/default60x45.png' => '0118abed',
- 'rsrc/image/icon/fatcow/thumbnails/image.p100.png' => 'da23cf97',
- 'rsrc/image/icon/fatcow/thumbnails/image160x120.png' => '79bb556a',
- 'rsrc/image/icon/fatcow/thumbnails/image280x210.png' => '91ae054a',
- 'rsrc/image/icon/fatcow/thumbnails/image60x45.png' => 'c5e1685e',
- 'rsrc/image/icon/fatcow/thumbnails/pdf.p100.png' => '87d5e065',
- 'rsrc/image/icon/fatcow/thumbnails/pdf160x120.png' => 'ac9edbf5',
- 'rsrc/image/icon/fatcow/thumbnails/pdf280x210.png' => '1c585653',
- 'rsrc/image/icon/fatcow/thumbnails/pdf60x45.png' => 'c0db4143',
- 'rsrc/image/icon/fatcow/thumbnails/zip.p100.png' => '6ea5aae4',
- 'rsrc/image/icon/fatcow/thumbnails/zip160x120.png' => '75f9cd0f',
- 'rsrc/image/icon/fatcow/thumbnails/zip280x210.png' => 'dfda5b8e',
- 'rsrc/image/icon/fatcow/thumbnails/zip60x45.png' => 'af11bf3e',
'rsrc/image/icon/lightbox/close-2.png' => 'cc40e7c8',
'rsrc/image/icon/lightbox/close-hover-2.png' => 'fb5d6d9e',
'rsrc/image/icon/lightbox/left-arrow-2.png' => '8426133b',
@@ -339,16 +324,17 @@ return array(
'rsrc/image/texture/table_header.png' => '5c433037',
'rsrc/image/texture/table_header_hover.png' => '038ec3b9',
'rsrc/image/texture/table_header_tall.png' => 'd56b434f',
- 'rsrc/js/application/aphlict/Aphlict.js' => '30a6303c',
- 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '6e1f0cba',
+ 'rsrc/js/application/aphlict/Aphlict.js' => '5359e785',
+ 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'e09f6208',
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974',
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761',
'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18',
+ 'rsrc/js/application/calendar/event-all-day.js' => 'ca5fa62a',
'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de',
- 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '6709c934',
+ 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'b7342ddb',
'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a',
- 'rsrc/js/application/conpherence/behavior-durable-column.js' => '657c2b50',
- 'rsrc/js/application/conpherence/behavior-menu.js' => '804b0773',
+ 'rsrc/js/application/conpherence/behavior-durable-column.js' => '16c695bf',
+ 'rsrc/js/application/conpherence/behavior-menu.js' => '4351c4a0',
'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861',
'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3',
'rsrc/js/application/conpherence/behavior-widget-pane.js' => '93568464',
@@ -358,7 +344,7 @@ return array(
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375',
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63',
'rsrc/js/application/differential/ChangesetViewManager.js' => '58562350',
- 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '2529c82d',
+ 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'd4c87bf4',
'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => 'e10f8e18',
'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d',
'rsrc/js/application/differential/behavior-comment-preview.js' => '8e1389b5',
@@ -404,7 +390,7 @@ return array(
'rsrc/js/application/policy/behavior-policy-control.js' => '9a340b3d',
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c',
'rsrc/js/application/ponder/behavior-votebox.js' => '4e9b766b',
- 'rsrc/js/application/projects/behavior-project-boards.js' => '60292820',
+ 'rsrc/js/application/projects/behavior-project-boards.js' => 'ba4fa35c',
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb',
'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
@@ -435,7 +421,7 @@ return array(
'rsrc/js/core/DragAndDropFileUpload.js' => '07de8873',
'rsrc/js/core/DraggableList.js' => 'a16ec1c6',
'rsrc/js/core/FileUpload.js' => '477359c8',
- 'rsrc/js/core/Hovercard.js' => '7e8468ae',
+ 'rsrc/js/core/Hovercard.js' => '14ac66f5',
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f',
'rsrc/js/core/MultirowRowManager.js' => 'b5d57730',
@@ -504,21 +490,22 @@ return array(
'aphront-tooltip-css' => '7672b60f',
'aphront-two-column-view-css' => '16ab3ad2',
'aphront-typeahead-control-css' => '0e403212',
- 'auth-css' => '1e655982',
+ 'auth-css' => '44975d4b',
'changeset-view-manager' => '58562350',
+ 'conduit-api-css' => '7bc725c4',
'config-options-css' => '7fedf08b',
'config-welcome-css' => '6abd79be',
- 'conpherence-durable-column-view' => '2e68a92f',
+ 'conpherence-durable-column-view' => '8c43d6ac',
'conpherence-menu-css' => 'f9f1d143',
- 'conpherence-message-pane-css' => '73631823',
- 'conpherence-notification-css' => 'd208f806',
- 'conpherence-thread-manager' => '6709c934',
- 'conpherence-transaction-css' => '25138b7f',
+ 'conpherence-message-pane-css' => '7cbf4cbb',
+ 'conpherence-notification-css' => '919974b6',
+ 'conpherence-thread-manager' => 'b7342ddb',
+ 'conpherence-transaction-css' => '42a457f6',
'conpherence-update-css' => '1099a660',
'conpherence-widget-pane-css' => '2af42ebe',
'differential-changeset-view-css' => 'e19cfd6e',
'differential-core-view-css' => '7ac3cabc',
- 'differential-inline-comment-editor' => '2529c82d',
+ 'differential-inline-comment-editor' => 'd4c87bf4',
'differential-results-table-css' => '181aa9d9',
'differential-revision-add-comment-css' => 'c47f8c40',
'differential-revision-comment-css' => '14b8565a',
@@ -537,9 +524,9 @@ return array(
'herald-rule-editor' => '9229e764',
'herald-test-css' => '778b008e',
'inline-comment-summary-css' => 'eb5f8e8c',
- 'javelin-aphlict' => '30a6303c',
+ 'javelin-aphlict' => '5359e785',
'javelin-behavior' => '61cbc29a',
- 'javelin-behavior-aphlict-dropdown' => '6e1f0cba',
+ 'javelin-behavior-aphlict-dropdown' => 'e09f6208',
'javelin-behavior-aphlict-listen' => 'b1a59974',
'javelin-behavior-aphlict-status' => 'ea681761',
'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884',
@@ -552,7 +539,7 @@ return array(
'javelin-behavior-choose-control' => '6153c708',
'javelin-behavior-config-reorder-fields' => '14a827de',
'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a',
- 'javelin-behavior-conpherence-menu' => '804b0773',
+ 'javelin-behavior-conpherence-menu' => '4351c4a0',
'javelin-behavior-conpherence-pontificate' => '21ba5861',
'javelin-behavior-conpherence-widget-pane' => '93568464',
'javelin-behavior-countdown-timer' => 'e4cc26b3',
@@ -579,8 +566,9 @@ return array(
'javelin-behavior-diffusion-locate-file' => '6d3e1947',
'javelin-behavior-diffusion-pull-lastmodified' => '2b228192',
'javelin-behavior-doorkeeper-tag' => 'e5822781',
- 'javelin-behavior-durable-column' => '657c2b50',
+ 'javelin-behavior-durable-column' => '16c695bf',
'javelin-behavior-error-log' => '6882e80a',
+ 'javelin-behavior-event-all-day' => 'ca5fa62a',
'javelin-behavior-fancy-datepicker' => '5c0f680f',
'javelin-behavior-global-drag-and-drop' => 'c8e57404',
'javelin-behavior-herald-rule-editor' => '7ebaeed3',
@@ -631,7 +619,7 @@ return array(
'javelin-behavior-policy-control' => '9a340b3d',
'javelin-behavior-policy-rule-editor' => '5e9f347c',
'javelin-behavior-ponder-votebox' => '4e9b766b',
- 'javelin-behavior-project-boards' => '60292820',
+ 'javelin-behavior-project-boards' => 'ba4fa35c',
'javelin-behavior-project-create' => '065227cc',
'javelin-behavior-quicksand-blacklist' => '7927a7d3',
'javelin-behavior-refresh-csrf' => '7814b593',
@@ -675,7 +663,7 @@ return array(
'javelin-resource' => '44959b73',
'javelin-routable' => 'b3e7d692',
'javelin-router' => '29274e2b',
- 'javelin-scrollbar' => 'eaa5b321',
+ 'javelin-scrollbar' => '087e919c',
'javelin-sound' => '949c0fe5',
'javelin-stratcom' => '6c53634d',
'javelin-tokenizer' => 'ab5f468d',
@@ -712,7 +700,7 @@ return array(
'phabricator-busy' => '59a7976a',
'phabricator-chatlog-css' => '852140ff',
'phabricator-content-source-view-css' => '4b8b05d4',
- 'phabricator-core-css' => 'cee2aadb',
+ 'phabricator-core-css' => '6230ff55',
'phabricator-countdown-css' => '86b7b0a0',
'phabricator-dashboard-css' => 'db1d30b0',
'phabricator-drag-and-drop-file-upload' => '07de8873',
@@ -722,7 +710,7 @@ return array(
'phabricator-file-upload' => '477359c8',
'phabricator-filetree-view-css' => 'fccf9f82',
'phabricator-flag-css' => '5337623f',
- 'phabricator-hovercard' => '7e8468ae',
+ 'phabricator-hovercard' => '14ac66f5',
'phabricator-hovercard-view-css' => '44394670',
'phabricator-keyboard-shortcut' => '1ae869f2',
'phabricator-keyboard-shortcut-manager' => 'c1700f6f',
@@ -741,7 +729,7 @@ return array(
'phabricator-side-menu-view-css' => 'a440478a',
'phabricator-slowvote-css' => '266df6a1',
'phabricator-source-code-view-css' => '2ceee894',
- 'phabricator-standard-page-view' => 'dc14c671',
+ 'phabricator-standard-page-view' => '062f0f54',
'phabricator-textareautils' => '5c93c52c',
'phabricator-title' => 'df5e11d2',
'phabricator-tooltip' => '1d298e3a',
@@ -756,7 +744,7 @@ return array(
'phabricator-uiexample-reactor-select' => 'a155550f',
'phabricator-uiexample-reactor-sendclass' => '1def2711',
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
- 'phabricator-zindex-css' => 'ef044fae',
+ 'phabricator-zindex-css' => '8c8c40aa',
'phame-css' => '88bd4705',
'pholio-css' => '95174bdd',
'pholio-edit-css' => '3ad9d1ee',
@@ -771,17 +759,17 @@ return array(
'phui-box-css' => 'a5bb366d',
'phui-button-css' => 'de610129',
'phui-calendar-css' => '8675968e',
- 'phui-calendar-day-css' => '75b8cc4a',
+ 'phui-calendar-day-css' => '38891735',
'phui-calendar-list-css' => 'c1d0ca59',
- 'phui-calendar-month-css' => 'a92e47d2',
+ 'phui-calendar-month-css' => '75e6a2ee',
'phui-crumbs-view-css' => 'aeff7a21',
'phui-document-view-css' => '7b564cf6',
'phui-feed-story-css' => 'c9f3a0b5',
'phui-font-icon-base-css' => '3dad2ae3',
'phui-fontkit-css' => '1e71371a',
'phui-form-css' => 'f535f938',
- 'phui-form-view-css' => 'ddec8479',
- 'phui-header-view-css' => 'a1d2905a',
+ 'phui-form-view-css' => 'e1abbe8e',
+ 'phui-header-view-css' => '5c0c1c39',
'phui-icon-view-css' => 'bc766998',
'phui-image-mask-css' => '5a8b09c8',
'phui-info-panel-css' => '27ea50a1',
@@ -789,7 +777,7 @@ return array(
'phui-inline-comment-view-css' => '2174771a',
'phui-list-view-css' => '2e25ebfb',
'phui-object-box-css' => '3a601bc5',
- 'phui-object-item-list-view-css' => '172ea456',
+ 'phui-object-item-list-view-css' => 'c259c94f',
'phui-pinboard-view-css' => 'eaab2b1b',
'phui-property-list-view-css' => 'd2d143ea',
'phui-remarkup-preview-css' => '19ad512b',
@@ -797,7 +785,7 @@ return array(
'phui-status-list-view-css' => '888cedb8',
'phui-tag-view-css' => '402691cc',
'phui-text-css' => 'cf019f54',
- 'phui-timeline-view-css' => 'b0fbc4d7',
+ 'phui-timeline-view-css' => 'a85542c8',
'phui-workboard-view-css' => '3279cbbf',
'phui-workpanel-view-css' => 'e495a5cc',
'phuix-action-list-view' => 'b5c256b8',
@@ -862,6 +850,12 @@ return array(
'javelin-uri',
'phabricator-file-upload',
),
+ '087e919c' => array(
+ 'javelin-install',
+ 'javelin-dom',
+ 'javelin-stratcom',
+ 'javelin-vector',
+ ),
'0a3f3021' => array(
'javelin-behavior',
'javelin-stratcom',
@@ -901,6 +895,13 @@ return array(
'javelin-json',
'phabricator-draggable-list',
),
+ '14ac66f5' => array(
+ 'javelin-install',
+ 'javelin-dom',
+ 'javelin-vector',
+ 'javelin-request',
+ 'javelin-uri',
+ ),
'14d7a8b8' => array(
'javelin-behavior',
'javelin-behavior-device',
@@ -911,6 +912,16 @@ return array(
'javelin-request',
'javelin-util',
),
+ '16c695bf' => array(
+ 'javelin-behavior',
+ 'javelin-dom',
+ 'javelin-stratcom',
+ 'javelin-behavior-device',
+ 'javelin-scrollbar',
+ 'javelin-quicksand',
+ 'phabricator-keyboard-shortcut',
+ 'conpherence-thread-manager',
+ ),
'1ad0a787' => array(
'javelin-install',
'javelin-reactor',
@@ -970,14 +981,6 @@ return array(
'phabricator-drag-and-drop-file-upload',
'phabricator-draggable-list',
),
- '2529c82d' => array(
- 'javelin-dom',
- 'javelin-util',
- 'javelin-stratcom',
- 'javelin-install',
- 'javelin-request',
- 'javelin-workflow',
- ),
'2818f5ce' => array(
'javelin-install',
'javelin-util',
@@ -1018,13 +1021,6 @@ return array(
'javelin-install',
'javelin-event',
),
- '30a6303c' => array(
- 'javelin-install',
- 'javelin-util',
- 'javelin-websocket',
- 'javelin-leader',
- 'javelin-json',
- ),
'316b8fa1' => array(
'javelin-install',
'javelin-typeahead-source',
@@ -1069,6 +1065,20 @@ return array(
'javelin-dom',
'javelin-request',
),
+ '4351c4a0' => 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',
+ ),
'44168bad' => array(
'javelin-behavior',
'javelin-dom',
@@ -1151,6 +1161,13 @@ return array(
'javelin-dom',
'javelin-reactor-dom',
),
+ '5359e785' => array(
+ 'javelin-install',
+ 'javelin-util',
+ 'javelin-websocket',
+ 'javelin-leader',
+ 'javelin-json',
+ ),
'54b612ba' => array(
'javelin-color',
'javelin-install',
@@ -1236,15 +1253,6 @@ return array(
'javelin-workflow',
'javelin-stratcom',
),
- 60292820 => array(
- 'javelin-behavior',
- 'javelin-dom',
- 'javelin-util',
- 'javelin-vector',
- 'javelin-stratcom',
- 'javelin-workflow',
- 'phabricator-draggable-list',
- ),
60479091 => array(
'phabricator-busy',
'javelin-behavior',
@@ -1274,26 +1282,6 @@ return array(
'javelin-workflow',
'javelin-dom',
),
- '657c2b50' => array(
- 'javelin-behavior',
- 'javelin-dom',
- 'javelin-stratcom',
- 'javelin-behavior-device',
- 'javelin-scrollbar',
- 'javelin-quicksand',
- 'phabricator-keyboard-shortcut',
- 'conpherence-thread-manager',
- ),
- '6709c934' => array(
- 'javelin-dom',
- 'javelin-util',
- 'javelin-stratcom',
- 'javelin-install',
- 'javelin-workflow',
- 'javelin-router',
- 'javelin-behavior-device',
- 'javelin-vector',
- ),
'6882e80a' => array(
'javelin-dom',
),
@@ -1335,16 +1323,6 @@ return array(
'phabricator-drag-and-drop-file-upload',
'phabricator-textareautils',
),
- '6e1f0cba' => array(
- 'javelin-behavior',
- 'javelin-request',
- 'javelin-stratcom',
- 'javelin-vector',
- 'javelin-dom',
- 'javelin-uri',
- 'javelin-behavior-device',
- 'phabricator-title',
- ),
'6eff08aa' => array(
'javelin-install',
'javelin-util',
@@ -1415,13 +1393,6 @@ return array(
'7e41274a' => array(
'javelin-install',
),
- '7e8468ae' => array(
- 'javelin-install',
- 'javelin-dom',
- 'javelin-vector',
- 'javelin-request',
- 'javelin-uri',
- ),
'7ebaeed3' => array(
'herald-rule-editor',
'javelin-behavior',
@@ -1430,20 +1401,6 @@ return array(
'javelin-behavior',
'javelin-history',
),
- '804b0773' => 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',
- ),
82439934 => array(
'javelin-behavior',
'javelin-dom',
@@ -1734,6 +1691,26 @@ return array(
'javelin-dom',
'javelin-util',
),
+ 'b7342ddb' => array(
+ 'javelin-dom',
+ 'javelin-util',
+ 'javelin-stratcom',
+ 'javelin-install',
+ 'javelin-aphlict',
+ 'javelin-workflow',
+ 'javelin-router',
+ 'javelin-behavior-device',
+ 'javelin-vector',
+ ),
+ 'ba4fa35c' => array(
+ 'javelin-behavior',
+ 'javelin-dom',
+ 'javelin-util',
+ 'javelin-vector',
+ 'javelin-stratcom',
+ 'javelin-workflow',
+ 'phabricator-draggable-list',
+ ),
'bba9eedf' => array(
'javelin-behavior',
'javelin-stratcom',
@@ -1813,6 +1790,14 @@ return array(
'javelin-dom',
'javelin-view',
),
+ 'd4c87bf4' => array(
+ 'javelin-dom',
+ 'javelin-util',
+ 'javelin-stratcom',
+ 'javelin-install',
+ 'javelin-request',
+ 'javelin-workflow',
+ ),
'd4eecc63' => array(
'javelin-behavior',
'javelin-dom',
@@ -1847,6 +1832,16 @@ return array(
'df5e11d2' => array(
'javelin-install',
),
+ 'e09f6208' => array(
+ 'javelin-behavior',
+ 'javelin-request',
+ 'javelin-stratcom',
+ 'javelin-vector',
+ 'javelin-dom',
+ 'javelin-uri',
+ 'javelin-behavior-device',
+ 'phabricator-title',
+ ),
'e10f8e18' => array(
'javelin-behavior',
'javelin-dom',
@@ -1921,12 +1916,6 @@ return array(
'phabricator-phtize',
'javelin-dom',
),
- 'eaa5b321' => array(
- 'javelin-install',
- 'javelin-dom',
- 'javelin-stratcom',
- 'javelin-vector',
- ),
'efe49472' => array(
'javelin-install',
'javelin-util',
diff --git a/resources/sql/autopatches/20150507.calendar.1.isallday.sql b/resources/sql/autopatches/20150507.calendar.1.isallday.sql
new file mode 100644
index 0000000000..172015b1be
--- /dev/null
+++ b/resources/sql/autopatches/20150507.calendar.1.isallday.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_calendar.calendar_event
+ ADD isAllDay BOOL NOT NULL;
diff --git a/resources/sql/autopatches/20150513.user.cache.1.sql b/resources/sql/autopatches/20150513.user.cache.1.sql
new file mode 100644
index 0000000000..f6bf6e1e6a
--- /dev/null
+++ b/resources/sql/autopatches/20150513.user.cache.1.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_user.user
+ ADD profileImageCache VARCHAR(255) COLLATE {$COLLATE_TEXT};
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 8e19b001f3..0fa277df27 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -293,6 +293,7 @@ phutil_register_library_map(array(
'DifferentialActionEmailCommand' => 'applications/differential/command/DifferentialActionEmailCommand.php',
'DifferentialActionMenuEventListener' => 'applications/differential/event/DifferentialActionMenuEventListener.php',
'DifferentialAddCommentView' => 'applications/differential/view/DifferentialAddCommentView.php',
+ 'DifferentialAdjustmentMapTestCase' => 'applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php',
'DifferentialAffectedPath' => 'applications/differential/storage/DifferentialAffectedPath.php',
'DifferentialApplyPatchField' => 'applications/differential/customfield/DifferentialApplyPatchField.php',
'DifferentialArcanistProjectField' => 'applications/differential/customfield/DifferentialArcanistProjectField.php',
@@ -391,6 +392,7 @@ phutil_register_library_map(array(
'DifferentialLandingActionMenuEventListener' => 'applications/differential/landing/DifferentialLandingActionMenuEventListener.php',
'DifferentialLandingStrategy' => 'applications/differential/landing/DifferentialLandingStrategy.php',
'DifferentialLegacyHunk' => 'applications/differential/storage/DifferentialLegacyHunk.php',
+ 'DifferentialLineAdjustmentMap' => 'applications/differential/parser/DifferentialLineAdjustmentMap.php',
'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php',
'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php',
'DifferentialLocalCommitsView' => 'applications/differential/view/DifferentialLocalCommitsView.php',
@@ -1493,7 +1495,6 @@ phutil_register_library_map(array(
'PhabricatorCacheTTLGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php',
'PhabricatorCaches' => 'applications/cache/PhabricatorCaches.php',
'PhabricatorCalendarApplication' => 'applications/calendar/application/PhabricatorCalendarApplication.php',
- 'PhabricatorCalendarBrowseController' => 'applications/calendar/controller/PhabricatorCalendarBrowseController.php',
'PhabricatorCalendarController' => 'applications/calendar/controller/PhabricatorCalendarController.php',
'PhabricatorCalendarDAO' => 'applications/calendar/storage/PhabricatorCalendarDAO.php',
'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php',
@@ -1859,6 +1860,7 @@ phutil_register_library_map(array(
'PhabricatorFileFilePHIDType' => 'applications/files/phid/PhabricatorFileFilePHIDType.php',
'PhabricatorFileHasObjectEdgeType' => 'applications/files/edge/PhabricatorFileHasObjectEdgeType.php',
'PhabricatorFileImageMacro' => 'applications/macro/storage/PhabricatorFileImageMacro.php',
+ 'PhabricatorFileImageTransform' => 'applications/files/transform/PhabricatorFileImageTransform.php',
'PhabricatorFileInfoController' => 'applications/files/controller/PhabricatorFileInfoController.php',
'PhabricatorFileLinkListView' => 'view/layout/PhabricatorFileLinkListView.php',
'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php',
@@ -1872,10 +1874,13 @@ phutil_register_library_map(array(
'PhabricatorFileTemporaryGarbageCollector' => 'applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php',
'PhabricatorFileTestCase' => 'applications/files/storage/__tests__/PhabricatorFileTestCase.php',
'PhabricatorFileTestDataGenerator' => 'applications/files/lipsum/PhabricatorFileTestDataGenerator.php',
+ 'PhabricatorFileThumbnailTransform' => 'applications/files/transform/PhabricatorFileThumbnailTransform.php',
'PhabricatorFileTransaction' => 'applications/files/storage/PhabricatorFileTransaction.php',
'PhabricatorFileTransactionComment' => 'applications/files/storage/PhabricatorFileTransactionComment.php',
'PhabricatorFileTransactionQuery' => 'applications/files/query/PhabricatorFileTransactionQuery.php',
+ 'PhabricatorFileTransform' => 'applications/files/transform/PhabricatorFileTransform.php',
'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php',
+ 'PhabricatorFileTransformListController' => 'applications/files/controller/PhabricatorFileTransformListController.php',
'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php',
'PhabricatorFileUploadDialogController' => 'applications/files/controller/PhabricatorFileUploadDialogController.php',
'PhabricatorFileUploadException' => 'applications/files/exception/PhabricatorFileUploadException.php',
@@ -2823,7 +2828,6 @@ phutil_register_library_map(array(
'PholioImageUploadController' => 'applications/pholio/controller/PholioImageUploadController.php',
'PholioInlineController' => 'applications/pholio/controller/PholioInlineController.php',
'PholioInlineListController' => 'applications/pholio/controller/PholioInlineListController.php',
- 'PholioInlineThumbController' => 'applications/pholio/controller/PholioInlineThumbController.php',
'PholioMock' => 'applications/pholio/storage/PholioMock.php',
'PholioMockCommentController' => 'applications/pholio/controller/PholioMockCommentController.php',
'PholioMockEditController' => 'applications/pholio/controller/PholioMockEditController.php',
@@ -3530,6 +3534,7 @@ phutil_register_library_map(array(
'DifferentialActionEmailCommand' => 'MetaMTAEmailTransactionCommand',
'DifferentialActionMenuEventListener' => 'PhabricatorEventListener',
'DifferentialAddCommentView' => 'AphrontView',
+ 'DifferentialAdjustmentMapTestCase' => 'ArcanistPhutilTestCase',
'DifferentialAffectedPath' => 'DifferentialDAO',
'DifferentialApplyPatchField' => 'DifferentialCustomField',
'DifferentialArcanistProjectField' => 'DifferentialCustomField',
@@ -3632,6 +3637,7 @@ phutil_register_library_map(array(
'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField',
'DifferentialLandingActionMenuEventListener' => 'PhabricatorEventListener',
'DifferentialLegacyHunk' => 'DifferentialHunk',
+ 'DifferentialLineAdjustmentMap' => 'Phobject',
'DifferentialLintField' => 'DifferentialCustomField',
'DifferentialLocalCommitsView' => 'AphrontView',
'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
@@ -4516,7 +4522,7 @@ phutil_register_library_map(array(
'PHUIHandleListView' => 'AphrontTagView',
'PHUIHandleTagListView' => 'AphrontTagView',
'PHUIHandleView' => 'AphrontView',
- 'PHUIHeaderView' => 'AphrontView',
+ 'PHUIHeaderView' => 'AphrontTagView',
'PHUIIconExample' => 'PhabricatorUIExample',
'PHUIIconView' => 'AphrontTagView',
'PHUIImageMaskExample' => 'PhabricatorUIExample',
@@ -4835,7 +4841,6 @@ phutil_register_library_map(array(
'PhabricatorCacheSpec' => 'Phobject',
'PhabricatorCacheTTLGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorCalendarApplication' => 'PhabricatorApplication',
- 'PhabricatorCalendarBrowseController' => 'PhabricatorCalendarController',
'PhabricatorCalendarController' => 'PhabricatorController',
'PhabricatorCalendarDAO' => 'PhabricatorLiskDAO',
'PhabricatorCalendarEvent' => array(
@@ -5261,6 +5266,7 @@ phutil_register_library_map(array(
'PhabricatorFlaggableInterface',
'PhabricatorPolicyInterface',
),
+ 'PhabricatorFileImageTransform' => 'PhabricatorFileTransform',
'PhabricatorFileInfoController' => 'PhabricatorFileController',
'PhabricatorFileLinkListView' => 'AphrontView',
'PhabricatorFileLinkView' => 'AphrontView',
@@ -5273,10 +5279,13 @@ phutil_register_library_map(array(
'PhabricatorFileTemporaryGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorFileTestCase' => 'PhabricatorTestCase',
'PhabricatorFileTestDataGenerator' => 'PhabricatorTestDataGenerator',
+ 'PhabricatorFileThumbnailTransform' => 'PhabricatorFileImageTransform',
'PhabricatorFileTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorFileTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorFileTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'PhabricatorFileTransform' => 'Phobject',
'PhabricatorFileTransformController' => 'PhabricatorFileController',
+ 'PhabricatorFileTransformListController' => 'PhabricatorFileController',
'PhabricatorFileUploadController' => 'PhabricatorFileController',
'PhabricatorFileUploadDialogController' => 'PhabricatorFileController',
'PhabricatorFileUploadException' => 'Exception',
@@ -6309,7 +6318,6 @@ phutil_register_library_map(array(
'PholioImageUploadController' => 'PholioController',
'PholioInlineController' => 'PholioController',
'PholioInlineListController' => 'PholioController',
- 'PholioInlineThumbController' => 'PholioController',
'PholioMock' => array(
'PholioDAO',
'PhabricatorMarkupInterface',
diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php
index 197e5a437c..ab6e0d9820 100644
--- a/src/aphront/AphrontRequest.php
+++ b/src/aphront/AphrontRequest.php
@@ -650,7 +650,7 @@ final class AphrontRequest {
* safe.
*/
public function isProxiedClusterRequest() {
- return (bool)AphrontRequest::getHTTPHeader('X-Phabricator-Cluster');
+ return (bool)self::getHTTPHeader('X-Phabricator-Cluster');
}
diff --git a/src/aphront/response/AphrontProxyResponse.php b/src/aphront/response/AphrontProxyResponse.php
index 7e27ea2b1b..175f2d2c53 100644
--- a/src/aphront/response/AphrontProxyResponse.php
+++ b/src/aphront/response/AphrontProxyResponse.php
@@ -65,7 +65,10 @@ abstract class AphrontProxyResponse extends AphrontResponse {
final public function buildResponseString() {
throw new Exception(
- 'AphrontProxyResponse must implement reduceProxyResponse().');
+ pht(
+ '%s must implement %s.',
+ __CLASS__,
+ 'reduceProxyResponse()'));
}
}
diff --git a/src/aphront/response/AphrontResponse.php b/src/aphront/response/AphrontResponse.php
index 0bbe22b5ec..b1e72f5529 100644
--- a/src/aphront/response/AphrontResponse.php
+++ b/src/aphront/response/AphrontResponse.php
@@ -154,7 +154,7 @@ abstract class AphrontResponse {
array_walk_recursive(
$object,
- array('AphrontResponse', 'processValueForJSONEncoding'));
+ array(__CLASS__, 'processValueForJSONEncoding'));
$response = json_encode($object);
diff --git a/src/applications/audit/constants/PhabricatorAuditStatusConstants.php b/src/applications/audit/constants/PhabricatorAuditStatusConstants.php
index e1a4378ab3..78924905b9 100644
--- a/src/applications/audit/constants/PhabricatorAuditStatusConstants.php
+++ b/src/applications/audit/constants/PhabricatorAuditStatusConstants.php
@@ -60,19 +60,19 @@ final class PhabricatorAuditStatusConstants {
public static function getStatusIcon($code) {
switch ($code) {
- case PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED:
- case PhabricatorAuditStatusConstants::RESIGNED:
+ case self::AUDIT_NOT_REQUIRED:
+ case self::RESIGNED:
$icon = PHUIStatusItemView::ICON_OPEN;
break;
- case PhabricatorAuditStatusConstants::AUDIT_REQUIRED:
- case PhabricatorAuditStatusConstants::AUDIT_REQUESTED:
+ case self::AUDIT_REQUIRED:
+ case self::AUDIT_REQUESTED:
$icon = PHUIStatusItemView::ICON_WARNING;
break;
- case PhabricatorAuditStatusConstants::CONCERNED:
+ case self::CONCERNED:
$icon = PHUIStatusItemView::ICON_REJECT;
break;
- case PhabricatorAuditStatusConstants::ACCEPTED:
- case PhabricatorAuditStatusConstants::CLOSED:
+ case self::ACCEPTED:
+ case self::CLOSED:
$icon = PHUIStatusItemView::ICON_ACCEPT;
break;
default:
diff --git a/src/applications/audit/storage/PhabricatorAuditInlineComment.php b/src/applications/audit/storage/PhabricatorAuditInlineComment.php
index 8c0aa78d5d..292e0274a5 100644
--- a/src/applications/audit/storage/PhabricatorAuditInlineComment.php
+++ b/src/applications/audit/storage/PhabricatorAuditInlineComment.php
@@ -115,7 +115,7 @@ final class PhabricatorAuditInlineComment
private static function buildProxies(array $inlines) {
$results = array();
foreach ($inlines as $key => $inline) {
- $results[$key] = PhabricatorAuditInlineComment::newFromModernComment(
+ $results[$key] = self::newFromModernComment(
$inline);
}
return $results;
diff --git a/src/applications/auth/controller/PhabricatorAuthRegisterController.php b/src/applications/auth/controller/PhabricatorAuthRegisterController.php
index 6c40375227..d27a644480 100644
--- a/src/applications/auth/controller/PhabricatorAuthRegisterController.php
+++ b/src/applications/auth/controller/PhabricatorAuthRegisterController.php
@@ -604,17 +604,9 @@ final class PhabricatorAuthRegisterController
return null;
}
- try {
- $xformer = new PhabricatorImageTransformer();
- return $xformer->executeProfileTransform(
- $file,
- $width = 50,
- $min_height = 50,
- $max_height = 50);
- } catch (Exception $ex) {
- phlog($ex);
- return null;
- }
+ $xform = PhabricatorFileTransform::getTransformByKey(
+ PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE);
+ return $xform->executeTransform($file);
}
protected function renderError($message) {
diff --git a/src/applications/auth/provider/PhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorAuthProvider.php
index b1b064c363..5ffb18b41a 100644
--- a/src/applications/auth/provider/PhabricatorAuthProvider.php
+++ b/src/applications/auth/provider/PhabricatorAuthProvider.php
@@ -15,8 +15,7 @@ abstract class PhabricatorAuthProvider {
public function getProviderConfig() {
if ($this->providerConfig === null) {
- throw new Exception(
- 'Call attachProviderConfig() before getProviderConfig()!');
+ throw new PhutilInvalidStateException('attachProviderConfig');
}
return $this->providerConfig;
}
diff --git a/src/applications/auth/view/PhabricatorAuthAccountView.php b/src/applications/auth/view/PhabricatorAuthAccountView.php
index a68cdfff07..8eb73144aa 100644
--- a/src/applications/auth/view/PhabricatorAuthAccountView.php
+++ b/src/applications/auth/view/PhabricatorAuthAccountView.php
@@ -89,15 +89,28 @@ final class PhabricatorAuthAccountView extends AphrontView {
$account_uri);
}
- $image_uri = $account->getProfileImageFile()->getProfileThumbURI();
+ $image_file = $account->getProfileImageFile();
+ $xform = PhabricatorFileTransform::getTransformByKey(
+ PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE);
+ $image_uri = $image_file->getURIForTransform($xform);
+ list($x, $y) = $xform->getTransformedDimensions($image_file);
+
+ $profile_image = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'auth-account-view-profile-image',
+ 'style' => 'background-image: url('.$image_uri.');',
+ ));
return phutil_tag(
'div',
array(
'class' => 'auth-account-view',
- 'style' => 'background-image: url('.$image_uri.')',
),
- $content);
+ array(
+ $profile_image,
+ $content,
+ ));
}
}
diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php
index d5237adc11..295d4db180 100644
--- a/src/applications/base/PhabricatorApplication.php
+++ b/src/applications/base/PhabricatorApplication.php
@@ -361,7 +361,7 @@ abstract class PhabricatorApplication implements PhabricatorPolicyInterface {
public static function getByClass($class_name) {
$selected = null;
- $applications = PhabricatorApplication::getAllApplications();
+ $applications = self::getAllApplications();
foreach ($applications as $application) {
if (get_class($application) == $class_name) {
diff --git a/src/applications/cache/PhabricatorCaches.php b/src/applications/cache/PhabricatorCaches.php
index 37708457b7..4e3af7a6a4 100644
--- a/src/applications/cache/PhabricatorCaches.php
+++ b/src/applications/cache/PhabricatorCaches.php
@@ -270,7 +270,7 @@ final class PhabricatorCaches {
}
private static function addNamespaceToCaches(array $caches) {
- $namespace = PhabricatorCaches::getNamespace();
+ $namespace = self::getNamespace();
if (!$namespace) {
return $caches;
}
diff --git a/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php b/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php
deleted file mode 100644
index ba2017cba9..0000000000
--- a/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php
+++ /dev/null
@@ -1,97 +0,0 @@
-getViewer();
-
- $now = time();
- $year_d = phabricator_format_local_time($now, $viewer, 'Y');
- $year = $request->getInt('year', $year_d);
- $month_d = phabricator_format_local_time($now, $viewer, 'm');
- $month = $request->getInt('month', $month_d);
- $day = phabricator_format_local_time($now, $viewer, 'j');
-
- $holidays = id(new PhabricatorCalendarHoliday())->loadAllWhere(
- 'day BETWEEN %s AND %s',
- "{$year}-{$month}-01",
- "{$year}-{$month}-31");
-
- $statuses = id(new PhabricatorCalendarEventQuery())
- ->setViewer($viewer)
- ->withDateRange(
- strtotime("{$year}-{$month}-01"),
- strtotime("{$year}-{$month}-01 next month"))
- ->execute();
-
- if ($month == $month_d && $year == $year_d) {
- $month_view = new PHUICalendarMonthView($month, $year, $day);
- } else {
- $month_view = new PHUICalendarMonthView($month, $year);
- }
-
- $month_view->setBrowseURI($request->getRequestURI());
- $month_view->setUser($viewer);
- $month_view->setHolidays($holidays);
-
- $phids = mpull($statuses, 'getUserPHID');
- $handles = $viewer->loadHandles($phids);
-
- /* Assign Colors */
- $unique = array_unique($phids);
- $allblue = false;
- $calcolors = CalendarColors::getColors();
- if (count($unique) > count($calcolors)) {
- $allblue = true;
- }
- $i = 0;
- $eventcolor = array();
- foreach ($unique as $phid) {
- if ($allblue) {
- $eventcolor[$phid] = CalendarColors::COLOR_SKY;
- } else {
- $eventcolor[$phid] = $calcolors[$i];
- }
- $i++;
- }
-
- foreach ($statuses as $status) {
- $event = new AphrontCalendarEventView();
- $event->setEpochRange($status->getDateFrom(), $status->getDateTo());
-
- $name_text = $handles[$status->getUserPHID()]->getName();
- $status_text = $status->getHumanStatus();
- $event->setUserPHID($status->getUserPHID());
- $event->setDescription(pht('%s (%s)', $name_text, $status_text));
- $event->setName($status_text);
- $event->setEventID($status->getID());
- $event->setColor($eventcolor[$status->getUserPHID()]);
- $month_view->addEvent($event);
- }
-
- $date = new DateTime("{$year}-{$month}-01");
- $crumbs = $this->buildApplicationCrumbs();
- $crumbs->addTextCrumb(pht('All Events'));
- $crumbs->addTextCrumb($date->format('F Y'));
-
- $nav = $this->buildSideNavView();
- $nav->selectFilter('all/');
- $nav->appendChild(
- array(
- $crumbs,
- $month_view,
- ));
-
- return $this->buildApplicationPage(
- $nav,
- array(
- 'title' => pht('Calendar'),
- ));
- }
-
-}
diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php
index 38d12b099b..e31b9bdf0e 100644
--- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php
+++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php
@@ -74,6 +74,7 @@ final class PhabricatorCalendarEventEditController
$name = $event->getName();
$description = $event->getDescription();
$type = $event->getStatus();
+ $is_all_day = $event->getIsAllDay();
$current_policies = id(new PhabricatorPolicyQuery())
->setViewer($user)
@@ -95,6 +96,7 @@ final class PhabricatorCalendarEventEditController
$subscribers = $request->getArr('subscribers');
$edit_policy = $request->getStr('editPolicy');
$view_policy = $request->getStr('viewPolicy');
+ $is_all_day = $request->getStr('isAllDay');
$invitees = $request->getArr('invitees');
$new_invitees = $this->getNewInviteeList($invitees, $event);
@@ -111,6 +113,11 @@ final class PhabricatorCalendarEventEditController
PhabricatorCalendarEventTransaction::TYPE_NAME)
->setNewValue($name);
+ $xactions[] = id(new PhabricatorCalendarEventTransaction())
+ ->setTransactionType(
+ PhabricatorCalendarEventTransaction::TYPE_ALL_DAY)
+ ->setNewValue($is_all_day);
+
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_START_DATE)
@@ -172,6 +179,16 @@ final class PhabricatorCalendarEventEditController
}
}
+ $all_day_id = celerity_generate_unique_node_id();
+ $start_date_id = celerity_generate_unique_node_id();
+ $end_date_id = celerity_generate_unique_node_id();
+
+ Javelin::initBehavior('event-all-day', array(
+ 'allDayID' => $all_day_id,
+ 'startDateID' => $start_date_id,
+ 'endDateID' => $end_date_id,
+ ));
+
$name = id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
@@ -184,19 +201,31 @@ final class PhabricatorCalendarEventEditController
->setValue($type)
->setOptions($event->getStatusOptions());
+ $all_day_checkbox = id(new AphrontFormCheckboxControl())
+ ->addCheckbox(
+ 'isAllDay',
+ 1,
+ pht('All Day Event'),
+ $is_all_day,
+ $all_day_id);
+
$start_control = id(new AphrontFormDateControl())
->setUser($user)
->setName('start')
->setLabel(pht('Start'))
->setError($error_start_date)
- ->setValue($start_value);
+ ->setValue($start_value)
+ ->setID($start_date_id)
+ ->setIsTimeDisabled($is_all_day);
$end_control = id(new AphrontFormDateControl())
->setUser($user)
->setName('end')
->setLabel(pht('End'))
->setError($error_end_date)
- ->setValue($end_value);
+ ->setValue($end_value)
+ ->setID($end_date_id)
+ ->setIsTimeDisabled($is_all_day);
$description = id(new AphrontFormTextAreaControl())
->setLabel(pht('Description'))
@@ -234,6 +263,7 @@ final class PhabricatorCalendarEventEditController
->setUser($user)
->appendChild($name)
->appendChild($status_select)
+ ->appendChild($all_day_checkbox)
->appendChild($start_control)
->appendChild($end_control)
->appendControl($view_policies)
diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php
index 6f6fa66e6e..742ec95e3f 100644
--- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php
+++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php
@@ -179,13 +179,31 @@ final class PhabricatorCalendarEventViewController
->setUser($viewer)
->setObject($event);
- $properties->addProperty(
- pht('Starts'),
- phabricator_datetime($event->getDateFrom(), $viewer));
+ if ($event->getIsAllDay()) {
+ $date_start = phabricator_date($event->getDateFrom(), $viewer);
+ $date_end = phabricator_date($event->getDateTo(), $viewer);
- $properties->addProperty(
- pht('Ends'),
- phabricator_datetime($event->getDateTo(), $viewer));
+ if ($date_start == $date_end) {
+ $properties->addProperty(
+ pht('Time'),
+ phabricator_date($event->getDateFrom(), $viewer));
+ } else {
+ $properties->addProperty(
+ pht('Starts'),
+ phabricator_date($event->getDateFrom(), $viewer));
+ $properties->addProperty(
+ pht('Ends'),
+ phabricator_date($event->getDateTo(), $viewer));
+ }
+ } else {
+ $properties->addProperty(
+ pht('Starts'),
+ phabricator_datetime($event->getDateFrom(), $viewer));
+
+ $properties->addProperty(
+ pht('Ends'),
+ phabricator_datetime($event->getDateTo(), $viewer));
+ }
$properties->addProperty(
pht('Host'),
diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
index 09ab1309eb..6e80d19236 100644
--- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
+++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
@@ -21,6 +21,7 @@ final class PhabricatorCalendarEventEditor
$types[] = PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION;
$types[] = PhabricatorCalendarEventTransaction::TYPE_CANCEL;
$types[] = PhabricatorCalendarEventTransaction::TYPE_INVITE;
+ $types[] = PhabricatorCalendarEventTransaction::TYPE_ALL_DAY;
$types[] = PhabricatorTransactions::TYPE_COMMENT;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
@@ -49,6 +50,8 @@ final class PhabricatorCalendarEventEditor
return $object->getDescription();
case PhabricatorCalendarEventTransaction::TYPE_CANCEL:
return $object->getIsCancelled();
+ case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY:
+ return (int)$object->getIsAllDay();
case PhabricatorCalendarEventTransaction::TYPE_INVITE:
$map = $xaction->getNewValue();
$phids = array_keys($map);
@@ -87,6 +90,8 @@ final class PhabricatorCalendarEventEditor
case PhabricatorCalendarEventTransaction::TYPE_CANCEL:
case PhabricatorCalendarEventTransaction::TYPE_INVITE:
return $xaction->getNewValue();
+ case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY:
+ return (int)$xaction->getNewValue();
case PhabricatorCalendarEventTransaction::TYPE_STATUS:
return (int)$xaction->getNewValue();
case PhabricatorCalendarEventTransaction::TYPE_START_DATE:
@@ -120,6 +125,9 @@ final class PhabricatorCalendarEventEditor
case PhabricatorCalendarEventTransaction::TYPE_CANCEL:
$object->setIsCancelled((int)$xaction->getNewValue());
return;
+ case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY:
+ $object->setIsAllDay((int)$xaction->getNewValue());
+ return;
case PhabricatorCalendarEventTransaction::TYPE_INVITE:
case PhabricatorTransactions::TYPE_COMMENT:
case PhabricatorTransactions::TYPE_VIEW_POLICY:
@@ -143,6 +151,7 @@ final class PhabricatorCalendarEventEditor
case PhabricatorCalendarEventTransaction::TYPE_STATUS:
case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION:
case PhabricatorCalendarEventTransaction::TYPE_CANCEL:
+ case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY:
return;
case PhabricatorCalendarEventTransaction::TYPE_INVITE:
$map = $xaction->getNewValue();
@@ -175,6 +184,16 @@ final class PhabricatorCalendarEventEditor
return parent::applyCustomExternalTransaction($object, $xaction);
}
+ protected function didApplyInternalEffects(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ $object->removeViewerTimezone($this->requireActor());
+
+ return $xactions;
+ }
+
+
protected function validateAllTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
index 1fd294781f..c66e367793 100644
--- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
+++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
@@ -56,7 +56,13 @@ final class PhabricatorCalendarEventQuery
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
- return $table->loadAllFromArray($data);
+ $events = $table->loadAllFromArray($data);
+
+ foreach ($events as $event) {
+ $event->applyViewerTimezone($this->getViewer());
+ }
+
+ return $events;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) {
diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
index b525420ca3..c8a788adb8 100644
--- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
+++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
@@ -57,8 +57,8 @@ final class PhabricatorCalendarEventSearchEngine
$min_range = $this->getDateFrom($saved)->getEpoch();
$max_range = $this->getDateTo($saved)->getEpoch();
- if ($saved->getParameter('display') == 'month' ||
- $saved->getParameter('display') == 'day') {
+ if ($this->isMonthView($saved) ||
+ $this->isDayView($saved)) {
list($start_year, $start_month, $start_day) =
$this->getDisplayYearAndMonthAndDay($saved);
@@ -67,20 +67,42 @@ final class PhabricatorCalendarEventSearchEngine
$timezone);
$next = clone $start_day;
- if ($saved->getParameter('display') == 'month') {
+ if ($this->isMonthView($saved)) {
$next->modify('+1 month');
- } else if ($saved->getParameter('display') == 'day') {
+ } else if ($this->isDayView($saved)) {
$next->modify('+6 day');
}
$display_start = $start_day->format('U');
$display_end = $next->format('U');
+ // 0 = Sunday is always the start of the week, for now
+ $start_of_week = 0;
+ $end_of_week = 6 - $start_of_week;
+
+ $first_of_month = $start_day->format('w');
+ $last_of_month = id(clone $next)->modify('-1 day')->format('w');
+
if (!$min_range || ($min_range < $display_start)) {
$min_range = $display_start;
+
+ if ($this->isMonthView($saved) &&
+ $first_of_month > $start_of_week) {
+ $min_range = id(clone $start_day)
+ ->modify('-'.$first_of_month.' days')
+ ->format('U');
+ }
}
if (!$max_range || ($max_range > $display_end)) {
$max_range = $display_end;
+
+ if ($this->isMonthView($saved) &&
+ $last_of_month < $end_of_week) {
+ $max_range = id(clone $next)
+ ->modify('+'.(6 - $first_of_month).' days')
+ ->format('U');
+ }
+
}
}
@@ -220,6 +242,7 @@ final class PhabricatorCalendarEventSearchEngine
protected function getBuiltinQueryNames() {
$names = array(
'month' => pht('Month View'),
+ 'day' => pht('Day View'),
'upcoming' => pht('Upcoming Events'),
'all' => pht('All Events'),
);
@@ -242,6 +265,8 @@ final class PhabricatorCalendarEventSearchEngine
switch ($query_key) {
case 'month':
return $query->setParameter('display', 'month');
+ case 'day':
+ return $query->setParameter('display', 'day');
case 'upcoming':
return $query->setParameter('upcoming', true);
case 'all':
@@ -266,9 +291,9 @@ final class PhabricatorCalendarEventSearchEngine
PhabricatorSavedQuery $query,
array $handles) {
- if ($query->getParameter('display') == 'month') {
+ if ($this->isMonthView($query)) {
return $this->buildCalendarView($events, $query, $handles);
- } else if ($query->getParameter('display') == 'day') {
+ } else if ($this->isDayView($query)) {
return $this->buildCalendarDayView($events, $query, $handles);
}
@@ -281,15 +306,12 @@ final class PhabricatorCalendarEventSearchEngine
$to = phabricator_datetime($event->getDateTo(), $viewer);
$creator_handle = $handles[$event->getUserPHID()];
- $name = (strlen($event->getName())) ?
- $event->getName() : $event->getTerseSummary($viewer);
-
$color = ($event->getStatus() == PhabricatorCalendarEvent::STATUS_AWAY)
? 'red'
: 'yellow';
$item = id(new PHUIObjectItemView())
- ->setHeader($name)
+ ->setHeader($event->getName())
->setHref($href)
->setBarColor($color)
->addByline(pht('Creator: %s', $creator_handle->renderLink()))
@@ -320,11 +342,15 @@ final class PhabricatorCalendarEventSearchEngine
if ($start_month == $now_month && $start_year == $now_year) {
$month_view = new PHUICalendarMonthView(
+ $this->getDateFrom($query),
+ $this->getDateTo($query),
$start_month,
$start_year,
$now_day);
} else {
$month_view = new PHUICalendarMonthView(
+ $this->getDateFrom($query),
+ $this->getDateTo($query),
$start_month,
$start_year);
}
@@ -354,9 +380,10 @@ final class PhabricatorCalendarEventSearchEngine
foreach ($statuses as $status) {
$event = new AphrontCalendarEventView();
$event->setEpochRange($status->getDateFrom(), $status->getDateTo());
+ $event->setIsAllDay($status->getIsAllDay());
$name_text = $handles[$status->getUserPHID()]->getName();
- $status_text = $status->getHumanStatus();
+ $status_text = $status->getName();
$event->setUserPHID($status->getUserPHID());
$event->setDescription(pht('%s (%s)', $name_text, $status_text));
$event->setName($status_text);
@@ -380,6 +407,8 @@ final class PhabricatorCalendarEventSearchEngine
$this->getDisplayYearAndMonthAndDay($query);
$day_view = new PHUICalendarDayView(
+ $this->getDateFrom($query),
+ $this->getDateTo($query),
$start_year,
$start_month,
$start_day);
@@ -389,9 +418,14 @@ final class PhabricatorCalendarEventSearchEngine
$phids = mpull($statuses, 'getUserPHID');
foreach ($statuses as $status) {
+ if ($status->getIsCancelled()) {
+ continue;
+ }
+
$event = new AphrontCalendarEventView();
$event->setEventID($status->getID());
$event->setEpochRange($status->getDateFrom(), $status->getDateTo());
+ $event->setIsAllDay($status->getIsAllDay());
$event->setName($status->getName());
$event->setURI('/'.$status->getMonogram());
@@ -419,9 +453,14 @@ final class PhabricatorCalendarEventSearchEngine
$epoch = time();
}
}
+ if ($this->isMonthView($query)) {
+ $day = 1;
+ } else {
+ $day = phabricator_format_local_time($epoch, $viewer, 'd');
+ }
$start_year = phabricator_format_local_time($epoch, $viewer, 'Y');
$start_month = phabricator_format_local_time($epoch, $viewer, 'm');
- $start_day = phabricator_format_local_time($epoch, $viewer, 'd');
+ $start_day = $day;
}
return array($start_year, $start_month, $start_day);
}
@@ -456,4 +495,23 @@ final class PhabricatorCalendarEventSearchEngine
return $value;
}
+ private function isMonthView(PhabricatorSavedQuery $query) {
+ if ($this->isDayView($query)) {
+ return false;
+ }
+ if ($query->getParameter('display') == 'month') {
+ return true;
+ }
+ }
+
+ private function isDayView(PhabricatorSavedQuery $query) {
+ if ($query->getParameter('display') == 'day') {
+ return true;
+ }
+ if ($this->calendarDay) {
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
index 03da6851a5..3dd984ef7d 100644
--- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php
+++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
@@ -17,12 +17,14 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
protected $status;
protected $description;
protected $isCancelled;
+ protected $isAllDay;
protected $mailKey;
protected $viewPolicy;
protected $editPolicy;
private $invitees = self::ATTACHABLE;
+ private $appliedViewer;
const STATUS_AWAY = 1;
const STATUS_SPORADIC = 2;
@@ -36,15 +38,112 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
return id(new PhabricatorCalendarEvent())
->setUserPHID($actor->getPHID())
->setIsCancelled(0)
+ ->setIsAllDay(0)
->setViewPolicy($actor->getPHID())
->setEditPolicy($actor->getPHID())
- ->attachInvitees(array());
+ ->attachInvitees(array())
+ ->applyViewerTimezone($actor);
+ }
+
+ public function applyViewerTimezone(PhabricatorUser $viewer) {
+ if ($this->appliedViewer) {
+ throw new Exception(pht('Viewer timezone is already applied!'));
+ }
+
+ $this->appliedViewer = $viewer;
+
+ if (!$this->getIsAllDay()) {
+ return $this;
+ }
+
+ $zone = $viewer->getTimeZone();
+
+
+ $this->setDateFrom(
+ $this->getDateEpochForTimeZone(
+ $this->getDateFrom(),
+ new DateTimeZone('Pacific/Kiritimati'),
+ 'Y-m-d',
+ null,
+ $zone));
+
+ $this->setDateTo(
+ $this->getDateEpochForTimeZone(
+ $this->getDateTo(),
+ new DateTimeZone('Pacific/Midway'),
+ 'Y-m-d 23:59:00',
+ '-1 day',
+ $zone));
+
+ return $this;
+ }
+
+
+ public function removeViewerTimezone(PhabricatorUser $viewer) {
+ if (!$this->appliedViewer) {
+ throw new Exception(pht('Viewer timezone is not applied!'));
+ }
+
+ if ($viewer->getPHID() != $this->appliedViewer->getPHID()) {
+ throw new Exception(pht('Removed viewer must match applied viewer!'));
+ }
+
+ $this->appliedViewer = null;
+
+ if (!$this->getIsAllDay()) {
+ return $this;
+ }
+
+ $zone = $viewer->getTimeZone();
+
+ $this->setDateFrom(
+ $this->getDateEpochForTimeZone(
+ $this->getDateFrom(),
+ $zone,
+ 'Y-m-d',
+ null,
+ new DateTimeZone('Pacific/Kiritimati')));
+
+ $this->setDateTo(
+ $this->getDateEpochForTimeZone(
+ $this->getDateTo(),
+ $zone,
+ 'Y-m-d',
+ '+1 day',
+ new DateTimeZone('Pacific/Midway')));
+
+ return $this;
+ }
+
+ private function getDateEpochForTimeZone(
+ $epoch,
+ $src_zone,
+ $format,
+ $adjust,
+ $dst_zone) {
+
+ $src = new DateTime('@'.$epoch);
+ $src->setTimeZone($src_zone);
+
+ if (strlen($adjust)) {
+ $adjust = ' '.$adjust;
+ }
+
+ $dst = new DateTime($src->format($format).$adjust, $dst_zone);
+ return $dst->format('U');
}
public function save() {
+ if ($this->appliedViewer) {
+ throw new Exception(
+ pht(
+ 'Can not save event with viewer timezone still applied!'));
+ }
+
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
+
return parent::save();
}
@@ -69,11 +168,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
);
}
- public function getHumanStatus() {
- $options = $this->getStatusOptions();
- return $options[$this->status];
- }
-
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
@@ -84,6 +178,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
'status' => 'uint32',
'description' => 'text',
'isCancelled' => 'bool',
+ 'isAllDay' => 'bool',
'mailKey' => 'bytes20',
),
self::CONFIG_KEY_SCHEMA => array(
@@ -105,7 +200,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
public function getTerseSummary(PhabricatorUser $viewer) {
$until = phabricator_date($this->dateTo, $viewer);
- if ($this->status == PhabricatorCalendarEvent::STATUS_SPORADIC) {
+ if ($this->status == self::STATUS_SPORADIC) {
return pht('Sporadic until %s', $until);
} else {
return pht('Away until %s', $until);
diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php
index 7e4e7ff512..d3e7a25efd 100644
--- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php
+++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php
@@ -9,6 +9,7 @@ final class PhabricatorCalendarEventTransaction
const TYPE_STATUS = 'calendar.status';
const TYPE_DESCRIPTION = 'calendar.description';
const TYPE_CANCEL = 'calendar.cancel';
+ const TYPE_ALL_DAY = 'calendar.allday';
const TYPE_INVITE = 'calendar.invite';
const MAILTAG_RESCHEDULE = 'calendar-reschedule';
@@ -37,6 +38,7 @@ final class PhabricatorCalendarEventTransaction
case self::TYPE_STATUS:
case self::TYPE_DESCRIPTION:
case self::TYPE_CANCEL:
+ case self::TYPE_ALL_DAY:
$phids[] = $this->getObjectPHID();
break;
case self::TYPE_INVITE:
@@ -58,6 +60,7 @@ final class PhabricatorCalendarEventTransaction
case self::TYPE_STATUS:
case self::TYPE_DESCRIPTION:
case self::TYPE_CANCEL:
+ case self::TYPE_ALL_DAY:
case self::TYPE_INVITE:
return ($old === null);
}
@@ -71,6 +74,7 @@ final class PhabricatorCalendarEventTransaction
case self::TYPE_END_DATE:
case self::TYPE_STATUS:
case self::TYPE_DESCRIPTION:
+ case self::TYPE_ALL_DAY:
case self::TYPE_CANCEL:
return 'fa-pencil';
break;
@@ -128,6 +132,16 @@ final class PhabricatorCalendarEventTransaction
return pht(
"%s updated the event's description.",
$this->renderHandleLink($author_phid));
+ case self::TYPE_ALL_DAY:
+ if ($new) {
+ return pht(
+ '%s made this an all day event.',
+ $this->renderHandleLink($author_phid));
+ } else {
+ return pht(
+ '%s converted this from an all day event.',
+ $this->renderHandleLink($author_phid));
+ }
case self::TYPE_CANCEL:
if ($new) {
return pht(
@@ -287,6 +301,18 @@ final class PhabricatorCalendarEventTransaction
'%s updated the description of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
+ case self::TYPE_ALL_DAY:
+ if ($new) {
+ return pht(
+ '%s made %s an all day event.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+ } else {
+ return pht(
+ '%s converted %s from an all day event.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+ }
case self::TYPE_CANCEL:
if ($new) {
return pht(
diff --git a/src/applications/calendar/view/AphrontCalendarEventView.php b/src/applications/calendar/view/AphrontCalendarEventView.php
index aebfaa9a50..11dbff3847 100644
--- a/src/applications/calendar/view/AphrontCalendarEventView.php
+++ b/src/applications/calendar/view/AphrontCalendarEventView.php
@@ -10,6 +10,7 @@ final class AphrontCalendarEventView extends AphrontView {
private $eventID;
private $color;
private $uri;
+ private $isAllDay;
public function setURI($uri) {
$this->uri = $uri;
@@ -81,14 +82,16 @@ final class AphrontCalendarEventView extends AphrontView {
}
}
- public function getAllDay() {
- $time = (60 * 60 * 22);
- if (($this->getEpochEnd() - $this->getEpochStart()) >= $time) {
- return true;
- }
- return false;
+ public function setIsAllDay($is_all_day) {
+ $this->isAllDay = $is_all_day;
+ return $this;
}
+ public function getIsAllDay() {
+ return $this->isAllDay;
+ }
+
+
public function getMultiDay() {
$nextday = strtotime('12:00 AM Tomorrow', $this->getEpochStart());
if ($this->getEpochEnd() > $nextday) {
diff --git a/src/applications/celerity/CelerityResourceGraph.php b/src/applications/celerity/CelerityResourceGraph.php
index a85c3ffbd8..715a64c35c 100644
--- a/src/applications/celerity/CelerityResourceGraph.php
+++ b/src/applications/celerity/CelerityResourceGraph.php
@@ -7,9 +7,7 @@ final class CelerityResourceGraph extends AbstractDirectedGraph {
protected function loadEdges(array $nodes) {
if (!$this->graphSet) {
- throw new Exception(
- 'Call setResourceGraph before loading the graph!'
- );
+ throw new PhutilInvalidStateException('setResourceGraph');
}
$graph = $this->getResourceGraph();
diff --git a/src/applications/celerity/resources/CelerityPhysicalResources.php b/src/applications/celerity/resources/CelerityPhysicalResources.php
index 995b9f1a55..41665d15f2 100644
--- a/src/applications/celerity/resources/CelerityPhysicalResources.php
+++ b/src/applications/celerity/resources/CelerityPhysicalResources.php
@@ -25,7 +25,7 @@ abstract class CelerityPhysicalResources extends CelerityResources {
$resources_map = array();
$resources_list = id(new PhutilSymbolLoader())
- ->setAncestorClass('CelerityPhysicalResources')
+ ->setAncestorClass(__CLASS__)
->loadObjects();
foreach ($resources_list as $resources) {
diff --git a/src/applications/conduit/call/ConduitCall.php b/src/applications/conduit/call/ConduitCall.php
index aa55d1b895..7d767cd8c1 100644
--- a/src/applications/conduit/call/ConduitCall.php
+++ b/src/applications/conduit/call/ConduitCall.php
@@ -150,5 +150,9 @@ final class ConduitCall {
return $method;
}
+ public function getMethodImplementation() {
+ return $this->handler;
+ }
+
}
diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php
index e8e69f18e3..cdc14da27d 100644
--- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php
@@ -21,6 +21,7 @@ final class PhabricatorConduitAPIController
$method = $this->method;
$api_request = null;
+ $method_implementation = null;
$log = new PhabricatorConduitMethodCallLog();
$log->setMethod($method);
@@ -36,6 +37,7 @@ final class PhabricatorConduitAPIController
list($metadata, $params) = $this->decodeConduitParams($request, $method);
$call = new ConduitCall($method, $params);
+ $method_implementation = $call->getMethodImplementation();
$result = null;
@@ -151,7 +153,8 @@ final class PhabricatorConduitAPIController
return $this->buildHumanReadableResponse(
$method,
$api_request,
- $response->toDictionary());
+ $response->toDictionary(),
+ $method_implementation);
case 'json':
default:
return id(new AphrontJSONResponse())
@@ -525,7 +528,8 @@ final class PhabricatorConduitAPIController
private function buildHumanReadableResponse(
$method,
ConduitAPIRequest $request = null,
- $result = null) {
+ $result = null,
+ ConduitAPIMethod $method_implementation = null) {
$param_rows = array();
$param_rows[] = array('Method', $this->renderAPIValue($method));
@@ -574,11 +578,20 @@ final class PhabricatorConduitAPIController
->addTextCrumb($method, $method_uri)
->addTextCrumb(pht('Call'));
+ $example_panel = null;
+ if ($request && $method_implementation) {
+ $params = $request->getAllParameters();
+ $example_panel = $this->renderExampleBox(
+ $method_implementation,
+ $params);
+ }
+
return $this->buildApplicationPage(
array(
$crumbs,
$param_panel,
$result_panel,
+ $example_panel,
),
array(
'title' => pht('Method Call Result'),
diff --git a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
index 62cdb7ba77..5c0ccfe6fb 100644
--- a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
@@ -3,31 +3,23 @@
final class PhabricatorConduitConsoleController
extends PhabricatorConduitController {
- private $method;
-
public function shouldAllowPublic() {
return true;
}
- public function willProcessRequest(array $data) {
- $this->method = $data['method'];
- }
-
- public function processRequest() {
-
- $request = $this->getRequest();
- $viewer = $request->getUser();
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+ $method_name = $request->getURIData('method');
$method = id(new PhabricatorConduitMethodQuery())
->setViewer($viewer)
- ->withMethods(array($this->method))
+ ->withMethods(array($method_name))
->executeOne();
-
if (!$method) {
return new Aphront404Response();
}
- $can_call_method = false;
+ $call_uri = '/api/'.$method->getAPIMethodName();
$status = $method->getMethodStatus();
$reason = $method->getMethodStatusDescription();
@@ -48,37 +40,13 @@ final class PhabricatorConduitConsoleController
break;
}
- $error_types = $method->getErrorTypes();
- $error_types['ERR-CONDUIT-CORE'] = pht('See error message for details.');
- $error_description = array();
- foreach ($error_types as $error => $meaning) {
- $error_description[] = hsprintf(
- '
%s: %s',
- $error,
- $meaning);
- }
- $error_description = phutil_tag('ul', array(), $error_description);
-
- $form = new AphrontFormView();
- $form
+ $form = id(new AphrontFormView())
+ ->setAction($call_uri)
->setUser($request->getUser())
- ->setAction('/api/'.$this->method)
- ->appendChild(
- id(new AphrontFormStaticControl())
- ->setLabel('Description')
- ->setValue($method->getMethodDescription()))
- ->appendChild(
- id(new AphrontFormStaticControl())
- ->setLabel('Returns')
- ->setValue($method->getReturnType()))
- ->appendChild(
- id(new AphrontFormMarkupControl())
- ->setLabel('Errors')
- ->setValue($error_description))
- ->appendChild(hsprintf(
- 'Enter parameters using '.
- 'JSON. For instance, to enter a list, type: '.
- '["apple", "banana", "cherry"]'));
+ ->appendRemarkupInstructions(
+ pht(
+ 'Enter parameters using **JSON**. For instance, to enter a '.
+ 'list, type: `["apple", "banana", "cherry"]`'));
$params = $method->getParamTypes();
foreach ($params as $param => $desc) {
@@ -117,12 +85,22 @@ final class PhabricatorConduitConsoleController
->setHeader($method->getAPIMethodName());
$form_box = id(new PHUIObjectBoxView())
- ->setHeader($header)
- ->setFormErrors($errors)
+ ->setHeaderText(pht('Call Method'))
->appendChild($form);
$content = array();
+ $properties = $this->buildMethodProperties($method);
+
+ $info_box = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('API Method: %s', $method->getAPIMethodName()))
+ ->setFormErrors($errors)
+ ->appendChild($properties);
+
+ $content[] = $info_box;
+ $content[] = $form_box;
+ $content[] = $this->renderExampleBox($method, null);
+
$query = $method->newQueryObject();
if ($query) {
$orders = $query->getBuiltinOrders();
@@ -185,7 +163,6 @@ final class PhabricatorConduitConsoleController
return $this->buildApplicationPage(
array(
$crumbs,
- $form_box,
$content,
),
array(
@@ -193,4 +170,41 @@ final class PhabricatorConduitConsoleController
));
}
+ private function buildMethodProperties(ConduitAPIMethod $method) {
+ $viewer = $this->getViewer();
+
+ $view = id(new PHUIPropertyListView());
+
+ $view->addProperty(
+ pht('Returns'),
+ $method->getReturnType());
+
+ $error_types = $method->getErrorTypes();
+ $error_types['ERR-CONDUIT-CORE'] = pht('See error message for details.');
+ $error_description = array();
+ foreach ($error_types as $error => $meaning) {
+ $error_description[] = hsprintf(
+ '
%s: %s',
+ $error,
+ $meaning);
+ }
+ $error_description = phutil_tag('ul', array(), $error_description);
+
+ $view->addProperty(
+ pht('Errors'),
+ $error_description);
+
+
+ $description = $method->getMethodDescription();
+ $description = PhabricatorMarkupEngine::renderOneObject(
+ id(new PhabricatorMarkupOneOff())->setContent($description),
+ 'default',
+ $viewer);
+ $view->addSectionHeader(pht('Description'));
+ $view->addTextContent($description);
+
+ return $view;
+ }
+
+
}
diff --git a/src/applications/conduit/controller/PhabricatorConduitController.php b/src/applications/conduit/controller/PhabricatorConduitController.php
index 4643511952..4fa11dbfad 100644
--- a/src/applications/conduit/controller/PhabricatorConduitController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitController.php
@@ -24,4 +24,250 @@ abstract class PhabricatorConduitController extends PhabricatorController {
return $this->buildSideNavView()->getMenu();
}
+ protected function renderExampleBox(ConduitAPIMethod $method, $params) {
+ $arc_example = id(new PHUIPropertyListView())
+ ->addRawContent($this->renderExample($method, 'arc', $params));
+
+ $curl_example = id(new PHUIPropertyListView())
+ ->addRawContent($this->renderExample($method, 'curl', $params));
+
+ $php_example = id(new PHUIPropertyListView())
+ ->addRawContent($this->renderExample($method, 'php', $params));
+
+ $panel_link = phutil_tag(
+ 'a',
+ array(
+ 'href' => '/settings/panel/apitokens/',
+ ),
+ pht('Conduit API Tokens'));
+
+ $panel_link = phutil_tag('strong', array(), $panel_link);
+
+ $messages = array(
+ pht(
+ 'Use the %s panel in Settings to generate or manage API tokens.',
+ $panel_link),
+ );
+
+ $info_view = id(new PHUIInfoView())
+ ->setErrors($messages)
+ ->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
+
+ return id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Examples'))
+ ->setInfoView($info_view)
+ ->addPropertyList($arc_example, pht('arc call-conduit'))
+ ->addPropertyList($curl_example, pht('cURL'))
+ ->addPropertyList($php_example, pht('PHP'));
+ }
+
+ private function renderExample(
+ ConduitAPIMethod $method,
+ $kind,
+ $params) {
+
+ switch ($kind) {
+ case 'arc':
+ $example = $this->buildArcanistExample($method, $params);
+ break;
+ case 'php':
+ $example = $this->buildPHPExample($method, $params);
+ break;
+ case 'curl':
+ $example = $this->buildCURLExample($method, $params);
+ break;
+ default:
+ throw new Exception(pht('Conduit client "%s" is not known.', $kind));
+ }
+
+ return $example;
+ }
+
+ private function buildArcanistExample(
+ ConduitAPIMethod $method,
+ $params) {
+
+ $parts = array();
+
+ $parts[] = '$ echo ';
+ if ($params === null) {
+ $parts[] = phutil_tag('strong', array(), '');
+ } else {
+ $params = $this->simplifyParams($params);
+ $params = id(new PhutilJSON())->encodeFormatted($params);
+ $params = trim($params);
+ $params = csprintf('%s', $params);
+ $parts[] = phutil_tag('strong', array('class' => 'real'), $params);
+ }
+
+ $parts[] = ' | ';
+ $parts[] = 'arc call-conduit ';
+
+ $parts[] = '--conduit-uri ';
+ $parts[] = phutil_tag(
+ 'strong',
+ array('class' => 'real'),
+ PhabricatorEnv::getURI('/'));
+ $parts[] = ' ';
+
+ $parts[] = '--conduit-token ';
+ $parts[] = phutil_tag('strong', array(), '');
+ $parts[] = ' ';
+
+ $parts[] = $method->getAPIMethodName();
+
+ return $this->renderExampleCode($parts);
+ }
+
+ private function buildPHPExample(
+ ConduitAPIMethod $method,
+ $params) {
+
+ $parts = array();
+
+ $libphutil_path = 'path/to/libphutil/src/__phutil_library_init__.php';
+
+ $parts[] = ''));
+ $parts[] = "\";\n";
+
+ $parts[] = '$api_parameters = ';
+ if ($params === null) {
+ $parts[] = 'array(';
+ $parts[] = phutil_tag('strong', array(), pht(''));
+ $parts[] = ');';
+ } else {
+ $params = $this->simplifyParams($params);
+ $params = phutil_var_export($params, true);
+ $parts[] = phutil_tag('strong', array('class' => 'real'), $params);
+ $parts[] = ';';
+ }
+ $parts[] = "\n\n";
+
+ $parts[] = '$client = new ConduitClient(';
+ $parts[] = phutil_tag(
+ 'strong',
+ array('class' => 'real'),
+ phutil_var_export(PhabricatorEnv::getURI('/'), true));
+ $parts[] = ");\n";
+
+ $parts[] = '$client->setConduitToken($api_token);';
+ $parts[] = "\n\n";
+
+ $parts[] = '$result = $client->callMethodSynchronous(';
+ $parts[] = phutil_tag(
+ 'strong',
+ array('class' => 'real'),
+ phutil_var_export($method->getAPIMethodName(), true));
+ $parts[] = ', ';
+ $parts[] = '$api_parameters';
+ $parts[] = ");\n";
+
+ $parts[] = 'print_r($result);';
+
+ return $this->renderExampleCode($parts);
+ }
+
+ private function buildCURLExample(
+ ConduitAPIMethod $method,
+ $params) {
+
+ $call_uri = '/api/'.$method->getAPIMethodName();
+
+ $parts = array();
+
+ $linebreak = array('\\', phutil_tag('br'), ' ');
+
+ $parts[] = '$ curl ';
+ $parts[] = phutil_tag(
+ 'strong',
+ array('class' => 'real'),
+ csprintf('%R', PhabricatorEnv::getURI($call_uri)));
+ $parts[] = ' ';
+ $parts[] = $linebreak;
+
+ $parts[] = '-d api.token=';
+ $parts[] = phutil_tag('strong', array(), 'api-token');
+ $parts[] = ' ';
+ $parts[] = $linebreak;
+
+ if ($params === null) {
+ $parts[] = '-d ';
+ $parts[] = phutil_tag('strong', array(), 'param');
+ $parts[] = '=';
+ $parts[] = phutil_tag('strong', array(), 'value');
+ $parts[] = ' ';
+ $parts[] = $linebreak;
+ $parts[] = phutil_tag('strong', array(), '...');
+ } else {
+ $lines = array();
+ $params = $this->simplifyParams($params);
+
+ foreach ($params as $key => $value) {
+ $pieces = $this->getQueryStringParts(null, $key, $value);
+ foreach ($pieces as $piece) {
+ $lines[] = array(
+ '-d ',
+ phutil_tag('strong', array('class' => 'real'), $piece),
+ );
+ }
+ }
+
+ $parts[] = phutil_implode_html(array(' ', $linebreak), $lines);
+ }
+
+ return $this->renderExampleCode($parts);
+ }
+
+ private function renderExampleCode($example) {
+ require_celerity_resource('conduit-api-css');
+
+ return phutil_tag(
+ 'div',
+ array(
+ 'class' => 'PhabricatorMonospaced conduit-api-example-code',
+ ),
+ $example);
+ }
+
+ private function simplifyParams(array $params) {
+ foreach ($params as $key => $value) {
+ if ($value === null) {
+ unset($params[$key]);
+ }
+ }
+ return $params;
+ }
+
+ private function getQueryStringParts($prefix, $key, $value) {
+ if ($prefix === null) {
+ $head = phutil_escape_uri($key);
+ } else {
+ $head = $prefix.'['.phutil_escape_uri($key).']';
+ }
+
+ if (!is_array($value)) {
+ return array(
+ $head.'='.phutil_escape_uri($value),
+ );
+ }
+
+ $results = array();
+ foreach ($value as $subkey => $subvalue) {
+ $subparts = $this->getQueryStringParts($head, $subkey, $subvalue);
+ foreach ($subparts as $subpart) {
+ $results[] = $subpart;
+ }
+ }
+
+ return $results;
+ }
+
}
diff --git a/src/applications/conduit/method/ConduitAPIMethod.php b/src/applications/conduit/method/ConduitAPIMethod.php
index a048e3e0f0..f6d464cfc5 100644
--- a/src/applications/conduit/method/ConduitAPIMethod.php
+++ b/src/applications/conduit/method/ConduitAPIMethod.php
@@ -100,9 +100,9 @@ abstract class ConduitAPIMethod
$name = $this->getAPIMethodName();
$map = array(
- ConduitAPIMethod::METHOD_STATUS_STABLE => 0,
- ConduitAPIMethod::METHOD_STATUS_UNSTABLE => 1,
- ConduitAPIMethod::METHOD_STATUS_DEPRECATED => 2,
+ self::METHOD_STATUS_STABLE => 0,
+ self::METHOD_STATUS_UNSTABLE => 1,
+ self::METHOD_STATUS_DEPRECATED => 2,
);
$ord = idx($map, $this->getMethodStatus(), 0);
diff --git a/src/applications/conduit/query/PhabricatorConduitLogQuery.php b/src/applications/conduit/query/PhabricatorConduitLogQuery.php
index 4d66cccae5..a08fd25a68 100644
--- a/src/applications/conduit/query/PhabricatorConduitLogQuery.php
+++ b/src/applications/conduit/query/PhabricatorConduitLogQuery.php
@@ -22,7 +22,7 @@ final class PhabricatorConduitLogQuery
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
- return $table->loadAllFromArray($data);;
+ return $table->loadAllFromArray($data);
}
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
diff --git a/src/applications/conduit/query/PhabricatorConduitTokenQuery.php b/src/applications/conduit/query/PhabricatorConduitTokenQuery.php
index 870043cac8..44586f1815 100644
--- a/src/applications/conduit/query/PhabricatorConduitTokenQuery.php
+++ b/src/applications/conduit/query/PhabricatorConduitTokenQuery.php
@@ -46,7 +46,7 @@ final class PhabricatorConduitTokenQuery
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
- return $table->loadAllFromArray($data);;
+ return $table->loadAllFromArray($data);
}
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
diff --git a/src/applications/conduit/storage/PhabricatorConduitToken.php b/src/applications/conduit/storage/PhabricatorConduitToken.php
index ab4d88335e..f5673fdab3 100644
--- a/src/applications/conduit/storage/PhabricatorConduitToken.php
+++ b/src/applications/conduit/storage/PhabricatorConduitToken.php
@@ -65,7 +65,7 @@ final class PhabricatorConduitToken
// to expire) so generate a new token.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
- $token = PhabricatorConduitToken::initializeNewToken(
+ $token = self::initializeNewToken(
$user->getPHID(),
self::TYPE_CLUSTER);
$token->save();
diff --git a/src/applications/config/check/PhabricatorSetupCheck.php b/src/applications/config/check/PhabricatorSetupCheck.php
index b40dd4c8cc..a689d14943 100644
--- a/src/applications/config/check/PhabricatorSetupCheck.php
+++ b/src/applications/config/check/PhabricatorSetupCheck.php
@@ -113,7 +113,7 @@ abstract class PhabricatorSetupCheck {
final public static function runAllChecks() {
$symbols = id(new PhutilSymbolLoader())
- ->setAncestorClass('PhabricatorSetupCheck')
+ ->setAncestorClass(__CLASS__)
->setConcreteOnly(true)
->selectAndLoadSymbols();
diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php
index 6982ef513f..7ac1df3e16 100644
--- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php
+++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php
@@ -187,7 +187,7 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject {
final public static function loadAll($external_only = false) {
$symbols = id(new PhutilSymbolLoader())
- ->setAncestorClass('PhabricatorApplicationConfigOptions')
+ ->setAncestorClass(__CLASS__)
->setConcreteOnly(true)
->selectAndLoadSymbols();
@@ -204,8 +204,12 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject {
$nclass = $symbol['name'];
throw new Exception(
- "Multiple PhabricatorApplicationConfigOptions subclasses have the ".
- "same key ('{$key}'): {$pclass}, {$nclass}.");
+ pht(
+ "Multiple %s subclasses have the same key ('%s'): %s, %s.",
+ __CLASS__,
+ $key,
+ $pclass,
+ $nclass));
}
$groups[$key] = $obj;
}
@@ -222,8 +226,10 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject {
$key = $option->getKey();
if (isset($options[$key])) {
throw new Exception(
- "Mulitple PhabricatorApplicationConfigOptions subclasses contain ".
- "an option named '{$key}'!");
+ pht(
+ "Mulitple % subclasses contain an option named '%s'!",
+ __CLASS__,
+ $key));
}
$options[$key] = $option;
}
diff --git a/src/applications/config/option/PhabricatorConfigOption.php b/src/applications/config/option/PhabricatorConfigOption.php
index f11c436402..e5c9773611 100644
--- a/src/applications/config/option/PhabricatorConfigOption.php
+++ b/src/applications/config/option/PhabricatorConfigOption.php
@@ -122,8 +122,7 @@ final class PhabricatorConfigOption
return $this->enumOptions;
}
- throw new Exception(
- 'Call setEnumOptions() before trying to access them!');
+ throw new PhutilInvalidStateException('setEnumOptions');
}
public function setKey($key) {
diff --git a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php
index 7a11d64684..57db889956 100644
--- a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php
+++ b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php
@@ -11,7 +11,7 @@ final class PhabricatorConfigSchemaQuery extends Phobject {
protected function getAPI() {
if (!$this->api) {
- throw new Exception(pht('Call setAPI() before issuing a query!'));
+ throw new PhutilInvalidStateException('setAPI');
}
return $this->api;
}
diff --git a/src/applications/conpherence/ConpherenceTransactionRenderer.php b/src/applications/conpherence/ConpherenceTransactionRenderer.php
index d6be83fb65..1b14f5f0d6 100644
--- a/src/applications/conpherence/ConpherenceTransactionRenderer.php
+++ b/src/applications/conpherence/ConpherenceTransactionRenderer.php
@@ -90,6 +90,7 @@ final class ConpherenceTransactionRenderer {
if ($previous_day != $current_day) {
$date_marker_transaction->setDateCreated(
$transaction->getDateCreated());
+ $date_marker_transaction->setID($previous_transaction->getID());
$rendered_transactions[] = $date_marker_transaction_view->render();
}
}
@@ -144,6 +145,15 @@ final class ConpherenceTransactionRenderer {
),
),
pht('Show Older Messages'));
+ $oldscrollbutton = javelin_tag(
+ 'div',
+ array(
+ 'sigil' => 'conpherence-transaction-view',
+ 'meta' => array(
+ 'id' => $oldest_transaction_id - 0.5,
+ ),
+ ),
+ $oldscrollbutton);
}
$newscrollbutton = '';
@@ -160,6 +170,15 @@ final class ConpherenceTransactionRenderer {
),
),
pht('Show Newer Messages'));
+ $newscrollbutton = javelin_tag(
+ 'div',
+ array(
+ 'sigil' => 'conpherence-transaction-view',
+ 'meta' => array(
+ 'id' => $newest_transaction_id + 0.5,
+ ),
+ ),
+ $newscrollbutton);
}
return hsprintf(
diff --git a/src/applications/conpherence/controller/ConpherenceColumnViewController.php b/src/applications/conpherence/controller/ConpherenceColumnViewController.php
index 0155784015..9a146b4b23 100644
--- a/src/applications/conpherence/controller/ConpherenceColumnViewController.php
+++ b/src/applications/conpherence/controller/ConpherenceColumnViewController.php
@@ -88,12 +88,19 @@ final class ConpherenceColumnViewController extends
PhabricatorPolicyCapability::CAN_EDIT);
}
+ $dropdown_query = id(new AphlictDropdownDataQuery())
+ ->setViewer($user);
+ $dropdown_query->execute();
$response = array(
'content' => hsprintf('%s', $durable_column),
'threadID' => $conpherence_id,
'threadPHID' => $conpherence_phid,
'latestTransactionID' => $latest_transaction_id,
'canEdit' => $can_edit,
+ 'aphlictDropdownData' => array(
+ $dropdown_query->getNotificationData(),
+ $dropdown_query->getConpherenceData(),
+ ),
);
return id(new AphrontAjaxResponse())->setContent($response);
diff --git a/src/applications/conpherence/controller/ConpherenceListController.php b/src/applications/conpherence/controller/ConpherenceListController.php
index f8be8f7de6..ad672ff6d8 100644
--- a/src/applications/conpherence/controller/ConpherenceListController.php
+++ b/src/applications/conpherence/controller/ConpherenceListController.php
@@ -25,6 +25,10 @@ final class ConpherenceListController extends ConpherenceController {
return $mode;
}
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$user = $request->getUser();
$title = pht('Conpherence');
diff --git a/src/applications/conpherence/controller/ConpherenceNewController.php b/src/applications/conpherence/controller/ConpherenceNewController.php
index a887404c7d..102cf0e5ea 100644
--- a/src/applications/conpherence/controller/ConpherenceNewController.php
+++ b/src/applications/conpherence/controller/ConpherenceNewController.php
@@ -42,9 +42,8 @@ final class ConpherenceNewController extends ConpherenceController {
}
}
} else {
- $uri = $this->getApplicationURI($conpherence->getID());
return id(new AphrontRedirectResponse())
- ->setURI($uri);
+ ->setURI('/'.$conpherence->getMonogram());
}
} else {
$participant_prefill = $request->getStr('participant');
diff --git a/src/applications/conpherence/controller/ConpherenceNewRoomController.php b/src/applications/conpherence/controller/ConpherenceNewRoomController.php
index ce9afcb195..a401da0a19 100644
--- a/src/applications/conpherence/controller/ConpherenceNewRoomController.php
+++ b/src/applications/conpherence/controller/ConpherenceNewRoomController.php
@@ -36,9 +36,8 @@ final class ConpherenceNewRoomController extends ConpherenceController {
->setActor($user)
->applyTransactions($conpherence, $xactions);
- $uri = $this->getApplicationURI($conpherence->getID());
return id(new AphrontRedirectResponse())
- ->setURI($uri);
+ ->setURI('/'.$conpherence->getMonogram());
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
diff --git a/src/applications/conpherence/controller/ConpherenceRoomListController.php b/src/applications/conpherence/controller/ConpherenceRoomListController.php
index 299ca6b8d0..43c36d20d1 100644
--- a/src/applications/conpherence/controller/ConpherenceRoomListController.php
+++ b/src/applications/conpherence/controller/ConpherenceRoomListController.php
@@ -2,6 +2,10 @@
final class ConpherenceRoomListController extends ConpherenceController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$user = $request->getUser();
diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php
index 269b87f0e6..eea1d1dc8a 100644
--- a/src/applications/conpherence/controller/ConpherenceUpdateController.php
+++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php
@@ -251,7 +251,7 @@ final class ConpherenceUpdateController
case 'redirect':
default:
return id(new AphrontRedirectResponse())
- ->setURI($this->getApplicationURI($conpherence->getID().'/'));
+ ->setURI('/'.$conpherence->getMonogram());
break;
}
}
@@ -453,6 +453,7 @@ final class ConpherenceUpdateController
$conpherence_id,
$latest_transaction_id) {
+ $minimal_display = $this->getRequest()->getExists('minimal_display');
$need_widget_data = false;
$need_transactions = false;
$need_participant_cache = true;
@@ -464,7 +465,7 @@ final class ConpherenceUpdateController
case ConpherenceUpdateActions::MESSAGE:
case ConpherenceUpdateActions::ADD_PERSON:
$need_transactions = true;
- $need_widget_data = true;
+ $need_widget_data = !$minimal_display;
break;
case ConpherenceUpdateActions::REMOVE_PERSON:
case ConpherenceUpdateActions::NOTIFICATIONS:
@@ -488,7 +489,7 @@ final class ConpherenceUpdateController
$data = ConpherenceTransactionRenderer::renderTransactions(
$user,
$conpherence,
- !$this->getRequest()->getExists('minimal_display'));
+ !$minimal_display);
$participant_obj = $conpherence->getParticipant($user->getPHID());
$participant_obj->markUpToDate($conpherence, $data['latest_transaction']);
} else if ($need_transactions) {
@@ -505,51 +506,61 @@ final class ConpherenceUpdateController
$header = null;
$people_widget = null;
$file_widget = null;
- switch ($action) {
- case ConpherenceUpdateActions::METADATA:
- $policy_objects = id(new PhabricatorPolicyQuery())
- ->setViewer($user)
- ->setObject($conpherence)
- ->execute();
- $header = $this->buildHeaderPaneContent($conpherence, $policy_objects);
- $nav_item = id(new ConpherenceThreadListView())
- ->setUser($user)
- ->setBaseURI($this->getApplicationURI())
- ->renderSingleThread($conpherence);
- break;
- case ConpherenceUpdateActions::MESSAGE:
- $file_widget = id(new ConpherenceFileWidgetView())
- ->setUser($this->getRequest()->getUser())
- ->setConpherence($conpherence)
- ->setUpdateURI($widget_uri);
- break;
- case ConpherenceUpdateActions::ADD_PERSON:
- $people_widget = id(new ConpherencePeopleWidgetView())
- ->setUser($user)
- ->setConpherence($conpherence)
- ->setUpdateURI($widget_uri);
- break;
- case ConpherenceUpdateActions::REMOVE_PERSON:
- case ConpherenceUpdateActions::NOTIFICATIONS:
- default:
- break;
- }
-
- $people_html = null;
- if ($people_widget) {
- $people_html = hsprintf('%s', $people_widget->render());
+ if (!$minimal_display) {
+ switch ($action) {
+ case ConpherenceUpdateActions::METADATA:
+ $policy_objects = id(new PhabricatorPolicyQuery())
+ ->setViewer($user)
+ ->setObject($conpherence)
+ ->execute();
+ $header = $this->buildHeaderPaneContent(
+ $conpherence,
+ $policy_objects);
+ $header = hsprintf('%s', $header);
+ $nav_item = id(new ConpherenceThreadListView())
+ ->setUser($user)
+ ->setBaseURI($this->getApplicationURI())
+ ->renderSingleThread($conpherence);
+ $nav_item = hsprintf('%s', $nav_item);
+ break;
+ case ConpherenceUpdateActions::MESSAGE:
+ $file_widget = id(new ConpherenceFileWidgetView())
+ ->setUser($this->getRequest()->getUser())
+ ->setConpherence($conpherence)
+ ->setUpdateURI($widget_uri);
+ $file_widget = $file_widget->render();
+ break;
+ case ConpherenceUpdateActions::ADD_PERSON:
+ $people_widget = id(new ConpherencePeopleWidgetView())
+ ->setUser($user)
+ ->setConpherence($conpherence)
+ ->setUpdateURI($widget_uri);
+ $people_widget = $people_widget->render();
+ break;
+ case ConpherenceUpdateActions::REMOVE_PERSON:
+ case ConpherenceUpdateActions::NOTIFICATIONS:
+ default:
+ break;
+ }
}
$data = $conpherence->getDisplayData($user);
+ $dropdown_query = id(new AphlictDropdownDataQuery())
+ ->setViewer($user);
+ $dropdown_query->execute();
$content = array(
'non_update' => $non_update,
'transactions' => hsprintf('%s', $rendered_transactions),
'conpherence_title' => (string) $data['title'],
'latest_transaction_id' => $new_latest_transaction_id,
- 'nav_item' => hsprintf('%s', $nav_item),
+ 'nav_item' => $nav_item,
'conpherence_phid' => $conpherence->getPHID(),
- 'header' => hsprintf('%s', $header),
- 'file_widget' => $file_widget ? $file_widget->render() : null,
- 'people_widget' => $people_html,
+ 'header' => $header,
+ 'file_widget' => $file_widget,
+ 'people_widget' => $people_widget,
+ 'aphlictDropdownData' => array(
+ $dropdown_query->getNotificationData(),
+ $dropdown_query->getConpherenceData(),
+ ),
);
return $content;
diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php
index 7a3027dc53..6293c1bbdd 100644
--- a/src/applications/conpherence/controller/ConpherenceViewController.php
+++ b/src/applications/conpherence/controller/ConpherenceViewController.php
@@ -5,6 +5,10 @@ final class ConpherenceViewController extends
const OLDER_FETCH_LIMIT = 5;
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$user = $request->getUser();
@@ -75,7 +79,7 @@ final class ConpherenceViewController extends
if ($before_transaction_id || $after_transaction_id) {
$header = null;
$form = null;
- $content = array('messages' => $messages);
+ $content = array('transactions' => $messages);
} else {
$policy_objects = id(new PhabricatorPolicyQuery())
->setViewer($user)
@@ -85,7 +89,7 @@ final class ConpherenceViewController extends
$form = $this->renderFormContent();
$content = array(
'header' => $header,
- 'messages' => $messages,
+ 'transactions' => $messages,
'form' => $form,
);
}
@@ -94,6 +98,9 @@ final class ConpherenceViewController extends
$content['title'] = $title = $d_data['title'];
if ($request->isAjax()) {
+ $dropdown_query = id(new AphlictDropdownDataQuery())
+ ->setViewer($user);
+ $dropdown_query->execute();
$content['threadID'] = $conpherence->getID();
$content['threadPHID'] = $conpherence->getPHID();
$content['latestTransactionID'] = $data['latest_transaction_id'];
@@ -101,6 +108,10 @@ final class ConpherenceViewController extends
$user,
$conpherence,
PhabricatorPolicyCapability::CAN_EDIT);
+ $content['aphlictDropdownData'] = array(
+ $dropdown_query->getNotificationData(),
+ $dropdown_query->getConpherenceData(),
+ );
return id(new AphrontAjaxResponse())->setContent($content);
}
@@ -131,7 +142,7 @@ final class ConpherenceViewController extends
$conpherence,
PhabricatorPolicyCapability::CAN_JOIN);
$participating = $conpherence->getParticipantIfExists($user->getPHID());
- if (!$can_join && !$participating) {
+ if (!$can_join && !$participating && $user->isLoggedIn()) {
return null;
}
$draft = PhabricatorDraft::newFromUserAndKey(
@@ -140,9 +151,20 @@ final class ConpherenceViewController extends
if ($participating) {
$action = ConpherenceUpdateActions::MESSAGE;
$button_text = pht('Send');
- } else {
+ } else if ($user->isLoggedIn()) {
$action = ConpherenceUpdateActions::JOIN_ROOM;
$button_text = pht('Join');
+ } else {
+ // user not logged in so give them a login button.
+ $login_href = id(new PhutilURI('/auth/start/'))
+ ->setQueryParam('next', '/'.$conpherence->getMonogram());
+ return id(new PHUIFormLayoutView())
+ ->addClass('login-to-participate')
+ ->appendChild(
+ id(new PHUIButtonView())
+ ->setTag('a')
+ ->setText(pht('Login to Participate'))
+ ->setHref((string)$login_href));
}
$update_uri = $this->getApplicationURI('update/'.$conpherence->getID().'/');
@@ -150,10 +172,10 @@ final class ConpherenceViewController extends
$form =
id(new AphrontFormView())
+ ->setUser($user)
->setAction($update_uri)
->addSigil('conpherence-pontificate')
->setWorkflow(true)
- ->setUser($user)
->addHiddenInput('action', $action)
->appendChild(
id(new PhabricatorRemarkupControl())
diff --git a/src/applications/conpherence/controller/ConpherenceWidgetController.php b/src/applications/conpherence/controller/ConpherenceWidgetController.php
index f7775dcc32..9a06f0dcf9 100644
--- a/src/applications/conpherence/controller/ConpherenceWidgetController.php
+++ b/src/applications/conpherence/controller/ConpherenceWidgetController.php
@@ -13,6 +13,10 @@ final class ConpherenceWidgetController extends ConpherenceController {
return $this->userPreferences;
}
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$request = $this->getRequest();
$user = $request->getUser();
@@ -26,6 +30,9 @@ final class ConpherenceWidgetController extends ConpherenceController {
->withIDs(array($conpherence_id))
->needWidgetData(true)
->executeOne();
+ if (!$conpherence) {
+ return new Aphront404Response();
+ }
$this->setConpherence($conpherence);
$this->setUserPreferences($user->loadPreferences());
@@ -138,8 +145,11 @@ final class ConpherenceWidgetController extends ConpherenceController {
PhabricatorPolicyCapability::CAN_JOIN);
if ($can_join) {
$text = pht('Settings are available after joining the room.');
- } else {
+ } else if ($viewer->isLoggedIn()) {
$text = pht('Settings not applicable to rooms you can not join.');
+ } else {
+ $text = pht(
+ 'Settings are available after logging in and joining the room.');
}
return phutil_tag(
'div',
diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php
index 03c341f090..b17044ead0 100644
--- a/src/applications/conpherence/query/ConpherenceThreadQuery.php
+++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php
@@ -285,10 +285,9 @@ final class ConpherenceThreadQuery
$conpherence->$method();
}
$flat_phids = array_mergev($handle_phids);
- $handles = id(new PhabricatorHandleQuery())
- ->setViewer($this->getViewer())
- ->withPHIDs($flat_phids)
- ->execute();
+ $viewer = $this->getViewer();
+ $handles = $viewer->loadHandles($flat_phids);
+ $handles = iterator_to_array($handles);
foreach ($handle_phids as $conpherence_phid => $phids) {
$conpherence = $conpherences[$conpherence_phid];
$conpherence->attachHandles(
diff --git a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php
index 2a23d6c073..f2a2d920bd 100644
--- a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php
+++ b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php
@@ -149,32 +149,48 @@ final class ConpherenceThreadSearchEngine
$viewer,
$conpherences);
+ $engines = array();
+
$fulltext = $query->getParameter('fulltext');
if (strlen($fulltext) && $conpherences) {
$context = $this->loadContextMessages($conpherences, $fulltext);
$author_phids = array();
- foreach ($context as $messages) {
+ foreach ($context as $phid => $messages) {
+ $conpherence = $conpherences[$phid];
+
+ $engine = id(new PhabricatorMarkupEngine())
+ ->setViewer($viewer)
+ ->setContextObject($conpherence);
+
foreach ($messages as $group) {
foreach ($group as $message) {
$xaction = $message['xaction'];
if ($xaction) {
$author_phids[] = $xaction->getAuthorPHID();
+ $engine->addObject(
+ $xaction->getComment(),
+ PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
}
+ $engine->process();
+
+ $engines[$phid] = $engine;
}
$handles = $viewer->loadHandles($author_phids);
+ $handles = iterator_to_array($handles);
} else {
$context = array();
}
$list = new PHUIObjectItemListView();
$list->setUser($viewer);
- foreach ($conpherences as $conpherence) {
+ foreach ($conpherences as $conpherence_phid => $conpherence) {
$created = phabricator_date($conpherence->getDateCreated(), $viewer);
$title = $conpherence->getDisplayTitle($viewer);
+ $monogram = $conpherence->getMonogram();
if ($conpherence->getIsRoom()) {
$icon_name = $conpherence->getPolicyIconName($policy_objects);
@@ -186,7 +202,7 @@ final class ConpherenceThreadSearchEngine
$item = id(new PHUIObjectItemView())
->setObjectName($conpherence->getMonogram())
->setHeader($title)
- ->setHref('/conpherence/'.$conpherence->getID().'/')
+ ->setHref('/'.$conpherence->getMonogram())
->setObject($conpherence)
->addIcon('none', $created)
->addIcon(
@@ -201,43 +217,37 @@ final class ConpherenceThreadSearchEngine
phabricator_datetime($conpherence->getDateModified(), $viewer)),
));
- $messages = idx($context, $conpherence->getPHID());
+ $messages = idx($context, $conpherence_phid);
if ($messages) {
-
- // TODO: This is egregiously under-designed.
-
foreach ($messages as $group) {
$rows = array();
- $rowc = array();
foreach ($group as $message) {
$xaction = $message['xaction'];
if (!$xaction) {
continue;
}
- $rowc[] = ($message['match'] ? 'highlighted' : null);
- $rows[] = array(
- $handles->renderHandle($xaction->getAuthorPHID()),
- $xaction->getComment()->getContent(),
- phabricator_datetime($xaction->getDateCreated(), $viewer),
- );
+ $history_href = '/'.$monogram.'#'.$xaction->getID();
+
+ $view = id(new ConpherenceTransactionView())
+ ->setUser($viewer)
+ ->setHandles($handles)
+ ->setMarkupEngine($engines[$conpherence_phid])
+ ->setConpherenceThread($conpherence)
+ ->setConpherenceTransaction($xaction)
+ ->setEpoch($xaction->getDateCreated(), $history_href)
+ ->addClass('conpherence-fulltext-result');
+
+ if ($message['match']) {
+ $view->addClass('conpherence-fulltext-match');
+ }
+
+ $rows[] = $view;
}
- $table = id(new AphrontTableView($rows))
- ->setHeaders(
- array(
- pht('User'),
- pht('Message'),
- pht('At'),
- ))
- ->setRowClasses($rowc)
- ->setColumnClasses(
- array(
- '',
- 'wide',
- ));
+
$box = id(new PHUIBoxView())
- ->appendChild($table)
- ->addMargin(PHUI::MARGIN_SMALL);
+ ->appendChild($rows)
+ ->addClass('conpherence-fulltext-results');
$item->appendChild($box);
}
}
@@ -362,8 +372,12 @@ final class ConpherenceThreadSearchEngine
$groups = array();
foreach ($hits as $thread_phid => $rows) {
$rows = ipull($rows, null, 'transactionPHID');
+ $done = array();
foreach ($rows as $phid => $row) {
- unset($rows[$phid]);
+ if (isset($done[$phid])) {
+ continue;
+ }
+ $done[$phid] = true;
$group = array();
@@ -381,7 +395,7 @@ final class ConpherenceThreadSearchEngine
if (isset($rows[$prev])) {
$match = true;
- unset($rows[$prev]);
+ $done[$prev] = true;
} else {
$match = false;
}
@@ -411,7 +425,7 @@ final class ConpherenceThreadSearchEngine
if (isset($rows[$next])) {
$match = true;
- unset($rows[$next]);
+ $done[$next] = true;
} else {
$match = false;
}
@@ -451,6 +465,9 @@ final class ConpherenceThreadSearchEngine
foreach ($group as $key => $list) {
foreach ($list as $lkey => $item) {
$xaction = idx($xactions, $item['phid']);
+ if ($xaction->shouldHide()) {
+ continue;
+ }
$groups[$thread_phid][$key][$lkey]['xaction'] = $xaction;
}
}
diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php
index 03f8f6997e..8ed0e4fd18 100644
--- a/src/applications/conpherence/storage/ConpherenceThread.php
+++ b/src/applications/conpherence/storage/ConpherenceThread.php
@@ -407,7 +407,7 @@ final class ConpherenceThread extends ConpherenceDAO
PhabricatorUser $viewer,
array $conpherences) {
- assert_instances_of($conpherences, 'ConpherenceThread');
+ assert_instances_of($conpherences, __CLASS__);
$grouped = mgroup($conpherences, 'getIsRoom');
$rooms = idx($grouped, 1, array());
diff --git a/src/applications/conpherence/view/ConpherenceTransactionView.php b/src/applications/conpherence/view/ConpherenceTransactionView.php
index 3ed5fa14eb..2dba770e54 100644
--- a/src/applications/conpherence/view/ConpherenceTransactionView.php
+++ b/src/applications/conpherence/view/ConpherenceTransactionView.php
@@ -103,10 +103,14 @@ final class ConpherenceTransactionView extends AphrontView {
$transaction = $this->getConpherenceTransaction();
switch ($transaction->getTransactionType()) {
case ConpherenceTransactionType::TYPE_DATE_MARKER:
- return phutil_tag(
+ return javelin_tag(
'div',
array(
'class' => 'conpherence-transaction-view date-marker',
+ 'sigil' => 'conpherence-transaction-view',
+ 'meta' => array(
+ 'id' => $transaction->getID() + 0.5,
+ ),
),
array(
phutil_tag(
@@ -134,11 +138,15 @@ final class ConpherenceTransactionView extends AphrontView {
'conpherence-transaction-header grouped',
array($actions, $info));
- return phutil_tag(
+ return javelin_tag(
'div',
array(
'class' => 'conpherence-transaction-view '.$classes,
'id' => $transaction_id,
+ 'sigil' => 'conpherence-transaction-view',
+ 'meta' => array(
+ 'id' => $transaction->getID(),
+ ),
),
array(
$image,
diff --git a/src/applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php b/src/applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php
index 0708c98eec..5d9723cc4b 100644
--- a/src/applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php
+++ b/src/applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php
@@ -12,7 +12,7 @@ final class DarkConsoleErrorLogPluginAPI {
// reenter autoloaders).
PhutilReadableSerializer::printableValue(null);
PhutilErrorHandler::setErrorListener(
- array('DarkConsoleErrorLogPluginAPI', 'handleErrors'));
+ array(__CLASS__, 'handleErrors'));
}
public static function enableDiscardMode() {
diff --git a/src/applications/daemon/query/PhabricatorDaemonLogQuery.php b/src/applications/daemon/query/PhabricatorDaemonLogQuery.php
index 5822194d06..961c1cfc61 100644
--- a/src/applications/daemon/query/PhabricatorDaemonLogQuery.php
+++ b/src/applications/daemon/query/PhabricatorDaemonLogQuery.php
@@ -68,8 +68,8 @@ final class PhabricatorDaemonLogQuery
}
protected function willFilterPage(array $daemons) {
- $unknown_delay = PhabricatorDaemonLogQuery::getTimeUntilUnknown();
- $dead_delay = PhabricatorDaemonLogQuery::getTimeUntilDead();
+ $unknown_delay = self::getTimeUntilUnknown();
+ $dead_delay = self::getTimeUntilDead();
$status_running = PhabricatorDaemonLog::STATUS_RUNNING;
$status_unknown = PhabricatorDaemonLog::STATUS_UNKNOWN;
diff --git a/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php b/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php
index a3598bfe25..b06ebdd7d2 100644
--- a/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php
+++ b/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php
@@ -27,7 +27,7 @@ final class PhabricatorDaemonLogEventsView extends AphrontView {
$rows = array();
if (!$this->user) {
- throw new Exception('Call setUser() before rendering!');
+ throw new PhutilInvalidStateException('setUser');
}
foreach ($this->events as $event) {
diff --git a/src/applications/daemon/view/PhabricatorDaemonLogListView.php b/src/applications/daemon/view/PhabricatorDaemonLogListView.php
index 88a92ced98..f45606e9e7 100644
--- a/src/applications/daemon/view/PhabricatorDaemonLogListView.php
+++ b/src/applications/daemon/view/PhabricatorDaemonLogListView.php
@@ -14,7 +14,7 @@ final class PhabricatorDaemonLogListView extends AphrontView {
$rows = array();
if (!$this->user) {
- throw new Exception('Call setUser() before rendering!');
+ throw new PhutilInvalidStateException('setUser');
}
$env_hash = PhabricatorEnv::calculateEnvironmentHash();
diff --git a/src/applications/differential/constants/DifferentialAction.php b/src/applications/differential/constants/DifferentialAction.php
index 9238990161..48a263c45a 100644
--- a/src/applications/differential/constants/DifferentialAction.php
+++ b/src/applications/differential/constants/DifferentialAction.php
@@ -22,71 +22,71 @@ final class DifferentialAction {
public static function getBasicStoryText($action, $author_name) {
switch ($action) {
- case DifferentialAction::ACTION_COMMENT:
+ case self::ACTION_COMMENT:
$title = pht('%s commented on this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_ACCEPT:
+ case self::ACTION_ACCEPT:
$title = pht('%s accepted this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_REJECT:
+ case self::ACTION_REJECT:
$title = pht('%s requested changes to this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_RETHINK:
+ case self::ACTION_RETHINK:
$title = pht('%s planned changes to this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_ABANDON:
+ case self::ACTION_ABANDON:
$title = pht('%s abandoned this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_CLOSE:
+ case self::ACTION_CLOSE:
$title = pht('%s closed this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_REQUEST:
+ case self::ACTION_REQUEST:
$title = pht('%s requested a review of this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_RECLAIM:
+ case self::ACTION_RECLAIM:
$title = pht('%s reclaimed this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_UPDATE:
+ case self::ACTION_UPDATE:
$title = pht('%s updated this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_RESIGN:
+ case self::ACTION_RESIGN:
$title = pht('%s resigned from this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_SUMMARIZE:
+ case self::ACTION_SUMMARIZE:
$title = pht('%s summarized this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_TESTPLAN:
+ case self::ACTION_TESTPLAN:
$title = pht('%s explained the test plan for this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_CREATE:
+ case self::ACTION_CREATE:
$title = pht('%s created this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_ADDREVIEWERS:
+ case self::ACTION_ADDREVIEWERS:
$title = pht('%s added reviewers to this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_ADDCCS:
+ case self::ACTION_ADDCCS:
$title = pht('%s added CCs to this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_CLAIM:
+ case self::ACTION_CLAIM:
$title = pht('%s commandeered this revision.',
$author_name);
break;
- case DifferentialAction::ACTION_REOPEN:
+ case self::ACTION_REOPEN:
$title = pht('%s reopened this revision.',
$author_name);
break;
@@ -127,9 +127,9 @@ final class DifferentialAction {
}
public static function allowReviewers($action) {
- if ($action == DifferentialAction::ACTION_ADDREVIEWERS ||
- $action == DifferentialAction::ACTION_REQUEST ||
- $action == DifferentialAction::ACTION_RESIGN) {
+ if ($action == self::ACTION_ADDREVIEWERS ||
+ $action == self::ACTION_REQUEST ||
+ $action == self::ACTION_RESIGN) {
return true;
}
return false;
diff --git a/src/applications/differential/constants/DifferentialChangeType.php b/src/applications/differential/constants/DifferentialChangeType.php
index 0a4f7861cc..2ce5392165 100644
--- a/src/applications/differential/constants/DifferentialChangeType.php
+++ b/src/applications/differential/constants/DifferentialChangeType.php
@@ -52,42 +52,42 @@ final class DifferentialChangeType {
public static function isOldLocationChangeType($type) {
static $types = array(
- DifferentialChangeType::TYPE_MOVE_AWAY => true,
- DifferentialChangeType::TYPE_COPY_AWAY => true,
- DifferentialChangeType::TYPE_MULTICOPY => true,
+ self::TYPE_MOVE_AWAY => true,
+ self::TYPE_COPY_AWAY => true,
+ self::TYPE_MULTICOPY => true,
);
return isset($types[$type]);
}
public static function isNewLocationChangeType($type) {
static $types = array(
- DifferentialChangeType::TYPE_MOVE_HERE => true,
- DifferentialChangeType::TYPE_COPY_HERE => true,
+ self::TYPE_MOVE_HERE => true,
+ self::TYPE_COPY_HERE => true,
);
return isset($types[$type]);
}
public static function isDeleteChangeType($type) {
static $types = array(
- DifferentialChangeType::TYPE_DELETE => true,
- DifferentialChangeType::TYPE_MOVE_AWAY => true,
- DifferentialChangeType::TYPE_MULTICOPY => true,
+ self::TYPE_DELETE => true,
+ self::TYPE_MOVE_AWAY => true,
+ self::TYPE_MULTICOPY => true,
);
return isset($types[$type]);
}
public static function isCreateChangeType($type) {
static $types = array(
- DifferentialChangeType::TYPE_ADD => true,
- DifferentialChangeType::TYPE_COPY_HERE => true,
- DifferentialChangeType::TYPE_MOVE_HERE => true,
+ self::TYPE_ADD => true,
+ self::TYPE_COPY_HERE => true,
+ self::TYPE_MOVE_HERE => true,
);
return isset($types[$type]);
}
public static function isModifyChangeType($type) {
static $types = array(
- DifferentialChangeType::TYPE_CHANGE => true,
+ self::TYPE_CHANGE => true,
);
return isset($types[$type]);
}
diff --git a/src/applications/differential/customfield/DifferentialRevisionIDField.php b/src/applications/differential/customfield/DifferentialRevisionIDField.php
index 3741c0126f..bb54951dc4 100644
--- a/src/applications/differential/customfield/DifferentialRevisionIDField.php
+++ b/src/applications/differential/customfield/DifferentialRevisionIDField.php
@@ -32,6 +32,14 @@ final class DifferentialRevisionIDField
}
public function parseValueFromCommitMessage($value) {
+ // If the value is just "D123" or similar, parse the ID from it directly.
+ $value = trim($value);
+ $matches = null;
+ if (preg_match('/^[dD]([1-9]\d*)\z/', $value, $matches)) {
+ return (int)$matches[1];
+ }
+
+ // Otherwise, try to extract a URI value.
return self::parseRevisionIDFromURI($value);
}
diff --git a/src/applications/differential/parser/DifferentialLineAdjustmentMap.php b/src/applications/differential/parser/DifferentialLineAdjustmentMap.php
new file mode 100644
index 0000000000..fde8f61f7d
--- /dev/null
+++ b/src/applications/differential/parser/DifferentialLineAdjustmentMap.php
@@ -0,0 +1,376 @@
+map;
+ }
+
+ public function getNearestMap() {
+ if ($this->nearestMap === null) {
+ $this->buildNearestMap();
+ }
+
+ return $this->nearestMap;
+ }
+
+ public function getFinalOffset() {
+ // Make sure we've built this map already.
+ $this->getNearestMap();
+ return $this->finalOffset;
+ }
+
+
+ /**
+ * Add a map to the end of the chain.
+ *
+ * When a line is mapped with @{method:mapLine}, it is mapped through all
+ * maps in the chain.
+ */
+ public function addMapToChain(DifferentialLineAdjustmentMap $map) {
+ if ($this->nextMapInChain) {
+ $this->nextMapInChain->addMapToChain($map);
+ } else {
+ $this->nextMapInChain = $map;
+ }
+ return $this;
+ }
+
+
+ /**
+ * Map a line across a change, or a series of changes.
+ *
+ * @param int Line to map
+ * @param bool True to map it as the end of a range.
+ * @return wild Spooky magic.
+ */
+ public function mapLine($line, $is_end) {
+ $nmap = $this->getNearestMap();
+
+ $deleted = false;
+ $offset = false;
+ if (isset($nmap[$line])) {
+ $line_range = $nmap[$line];
+ if ($is_end) {
+ $to_line = end($line_range);
+ } else {
+ $to_line = reset($line_range);
+ }
+ if ($to_line <= 0) {
+ // If we're tracing the first line and this block is collapsing,
+ // compute the offset from the top of the block.
+ if (!$is_end && $this->isInverse) {
+ $offset = 0;
+ $cursor = $line - 1;
+ while (isset($nmap[$cursor])) {
+ $prev = $nmap[$cursor];
+ $prev = reset($prev);
+ if ($prev == $to_line) {
+ $offset++;
+ } else {
+ break;
+ }
+ $cursor--;
+ }
+ }
+
+ $to_line = -$to_line;
+ if (!$this->isInverse) {
+ $deleted = true;
+ }
+ }
+ $line = $to_line;
+ } else {
+ $line = $line + $this->finalOffset;
+ }
+
+ if ($this->nextMapInChain) {
+ $chain = $this->nextMapInChain->mapLine($line, $is_end);
+ list($chain_deleted, $chain_offset, $line) = $chain;
+ $deleted = ($deleted || $chain_deleted);
+ if ($chain_offset !== false) {
+ if ($offset === false) {
+ $offset = 0;
+ }
+ $offset += $chain_offset;
+ }
+ }
+
+ return array($deleted, $offset, $line);
+ }
+
+
+ /**
+ * Build a derived map which maps deleted lines to the nearest valid line.
+ *
+ * This computes a "nearest line" map and a final-line offset. These
+ * derived maps allow us to map deleted code to the previous (or next) line
+ * which actually exists.
+ */
+ private function buildNearestMap() {
+ $map = $this->map;
+ $nmap = array();
+
+ $nearest = 0;
+ foreach ($map as $key => $value) {
+ if ($value) {
+ $nmap[$key] = $value;
+ $nearest = end($value);
+ } else {
+ $nmap[$key][0] = -$nearest;
+ }
+ }
+
+ if (isset($key)) {
+ $this->finalOffset = ($nearest - $key);
+ } else {
+ $this->finalOffset = 0;
+ }
+
+ foreach (array_reverse($map, true) as $key => $value) {
+ if ($value) {
+ $nearest = reset($value);
+ } else {
+ $nmap[$key][1] = -$nearest;
+ }
+ }
+
+ $this->nearestMap = $nmap;
+
+ return $this;
+ }
+
+ public static function newFromHunks(array $hunks) {
+ assert_instances_of($hunks, 'DifferentialHunk');
+
+ $map = array();
+ $o = 0;
+ $n = 0;
+
+ $hunks = msort($hunks, 'getOldOffset');
+ foreach ($hunks as $hunk) {
+
+ // If the hunks are disjoint, add the implied missing lines where
+ // nothing changed.
+ $min = ($hunk->getOldOffset() - 1);
+ while ($o < $min) {
+ $o++;
+ $n++;
+ $map[$o][] = $n;
+ }
+
+ $lines = $hunk->getStructuredLines();
+ foreach ($lines as $line) {
+ switch ($line['type']) {
+ case '-':
+ $o++;
+ $map[$o] = array();
+ break;
+ case '+':
+ $n++;
+ $map[$o][] = $n;
+ break;
+ case ' ':
+ $o++;
+ $n++;
+ $map[$o][] = $n;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ $map = self::reduceMapRanges($map);
+
+ return self::newFromMap($map);
+ }
+
+ public static function newFromMap(array $map) {
+ $obj = new DifferentialLineAdjustmentMap();
+ $obj->map = $map;
+ return $obj;
+ }
+
+ public static function newInverseMap(DifferentialLineAdjustmentMap $map) {
+ $old = $map->getMap();
+ $inv = array();
+ $last = 0;
+ foreach ($old as $k => $v) {
+ if (count($v) > 1) {
+ $v = range(reset($v), end($v));
+ }
+ if ($k == 0) {
+ foreach ($v as $line) {
+ $inv[$line] = array();
+ $last = $line;
+ }
+ } else if ($v) {
+ $first = true;
+ foreach ($v as $line) {
+ if ($first) {
+ $first = false;
+ $inv[$line][] = $k;
+ $last = $line;
+ } else {
+ $inv[$line] = array();
+ }
+ }
+ } else {
+ $inv[$last][] = $k;
+ }
+ }
+
+ $inv = self::reduceMapRanges($inv);
+
+ $obj = new DifferentialLineAdjustmentMap();
+ $obj->map = $inv;
+ $obj->isInverse = !$map->isInverse;
+ return $obj;
+ }
+
+ private static function reduceMapRanges(array $map) {
+ foreach ($map as $key => $values) {
+ if (count($values) > 2) {
+ $map[$key] = array(reset($values), end($values));
+ }
+ }
+ return $map;
+ }
+
+
+ public static function loadMaps(array $maps) {
+ $keys = array();
+ foreach ($maps as $map) {
+ list($u, $v) = $map;
+ $keys[self::getCacheKey($u, $v)] = $map;
+ }
+
+ $cache = new PhabricatorKeyValueDatabaseCache();
+ $cache = new PhutilKeyValueCacheProfiler($cache);
+ $cache->setProfiler(PhutilServiceProfiler::getInstance());
+
+ $results = array();
+
+ if ($keys) {
+ $caches = $cache->getKeys(array_keys($keys));
+ foreach ($caches as $key => $value) {
+ list($u, $v) = $keys[$key];
+ try {
+ $results[$u][$v] = self::newFromMap(
+ phutil_json_decode($value));
+ } catch (Exception $ex) {
+ // Ignore, rebuild below.
+ }
+ unset($keys[$key]);
+ }
+ }
+
+ if ($keys) {
+ $built = self::buildMaps($maps);
+
+ $write = array();
+ foreach ($built as $u => $list) {
+ foreach ($list as $v => $map) {
+ $write[self::getCacheKey($u, $v)] = json_encode($map->getMap());
+ $results[$u][$v] = $map;
+ }
+ }
+
+ $cache->setKeys($write);
+ }
+
+ return $results;
+ }
+
+ private static function buildMaps(array $maps) {
+ $need = array();
+ foreach ($maps as $map) {
+ list($u, $v) = $map;
+ $need[$u] = $u;
+ $need[$v] = $v;
+ }
+
+ if ($need) {
+ $changesets = id(new DifferentialChangesetQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withIDs($need)
+ ->needHunks(true)
+ ->execute();
+ $changesets = mpull($changesets, null, 'getID');
+ }
+
+ $results = array();
+ foreach ($maps as $map) {
+ list($u, $v) = $map;
+ $u_set = idx($changesets, $u);
+ $v_set = idx($changesets, $v);
+
+ if (!$u_set || !$v_set) {
+ continue;
+ }
+
+ // This is the simple case.
+ if ($u == $v) {
+ $results[$u][$v] = self::newFromHunks(
+ $u_set->getHunks());
+ continue;
+ }
+
+ $u_old = $u_set->makeOldFile();
+ $v_old = $v_set->makeOldFile();
+
+ // No difference between the two left sides.
+ if ($u_old == $v_old) {
+ $results[$u][$v] = self::newFromMap(
+ array());
+ continue;
+ }
+
+ // If we're missing context, this won't currently work. We can
+ // make this case work, but it's fairly rare.
+ $u_hunks = $u_set->getHunks();
+ $v_hunks = $v_set->getHunks();
+ if (count($u_hunks) != 1 ||
+ count($v_hunks) != 1 ||
+ head($u_hunks)->getOldOffset() != 1 ||
+ head($u_hunks)->getNewOffset() != 1 ||
+ head($v_hunks)->getOldOffset() != 1 ||
+ head($v_hunks)->getNewOffset() != 1) {
+ continue;
+ }
+
+ $changeset = id(new PhabricatorDifferenceEngine())
+ ->setIgnoreWhitespace(true)
+ ->generateChangesetFromFileContent($u_old, $v_old);
+
+ $results[$u][$v] = self::newFromHunks(
+ $changeset->getHunks());
+ }
+
+ return $results;
+ }
+
+ private static function getCacheKey($u, $v) {
+ return 'diffadjust.v1('.$u.','.$v.')';
+ }
+
+}
diff --git a/src/applications/differential/query/DifferentialInlineCommentQuery.php b/src/applications/differential/query/DifferentialInlineCommentQuery.php
index 711c638bf1..3fb26739c0 100644
--- a/src/applications/differential/query/DifferentialInlineCommentQuery.php
+++ b/src/applications/differential/query/DifferentialInlineCommentQuery.php
@@ -323,6 +323,7 @@ final class DifferentialInlineCommentQuery
'new' => $is_new,
'reason' => $reason,
'href' => $href,
+ 'originalID' => $changeset->getID(),
));
$results[] = $inline;
@@ -348,6 +349,107 @@ final class DifferentialInlineCommentQuery
}
}
+ // Adjust inline line numbers to account for content changes across
+ // updates and rebases.
+ $plan = array();
+ $need = array();
+ foreach ($results as $inline) {
+ $ghost = $inline->getIsGhost();
+ if (!$ghost) {
+ // If this isn't a "ghost" inline, ignore it.
+ continue;
+ }
+
+ $src_id = $ghost['originalID'];
+ $dst_id = $inline->getChangesetID();
+
+ $xforms = array();
+
+ // If the comment is on the right, transform it through the inverse map
+ // back to the left.
+ if ($inline->getIsNewFile()) {
+ $xforms[] = array($src_id, $src_id, true);
+ }
+
+ // Transform it across rebases.
+ $xforms[] = array($src_id, $dst_id, false);
+
+ // If the comment is on the right, transform it back onto the right.
+ if ($inline->getIsNewFile()) {
+ $xforms[] = array($dst_id, $dst_id, false);
+ }
+
+ $key = array();
+ foreach ($xforms as $xform) {
+ list($u, $v, $inverse) = $xform;
+
+ $short = $u.'/'.$v;
+ $need[$short] = array($u, $v);
+
+ $part = $u.($inverse ? '<' : '>').$v;
+ $key[] = $part;
+ }
+ $key = implode(',', $key);
+
+ if (empty($plan[$key])) {
+ $plan[$key] = array(
+ 'xforms' => $xforms,
+ 'inlines' => array(),
+ );
+ }
+
+ $plan[$key]['inlines'][] = $inline;
+ }
+
+ if ($need) {
+ $maps = DifferentialLineAdjustmentMap::loadMaps($need);
+ } else {
+ $maps = array();
+ }
+
+ foreach ($plan as $step) {
+ $xforms = $step['xforms'];
+
+ $chain = null;
+ foreach ($xforms as $xform) {
+ list($u, $v, $inverse) = $xform;
+ $map = idx(idx($maps, $u, array()), $v);
+ if (!$map) {
+ continue 2;
+ }
+
+ if ($inverse) {
+ $map = DifferentialLineAdjustmentMap::newInverseMap($map);
+ } else {
+ $map = clone $map;
+ }
+
+ if ($chain) {
+ $chain->addMapToChain($map);
+ } else {
+ $chain = $map;
+ }
+ }
+
+ foreach ($step['inlines'] as $inline) {
+ $head_line = $inline->getLineNumber();
+ $tail_line = ($head_line + $inline->getLineLength());
+
+ $head_info = $chain->mapLine($head_line, false);
+ $tail_info = $chain->mapLine($tail_line, true);
+
+ list($head_deleted, $head_offset, $head_line) = $head_info;
+ list($tail_deleted, $tail_offset, $tail_line) = $tail_info;
+
+ if ($head_offset !== false) {
+ $inline->setLineNumber($head_line + 1 + $head_offset);
+ } else {
+ $inline->setLineNumber($head_line);
+ $inline->setLineLength($tail_line - $head_line);
+ }
+ }
+ }
+
return $results;
}
diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
index a9eef41ac8..3eb0190664 100644
--- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
@@ -278,6 +278,7 @@ final class DifferentialChangesetTwoUpRenderer
$scaffold->addInlineView($companion);
unset($new_comments[$n_num][$key]);
+ break;
}
}
}
diff --git a/src/applications/differential/storage/DifferentialHunk.php b/src/applications/differential/storage/DifferentialHunk.php
index f22ba59d2b..bd13cadfec 100644
--- a/src/applications/differential/storage/DifferentialHunk.php
+++ b/src/applications/differential/storage/DifferentialHunk.php
@@ -117,7 +117,7 @@ abstract class DifferentialHunk extends DifferentialDAO
return $this->splitLines;
}
- private function getStructuredLines() {
+ public function getStructuredLines() {
if ($this->structuredLines === null) {
$lines = $this->getSplitLines();
diff --git a/src/applications/differential/storage/DifferentialTransaction.php b/src/applications/differential/storage/DifferentialTransaction.php
index 5cb6fd7e87..2857d24272 100644
--- a/src/applications/differential/storage/DifferentialTransaction.php
+++ b/src/applications/differential/storage/DifferentialTransaction.php
@@ -568,7 +568,7 @@ final class DifferentialTransaction extends PhabricatorApplicationTransaction {
'this revision.');
}
break;
- case DifferentialTransaction::TYPE_ACTION:
+ case self::TYPE_ACTION:
switch ($this->getNewValue()) {
case DifferentialAction::ACTION_CLOSE:
return pht('This revision is already closed.');
diff --git a/src/applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php b/src/applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php
new file mode 100644
index 0000000000..8fc28953fc
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php
@@ -0,0 +1,294 @@
+ array(1),
+ 2 => array(2),
+ 3 => array(3),
+ 4 => array(),
+ 5 => array(),
+ 6 => array(),
+ 7 => array(4),
+ 8 => array(5),
+ 9 => array(6),
+ 10 => array(7),
+ 11 => array(8),
+ 12 => array(9),
+ 13 => array(10),
+ 14 => array(11),
+ 15 => array(12),
+ 16 => array(13),
+ 17 => array(14),
+ 18 => array(15),
+ 19 => array(16),
+ 20 => array(17, 20),
+ 21 => array(21),
+ 22 => array(22),
+ 23 => array(23),
+ 24 => array(24),
+ 25 => array(25),
+ 26 => array(26),
+ );
+
+ $hunks = $this->loadHunks('add.diff');
+ $this->assertEqual(
+ array(
+ 0 => array(1, 26),
+ ),
+ DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap());
+
+ $hunks = $this->loadHunks('change.diff');
+ $this->assertEqual(
+ $change_map,
+ DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap());
+
+ $hunks = $this->loadHunks('remove.diff');
+ $this->assertEqual(
+ array_fill_keys(range(1, 26), array()),
+ DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap());
+
+ // With the contextless diff, we don't get the last few similar lines
+ // in the map.
+ $reduced_map = $change_map;
+ unset($reduced_map[24]);
+ unset($reduced_map[25]);
+ unset($reduced_map[26]);
+
+ $hunks = $this->loadHunks('context.diff');
+ $this->assertEqual(
+ $reduced_map,
+ DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap());
+ }
+
+
+ public function testInverseMaps() {
+ $change_map = array(
+ 1 => array(1),
+ 2 => array(2),
+ 3 => array(3, 6),
+ 4 => array(7),
+ 5 => array(8),
+ 6 => array(9),
+ 7 => array(10),
+ 8 => array(11),
+ 9 => array(12),
+ 10 => array(13),
+ 11 => array(14),
+ 12 => array(15),
+ 13 => array(16),
+ 14 => array(17),
+ 15 => array(18),
+ 16 => array(19),
+ 17 => array(20),
+ 18 => array(),
+ 19 => array(),
+ 20 => array(),
+ 21 => array(21),
+ 22 => array(22),
+ 23 => array(23),
+ 24 => array(24),
+ 25 => array(25),
+ 26 => array(26),
+ );
+
+ $hunks = $this->loadHunks('add.diff');
+ $this->assertEqual(
+ array_fill_keys(range(1, 26), array()),
+ DifferentialLineAdjustmentMap::newInverseMap(
+ DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap());
+
+ $hunks = $this->loadHunks('change.diff');
+ $this->assertEqual(
+ $change_map,
+ DifferentialLineAdjustmentMap::newInverseMap(
+ DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap());
+
+ $hunks = $this->loadHunks('remove.diff');
+ $this->assertEqual(
+ array(
+ 0 => array(1, 26),
+ ),
+ DifferentialLineAdjustmentMap::newInverseMap(
+ DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap());
+
+ // With the contextless diff, we don't get the last few similar lines
+ // in the map.
+ $reduced_map = $change_map;
+ unset($reduced_map[24]);
+ unset($reduced_map[25]);
+ unset($reduced_map[26]);
+
+ $hunks = $this->loadHunks('context.diff');
+ $this->assertEqual(
+ $reduced_map,
+ DifferentialLineAdjustmentMap::newInverseMap(
+ DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap());
+ }
+
+
+ public function testNearestMaps() {
+ $change_map = array(
+ 1 => array(1),
+ 2 => array(2),
+ 3 => array(3),
+ 4 => array(-3, -4),
+ 5 => array(-3, -4),
+ 6 => array(-3, -4),
+ 7 => array(4),
+ 8 => array(5),
+ 9 => array(6),
+ 10 => array(7),
+ 11 => array(8),
+ 12 => array(9),
+ 13 => array(10),
+ 14 => array(11),
+ 15 => array(12),
+ 16 => array(13),
+ 17 => array(14),
+ 18 => array(15),
+ 19 => array(16),
+ 20 => array(17, 20),
+ 21 => array(21),
+ 22 => array(22),
+ 23 => array(23),
+ 24 => array(24),
+ 25 => array(25),
+ 26 => array(26),
+ );
+
+ $hunks = $this->loadHunks('add.diff');
+ $map = DifferentialLineAdjustmentMap::newFromHunks($hunks);
+ $this->assertEqual(
+ array(
+ 0 => array(1, 26),
+ ),
+ $map->getNearestMap());
+ $this->assertEqual(26, $map->getFinalOffset());
+
+
+ $hunks = $this->loadHunks('change.diff');
+ $map = DifferentialLineAdjustmentMap::newFromHunks($hunks);
+ $this->assertEqual(
+ $change_map,
+ $map->getNearestMap());
+ $this->assertEqual(0, $map->getFinalOffset());
+
+
+ $hunks = $this->loadHunks('remove.diff');
+ $map = DifferentialLineAdjustmentMap::newFromHunks($hunks);
+ $this->assertEqual(
+ array_fill_keys(
+ range(1, 26),
+ array(0, 0)),
+ $map->getNearestMap());
+ $this->assertEqual(-26, $map->getFinalOffset());
+
+
+ $reduced_map = $change_map;
+ unset($reduced_map[24]);
+ unset($reduced_map[25]);
+ unset($reduced_map[26]);
+
+ $hunks = $this->loadHunks('context.diff');
+ $map = DifferentialLineAdjustmentMap::newFromHunks($hunks);
+ $this->assertEqual(
+ $reduced_map,
+ $map->getNearestMap());
+ $this->assertEqual(0, $map->getFinalOffset());
+
+
+ $hunks = $this->loadHunks('insert.diff');
+ $map = DifferentialLineAdjustmentMap::newFromHunks($hunks);
+ $this->assertEqual(
+ array(
+ 1 => array(1),
+ 2 => array(2),
+ 3 => array(3),
+ 4 => array(4),
+ 5 => array(5),
+ 6 => array(6),
+ 7 => array(7),
+ 8 => array(8),
+ 9 => array(9),
+ 10 => array(10, 13),
+ 11 => array(14),
+ 12 => array(15),
+ 13 => array(16),
+ ),
+ $map->getNearestMap());
+ $this->assertEqual(3, $map->getFinalOffset());
+ }
+
+
+ public function testChainMaps() {
+ // This test simulates porting inlines forward across a rebase.
+ // Part 1 is the original diff.
+ // Part 2 is the rebase, which we would normally compute synthetically.
+ // Part 3 is the updated diff against the rebased changes.
+
+ $diff1 = $this->loadHunks('chain.adjust.1.diff');
+ $diff2 = $this->loadHunks('chain.adjust.2.diff');
+ $diff3 = $this->loadHunks('chain.adjust.3.diff');
+
+ $map = DifferentialLineAdjustmentMap::newInverseMap(
+ DifferentialLineAdjustmentMap::newFromHunks($diff1));
+
+ $map->addMapToChain(
+ DifferentialLineAdjustmentMap::newFromHunks($diff2));
+
+ $map->addMapToChain(
+ DifferentialLineAdjustmentMap::newFromHunks($diff3));
+
+ $actual = array();
+ for ($ii = 1; $ii <= 13; $ii++) {
+ $actual[$ii] = array(
+ $map->mapLine($ii, false),
+ $map->mapLine($ii, true),
+ );
+ }
+
+ $this->assertEqual(
+ array(
+ 1 => array(array(false, false, 1), array(false, false, 1)),
+ 2 => array(array(true, false, 1), array(true, false, 2)),
+ 3 => array(array(true, false, 1), array(true, false, 2)),
+ 4 => array(array(false, false, 2), array(false, false, 2)),
+ 5 => array(array(false, false, 3), array(false, false, 3)),
+ 6 => array(array(false, false, 4), array(false, false, 4)),
+ 7 => array(array(false, false, 5), array(false, false, 8)),
+ 8 => array(array(false, 0, 5), array(false, false, 9)),
+ 9 => array(array(false, 1, 5), array(false, false, 9)),
+ 10 => array(array(false, 2, 5), array(false, false, 9)),
+ 11 => array(array(false, false, 9), array(false, false, 9)),
+ 12 => array(array(false, false, 10), array(false, false, 10)),
+ 13 => array(array(false, false, 11), array(false, false, 11)),
+ ),
+ $actual);
+ }
+
+
+ private function loadHunks($name) {
+ $root = dirname(__FILE__).'/map/';
+ $data = Filesystem::readFile($root.$name);
+
+ $parser = new ArcanistDiffParser();
+ $changes = $parser->parseDiff($data);
+
+ $viewer = PhabricatorUser::getOmnipotentUser();
+ $diff = DifferentialDiff::newFromRawChanges($viewer, $changes);
+
+ $changesets = $diff->getChangesets();
+ if (count($changesets) !== 1) {
+ throw new Exception(
+ pht(
+ 'Expected exactly one changeset from "%s".',
+ $name));
+ }
+ $changeset = head($changesets);
+
+ return $changeset->getHunks();
+ }
+
+}
diff --git a/src/applications/differential/storage/__tests__/map/add.diff b/src/applications/differential/storage/__tests__/map/add.diff
new file mode 100644
index 0000000000..97e60a8b7c
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/add.diff
@@ -0,0 +1,32 @@
+diff --git a/alphabet b/alphabet
+new file mode 100644
+index 0000000..0edb856
+--- /dev/null
++++ b/alphabet
+@@ -0,0 +1,26 @@
++a
++b
++c
++d
++e
++f
++g
++h
++i
++j
++k
++l
++m
++n
++o
++p
++q
++r
++s
++t
++u
++v
++w
++x
++y
++z
diff --git a/src/applications/differential/storage/__tests__/map/chain.adjust.1.diff b/src/applications/differential/storage/__tests__/map/chain.adjust.1.diff
new file mode 100644
index 0000000000..8370a66e1a
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/chain.adjust.1.diff
@@ -0,0 +1,14 @@
+diff --git a/alphabet b/alphabet
+index 92dfa21..292798b 100644
+--- a/alphabet
++++ b/alphabet
+@@ -5,6 +5,9 @@ d
+ e
+ f
+ g
++G1
++G2
++G3
+ h
+ i
+ j
diff --git a/src/applications/differential/storage/__tests__/map/chain.adjust.2.diff b/src/applications/differential/storage/__tests__/map/chain.adjust.2.diff
new file mode 100644
index 0000000000..ac6f8c854a
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/chain.adjust.2.diff
@@ -0,0 +1,11 @@
+diff --git a/alphabet b/alphabet
+index 92dfa21..e3344af 100644
+--- a/alphabet
++++ b/alphabet
+@@ -1,6 +1,4 @@
+ a
+-b
+-c
+ d
+ e
+ f
diff --git a/src/applications/differential/storage/__tests__/map/chain.adjust.3.diff b/src/applications/differential/storage/__tests__/map/chain.adjust.3.diff
new file mode 100644
index 0000000000..4d23f185fd
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/chain.adjust.3.diff
@@ -0,0 +1,14 @@
+diff --git a/alphabet b/alphabet
+index e3344af..febfe3e 100644
+--- a/alphabet
++++ b/alphabet
+@@ -3,6 +3,9 @@ d
+ e
+ f
+ g
++G1x
++G2x
++G3x
+ h
+ i
+ j
diff --git a/src/applications/differential/storage/__tests__/map/change.diff b/src/applications/differential/storage/__tests__/map/change.diff
new file mode 100644
index 0000000000..7ef945267f
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/change.diff
@@ -0,0 +1,34 @@
+diff --git a/alphabet b/alphabet
+index 0edb856..2449de2 100644
+--- a/alphabet
++++ b/alphabet
+@@ -1,26 +1,26 @@
+ a
+ b
+ c
+-d
+-e
+-f
+ g
+ h
+ i
+ j
+ k
+ l
+ m
+ n
+ o
+ p
+ q
+ r
+ s
+ t
++tx
++ty
++tz
+ u
+ v
+ w
+ x
+ y
+ z
diff --git a/src/applications/differential/storage/__tests__/map/context.diff b/src/applications/differential/storage/__tests__/map/context.diff
new file mode 100644
index 0000000000..ab77e4a9ba
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/context.diff
@@ -0,0 +1,24 @@
+diff --git a/alphabet b/alphabet
+index 0edb856..2449de2 100644
+--- a/alphabet
++++ b/alphabet
+@@ -1,9 +1,6 @@
+ a
+ b
+ c
+-d
+-e
+-f
+ g
+ h
+ i
+@@ -18,6 +15,9 @@ q
+ r
+ s
+ t
++tx
++ty
++tz
+ u
+ v
+ w
diff --git a/src/applications/differential/storage/__tests__/map/insert.diff b/src/applications/differential/storage/__tests__/map/insert.diff
new file mode 100644
index 0000000000..9a726955e7
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/insert.diff
@@ -0,0 +1,14 @@
+diff --git a/alphabet b/alphabet
+index f2b41ef..755b349 100644
+--- a/alphabet
++++ b/alphabet
+@@ -8,6 +8,9 @@ g
+ h
+ i
+ j
++j1
++j2
++j3
+ k
+ l
+ n
diff --git a/src/applications/differential/storage/__tests__/map/remove.diff b/src/applications/differential/storage/__tests__/map/remove.diff
new file mode 100644
index 0000000000..0feafbbfc3
--- /dev/null
+++ b/src/applications/differential/storage/__tests__/map/remove.diff
@@ -0,0 +1,32 @@
+diff --git a/alphabet b/alphabet
+deleted file mode 100644
+index 2449de2..0000000
+--- a/alphabet
++++ /dev/null
+@@ -1,26 +0,0 @@
+-a
+-b
+-c
+-g
+-h
+-i
+-j
+-k
+-l
+-m
+-n
+-o
+-p
+-q
+-r
+-s
+-t
+-tx
+-ty
+-tz
+-u
+-v
+-w
+-x
+-y
+-z
diff --git a/src/applications/differential/view/DifferentialLocalCommitsView.php b/src/applications/differential/view/DifferentialLocalCommitsView.php
index 489674b0ab..ab83016e2a 100644
--- a/src/applications/differential/view/DifferentialLocalCommitsView.php
+++ b/src/applications/differential/view/DifferentialLocalCommitsView.php
@@ -19,7 +19,7 @@ final class DifferentialLocalCommitsView extends AphrontView {
public function render() {
$user = $this->user;
if (!$user) {
- throw new Exception('Call setUser() before render()-ing this view.');
+ throw new PhutilInvalidStateException('setUser');
}
$local = $this->localCommits;
diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php
index 1dd5a5cc57..121792585d 100644
--- a/src/applications/differential/view/DifferentialRevisionListView.php
+++ b/src/applications/differential/view/DifferentialRevisionListView.php
@@ -57,10 +57,9 @@ final class DifferentialRevisionListView extends AphrontView {
}
public function render() {
-
$user = $this->user;
if (!$user) {
- throw new Exception('Call setUser() before render()!');
+ throw new PhutilInvalidStateException('setUser');
}
$fresh = PhabricatorEnv::getEnvConfig('differential.days-fresh');
diff --git a/src/applications/diffusion/data/DiffusionPathChange.php b/src/applications/diffusion/data/DiffusionPathChange.php
index b1d7286fdd..6f96057014 100644
--- a/src/applications/diffusion/data/DiffusionPathChange.php
+++ b/src/applications/diffusion/data/DiffusionPathChange.php
@@ -119,7 +119,7 @@ final class DiffusionPathChange {
}
final public static function convertToArcanistChanges(array $changes) {
- assert_instances_of($changes, 'DiffusionPathChange');
+ assert_instances_of($changes, __CLASS__);
$direct = array();
$result = array();
foreach ($changes as $path) {
@@ -145,7 +145,7 @@ final class DiffusionPathChange {
final public static function convertToDifferentialChangesets(
PhabricatorUser $user,
array $changes) {
- assert_instances_of($changes, 'DiffusionPathChange');
+ assert_instances_of($changes, __CLASS__);
$arcanist_changes = self::convertToArcanistChanges($changes);
$diff = DifferentialDiff::newEphemeralFromRawChanges(
$arcanist_changes);
diff --git a/src/applications/diffusion/query/DiffusionLintCountQuery.php b/src/applications/diffusion/query/DiffusionLintCountQuery.php
index f85505af76..4441ccf3ef 100644
--- a/src/applications/diffusion/query/DiffusionLintCountQuery.php
+++ b/src/applications/diffusion/query/DiffusionLintCountQuery.php
@@ -23,11 +23,11 @@ final class DiffusionLintCountQuery extends PhabricatorQuery {
public function execute() {
if (!$this->paths) {
- throw new Exception(pht('Call withPaths() before execute()!'));
+ throw new PhutilInvalidStateException('withPaths');
}
if (!$this->branchIDs) {
- throw new Exception(pht('Call withBranchIDs() before execute()!'));
+ throw new PhutilInvalidStateException('withBranchIDs');
}
$conn_r = id(new PhabricatorRepositoryCommit())->establishConnection('r');
diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php
index 0e8472c24d..e14198371c 100644
--- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php
+++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php
@@ -17,7 +17,7 @@ abstract class DiffusionLowLevelQuery extends Phobject {
public function execute() {
if (!$this->getRepository()) {
- throw new Exception('Call setRepository() before execute()!');
+ throw new PhutilInvalidStateException('setRepository');
}
return $this->executeQuery();
diff --git a/src/applications/diviner/atom/DivinerAtom.php b/src/applications/diviner/atom/DivinerAtom.php
index 46c1c70a9e..ef1a079a41 100644
--- a/src/applications/diviner/atom/DivinerAtom.php
+++ b/src/applications/diviner/atom/DivinerAtom.php
@@ -95,16 +95,14 @@ final class DivinerAtom {
public function getDocblockText() {
if ($this->docblockText === null) {
- throw new Exception(
- pht('Call %s before %s!', 'setDocblockRaw()', 'getDocblockText()'));
+ throw new PhutilInvalidStateException('setDocblockRaw');
}
return $this->docblockText;
}
public function getDocblockMeta() {
if ($this->docblockMeta === null) {
- throw new Exception(
- pht('Call %s before %s!', 'setDocblockRaw()', 'getDocblockMeta()'));
+ throw new PhutilInvalidStateException('setDocblockRaw');
}
return $this->docblockMeta;
}
diff --git a/src/applications/diviner/storage/DivinerLiveSymbol.php b/src/applications/diviner/storage/DivinerLiveSymbol.php
index b6a7cde226..ba35eed255 100644
--- a/src/applications/diviner/storage/DivinerLiveSymbol.php
+++ b/src/applications/diviner/storage/DivinerLiveSymbol.php
@@ -174,7 +174,7 @@ final class DivinerLiveSymbol extends DivinerDAO
}
public function attachExtends(array $extends) {
- assert_instances_of($extends, 'DivinerLiveSymbol');
+ assert_instances_of($extends, __CLASS__);
$this->extends = $extends;
return $this;
}
@@ -184,7 +184,7 @@ final class DivinerLiveSymbol extends DivinerDAO
}
public function attachChildren(array $children) {
- assert_instances_of($children, 'DivinerLiveSymbol');
+ assert_instances_of($children, __CLASS__);
$this->children = $children;
return $this;
}
diff --git a/src/applications/doorkeeper/view/DoorkeeperTagView.php b/src/applications/doorkeeper/view/DoorkeeperTagView.php
index 66f759f43c..8dee79d72b 100644
--- a/src/applications/doorkeeper/view/DoorkeeperTagView.php
+++ b/src/applications/doorkeeper/view/DoorkeeperTagView.php
@@ -12,7 +12,7 @@ final class DoorkeeperTagView extends AphrontView {
public function render() {
$xobj = $this->xobj;
if (!$xobj) {
- throw new Exception('Call setExternalObject() before render()!');
+ throw new PhutilInvalidStateException('setExternalObject');
}
$tag_id = celerity_generate_unique_node_id();
diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
index 18ca597a90..43891d0d07 100644
--- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
@@ -377,7 +377,7 @@ abstract class DrydockBlueprintImplementation {
if ($list === null) {
$blueprints = id(new PhutilSymbolLoader())
->setType('class')
- ->setAncestorClass('DrydockBlueprintImplementation')
+ ->setAncestorClass(__CLASS__)
->setConcreteOnly(true)
->selectAndLoadSymbols();
$list = ipull($blueprints, 'name', 'name');
diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php
index cb61e54490..252c922065 100644
--- a/src/applications/drydock/storage/DrydockLease.php
+++ b/src/applications/drydock/storage/DrydockLease.php
@@ -149,7 +149,7 @@ final class DrydockLease extends DrydockDAO
}
public static function waitForLeases(array $leases) {
- assert_instances_of($leases, 'DrydockLease');
+ assert_instances_of($leases, __CLASS__);
$task_ids = array_filter(mpull($leases, 'getTaskID'));
diff --git a/src/applications/feed/story/PhabricatorFeedStory.php b/src/applications/feed/story/PhabricatorFeedStory.php
index a115985704..f94c2601e8 100644
--- a/src/applications/feed/story/PhabricatorFeedStory.php
+++ b/src/applications/feed/story/PhabricatorFeedStory.php
@@ -48,7 +48,7 @@ abstract class PhabricatorFeedStory
try {
$ok =
class_exists($class) &&
- is_subclass_of($class, 'PhabricatorFeedStory');
+ is_subclass_of($class, __CLASS__);
} catch (PhutilMissingSymbolException $ex) {
$ok = false;
}
@@ -453,15 +453,9 @@ abstract class PhabricatorFeedStory
* @task policy
*/
public function getPolicy($capability) {
- // If this story's primary object is a policy-aware object, use its policy
- // to control story visiblity.
-
- $primary_phid = $this->getPrimaryObjectPHID();
- if (isset($this->objects[$primary_phid])) {
- $object = $this->objects[$primary_phid];
- if ($object instanceof PhabricatorPolicyInterface) {
- return $object->getPolicy($capability);
- }
+ $policy_object = $this->getPrimaryPolicyObject();
+ if ($policy_object) {
+ return $policy_object->getPolicy($capability);
}
// TODO: Remove this once all objects are policy-aware. For now, keep
@@ -476,6 +470,11 @@ abstract class PhabricatorFeedStory
* @task policy
*/
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ $policy_object = $this->getPrimaryPolicyObject();
+ if ($policy_object) {
+ return $policy_object->hasAutomaticCapability($capability, $viewer);
+ }
+
return false;
}
@@ -484,6 +483,26 @@ abstract class PhabricatorFeedStory
}
+ /**
+ * Get the policy object this story is about, if such a policy object
+ * exists.
+ *
+ * @return PhabricatorPolicyInterface|null Policy object, if available.
+ * @task policy
+ */
+ private function getPrimaryPolicyObject() {
+ $primary_phid = $this->getPrimaryObjectPHID();
+ if (empty($this->objects[$primary_phid])) {
+ $object = $this->objects[$primary_phid];
+ if ($object instanceof PhabricatorPolicyInterface) {
+ return $object;
+ }
+ }
+
+ return null;
+ }
+
+
/* -( PhabricatorMarkupInterface Implementation )--------------------------- */
diff --git a/src/applications/files/PhabricatorImageTransformer.php b/src/applications/files/PhabricatorImageTransformer.php
index 95cf1fac7a..829f5d9f2d 100644
--- a/src/applications/files/PhabricatorImageTransformer.php
+++ b/src/applications/files/PhabricatorImageTransformer.php
@@ -20,51 +20,6 @@ final class PhabricatorImageTransformer {
));
}
- public function executeThumbTransform(
- PhabricatorFile $file,
- $x,
- $y) {
-
- $image = $this->crudelyScaleTo($file, $x, $y);
-
- return PhabricatorFile::newFromFileData(
- $image,
- array(
- 'name' => 'thumb-'.$file->getName(),
- 'canCDN' => true,
- ));
- }
-
- public function executeProfileTransform(
- PhabricatorFile $file,
- $x,
- $min_y,
- $max_y) {
-
- $image = $this->crudelyCropTo($file, $x, $min_y, $max_y);
-
- return PhabricatorFile::newFromFileData(
- $image,
- array(
- 'name' => 'profile-'.$file->getName(),
- 'canCDN' => true,
- ));
- }
-
- public function executePreviewTransform(
- PhabricatorFile $file,
- $size) {
-
- $image = $this->generatePreview($file, $size);
-
- return PhabricatorFile::newFromFileData(
- $image,
- array(
- 'name' => 'preview-'.$file->getName(),
- 'canCDN' => true,
- ));
- }
-
public function executeConpherenceTransform(
PhabricatorFile $file,
$top,
@@ -83,38 +38,11 @@ final class PhabricatorImageTransformer {
$image,
array(
'name' => 'conpherence-'.$file->getName(),
+ 'profile' => true,
'canCDN' => true,
));
}
- private function crudelyCropTo(PhabricatorFile $file, $x, $min_y, $max_y) {
- $data = $file->loadFileData();
- $img = imagecreatefromstring($data);
- $sx = imagesx($img);
- $sy = imagesy($img);
-
- $scaled_y = ($x / $sx) * $sy;
- if ($scaled_y > $max_y) {
- // This image is very tall and thin.
- $scaled_y = $max_y;
- } else if ($scaled_y < $min_y) {
- // This image is very short and wide.
- $scaled_y = $min_y;
- }
-
- $cropped = $this->applyScaleWithImagemagick($file, $x, $scaled_y);
- if ($cropped != null) {
- return $cropped;
- }
-
- $img = $this->applyScaleTo(
- $file,
- $x,
- $scaled_y);
-
- return self::saveImageDataInAnyFormat($img, $file->getMimeType());
- }
-
private function crasslyCropTo(PhabricatorFile $file, $top, $left, $w, $h) {
$data = $file->loadFileData();
$src = imagecreatefromstring($data);
@@ -137,21 +65,6 @@ final class PhabricatorImageTransformer {
return self::saveImageDataInAnyFormat($dst, $file->getMimeType());
}
-
- /**
- * Very crudely scale an image up or down to an exact size.
- */
- private function crudelyScaleTo(PhabricatorFile $file, $dx, $dy) {
- $scaled = $this->applyScaleWithImagemagick($file, $dx, $dy);
-
- if ($scaled != null) {
- return $scaled;
- }
-
- $dst = $this->applyScaleTo($file, $dx, $dy);
- return self::saveImageDataInAnyFormat($dst, $file->getMimeType());
- }
-
private function getBlankDestinationFile($dx, $dy) {
$dst = imagecreatetruecolor($dx, $dy);
imagesavealpha($dst, true);
@@ -160,65 +73,6 @@ final class PhabricatorImageTransformer {
return $dst;
}
- private function applyScaleTo(PhabricatorFile $file, $dx, $dy) {
- $data = $file->loadFileData();
- $src = imagecreatefromstring($data);
-
- $x = imagesx($src);
- $y = imagesy($src);
-
- $scale = min(($dx / $x), ($dy / $y), 1);
-
- $sdx = $scale * $x;
- $sdy = $scale * $y;
-
- $dst = $this->getBlankDestinationFile($dx, $dy);
- imagesavealpha($dst, true);
- imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 255, 255, 255, 127));
-
- imagecopyresampled(
- $dst,
- $src,
- ($dx - $sdx) / 2, ($dy - $sdy) / 2,
- 0, 0,
- $sdx, $sdy,
- $x, $y);
-
- return $dst;
-
- }
-
- public static function getPreviewDimensions(PhabricatorFile $file, $size) {
- $metadata = $file->getMetadata();
- $x = idx($metadata, PhabricatorFile::METADATA_IMAGE_WIDTH);
- $y = idx($metadata, PhabricatorFile::METADATA_IMAGE_HEIGHT);
-
- if (!$x || !$y) {
- $data = $file->loadFileData();
- $src = imagecreatefromstring($data);
-
- $x = imagesx($src);
- $y = imagesy($src);
- }
-
- $scale = min($size / $x, $size / $y, 1);
-
- $dx = max($size / 4, $scale * $x);
- $dy = max($size / 4, $scale * $y);
-
- $sdx = $scale * $x;
- $sdy = $scale * $y;
-
- return array(
- 'x' => $x,
- 'y' => $y,
- 'dx' => $dx,
- 'dy' => $dy,
- 'sdx' => $sdx,
- 'sdy' => $sdy,
- );
- }
-
public static function getScaleForCrop(
PhabricatorFile $file,
$des_width,
@@ -241,31 +95,6 @@ final class PhabricatorImageTransformer {
return $scale;
}
- private function generatePreview(PhabricatorFile $file, $size) {
- $data = $file->loadFileData();
- $src = imagecreatefromstring($data);
-
- $dimensions = self::getPreviewDimensions($file, $size);
- $x = $dimensions['x'];
- $y = $dimensions['y'];
- $dx = $dimensions['dx'];
- $dy = $dimensions['dy'];
- $sdx = $dimensions['sdx'];
- $sdy = $dimensions['sdy'];
-
- $dst = $this->getBlankDestinationFile($dx, $dy);
-
- imagecopyresampled(
- $dst,
- $src,
- ($dx - $sdx) / 2, ($dy - $sdy) / 2,
- 0, 0,
- $sdx, $sdy,
- $x, $y);
-
- return self::saveImageDataInAnyFormat($dst, $file->getMimeType());
- }
-
private function applyMemeToFile(
PhabricatorFile $file,
$upper_text,
@@ -402,49 +231,6 @@ final class PhabricatorImageTransformer {
);
}
- private function applyScaleWithImagemagick(PhabricatorFile $file, $dx, $dy) {
- $img_type = $file->getMimeType();
- $imagemagick = PhabricatorEnv::getEnvConfig('files.enable-imagemagick');
-
- if ($img_type != 'image/gif' || $imagemagick == false) {
- return null;
- }
-
- $data = $file->loadFileData();
- $src = imagecreatefromstring($data);
-
- $x = imagesx($src);
- $y = imagesy($src);
-
- if (self::isEnormousGIF($x, $y)) {
- return null;
- }
-
- $scale = min(($dx / $x), ($dy / $y), 1);
-
- $sdx = $scale * $x;
- $sdy = $scale * $y;
-
- $input = new TempFile();
- Filesystem::writeFile($input, $data);
-
- $resized = new TempFile();
-
- $future = new ExecFuture(
- 'convert %s -coalesce -resize %sX%s%s %s',
- $input,
- $sdx,
- $sdy,
- '!',
- $resized);
-
- // Don't spend more than 10 seconds resizing; just fail if it takes longer
- // than that.
- $future->setTimeout(10)->resolvex();
-
- return Filesystem::readFile($resized);
- }
-
private function applyMemeWithImagemagick(
$input,
$above,
@@ -482,57 +268,6 @@ final class PhabricatorImageTransformer {
return Filesystem::readFile($output);
}
-/* -( Detecting Enormous Files )------------------------------------------- */
-
-
- /**
- * Determine if an image is enormous (too large to transform).
- *
- * Attackers can perform a denial of service attack by uploading highly
- * compressible images with enormous dimensions but a very small filesize.
- * Transforming them (e.g., into thumbnails) may consume huge quantities of
- * memory and CPU relative to the resources required to transmit the file.
- *
- * In general, we respond to these images by declining to transform them, and
- * using a default thumbnail instead.
- *
- * @param int Width of the image, in pixels.
- * @param int Height of the image, in pixels.
- * @return bool True if this image is enormous (too large to transform).
- * @task enormous
- */
- public static function isEnormousImage($x, $y) {
- // This is just a sanity check, but if we don't have valid dimensions we
- // shouldn't be trying to transform the file.
- if (($x <= 0) || ($y <= 0)) {
- return true;
- }
-
- return ($x * $y) > (4096 * 4096);
- }
-
-
- /**
- * Determine if a GIF is enormous (too large to transform).
- *
- * For discussion, see @{method:isEnormousImage}. We need to be more
- * careful about GIFs, because they can also have a large number of frames
- * despite having a very small filesize. We're more conservative about
- * calling GIFs enormous than about calling images in general enormous.
- *
- * @param int Width of the GIF, in pixels.
- * @param int Height of the GIF, in pixels.
- * @return bool True if this image is enormous (too large to transform).
- * @task enormous
- */
- public static function isEnormousGIF($x, $y) {
- if (self::isEnormousImage($x, $y)) {
- return true;
- }
-
- return ($x * $y) > (800 * 800);
- }
-
/* -( Saving Image Data )-------------------------------------------------- */
diff --git a/src/applications/files/application/PhabricatorFilesApplication.php b/src/applications/files/application/PhabricatorFilesApplication.php
index 1187b0545b..ed4801ace6 100644
--- a/src/applications/files/application/PhabricatorFilesApplication.php
+++ b/src/applications/files/application/PhabricatorFilesApplication.php
@@ -91,6 +91,8 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
'(?P[^/]+)/'.
'(?P[^/]+)/'
=> 'PhabricatorFileTransformController',
+ 'transforms/(?P[1-9]\d*)/' =>
+ 'PhabricatorFileTransformListController',
'uploaddialog/' => 'PhabricatorFileUploadDialogController',
'download/(?P[^/]+)/' => 'PhabricatorFileDialogController',
),
diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php
index 6903cff5d3..d1e6041131 100644
--- a/src/applications/files/controller/PhabricatorFileComposeController.php
+++ b/src/applications/files/controller/PhabricatorFileComposeController.php
@@ -58,7 +58,7 @@ final class PhabricatorFileComposeController
}
$root = dirname(phutil_get_library_root('phabricator'));
- $icon_file = $root.'/resources/sprite/projects_1x/'.$icon.'.png';
+ $icon_file = $root.'/resources/sprite/projects_2x/'.$icon.'.png';
$icon_data = Filesystem::readFile($icon_file);
@@ -68,6 +68,7 @@ final class PhabricatorFileComposeController
$data,
array(
'name' => 'project.png',
+ 'profile' => true,
'canCDN' => true,
));
@@ -241,7 +242,7 @@ final class PhabricatorFileComposeController
}
$dialog_id = celerity_generate_unique_node_id();
- $color_input_id = celerity_generate_unique_node_id();;
+ $color_input_id = celerity_generate_unique_node_id();
$icon_input_id = celerity_generate_unique_node_id();
$preview_id = celerity_generate_unique_node_id();
@@ -325,10 +326,10 @@ final class PhabricatorFileComposeController
$color_string = idx($map, $color, '#ff00ff');
$color_const = hexdec(trim($color_string, '#'));
- $canvas = imagecreatetruecolor(50, 50);
+ $canvas = imagecreatetruecolor(100, 100);
imagefill($canvas, 0, 0, $color_const);
- imagecopy($canvas, $icon_img, 0, 0, 0, 0, 50, 50);
+ imagecopy($canvas, $icon_img, 0, 0, 0, 0, 100, 100);
return PhabricatorImageTransformer::saveImageDataInAnyFormat(
$canvas,
diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php
index 2a66a3807e..e528eb15f2 100644
--- a/src/applications/files/controller/PhabricatorFileDataController.php
+++ b/src/applications/files/controller/PhabricatorFileDataController.php
@@ -227,7 +227,7 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
private function getFile() {
if (!$this->file) {
- throw new Exception(pht('Call loadFile() before getFile()!'));
+ throw new PhutilInvalidStateException('loadFile');
}
return $this->file;
}
diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php
index 3d9731f6b4..0e3d041eac 100644
--- a/src/applications/files/controller/PhabricatorFileInfoController.php
+++ b/src/applications/files/controller/PhabricatorFileInfoController.php
@@ -170,6 +170,12 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
->setWorkflow(true)
->setDisabled(!$can_edit));
+ $view->addAction(
+ id(new PhabricatorActionView())
+ ->setName(pht('View Transforms'))
+ ->setIcon('fa-crop')
+ ->setHref($this->getApplicationURI("/transforms/{$id}/")));
+
return $view;
}
@@ -241,6 +247,12 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
$finfo->addProperty(pht('Builtin'), $builtin_string);
+ $is_profile = $file->getIsProfileImage()
+ ? pht('Yes')
+ : pht('No');
+
+ $finfo->addProperty(pht('Profile'), $is_profile);
+
$storage_properties = new PHUIPropertyListView();
$box->addPropertyList($storage_properties, pht('Storage'));
@@ -267,7 +279,6 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
$user->renderHandleList($phids));
}
-
if ($file->isViewableImage()) {
$image = phutil_tag(
'img',
diff --git a/src/applications/files/controller/PhabricatorFileTransformController.php b/src/applications/files/controller/PhabricatorFileTransformController.php
index 248a6dddd2..5062fe76fc 100644
--- a/src/applications/files/controller/PhabricatorFileTransformController.php
+++ b/src/applications/files/controller/PhabricatorFileTransformController.php
@@ -3,138 +3,89 @@
final class PhabricatorFileTransformController
extends PhabricatorFileController {
- private $transform;
- private $phid;
- private $key;
-
public function shouldRequireLogin() {
return false;
}
- public function willProcessRequest(array $data) {
- $this->transform = $data['transform'];
- $this->phid = $data['phid'];
- $this->key = $data['key'];
- }
-
- public function processRequest() {
- $viewer = $this->getRequest()->getUser();
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
// NOTE: This is a public/CDN endpoint, and permission to see files is
// controlled by knowing the secret key, not by authentication.
+ $is_regenerate = $request->getBool('regenerate');
+
+ $source_phid = $request->getURIData('phid');
$file = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
- ->withPHIDs(array($this->phid))
+ ->withPHIDs(array($source_phid))
->executeOne();
if (!$file) {
return new Aphront404Response();
}
- if (!$file->validateSecretKey($this->key)) {
+ $secret_key = $request->getURIData('key');
+ if (!$file->validateSecretKey($secret_key)) {
return new Aphront403Response();
}
+ $transform = $request->getURIData('transform');
$xform = id(new PhabricatorTransformedFile())
->loadOneWhere(
'originalPHID = %s AND transform = %s',
- $this->phid,
- $this->transform);
+ $source_phid,
+ $transform);
if ($xform) {
- return $this->buildTransformedFileResponse($xform);
+ if ($is_regenerate) {
+ $this->destroyTransform($xform);
+ } else {
+ return $this->buildTransformedFileResponse($xform);
+ }
}
- $type = $file->getMimeType();
-
- if (!$file->isViewableInBrowser() || !$file->isTransformableImage()) {
- return $this->buildDefaultTransformation($file);
+ $xforms = PhabricatorFileTransform::getAllTransforms();
+ if (!isset($xforms[$transform])) {
+ return new Aphront404Response();
}
+ $xform = $xforms[$transform];
+
// We're essentially just building a cache here and don't need CSRF
// protection.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
- switch ($this->transform) {
- case 'thumb-profile':
- $xformed_file = $this->executeThumbTransform($file, 50, 50);
- break;
- case 'thumb-280x210':
- $xformed_file = $this->executeThumbTransform($file, 280, 210);
- break;
- case 'thumb-220x165':
- $xformed_file = $this->executeThumbTransform($file, 220, 165);
- break;
- case 'preview-100':
- $xformed_file = $this->executePreviewTransform($file, 100);
- break;
- case 'preview-220':
- $xformed_file = $this->executePreviewTransform($file, 220);
- break;
- case 'thumb-160x120':
- $xformed_file = $this->executeThumbTransform($file, 160, 120);
- break;
- case 'thumb-60x45':
- $xformed_file = $this->executeThumbTransform($file, 60, 45);
- break;
- default:
- return new Aphront400Response();
+ $xformed_file = null;
+ if ($xform->canApplyTransform($file)) {
+ try {
+ $xformed_file = $xforms[$transform]->applyTransform($file);
+ } catch (Exception $ex) {
+ // In normal transform mode, we ignore failures and generate a
+ // default transform below. If we're explicitly regenerating the
+ // thumbnail, rethrow the exception.
+ if ($is_regenerate) {
+ throw $ex;
+ }
+ }
+ }
+
+ if (!$xformed_file) {
+ $xformed_file = $xform->getDefaultTransform($file);
}
if (!$xformed_file) {
return new Aphront400Response();
}
- $xform = new PhabricatorTransformedFile();
- $xform->setOriginalPHID($this->phid);
- $xform->setTransform($this->transform);
- $xform->setTransformedPHID($xformed_file->getPHID());
- $xform->save();
+ $xform = id(new PhabricatorTransformedFile())
+ ->setOriginalPHID($source_phid)
+ ->setTransform($transform)
+ ->setTransformedPHID($xformed_file->getPHID())
+ ->save();
return $this->buildTransformedFileResponse($xform);
}
- private function buildDefaultTransformation(PhabricatorFile $file) {
- static $regexps = array(
- '@application/zip@' => 'zip',
- '@image/@' => 'image',
- '@application/pdf@' => 'pdf',
- '@.*@' => 'default',
- );
-
- $type = $file->getMimeType();
- $prefix = 'default';
- foreach ($regexps as $regexp => $implied_prefix) {
- if (preg_match($regexp, $type)) {
- $prefix = $implied_prefix;
- break;
- }
- }
-
- switch ($this->transform) {
- case 'thumb-280x210':
- $suffix = '280x210';
- break;
- case 'thumb-160x120':
- $suffix = '160x120';
- break;
- case 'thumb-60x45':
- $suffix = '60x45';
- break;
- case 'preview-100':
- $suffix = '.p100';
- break;
- default:
- throw new Exception('Unsupported transformation type!');
- }
-
- $path = celerity_get_resource_uri(
- "rsrc/image/icon/fatcow/thumbnails/{$prefix}{$suffix}.png");
-
- return id(new AphrontRedirectResponse())
- ->setURI($path);
- }
-
private function buildTransformedFileResponse(
PhabricatorTransformedFile $xform) {
@@ -152,14 +103,22 @@ final class PhabricatorFileTransformController
return $file->getRedirectResponse();
}
- private function executePreviewTransform(PhabricatorFile $file, $size) {
- $xformer = new PhabricatorImageTransformer();
- return $xformer->executePreviewTransform($file, $size);
- }
+ private function destroyTransform(PhabricatorTransformedFile $xform) {
+ $file = id(new PhabricatorFileQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPHIDs(array($xform->getTransformedPHID()))
+ ->executeOne();
- private function executeThumbTransform(PhabricatorFile $file, $x, $y) {
- $xformer = new PhabricatorImageTransformer();
- return $xformer->executeThumbTransform($file, $x, $y);
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+
+ if (!$file) {
+ $xform->delete();
+ } else {
+ $engine = new PhabricatorDestructionEngine();
+ $engine->destroyObject($file);
+ }
+
+ unset($unguarded);
}
}
diff --git a/src/applications/files/controller/PhabricatorFileTransformListController.php b/src/applications/files/controller/PhabricatorFileTransformListController.php
new file mode 100644
index 0000000000..6ef1818962
--- /dev/null
+++ b/src/applications/files/controller/PhabricatorFileTransformListController.php
@@ -0,0 +1,138 @@
+getViewer();
+
+ $file = id(new PhabricatorFileQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($request->getURIData('id')))
+ ->executeOne();
+ if (!$file) {
+ return new Aphront404Response();
+ }
+
+ $monogram = $file->getMonogram();
+
+ $xdst = id(new PhabricatorTransformedFile())->loadAllWhere(
+ 'transformedPHID = %s',
+ $file->getPHID());
+
+ $dst_rows = array();
+ foreach ($xdst as $source) {
+ $dst_rows[] = array(
+ $source->getTransform(),
+ $viewer->renderHandle($source->getOriginalPHID()),
+ );
+ }
+ $dst_table = id(new AphrontTableView($dst_rows))
+ ->setHeaders(
+ array(
+ pht('Key'),
+ pht('Source'),
+ ))
+ ->setColumnClasses(
+ array(
+ '',
+ 'wide',
+ ))
+ ->setNoDataString(
+ pht(
+ 'This file was not created by transforming another file.'));
+
+ $xsrc = id(new PhabricatorTransformedFile())->loadAllWhere(
+ 'originalPHID = %s',
+ $file->getPHID());
+ $xsrc = mpull($xsrc, 'getTransformedPHID', 'getTransform');
+
+ $src_rows = array();
+ $xforms = PhabricatorFileTransform::getAllTransforms();
+ foreach ($xforms as $xform) {
+ $dst_phid = idx($xsrc, $xform->getTransformKey());
+
+ if ($xform->canApplyTransform($file)) {
+ $can_apply = pht('Yes');
+
+ $view_href = $file->getURIForTransform($xform);
+ $view_href = new PhutilURI($view_href);
+ $view_href->setQueryParam('regenerate', 'true');
+
+ $view_text = pht('Regenerate');
+
+ $view_link = phutil_tag(
+ 'a',
+ array(
+ 'class' => 'small grey button',
+ 'href' => $view_href,
+ ),
+ $view_text);
+ } else {
+ $can_apply = phutil_tag('em', array(), pht('No'));
+ $view_link = phutil_tag('em', array(), pht('None'));
+ }
+
+ if ($dst_phid) {
+ $dst_link = $viewer->renderHandle($dst_phid);
+ } else {
+ $dst_link = phutil_tag('em', array(), pht('None'));
+ }
+
+ $src_rows[] = array(
+ $xform->getTransformName(),
+ $xform->getTransformKey(),
+ $can_apply,
+ $dst_link,
+ $view_link,
+ );
+ }
+
+ $src_table = id(new AphrontTableView($src_rows))
+ ->setHeaders(
+ array(
+ pht('Name'),
+ pht('Key'),
+ pht('Supported'),
+ pht('Transform'),
+ pht('View'),
+ ))
+ ->setColumnClasses(
+ array(
+ 'wide',
+ '',
+ '',
+ '',
+ 'action',
+ ));
+
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addTextCrumb($monogram, '/'.$monogram);
+ $crumbs->addTextCrumb(pht('Transforms'));
+
+ $dst_box = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('File Sources'))
+ ->appendChild($dst_table);
+
+ $src_box = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Available Transforms'))
+ ->appendChild($src_table);
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $dst_box,
+ $src_box,
+ ),
+ array(
+ 'title' => array(
+ pht('%s %s', $monogram, $file->getName()),
+ pht('Tranforms'),
+ ),
+ ));
+ }
+}
diff --git a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php
index fe0bb1da7a..5ffb70be7f 100644
--- a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php
+++ b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php
@@ -107,11 +107,17 @@ final class PhabricatorEmbedFileRemarkupRule
break;
case 'thumb':
default:
- $attrs['src'] = $file->getPreview220URI();
- $dimensions =
- PhabricatorImageTransformer::getPreviewDimensions($file, 220);
- $attrs['width'] = $dimensions['sdx'];
- $attrs['height'] = $dimensions['sdy'];
+ $preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW;
+ $xform = PhabricatorFileTransform::getTransformByKey($preview_key);
+ $attrs['src'] = $file->getURIForTransform($xform);
+
+ $dimensions = $xform->getTransformedDimensions($file);
+ if ($dimensions) {
+ list($x, $y) = $dimensions;
+ $attrs['width'] = $x;
+ $attrs['height'] = $y;
+ }
+
$image_class = 'phabricator-remarkup-embed-image';
break;
}
diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php
index 5bc0eef0e6..f8e99bc965 100644
--- a/src/applications/files/storage/PhabricatorFile.php
+++ b/src/applications/files/storage/PhabricatorFile.php
@@ -34,6 +34,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
const METADATA_CAN_CDN = 'canCDN';
const METADATA_BUILTIN = 'builtin';
const METADATA_PARTIAL = 'partial';
+ const METADATA_PROFILE = 'profile';
protected $name;
protected $mimeType;
@@ -214,7 +215,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
if (!$file) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
- $file = PhabricatorFile::newFromFileData($data, $params);
+ $file = self::newFromFileData($data, $params);
unset($unguarded);
}
@@ -235,7 +236,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
$copy_of_byte_size = $file->getByteSize();
$copy_of_mime_type = $file->getMimeType();
- $new_file = PhabricatorFile::initializeNewFile();
+ $new_file = self::initializeNewFile();
$new_file->setByteSize($copy_of_byte_size);
@@ -261,7 +262,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
$length,
array $params) {
- $file = PhabricatorFile::initializeNewFile();
+ $file = self::initializeNewFile();
$file->setByteSize($length);
@@ -315,7 +316,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
throw new Exception(pht('No valid storage engines are available!'));
}
- $file = PhabricatorFile::initializeNewFile();
+ $file = self::initializeNewFile();
$data_handle = null;
$engine_identifier = null;
@@ -760,6 +761,10 @@ final class PhabricatorFile extends PhabricatorFileDAO
return (string) $uri;
}
+ public function getURIForTransform(PhabricatorFileTransform $transform) {
+ return $this->getTransformedURI($transform->getTransformKey());
+ }
+
private function getTransformedURI($transform) {
$parts = array();
$parts[] = 'file';
@@ -780,34 +785,6 @@ final class PhabricatorFile extends PhabricatorFileDAO
return PhabricatorEnv::getCDNURI($path);
}
- public function getProfileThumbURI() {
- return $this->getTransformedURI('thumb-profile');
- }
-
- public function getThumb60x45URI() {
- return $this->getTransformedURI('thumb-60x45');
- }
-
- public function getThumb160x120URI() {
- return $this->getTransformedURI('thumb-160x120');
- }
-
- public function getPreview100URI() {
- return $this->getTransformedURI('preview-100');
- }
-
- public function getPreview220URI() {
- return $this->getTransformedURI('preview-220');
- }
-
- public function getThumb220x165URI() {
- return $this->getTransfomredURI('thumb-220x165');
- }
-
- public function getThumb280x210URI() {
- return $this->getTransformedURI('thumb-280x210');
- }
-
public function isViewableInBrowser() {
return ($this->getViewableMimeType() !== null);
}
@@ -1040,7 +1017,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
- $file = PhabricatorFile::newFromFileData($data, $params);
+ $file = self::newFromFileData($data, $params);
$xform = id(new PhabricatorTransformedFile())
->setOriginalPHID(PhabricatorPHIDConstants::PHID_VOID)
->setTransform('builtin:'.$name)
@@ -1136,6 +1113,15 @@ final class PhabricatorFile extends PhabricatorFileDAO
return $this;
}
+ public function getIsProfileImage() {
+ return idx($this->metadata, self::METADATA_PROFILE);
+ }
+
+ public function setIsProfileImage($value) {
+ $this->metadata[self::METADATA_PROFILE] = $value;
+ return $this;
+ }
+
protected function generateOneTimeToken() {
$key = Filesystem::readRandomCharacters(16);
@@ -1237,6 +1223,11 @@ final class PhabricatorFile extends PhabricatorFileDAO
$this->setBuiltinName($builtin);
}
+ $profile = idx($params, 'profile');
+ if ($profile) {
+ $this->setIsProfileImage(true);
+ }
+
$mime_type = idx($params, 'mime-type');
if ($mime_type) {
$this->setMimeType($mime_type);
@@ -1304,6 +1295,9 @@ final class PhabricatorFile extends PhabricatorFileDAO
if ($this->isBuiltin()) {
return PhabricatorPolicies::getMostOpenPolicy();
}
+ if ($this->getIsProfileImage()) {
+ return PhabricatorPolicies::getMostOpenPolicy();
+ }
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return PhabricatorPolicies::POLICY_NOONE;
diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php
new file mode 100644
index 0000000000..ff8bd14683
--- /dev/null
+++ b/src/applications/files/transform/PhabricatorFileImageTransform.php
@@ -0,0 +1,382 @@
+|null Width and height, if available.
+ */
+ public function getTransformedDimensions(PhabricatorFile $file) {
+ return null;
+ }
+
+ public function canApplyTransform(PhabricatorFile $file) {
+ if (!$file->isViewableImage()) {
+ return false;
+ }
+
+ if (!$file->isTransformableImage()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ protected function willTransformFile(PhabricatorFile $file) {
+ $this->file = $file;
+ $this->data = null;
+ $this->image = null;
+ $this->imageX = null;
+ $this->imageY = null;
+ }
+
+ protected function getFileProperties() {
+ return array();
+ }
+
+ protected function applyCropAndScale(
+ $dst_w, $dst_h,
+ $src_x, $src_y,
+ $src_w, $src_h,
+ $use_w, $use_h,
+ $scale_up) {
+
+ // Figure out the effective destination width, height, and offsets.
+ $cpy_w = min($dst_w, $use_w);
+ $cpy_h = min($dst_h, $use_h);
+
+ // If we aren't scaling up, and are copying a very small source image,
+ // we're just going to center it in the destination image.
+ if (!$scale_up) {
+ $cpy_w = min($cpy_w, $src_w);
+ $cpy_h = min($cpy_h, $src_h);
+ }
+
+ $off_x = ($dst_w - $cpy_w) / 2;
+ $off_y = ($dst_h - $cpy_h) / 2;
+
+ if ($this->shouldUseImagemagick()) {
+ $argv = array();
+ $argv[] = '-coalesce';
+ $argv[] = '-shave';
+ $argv[] = $src_x.'x'.$src_y;
+ $argv[] = '-resize';
+
+ if ($scale_up) {
+ $argv[] = $dst_w.'x'.$dst_h;
+ } else {
+ $argv[] = $dst_w.'x'.$dst_h.'>';
+ }
+
+ $argv[] = '-bordercolor';
+ $argv[] = 'rgba(255, 255, 255, 0)';
+ $argv[] = '-border';
+ $argv[] = $off_x.'x'.$off_y;
+
+ return $this->applyImagemagick($argv);
+ }
+
+ $src = $this->getImage();
+ $dst = $this->newEmptyImage($dst_w, $dst_h);
+
+ $trap = new PhutilErrorTrap();
+ $ok = @imagecopyresampled(
+ $dst,
+ $src,
+ $off_x, $off_y,
+ $src_x, $src_y,
+ $cpy_w, $cpy_h,
+ $src_w, $src_h);
+ $errors = $trap->getErrorsAsString();
+ $trap->destroy();
+
+ if ($ok === false) {
+ throw new Exception(
+ pht(
+ 'Failed to imagecopyresampled() image: %s',
+ $errors));
+ }
+
+ $data = PhabricatorImageTransformer::saveImageDataInAnyFormat(
+ $dst,
+ $this->file->getMimeType());
+
+ return $this->newFileFromData($data);
+ }
+
+ protected function applyImagemagick(array $argv) {
+ $tmp = new TempFile();
+ Filesystem::writeFile($tmp, $this->getData());
+
+ $out = new TempFile();
+
+ $future = new ExecFuture('convert %s %Ls %s', $tmp, $argv, $out);
+ // Don't spend more than 10 seconds resizing; just fail if it takes longer
+ // than that.
+ $future->setTimeout(10)->resolvex();
+
+ $data = Filesystem::readFile($out);
+
+ return $this->newFileFromData($data);
+ }
+
+
+ /**
+ * Create a new @{class:PhabricatorFile} from raw data.
+ *
+ * @param string Raw file data.
+ */
+ protected function newFileFromData($data) {
+ if ($this->file) {
+ $name = $this->file->getName();
+ } else {
+ $name = 'default.png';
+ }
+
+ $defaults = array(
+ 'canCDN' => true,
+ 'name' => $this->getTransformKey().'-'.$name,
+ );
+
+ $properties = $this->getFileProperties() + $defaults;
+
+ return PhabricatorFile::newFromFileData($data, $properties);
+ }
+
+
+ /**
+ * Create a new image filled with transparent pixels.
+ *
+ * @param int Desired image width.
+ * @param int Desired image height.
+ * @return resource New image resource.
+ */
+ protected function newEmptyImage($w, $h) {
+ $w = (int)$w;
+ $h = (int)$h;
+
+ if (($w <= 0) || ($h <= 0)) {
+ throw new Exception(
+ pht('Can not create an image with nonpositive dimensions.'));
+ }
+
+ $trap = new PhutilErrorTrap();
+ $img = @imagecreatetruecolor($w, $h);
+ $errors = $trap->getErrorsAsString();
+ $trap->destroy();
+ if ($img === false) {
+ throw new Exception(
+ pht(
+ 'Unable to imagecreatetruecolor() a new empty image: %s',
+ $errors));
+ }
+
+ $trap = new PhutilErrorTrap();
+ $ok = @imagesavealpha($img, true);
+ $errors = $trap->getErrorsAsString();
+ $trap->destroy();
+ if ($ok === false) {
+ throw new Exception(
+ pht(
+ 'Unable to imagesavealpha() a new empty image: %s',
+ $errors));
+ }
+
+ $trap = new PhutilErrorTrap();
+ $color = @imagecolorallocatealpha($img, 255, 255, 255, 127);
+ $errors = $trap->getErrorsAsString();
+ $trap->destroy();
+ if ($color === false) {
+ throw new Exception(
+ pht(
+ 'Unable to imagecolorallocatealpha() a new empty image: %s',
+ $errors));
+ }
+
+ $trap = new PhutilErrorTrap();
+ $ok = @imagefill($img, 0, 0, $color);
+ $errors = $trap->getErrorsAsString();
+ $trap->destroy();
+ if ($ok === false) {
+ throw new Exception(
+ pht(
+ 'Unable to imagefill() a new empty image: %s',
+ $errors));
+ }
+
+ return $img;
+ }
+
+
+ /**
+ * Get the pixel dimensions of the image being transformed.
+ *
+ * @return list Width and height of the image.
+ */
+ protected function getImageDimensions() {
+ if ($this->imageX === null) {
+ $image = $this->getImage();
+
+ $trap = new PhutilErrorTrap();
+ $x = @imagesx($image);
+ $y = @imagesy($image);
+ $errors = $trap->getErrorsAsString();
+ $trap->destroy();
+
+ if (($x === false) || ($y === false) || ($x <= 0) || ($y <= 0)) {
+ throw new Exception(
+ pht(
+ 'Unable to determine image dimensions with '.
+ 'imagesx()/imagesy(): %s',
+ $errors));
+ }
+
+ $this->imageX = $x;
+ $this->imageY = $y;
+ }
+
+ return array($this->imageX, $this->imageY);
+ }
+
+
+ /**
+ * Get the raw file data for the image being transformed.
+ *
+ * @return string Raw file data.
+ */
+ protected function getData() {
+ if ($this->data !== null) {
+ return $this->data;
+ }
+
+ $file = $this->file;
+
+ $max_size = (1024 * 1024 * 4);
+ $img_size = $file->getByteSize();
+ if ($img_size > $max_size) {
+ throw new Exception(
+ pht(
+ 'This image is too large to transform. The transform limit is %s '.
+ 'bytes, but the image size is %s bytes.',
+ new PhutilNumber($max_size),
+ new PhutilNumber($img_size)));
+ }
+
+ $data = $file->loadFileData();
+ $this->data = $data;
+ return $this->data;
+ }
+
+
+ /**
+ * Get the GD image resource for the image being transformed.
+ *
+ * @return resource GD image resource.
+ */
+ protected function getImage() {
+ if ($this->image !== null) {
+ return $this->image;
+ }
+
+ if (!function_exists('imagecreatefromstring')) {
+ throw new Exception(
+ pht(
+ 'Unable to transform image: the imagecreatefromstring() function '.
+ 'is not available. Install or enable the "gd" extension for PHP.'));
+ }
+
+ $data = $this->getData();
+ $data = (string)$data;
+
+ // First, we're going to write the file to disk and use getimagesize()
+ // to determine its dimensions without actually loading the pixel data
+ // into memory. For very large images, we'll bail out.
+
+ // In particular, this defuses a resource exhaustion attack where the
+ // attacker uploads a 40,000 x 40,000 pixel PNGs of solid white. These
+ // kinds of files compress extremely well, but require a huge amount
+ // of memory and CPU to process.
+
+ $tmp = new TempFile();
+ Filesystem::writeFile($tmp, $data);
+ $tmp_path = (string)$tmp;
+
+ $trap = new PhutilErrorTrap();
+ $info = @getimagesize($tmp_path);
+ $errors = $trap->getErrorsAsString();
+ $trap->destroy();
+
+ unset($tmp);
+
+ if ($info === false) {
+ throw new Exception(
+ pht(
+ 'Unable to get image information with getimagesize(): %s',
+ $errors));
+ }
+
+ list($width, $height) = $info;
+ if (($width <= 0) || ($height <= 0)) {
+ throw new Exception(
+ pht(
+ 'Unable to determine image width and height with getimagesize().'));
+ }
+
+ $max_pixels = (4096 * 4096);
+ $img_pixels = ($width * $height);
+
+ if ($img_pixels > $max_pixels) {
+ throw new Exception(
+ pht(
+ 'This image (with dimensions %spx x %spx) is too large to '.
+ 'transform. The image has %s pixels, but transforms are limited '.
+ 'to images with %s or fewer pixels.',
+ new PhutilNumber($width),
+ new PhutilNumber($height),
+ new PhutilNumber($img_pixels),
+ new PhutilNumber($max_pixels)));
+ }
+
+ $trap = new PhutilErrorTrap();
+ $image = @imagecreatefromstring($data);
+ $errors = $trap->getErrorsAsString();
+ $trap->destroy();
+
+ if ($image === false) {
+ throw new Exception(
+ pht(
+ 'Unable to load image data with imagecreatefromstring(): %s',
+ $errors));
+ }
+
+ $this->image = $image;
+ return $this->image;
+ }
+
+ private function shouldUseImagemagick() {
+ if (!PhabricatorEnv::getEnvConfig('files.enable-imagemagick')) {
+ return false;
+ }
+
+ if ($this->file->getMimeType() != 'image/gif') {
+ return false;
+ }
+
+ // Don't try to preserve the animation in huge GIFs.
+ list($x, $y) = $this->getImageDimensions();
+ if (($x * $y) > (512 * 512)) {
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php
new file mode 100644
index 0000000000..af97f8a9fa
--- /dev/null
+++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php
@@ -0,0 +1,225 @@
+name = $name;
+ return $this;
+ }
+
+ public function setKey($key) {
+ $this->key = $key;
+ return $this;
+ }
+
+ public function setDimensions($x, $y) {
+ $this->dstX = $x;
+ $this->dstY = $y;
+ return $this;
+ }
+
+ public function setScaleUp($scale) {
+ $this->scaleUp = $scale;
+ return $this;
+ }
+
+ public function getTransformName() {
+ return $this->name;
+ }
+
+ public function getTransformKey() {
+ return $this->key;
+ }
+
+ protected function getFileProperties() {
+ $properties = array();
+ switch ($this->key) {
+ case self::TRANSFORM_PROFILE:
+ $properties['profile'] = true;
+ $properties['name'] = 'profile';
+ break;
+ }
+ return $properties;
+ }
+
+ public function generateTransforms() {
+ return array(
+ id(new PhabricatorFileThumbnailTransform())
+ ->setName(pht("Profile (100px \xC3\x97 100px)"))
+ ->setKey(self::TRANSFORM_PROFILE)
+ ->setDimensions(100, 100)
+ ->setScaleUp(true),
+ id(new PhabricatorFileThumbnailTransform())
+ ->setName(pht("Pinboard (280px \xC3\x97 210px)"))
+ ->setKey(self::TRANSFORM_PINBOARD)
+ ->setDimensions(280, 210),
+ id(new PhabricatorFileThumbnailTransform())
+ ->setName(pht('Thumbgrid (100px)'))
+ ->setKey(self::TRANSFORM_THUMBGRID)
+ ->setDimensions(100, null),
+ id(new PhabricatorFileThumbnailTransform())
+ ->setName(pht('Preview (220px)'))
+ ->setKey(self::TRANSFORM_PREVIEW)
+ ->setDimensions(220, null),
+ );
+ }
+
+ public function applyTransform(PhabricatorFile $file) {
+ $this->willTransformFile($file);
+
+ list($src_x, $src_y) = $this->getImageDimensions();
+ $dst_x = $this->dstX;
+ $dst_y = $this->dstY;
+
+ $dimensions = $this->computeDimensions(
+ $src_x,
+ $src_y,
+ $dst_x,
+ $dst_y);
+
+ $copy_x = $dimensions['copy_x'];
+ $copy_y = $dimensions['copy_y'];
+ $use_x = $dimensions['use_x'];
+ $use_y = $dimensions['use_y'];
+ $dst_x = $dimensions['dst_x'];
+ $dst_y = $dimensions['dst_y'];
+
+ return $this->applyCropAndScale(
+ $dst_x,
+ $dst_y,
+ ($src_x - $copy_x) / 2,
+ ($src_y - $copy_y) / 2,
+ $copy_x,
+ $copy_y,
+ $use_x,
+ $use_y,
+ $this->scaleUp);
+ }
+
+
+ public function getTransformedDimensions(PhabricatorFile $file) {
+ $dst_x = $this->dstX;
+ $dst_y = $this->dstY;
+
+ // If this is transform has fixed dimensions, we can trivially predict
+ // the dimensions of the transformed file.
+ if ($dst_y !== null) {
+ return array($dst_x, $dst_y);
+ }
+
+ $src_x = $file->getImageWidth();
+ $src_y = $file->getImageHeight();
+
+ if (!$src_x || !$src_y) {
+ return null;
+ }
+
+ $dimensions = $this->computeDimensions(
+ $src_x,
+ $src_y,
+ $dst_x,
+ $dst_y);
+
+ return array($dimensions['dst_x'], $dimensions['dst_y']);
+ }
+
+
+ private function computeDimensions($src_x, $src_y, $dst_x, $dst_y) {
+ if ($dst_y === null) {
+ // If we only have one dimension, it represents a maximum dimension.
+ // The other dimension of the transform is scaled appropriately, except
+ // that we never generate images with crazily extreme aspect ratios.
+ if ($src_x < $src_y) {
+ // This is a tall, narrow image. Use the maximum dimension for the
+ // height and scale the width.
+ $use_y = $dst_x;
+ $dst_y = $dst_x;
+
+ $use_x = $dst_y * ($src_x / $src_y);
+ $dst_x = max($dst_y / 4, $use_x);
+ } else {
+ // This is a short, wide image. Use the maximum dimension for the width
+ // and scale the height.
+ $use_x = $dst_x;
+
+ $use_y = $dst_x * ($src_y / $src_x);
+ $dst_y = max($dst_x / 4, $use_y);
+ }
+
+ // In this mode, we always copy the entire source image. We may generate
+ // margins in the output.
+ $copy_x = $src_x;
+ $copy_y = $src_y;
+ } else {
+ $scale_up = $this->scaleUp;
+
+ // Otherwise, both dimensions are fixed. Figure out how much we'd have to
+ // scale the image down along each dimension to get the entire thing to
+ // fit.
+ $scale_x = ($dst_x / $src_x);
+ $scale_y = ($dst_y / $src_y);
+
+ if (!$scale_up) {
+ $scale_x = min($scale_x, 1);
+ $scale_y = min($scale_y, 1);
+ }
+
+ if ($scale_x > $scale_y) {
+ // This image is relatively tall and narrow. We're going to crop off the
+ // top and bottom.
+ $scale = $scale_x;
+ } else {
+ // This image is relatively short and wide. We're going to crop off the
+ // left and right.
+ $scale = $scale_y;
+ }
+
+ $copy_x = $dst_x / $scale;
+ $copy_y = $dst_y / $scale;
+
+ if (!$scale_up) {
+ $copy_x = min($src_x, $copy_x);
+ $copy_y = min($src_y, $copy_y);
+ }
+
+ // In this mode, we always use the entire destination image. We may
+ // crop the source input.
+ $use_x = $dst_x;
+ $use_y = $dst_y;
+ }
+
+ return array(
+ 'copy_x' => $copy_x,
+ 'copy_y' => $copy_y,
+ 'use_x' => $use_x,
+ 'use_y' => $use_y,
+ 'dst_x' => $dst_x,
+ 'dst_y' => $dst_y,
+ );
+ }
+
+
+ public function getDefaultTransform(PhabricatorFile $file) {
+ $x = (int)$this->dstX;
+ $y = (int)$this->dstY;
+ $name = 'image-'.$x.'x'.nonempty($y, $x).'.png';
+
+ $root = dirname(phutil_get_library_root('phabricator'));
+ $data = Filesystem::readFile($root.'/resources/builtin/'.$name);
+
+ return $this->newFileFromData($data);
+ }
+
+}
diff --git a/src/applications/files/transform/PhabricatorFileTransform.php b/src/applications/files/transform/PhabricatorFileTransform.php
new file mode 100644
index 0000000000..caaf46920d
--- /dev/null
+++ b/src/applications/files/transform/PhabricatorFileTransform.php
@@ -0,0 +1,74 @@
+canApplyTransform($file)) {
+ try {
+ return $this->applyTransform($file);
+ } catch (Exception $ex) {
+ // Ignore.
+ }
+ }
+
+ return $this->getDefaultTransform($file);
+ }
+
+ public static function getAllTransforms() {
+ static $map;
+
+ if ($map === null) {
+ $xforms = id(new PhutilSymbolLoader())
+ ->setAncestorClass(__CLASS__)
+ ->loadObjects();
+
+ $result = array();
+ foreach ($xforms as $xform_template) {
+ foreach ($xform_template->generateTransforms() as $xform) {
+ $key = $xform->getTransformKey();
+ if (isset($result[$key])) {
+ throw new Exception(
+ pht(
+ 'Two %s objects define the same transform key ("%s"), but '.
+ 'each transform must have a unique key.',
+ __CLASS__,
+ $key));
+ }
+ $result[$key] = $xform;
+ }
+ }
+
+ $map = $result;
+ }
+
+ return $map;
+ }
+
+ public static function getTransformByKey($key) {
+ $all = self::getAllTransforms();
+
+ $xform = idx($all, $key);
+ if (!$xform) {
+ throw new Exception(
+ pht(
+ 'No file transform with key "%s" exists.',
+ $key));
+ }
+
+ return $xform;
+ }
+
+}
diff --git a/src/applications/fund/phortune/FundBackerCart.php b/src/applications/fund/phortune/FundBackerCart.php
index 3dc25d4bbf..9cf530f23e 100644
--- a/src/applications/fund/phortune/FundBackerCart.php
+++ b/src/applications/fund/phortune/FundBackerCart.php
@@ -33,8 +33,7 @@ final class FundBackerCart extends PhortuneCartImplementation {
$initiative = $this->getInitiative();
if (!$initiative) {
- throw new Exception(
- pht('Call setInitiative() before building a cart!'));
+ throw new PhutilInvalidStateException('setInitiative');
}
$cart->setMetadataValue('initiativePHID', $initiative->getPHID());
diff --git a/src/applications/fund/storage/FundInitiativeTransaction.php b/src/applications/fund/storage/FundInitiativeTransaction.php
index 3531ff3e5e..c06d62e229 100644
--- a/src/applications/fund/storage/FundInitiativeTransaction.php
+++ b/src/applications/fund/storage/FundInitiativeTransaction.php
@@ -38,7 +38,7 @@ final class FundInitiativeTransaction
$type = $this->getTransactionType();
switch ($type) {
- case FundInitiativeTransaction::TYPE_MERCHANT:
+ case self::TYPE_MERCHANT:
if ($old) {
$phids[] = $old;
}
@@ -46,7 +46,7 @@ final class FundInitiativeTransaction
$phids[] = $new;
}
break;
- case FundInitiativeTransaction::TYPE_REFUND:
+ case self::TYPE_REFUND:
$phids[] = $this->getMetadataValue(self::PROPERTY_BACKER);
break;
}
@@ -63,7 +63,7 @@ final class FundInitiativeTransaction
$type = $this->getTransactionType();
switch ($type) {
- case FundInitiativeTransaction::TYPE_NAME:
+ case self::TYPE_NAME:
if ($old === null) {
return pht(
'%s created this initiative.',
@@ -76,15 +76,15 @@ final class FundInitiativeTransaction
$new);
}
break;
- case FundInitiativeTransaction::TYPE_RISKS:
+ case self::TYPE_RISKS:
return pht(
'%s edited the risks for this initiative.',
$this->renderHandleLink($author_phid));
- case FundInitiativeTransaction::TYPE_DESCRIPTION:
+ case self::TYPE_DESCRIPTION:
return pht(
'%s edited the description of this initiative.',
$this->renderHandleLink($author_phid));
- case FundInitiativeTransaction::TYPE_STATUS:
+ case self::TYPE_STATUS:
switch ($new) {
case FundInitiative::STATUS_OPEN:
return pht(
@@ -96,14 +96,14 @@ final class FundInitiativeTransaction
$this->renderHandleLink($author_phid));
}
break;
- case FundInitiativeTransaction::TYPE_BACKER:
+ case self::TYPE_BACKER:
$amount = $this->getMetadataValue(self::PROPERTY_AMOUNT);
$amount = PhortuneCurrency::newFromString($amount);
return pht(
'%s backed this initiative with %s.',
$this->renderHandleLink($author_phid),
$amount->formatForDisplay());
- case FundInitiativeTransaction::TYPE_REFUND:
+ case self::TYPE_REFUND:
$amount = $this->getMetadataValue(self::PROPERTY_AMOUNT);
$amount = PhortuneCurrency::newFromString($amount);
@@ -114,7 +114,7 @@ final class FundInitiativeTransaction
$this->renderHandleLink($author_phid),
$amount->formatForDisplay(),
$this->renderHandleLink($backer_phid));
- case FundInitiativeTransaction::TYPE_MERCHANT:
+ case self::TYPE_MERCHANT:
if ($old === null) {
return pht(
'%s set this initiative to pay to %s.',
@@ -142,7 +142,7 @@ final class FundInitiativeTransaction
$type = $this->getTransactionType();
switch ($type) {
- case FundInitiativeTransaction::TYPE_NAME:
+ case self::TYPE_NAME:
if ($old === null) {
return pht(
'%s created %s.',
@@ -156,12 +156,12 @@ final class FundInitiativeTransaction
$this->renderHandleLink($object_phid));
}
break;
- case FundInitiativeTransaction::TYPE_DESCRIPTION:
+ case self::TYPE_DESCRIPTION:
return pht(
'%s updated the description for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
- case FundInitiativeTransaction::TYPE_STATUS:
+ case self::TYPE_STATUS:
switch ($new) {
case FundInitiative::STATUS_OPEN:
return pht(
@@ -175,7 +175,7 @@ final class FundInitiativeTransaction
$this->renderHandleLink($object_phid));
}
break;
- case FundInitiativeTransaction::TYPE_BACKER:
+ case self::TYPE_BACKER:
$amount = $this->getMetadataValue(self::PROPERTY_AMOUNT);
$amount = PhortuneCurrency::newFromString($amount);
return pht(
@@ -183,7 +183,7 @@ final class FundInitiativeTransaction
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$amount->formatForDisplay());
- case FundInitiativeTransaction::TYPE_REFUND:
+ case self::TYPE_REFUND:
$amount = $this->getMetadataValue(self::PROPERTY_AMOUNT);
$amount = PhortuneCurrency::newFromString($amount);
@@ -223,8 +223,8 @@ final class FundInitiativeTransaction
public function shouldHide() {
$old = $this->getOldValue();
switch ($this->getTransactionType()) {
- case FundInitiativeTransaction::TYPE_DESCRIPTION:
- case FundInitiativeTransaction::TYPE_RISKS:
+ case self::TYPE_DESCRIPTION:
+ case self::TYPE_RISKS:
return ($old === null);
}
return parent::shouldHide();
@@ -232,8 +232,8 @@ final class FundInitiativeTransaction
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
- case FundInitiativeTransaction::TYPE_DESCRIPTION:
- case FundInitiativeTransaction::TYPE_RISKS:
+ case self::TYPE_DESCRIPTION:
+ case self::TYPE_RISKS:
return ($this->getOldValue() !== null);
}
diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
index 5057097fac..4fe1128ac0 100644
--- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
+++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
@@ -4,7 +4,7 @@ abstract class HarbormasterBuildStepImplementation {
public static function getImplementations() {
return id(new PhutilSymbolLoader())
- ->setAncestorClass('HarbormasterBuildStepImplementation')
+ ->setAncestorClass(__CLASS__)
->loadObjects();
}
diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php
index b53e2bc2d3..81f8e66d07 100644
--- a/src/applications/harbormaster/storage/HarbormasterBuildable.php
+++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php
@@ -88,7 +88,7 @@ final class HarbormasterBuildable extends HarbormasterDAO
if ($buildable) {
return $buildable;
}
- $buildable = HarbormasterBuildable::initializeNewBuildable($actor)
+ $buildable = self::initializeNewBuildable($actor)
->setBuildablePHID($buildable_object_phid)
->setContainerPHID($container_object_phid);
$buildable->save();
@@ -116,7 +116,7 @@ final class HarbormasterBuildable extends HarbormasterDAO
return;
}
- $buildable = HarbormasterBuildable::createOrLoadExisting(
+ $buildable = self::createOrLoadExisting(
PhabricatorUser::getOmnipotentUser(),
$phid,
$container_phid);
diff --git a/src/applications/help/application/PhabricatorHelpApplication.php b/src/applications/help/application/PhabricatorHelpApplication.php
index c86d5cd0d0..b1f66b02cd 100644
--- a/src/applications/help/application/PhabricatorHelpApplication.php
+++ b/src/applications/help/application/PhabricatorHelpApplication.php
@@ -43,7 +43,7 @@ final class PhabricatorHelpApplication extends PhabricatorApplication {
array(
'bubbleID' => $help_id,
'dropdownID' => 'phabricator-help-menu',
- 'applicationClass' => 'PhabricatorHelpApplication',
+ 'applicationClass' => __CLASS__,
'local' => true,
'desktop' => true,
'right' => true,
diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php
index 6a51ade55f..f56572a398 100644
--- a/src/applications/herald/adapter/HeraldAdapter.php
+++ b/src/applications/herald/adapter/HeraldAdapter.php
@@ -1083,7 +1083,7 @@ abstract class HeraldAdapter {
public static function getEnabledAdapterMap(PhabricatorUser $viewer) {
$map = array();
- $adapters = HeraldAdapter::getAllAdapters();
+ $adapters = self::getAllAdapters();
foreach ($adapters as $adapter) {
if (!$adapter->isAvailableToUser($viewer)) {
continue;
diff --git a/src/applications/macro/controller/PhabricatorMacroMemeController.php b/src/applications/macro/controller/PhabricatorMacroMemeController.php
index badb1759ad..ff25a8a8a4 100644
--- a/src/applications/macro/controller/PhabricatorMacroMemeController.php
+++ b/src/applications/macro/controller/PhabricatorMacroMemeController.php
@@ -14,7 +14,7 @@ final class PhabricatorMacroMemeController
$lower_text = $request->getStr('lowertext');
$user = $request->getUser();
- $uri = PhabricatorMacroMemeController::generateMacro($user, $macro_name,
+ $uri = self::generateMacro($user, $macro_name,
$upper_text, $lower_text);
if ($uri === false) {
return new Aphront404Response();
diff --git a/src/applications/macro/markup/PhabricatorMemeRemarkupRule.php b/src/applications/macro/markup/PhabricatorMemeRemarkupRule.php
index 0c949e130c..004e415450 100644
--- a/src/applications/macro/markup/PhabricatorMemeRemarkupRule.php
+++ b/src/applications/macro/markup/PhabricatorMemeRemarkupRule.php
@@ -10,7 +10,7 @@ final class PhabricatorMemeRemarkupRule extends PhutilRemarkupRule {
public function apply($text) {
return preg_replace_callback(
- '@{meme,((?:[^}\\\\]+|\\\\.)+)}$@m',
+ '@{meme,((?:[^}\\\\]+|\\\\.)+)}@m',
array($this, 'markupMeme'),
$text);
}
diff --git a/src/applications/macro/query/PhabricatorMacroSearchEngine.php b/src/applications/macro/query/PhabricatorMacroSearchEngine.php
index 8a810c886f..8d632e818f 100644
--- a/src/applications/macro/query/PhabricatorMacroSearchEngine.php
+++ b/src/applications/macro/query/PhabricatorMacroSearchEngine.php
@@ -179,14 +179,18 @@ final class PhabricatorMacroSearchEngine
assert_instances_of($macros, 'PhabricatorFileImageMacro');
$viewer = $this->requireViewer();
+ $xform = PhabricatorFileTransform::getTransformByKey(
+ PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD);
+
$pinboard = new PHUIPinboardView();
foreach ($macros as $macro) {
$file = $macro->getFile();
$item = new PHUIPinboardItemView();
if ($file) {
- $item->setImageURI($file->getThumb280x210URI());
- $item->setImageSize(280, 210);
+ $item->setImageURI($file->getURIForTransform($xform));
+ list($x, $y) = $xform->getTransformedDimensions($file);
+ $item->setImageSize($x, $y);
}
if ($macro->getDateCreated()) {
diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php
index 60e3425af6..4a6381581b 100644
--- a/src/applications/maniphest/query/ManiphestTaskQuery.php
+++ b/src/applications/maniphest/query/ManiphestTaskQuery.php
@@ -754,7 +754,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$id = $result->getID();
if ($this->groupBy == self::GROUP_PROJECT) {
- return rtrim($id.'.'.$result->getGroupByProjectPHID(), '.');;
+ return rtrim($id.'.'.$result->getGroupByProjectPHID(), '.');
}
return $id;
diff --git a/src/applications/metamta/command/MetaMTAEmailTransactionCommand.php b/src/applications/metamta/command/MetaMTAEmailTransactionCommand.php
index e6011768df..cc78df3492 100644
--- a/src/applications/metamta/command/MetaMTAEmailTransactionCommand.php
+++ b/src/applications/metamta/command/MetaMTAEmailTransactionCommand.php
@@ -91,7 +91,7 @@ abstract class MetaMTAEmailTransactionCommand extends Phobject {
}
public static function getCommandMap(array $commands) {
- assert_instances_of($commands, 'MetaMTAEmailTransactionCommand');
+ assert_instances_of($commands, __CLASS__);
$map = array();
foreach ($commands as $command) {
diff --git a/src/applications/metamta/contentsource/PhabricatorContentSource.php b/src/applications/metamta/contentsource/PhabricatorContentSource.php
index a1cbd061f2..e5a29572c1 100644
--- a/src/applications/metamta/contentsource/PhabricatorContentSource.php
+++ b/src/applications/metamta/contentsource/PhabricatorContentSource.php
@@ -46,13 +46,13 @@ final class PhabricatorContentSource {
public static function newConsoleSource() {
return self::newForSource(
- PhabricatorContentSource::SOURCE_CONSOLE,
+ self::SOURCE_CONSOLE,
array());
}
public static function newFromRequest(AphrontRequest $request) {
return self::newForSource(
- PhabricatorContentSource::SOURCE_WEB,
+ self::SOURCE_WEB,
array(
'ip' => $request->getRemoteAddr(),
));
@@ -60,7 +60,7 @@ final class PhabricatorContentSource {
public static function newFromConduitRequest(ConduitAPIRequest $request) {
return self::newForSource(
- PhabricatorContentSource::SOURCE_CONDUIT,
+ self::SOURCE_CONDUIT,
array());
}
diff --git a/src/applications/multimeter/data/MultimeterControl.php b/src/applications/multimeter/data/MultimeterControl.php
index 2bc849c185..892e9e56cd 100644
--- a/src/applications/multimeter/data/MultimeterControl.php
+++ b/src/applications/multimeter/data/MultimeterControl.php
@@ -88,7 +88,7 @@ final class MultimeterControl {
}
if ($this->sampleRate === null) {
- throw new Exception(pht('Call setSampleRate() before saving events!'));
+ throw new PhutilInvalidStateException('setSampleRate');
}
$this->addServiceEvents();
diff --git a/src/applications/nuance/storage/NuanceItem.php b/src/applications/nuance/storage/NuanceItem.php
index 2db403cd2e..50d7ee72ef 100644
--- a/src/applications/nuance/storage/NuanceItem.php
+++ b/src/applications/nuance/storage/NuanceItem.php
@@ -20,7 +20,7 @@ final class NuanceItem
public static function initializeNewItem(PhabricatorUser $user) {
return id(new NuanceItem())
->setDateNuanced(time())
- ->setStatus(NuanceItem::STATUS_OPEN);
+ ->setStatus(self::STATUS_OPEN);
}
protected function getConfiguration() {
diff --git a/src/applications/paste/storage/PhabricatorPasteTransaction.php b/src/applications/paste/storage/PhabricatorPasteTransaction.php
index cc4e36d2ff..34cc6bd92d 100644
--- a/src/applications/paste/storage/PhabricatorPasteTransaction.php
+++ b/src/applications/paste/storage/PhabricatorPasteTransaction.php
@@ -67,7 +67,7 @@ final class PhabricatorPasteTransaction
$type = $this->getTransactionType();
switch ($type) {
- case PhabricatorPasteTransaction::TYPE_CONTENT:
+ case self::TYPE_CONTENT:
if ($old === null) {
return pht(
'%s created this paste.',
@@ -78,13 +78,13 @@ final class PhabricatorPasteTransaction
$this->renderHandleLink($author_phid));
}
break;
- case PhabricatorPasteTransaction::TYPE_TITLE:
+ case self::TYPE_TITLE:
return pht(
'%s updated the paste\'s title to "%s".',
$this->renderHandleLink($author_phid),
$new);
break;
- case PhabricatorPasteTransaction::TYPE_LANGUAGE:
+ case self::TYPE_LANGUAGE:
return pht(
"%s updated the paste's language.",
$this->renderHandleLink($author_phid));
@@ -103,7 +103,7 @@ final class PhabricatorPasteTransaction
$type = $this->getTransactionType();
switch ($type) {
- case PhabricatorPasteTransaction::TYPE_CONTENT:
+ case self::TYPE_CONTENT:
if ($old === null) {
return pht(
'%s created %s.',
@@ -116,13 +116,13 @@ final class PhabricatorPasteTransaction
$this->renderHandleLink($object_phid));
}
break;
- case PhabricatorPasteTransaction::TYPE_TITLE:
+ case self::TYPE_TITLE:
return pht(
'%s updated the title for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
- case PhabricatorPasteTransaction::TYPE_LANGUAGE:
+ case self::TYPE_LANGUAGE:
return pht(
'%s updated the language for %s.',
$this->renderHandleLink($author_phid),
diff --git a/src/applications/paste/view/PasteEmbedView.php b/src/applications/paste/view/PasteEmbedView.php
index 46301e5a31..650905c46c 100644
--- a/src/applications/paste/view/PasteEmbedView.php
+++ b/src/applications/paste/view/PasteEmbedView.php
@@ -28,7 +28,7 @@ final class PasteEmbedView extends AphrontView {
public function render() {
if (!$this->paste) {
- throw new Exception('Call setPaste() before render()!');
+ throw new PhutilInvalidStateException('setPaste');
}
$lines = phutil_split_lines($this->paste->getContent());
diff --git a/src/applications/people/controller/PhabricatorPeopleCalendarController.php b/src/applications/people/controller/PhabricatorPeopleCalendarController.php
index 06c7033fd7..168c1def70 100644
--- a/src/applications/people/controller/PhabricatorPeopleCalendarController.php
+++ b/src/applications/people/controller/PhabricatorPeopleCalendarController.php
@@ -35,29 +35,41 @@ final class PhabricatorPeopleCalendarController
$month = $request->getInt('month', $month_d);
$day = phabricator_format_local_time($now, $user, 'j');
-
- $holidays = id(new PhabricatorCalendarHoliday())->loadAllWhere(
- 'day BETWEEN %s AND %s',
- "{$year}-{$month}-01",
- "{$year}-{$month}-31");
+ $start_epoch = strtotime("{$year}-{$month}-01");
+ $end_epoch = strtotime("{$year}-{$month}-01 next month");
$statuses = id(new PhabricatorCalendarEventQuery())
->setViewer($user)
->withInvitedPHIDs(array($user->getPHID()))
->withDateRange(
- strtotime("{$year}-{$month}-01"),
- strtotime("{$year}-{$month}-01 next month"))
+ $start_epoch,
+ $end_epoch)
->execute();
+ $start_range_value = AphrontFormDateControlValue::newFromEpoch(
+ $user,
+ $start_epoch);
+ $end_range_value = AphrontFormDateControlValue::newFromEpoch(
+ $user,
+ $end_epoch);
+
if ($month == $month_d && $year == $year_d) {
- $month_view = new PHUICalendarMonthView($month, $year, $day);
+ $month_view = new PHUICalendarMonthView(
+ $start_range_value,
+ $end_range_value,
+ $month,
+ $year,
+ $day);
} else {
- $month_view = new PHUICalendarMonthView($month, $year);
+ $month_view = new PHUICalendarMonthView(
+ $start_range_value,
+ $end_range_value,
+ $month,
+ $year);
}
$month_view->setBrowseURI($request->getRequestURI());
$month_view->setUser($user);
- $month_view->setHolidays($holidays);
$month_view->setImage($picture);
$phids = mpull($statuses, 'getUserPHID');
@@ -67,7 +79,7 @@ final class PhabricatorPeopleCalendarController
$event = new AphrontCalendarEventView();
$event->setEpochRange($status->getDateFrom(), $status->getDateTo());
$event->setUserPHID($status->getUserPHID());
- $event->setName($status->getHumanStatus());
+ $event->setName($status->getName());
$event->setDescription($status->getDescription());
$event->setEventID($status->getID());
$month_view->addEvent($event);
diff --git a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
index 231181614d..0f59e23286 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
@@ -70,12 +70,9 @@ final class PhabricatorPeopleProfilePictureController
'This server only supports these image formats: %s.',
implode(', ', $supported_formats));
} else {
- $xformer = new PhabricatorImageTransformer();
- $xformed = $xformer->executeProfileTransform(
- $file,
- $width = 50,
- $min_height = 50,
- $max_height = 50);
+ $xform = PhabricatorFileTransform::getTransformByKey(
+ PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE);
+ $xformed = $xform->executeTransform($file);
}
}
diff --git a/src/applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php b/src/applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php
index f5c3c73a9a..6cc6abf37b 100644
--- a/src/applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php
+++ b/src/applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php
@@ -14,7 +14,7 @@ final class PhabricatorUserEditorTestCase extends PhabricatorTestCase {
$this->registerUser(
'PhabricatorUserEditorTestCaseOK',
- 'PhabricatorUserEditorTestCase@example.com');
+ 'PhabricatorUserEditorTest@example.com');
$this->assertTrue(true);
}
@@ -45,7 +45,7 @@ final class PhabricatorUserEditorTestCase extends PhabricatorTestCase {
try {
$this->registerUser(
'PhabricatorUserEditorTestCaseDomain',
- 'PhabricatorUserEditorTestCase@whitehouse.gov');
+ 'PhabricatorUserEditorTest@whitehouse.gov');
} catch (Exception $ex) {
$caught = $ex;
}
diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php
index ac9d8b0200..b8b7ad1e29 100644
--- a/src/applications/people/query/PhabricatorPeopleQuery.php
+++ b/src/applications/people/query/PhabricatorPeopleQuery.php
@@ -148,26 +148,55 @@ final class PhabricatorPeopleQuery
}
if ($this->needProfileImage) {
- $user_profile_file_phids = mpull($users, 'getProfileImagePHID');
- $user_profile_file_phids = array_filter($user_profile_file_phids);
- if ($user_profile_file_phids) {
- $files = id(new PhabricatorFileQuery())
- ->setParentQuery($this)
- ->setViewer($this->getViewer())
- ->withPHIDs($user_profile_file_phids)
- ->execute();
- $files = mpull($files, null, 'getPHID');
- } else {
- $files = array();
- }
+ $rebuild = array();
foreach ($users as $user) {
- $image_phid = $user->getProfileImagePHID();
- if (isset($files[$image_phid])) {
- $profile_image_uri = $files[$image_phid]->getBestURI();
- } else {
- $profile_image_uri = PhabricatorUser::getDefaultProfileImageURI();
+ $image_uri = $user->getProfileImageCache();
+ if ($image_uri) {
+ // This user has a valid cache, so we don't need to fetch any
+ // data or rebuild anything.
+
+ $user->attachProfileImageURI($image_uri);
+ continue;
+ }
+
+ // This user's cache is invalid or missing, so we're going to rebuild
+ // it.
+ $rebuild[] = $user;
+ }
+
+ if ($rebuild) {
+ $file_phids = mpull($rebuild, 'getProfileImagePHID');
+ $file_phids = array_filter($file_phids);
+
+ if ($file_phids) {
+ // NOTE: We're using the omnipotent user here because older profile
+ // images do not have the 'profile' flag, so they may not be visible
+ // to the executing viewer. At some point, we could migrate to add
+ // this flag and then use the real viewer, or just use the real
+ // viewer after enough time has passed to limit the impact of old
+ // data. The consequence of missing here is that we cache a default
+ // image when a real image exists.
+ $files = id(new PhabricatorFileQuery())
+ ->setParentQuery($this)
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPHIDs($file_phids)
+ ->execute();
+ $files = mpull($files, null, 'getPHID');
+ } else {
+ $files = array();
+ }
+
+ foreach ($rebuild as $user) {
+ $image_phid = $user->getProfileImagePHID();
+ if (isset($files[$image_phid])) {
+ $image_uri = $files[$image_phid]->getBestURI();
+ } else {
+ $image_uri = PhabricatorUser::getDefaultProfileImageURI();
+ }
+
+ $user->writeProfileImageCache($image_uri);
+ $user->attachProfileImageURI($image_uri);
}
- $user->attachProfileImageURI($profile_image_uri);
}
}
diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php
index ea138042b2..3fbfac062f 100644
--- a/src/applications/people/storage/PhabricatorUser.php
+++ b/src/applications/people/storage/PhabricatorUser.php
@@ -1,6 +1,7 @@
'uint32',
'accountSecret' => 'bytes64',
'isEnrolledInMultiFactor' => 'bool',
+ 'profileImageCache' => 'text255?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
@@ -160,6 +163,9 @@ final class PhabricatorUser
'columns' => array('isApproved'),
),
),
+ self::CONFIG_NO_MUTATE => array(
+ 'profileImageCache' => true,
+ ),
) + parent::getConfiguration();
}
@@ -682,6 +688,10 @@ EOBODY;
}
}
+ public function getTimeZone() {
+ return new DateTimeZone($this->getTimezoneIdentifier());
+ }
+
public function __toString() {
return $this->getUsername();
}
@@ -717,6 +727,72 @@ EOBODY;
}
+/* -( Profile Image Cache )------------------------------------------------ */
+
+
+ /**
+ * Get this user's cached profile image URI.
+ *
+ * @return string|null Cached URI, if a URI is cached.
+ * @task image-cache
+ */
+ public function getProfileImageCache() {
+ $version = $this->getProfileImageVersion();
+
+ $parts = explode(',', $this->profileImageCache, 2);
+ if (count($parts) !== 2) {
+ return null;
+ }
+
+ if ($parts[0] !== $version) {
+ return null;
+ }
+
+ return $parts[1];
+ }
+
+
+ /**
+ * Generate a new cache value for this user's profile image.
+ *
+ * @return string New cache value.
+ * @task image-cache
+ */
+ public function writeProfileImageCache($uri) {
+ $version = $this->getProfileImageVersion();
+ $cache = "{$version},{$uri}";
+
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+ queryfx(
+ $this->establishConnection('w'),
+ 'UPDATE %T SET profileImageCache = %s WHERE id = %d',
+ $this->getTableName(),
+ $cache,
+ $this->getID());
+ unset($unguarded);
+ }
+
+
+ /**
+ * Get a version identifier for a user's profile image.
+ *
+ * This version will change if the image changes, or if any of the
+ * environment configuration which goes into generating a URI changes.
+ *
+ * @return string Cache version.
+ * @task image-cache
+ */
+ private function getProfileImageVersion() {
+ $parts = array(
+ PhabricatorEnv::getCDNURI('/'),
+ PhabricatorEnv::getEnvConfig('cluster.instance'),
+ $this->getProfileImagePHID(),
+ );
+ $parts = serialize($parts);
+ return PhabricatorHash::digestForIndex($parts);
+ }
+
+
/* -( Multi-Factor Authentication )---------------------------------------- */
diff --git a/src/applications/phame/application/PhabricatorPhameApplication.php b/src/applications/phame/application/PhabricatorPhameApplication.php
index b466ad5191..4d8cebffa0 100644
--- a/src/applications/phame/application/PhabricatorPhameApplication.php
+++ b/src/applications/phame/application/PhabricatorPhameApplication.php
@@ -69,4 +69,10 @@ final class PhabricatorPhameApplication extends PhabricatorApplication {
);
}
+ public function getQuicksandURIPatternBlacklist() {
+ return array(
+ '/phame/live/.*',
+ );
+ }
+
}
diff --git a/src/applications/phid/handle/pool/PhabricatorHandleList.php b/src/applications/phid/handle/pool/PhabricatorHandleList.php
index d21e461871..7a8fed529f 100644
--- a/src/applications/phid/handle/pool/PhabricatorHandleList.php
+++ b/src/applications/phid/handle/pool/PhabricatorHandleList.php
@@ -24,6 +24,7 @@ final class PhabricatorHandleList
private $handlePool;
private $phids;
+ private $count;
private $handles;
private $cursor;
private $map;
@@ -35,6 +36,7 @@ final class PhabricatorHandleList
public function setPHIDs(array $phids) {
$this->phids = $phids;
+ $this->count = count($phids);
return $this;
}
@@ -119,7 +121,7 @@ final class PhabricatorHandleList
}
public function valid() {
- return isset($this->phids[$this->cursor]);
+ return ($this->cursor < $this->count);
}
@@ -156,8 +158,9 @@ final class PhabricatorHandleList
private function raiseImmutableException() {
throw new Exception(
pht(
- 'Trying to mutate a PhabricatorHandleList, but this is not permitted; '.
- 'handle lists are immutable.'));
+ 'Trying to mutate a %s, but this is not permitted; '.
+ 'handle lists are immutable.',
+ __CLASS__));
}
@@ -165,7 +168,7 @@ final class PhabricatorHandleList
public function count() {
- return count($this->phids);
+ return $this->count;
}
}
diff --git a/src/applications/phid/handle/pool/PhabricatorHandlePool.php b/src/applications/phid/handle/pool/PhabricatorHandlePool.php
index a1195225bb..eaa48828a4 100644
--- a/src/applications/phid/handle/pool/PhabricatorHandlePool.php
+++ b/src/applications/phid/handle/pool/PhabricatorHandlePool.php
@@ -61,12 +61,17 @@ final class PhabricatorHandlePool extends Phobject {
// If we need any handles, bulk load everything in the queue.
if ($need) {
+ // Clear the list of PHIDs that need to be loaded before performing the
+ // actual fetch. This prevents us from looping if we need to reenter the
+ // HandlePool while loading handles.
+ $fetch_phids = array_keys($this->unloadedPHIDs);
+ $this->unloadedPHIDs = array();
+
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->getViewer())
- ->withPHIDs(array_keys($this->unloadedPHIDs))
+ ->withPHIDs($fetch_phids)
->execute();
$this->handles += $handles;
- $this->unloadedPHIDs = array();
}
return array_select_keys($this->handles, $phids);
diff --git a/src/applications/phid/query/PhabricatorObjectQuery.php b/src/applications/phid/query/PhabricatorObjectQuery.php
index 49d238c6a9..26d0668cc2 100644
--- a/src/applications/phid/query/PhabricatorObjectQuery.php
+++ b/src/applications/phid/query/PhabricatorObjectQuery.php
@@ -78,7 +78,7 @@ final class PhabricatorObjectQuery
public function getNamedResults() {
if ($this->namedResults === null) {
- throw new Exception('Call execute() before getNamedResults()!');
+ throw new PhutilInvalidStateException('execute');
}
return $this->namedResults;
}
@@ -125,8 +125,19 @@ final class PhabricatorObjectQuery
$groups[$type][] = $phid;
}
+ $in_flight = $this->getPHIDsInFlight();
foreach ($groups as $type => $group) {
- if (isset($types[$type])) {
+ // Don't try to load PHIDs which are already "in flight"; this prevents
+ // us from recursing indefinitely if policy checks or edges form a loop.
+ // We will decline to load the corresponding objects.
+ foreach ($group as $key => $phid) {
+ if (isset($in_flight[$phid])) {
+ unset($group[$key]);
+ }
+ }
+
+ if ($group && isset($types[$type])) {
+ $this->putPHIDsInFlight($group);
$objects = $types[$type]->loadObjects($this, $group);
$results += mpull($objects, null, 'getPHID');
}
diff --git a/src/applications/phid/type/PhabricatorPHIDType.php b/src/applications/phid/type/PhabricatorPHIDType.php
index 4445ab863d..fd64ab7bd1 100644
--- a/src/applications/phid/type/PhabricatorPHIDType.php
+++ b/src/applications/phid/type/PhabricatorPHIDType.php
@@ -169,9 +169,13 @@ abstract class PhabricatorPHIDType {
$that_class = $original[$type];
$this_class = get_class($object);
throw new Exception(
- "Two PhabricatorPHIDType classes ({$that_class}, {$this_class}) ".
- "both handle PHID type '{$type}'. A type may be handled by only ".
- "one class.");
+ pht(
+ "Two %s classes (%s, %s) both handle PHID type '%s'. ".
+ "A type may be handled by only one class.",
+ __CLASS__,
+ $that_class,
+ $this_class,
+ $type));
}
$original[$type] = get_class($object);
diff --git a/src/applications/pholio/application/PhabricatorPholioApplication.php b/src/applications/pholio/application/PhabricatorPholioApplication.php
index e56f989630..801a2de4e8 100644
--- a/src/applications/pholio/application/PhabricatorPholioApplication.php
+++ b/src/applications/pholio/application/PhabricatorPholioApplication.php
@@ -49,7 +49,6 @@ final class PhabricatorPholioApplication extends PhabricatorApplication {
'inline/' => array(
'(?:(?P\d+)/)?' => 'PholioInlineController',
'list/(?P\d+)/' => 'PholioInlineListController',
- 'thumb/(?P\d+)/' => 'PholioInlineThumbController',
),
'image/' => array(
'upload/' => 'PholioImageUploadController',
diff --git a/src/applications/pholio/controller/PholioInlineThumbController.php b/src/applications/pholio/controller/PholioInlineThumbController.php
deleted file mode 100644
index 624ce3d3ea..0000000000
--- a/src/applications/pholio/controller/PholioInlineThumbController.php
+++ /dev/null
@@ -1,46 +0,0 @@
-imageid = idx($data, 'imageid');
- }
-
- public function processRequest() {
- $request = $this->getRequest();
- $user = $request->getUser();
-
- $image = id(new PholioImage())->load($this->imageid);
-
- if ($image == null) {
- return new Aphront404Response();
- }
-
- $mock = id(new PholioMockQuery())
- ->setViewer($user)
- ->withIDs(array($image->getMockID()))
- ->executeOne();
-
- if (!$mock) {
- return new Aphront404Response();
- }
-
- $file = id(new PhabricatorFileQuery())
- ->setViewer($user)
- ->witHPHIDs(array($image->getFilePHID()))
- ->executeOne();
-
- if (!$file) {
- return new Aphront404Response();
- }
-
- return id(new AphrontRedirectResponse())->setURI($file->getThumb60x45URI());
- }
-
-}
diff --git a/src/applications/pholio/query/PholioMockSearchEngine.php b/src/applications/pholio/query/PholioMockSearchEngine.php
index 226f225e11..057a370649 100644
--- a/src/applications/pholio/query/PholioMockSearchEngine.php
+++ b/src/applications/pholio/query/PholioMockSearchEngine.php
@@ -141,15 +141,22 @@ final class PholioMockSearchEngine extends PhabricatorApplicationSearchEngine {
$viewer = $this->requireViewer();
+ $xform = PhabricatorFileTransform::getTransformByKey(
+ PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD);
+
$board = new PHUIPinboardView();
foreach ($mocks as $mock) {
+ $image = $mock->getCoverFile();
+ $image_uri = $image->getURIForTransform($xform);
+ list($x, $y) = $xform->getTransformedDimensions($image);
+
$header = 'M'.$mock->getID().' '.$mock->getName();
$item = id(new PHUIPinboardItemView())
->setHeader($header)
->setURI('/M'.$mock->getID())
- ->setImageURI($mock->getCoverFile()->getThumb280x210URI())
- ->setImageSize(280, 210)
+ ->setImageURI($image_uri)
+ ->setImageSize($x, $y)
->setDisabled($mock->isClosed())
->addIconCount('fa-picture-o', count($mock->getImages()))
->addIconCount('fa-trophy', $mock->getTokenCount());
diff --git a/src/applications/pholio/remarkup/PholioRemarkupRule.php b/src/applications/pholio/remarkup/PholioRemarkupRule.php
index e8c8f00a17..2b1ca0f876 100644
--- a/src/applications/pholio/remarkup/PholioRemarkupRule.php
+++ b/src/applications/pholio/remarkup/PholioRemarkupRule.php
@@ -25,6 +25,10 @@ final class PholioRemarkupRule extends PhabricatorObjectRemarkupRule {
$href = $href.'/'.$id[1].'/';
}
+ if ($this->getEngine()->getConfig('uri.full')) {
+ $href = PhabricatorEnv::getURI($href);
+ }
+
return $href;
}
diff --git a/src/applications/pholio/view/PholioMockEmbedView.php b/src/applications/pholio/view/PholioMockEmbedView.php
index 81dfa670a3..3429cfd569 100644
--- a/src/applications/pholio/view/PholioMockEmbedView.php
+++ b/src/applications/pholio/view/PholioMockEmbedView.php
@@ -17,7 +17,7 @@ final class PholioMockEmbedView extends AphrontView {
public function render() {
if (!$this->mock) {
- throw new Exception('Call setMock() before render()!');
+ throw new PhutilInvalidStateException('setMock');
}
$mock = $this->mock;
@@ -28,25 +28,29 @@ final class PholioMockEmbedView extends AphrontView {
$this->mock->getImages(), array_flip($this->images));
}
+ $xform = PhabricatorFileTransform::getTransformByKey(
+ PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD);
+
if ($images_to_show) {
- foreach ($images_to_show as $image) {
- $thumbfile = $image->getFile();
- $thumbnail = $thumbfile->getThumb280x210URI();
- }
+ $image = head($images_to_show);
+ $thumbfile = $image->getFile();
$header = 'M'.$mock->getID().' '.$mock->getName().
' (#'.$image->getID().')';
$uri = '/M'.$this->mock->getID().'/'.$image->getID().'/';
} else {
- $thumbnail = $mock->getCoverFile()->getThumb280x210URI();
+ $thumbfile = $mock->getCoverFile();
$header = 'M'.$mock->getID().' '.$mock->getName();
$uri = '/M'.$this->mock->getID();
}
+ $thumbnail = $thumbfile->getURIForTransform($xform);
+ list($x, $y) = $xform->getTransformedDimensions($thumbfile);
+
$item = id(new PHUIPinboardItemView())
->setHeader($header)
->setURI($uri)
->setImageURI($thumbnail)
- ->setImageSize(280, 210)
+ ->setImageSize($x, $y)
->setDisabled($mock->isClosed())
->addIconCount('fa-picture-o', count($mock->getImages()))
->addIconCount('fa-trophy', $mock->getTokenCount());
diff --git a/src/applications/pholio/view/PholioMockImagesView.php b/src/applications/pholio/view/PholioMockImagesView.php
index f894c698f2..d59e701579 100644
--- a/src/applications/pholio/view/PholioMockImagesView.php
+++ b/src/applications/pholio/view/PholioMockImagesView.php
@@ -68,8 +68,11 @@ final class PholioMockImagesView extends AphrontView {
// TODO: We could maybe do a better job with tailoring this, which is the
// image shown on the review stage.
- $nonimage_uri = celerity_get_resource_uri(
- 'rsrc/image/icon/fatcow/thumbnails/default.p100.png');
+ $default_name = 'image-100x100.png';
+ $builtins = PhabricatorFile::loadBuiltins(
+ $this->getUser(),
+ array($default_name));
+ $default = $builtins[$default_name];
$engine = id(new PhabricatorMarkupEngine())
->setViewer($this->getUser());
@@ -97,7 +100,7 @@ final class PholioMockImagesView extends AphrontView {
'fullURI' => $file->getBestURI(),
'stageURI' => ($file->isViewableImage()
? $file->getBestURI()
- : $nonimage_uri),
+ : $default->getBestURI()),
'pageURI' => $this->getImagePageURI($image, $mock),
'downloadURI' => $file->getDownloadURI(),
'historyURI' => $history_uri,
diff --git a/src/applications/pholio/view/PholioMockThumbGridView.php b/src/applications/pholio/view/PholioMockThumbGridView.php
index df9fe1aa06..8e9d3007c5 100644
--- a/src/applications/pholio/view/PholioMockThumbGridView.php
+++ b/src/applications/pholio/view/PholioMockThumbGridView.php
@@ -114,28 +114,34 @@ final class PholioMockThumbGridView extends AphrontView {
private function renderThumbnail(PholioImage $image) {
$thumbfile = $image->getFile();
+ $preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_THUMBGRID;
+ $xform = PhabricatorFileTransform::getTransformByKey($preview_key);
+
+ $attributes = array(
+ 'class' => 'pholio-mock-thumb-grid-image',
+ 'src' => $thumbfile->getURIForTransform($xform),
+ );
+
if ($image->getFile()->isViewableImage()) {
- $dimensions = PhabricatorImageTransformer::getPreviewDimensions(
- $thumbfile,
- 100);
+ $dimensions = $xform->getTransformedDimensions($thumbfile);
+ if ($dimensions) {
+ list($x, $y) = $dimensions;
+ $attributes += array(
+ 'width' => $x,
+ 'height' => $y,
+ 'style' => 'top: '.floor((100 - $y) / 2).'px',
+ );
+ }
} else {
// If this is a PDF or a text file or something, we'll end up using a
// generic thumbnail which is always sized correctly.
- $dimensions = array(
- 'sdx' => 100,
- 'sdy' => 100,
+ $attributes += array(
+ 'width' => 100,
+ 'height' => 100,
);
}
- $tag = phutil_tag(
- 'img',
- array(
- 'width' => $dimensions['sdx'],
- 'height' => $dimensions['sdy'],
- 'src' => $thumbfile->getPreview100URI(),
- 'class' => 'pholio-mock-thumb-grid-image',
- 'style' => 'top: '.floor((100 - $dimensions['sdy'] ) / 2).'px',
- ));
+ $tag = phutil_tag('img', $attributes);
$classes = array('pholio-mock-thumb-grid-item');
if ($image->getIsObsolete()) {
diff --git a/src/applications/pholio/view/PholioUploadedImageView.php b/src/applications/pholio/view/PholioUploadedImageView.php
index fb8a82431e..2ff3ba0390 100644
--- a/src/applications/pholio/view/PholioUploadedImageView.php
+++ b/src/applications/pholio/view/PholioUploadedImageView.php
@@ -38,11 +38,15 @@ final class PholioUploadedImageView extends AphrontView {
->setSigil('image-description')
->setLabel(pht('Description'));
+ $xform = PhabricatorFileTransform::getTransformByKey(
+ PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD);
+ $thumbnail_uri = $file->getURIForTransform($xform);
+
$thumb_frame = phutil_tag(
'div',
array(
'class' => 'pholio-thumb-frame',
- 'style' => 'background-image: url('.$file->getThumb280x210URI().');',
+ 'style' => 'background-image: url('.$thumbnail_uri.');',
));
$handle = javelin_tag(
diff --git a/src/applications/phortune/cart/PhortuneSubscriptionCart.php b/src/applications/phortune/cart/PhortuneSubscriptionCart.php
index 6c17e00331..ff71106932 100644
--- a/src/applications/phortune/cart/PhortuneSubscriptionCart.php
+++ b/src/applications/phortune/cart/PhortuneSubscriptionCart.php
@@ -34,8 +34,7 @@ final class PhortuneSubscriptionCart
$subscription = $this->getSubscription();
if (!$subscription) {
- throw new Exception(
- pht('Call setSubscription() before building a cart!'));
+ throw new PhutilInvalidStateException('setSubscription');
}
$cart->setMetadataValue('subscriptionPHID', $subscription->getPHID());
diff --git a/src/applications/phortune/currency/PhortuneCurrency.php b/src/applications/phortune/currency/PhortuneCurrency.php
index a473738ed6..b59d9b144a 100644
--- a/src/applications/phortune/currency/PhortuneCurrency.php
+++ b/src/applications/phortune/currency/PhortuneCurrency.php
@@ -68,10 +68,10 @@ final class PhortuneCurrency extends Phobject {
}
public static function newFromList(array $list) {
- assert_instances_of($list, 'PhortuneCurrency');
+ assert_instances_of($list, __CLASS__);
if (!$list) {
- return PhortuneCurrency::newEmptyCurrency();
+ return self::newEmptyCurrency();
}
$total = null;
@@ -201,8 +201,8 @@ final class PhortuneCurrency extends Phobject {
*/
public function assertInRange($minimum, $maximum) {
if ($minimum !== null && $maximum !== null) {
- $min = PhortuneCurrency::newFromString($minimum);
- $max = PhortuneCurrency::newFromString($maximum);
+ $min = self::newFromString($minimum);
+ $max = self::newFromString($maximum);
if ($min->value > $max->value) {
throw new Exception(
pht(
@@ -213,7 +213,7 @@ final class PhortuneCurrency extends Phobject {
}
if ($minimum !== null) {
- $min = PhortuneCurrency::newFromString($minimum);
+ $min = self::newFromString($minimum);
if ($min->value > $this->value) {
throw new Exception(
pht(
@@ -223,7 +223,7 @@ final class PhortuneCurrency extends Phobject {
}
if ($maximum !== null) {
- $max = PhortuneCurrency::newFromString($maximum);
+ $max = self::newFromString($maximum);
if ($max->value < $this->value) {
throw new Exception(
pht(
diff --git a/src/applications/phortune/provider/PhortunePaymentProvider.php b/src/applications/phortune/provider/PhortunePaymentProvider.php
index 49c80dcd72..da36779d06 100644
--- a/src/applications/phortune/provider/PhortunePaymentProvider.php
+++ b/src/applications/phortune/provider/PhortunePaymentProvider.php
@@ -118,7 +118,7 @@ abstract class PhortunePaymentProvider {
public static function getAllProviders() {
return id(new PhutilSymbolLoader())
- ->setAncestorClass('PhortunePaymentProvider')
+ ->setAncestorClass(__CLASS__)
->loadObjects();
}
diff --git a/src/applications/phortune/storage/PhortuneAccount.php b/src/applications/phortune/storage/PhortuneAccount.php
index facb9d5089..e86fd53df2 100644
--- a/src/applications/phortune/storage/PhortuneAccount.php
+++ b/src/applications/phortune/storage/PhortuneAccount.php
@@ -27,7 +27,7 @@ final class PhortuneAccount extends PhortuneDAO
PhabricatorUser $actor,
PhabricatorContentSource $content_source) {
- $account = PhortuneAccount::initializeNewAccount($actor);
+ $account = self::initializeNewAccount($actor);
$xactions = array();
$xactions[] = id(new PhortuneAccountTransaction())
diff --git a/src/applications/phortune/storage/PhortuneCart.php b/src/applications/phortune/storage/PhortuneCart.php
index 46d474cc93..70ecbb21c9 100644
--- a/src/applications/phortune/storage/PhortuneCart.php
+++ b/src/applications/phortune/storage/PhortuneCart.php
@@ -136,7 +136,7 @@ final class PhortuneCart extends PhortuneDAO
}
$charge->save();
- $this->setStatus(PhortuneCart::STATUS_PURCHASING)->save();
+ $this->setStatus(self::STATUS_PURCHASING)->save();
$this->endReadLocking();
$this->saveTransaction();
diff --git a/src/applications/phragment/storage/PhragmentFragment.php b/src/applications/phragment/storage/PhragmentFragment.php
index 3f5719178b..574283d7a7 100644
--- a/src/applications/phragment/storage/PhragmentFragment.php
+++ b/src/applications/phragment/storage/PhragmentFragment.php
@@ -256,7 +256,7 @@ final class PhragmentFragment extends PhragmentDAO
$mappings[$path],
array('name' => basename($path)));
}
- PhragmentFragment::createFromFile(
+ self::createFromFile(
$viewer,
$file,
$base_path.'/'.$path,
diff --git a/src/applications/policy/constants/PhabricatorPolicies.php b/src/applications/policy/constants/PhabricatorPolicies.php
index 859010eca2..c33b9bf909 100644
--- a/src/applications/policy/constants/PhabricatorPolicies.php
+++ b/src/applications/policy/constants/PhabricatorPolicies.php
@@ -15,9 +15,9 @@ final class PhabricatorPolicies extends PhabricatorPolicyConstants {
*/
public static function getMostOpenPolicy() {
if (PhabricatorEnv::getEnvConfig('policy.allow-public')) {
- return PhabricatorPolicies::POLICY_PUBLIC;
+ return self::POLICY_PUBLIC;
} else {
- return PhabricatorPolicies::POLICY_USER;
+ return self::POLICY_USER;
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
index 031427d058..b9c366fecc 100644
--- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php
+++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
@@ -383,6 +383,7 @@ final class PhabricatorProjectBoardViewController
array(
'title' => pht('%s Board', $project->getName()),
'showFooter' => false,
+ 'pageObjects' => array($project->getPHID()),
));
}
diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php
index 58e345930e..ca99159718 100644
--- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php
+++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php
@@ -68,12 +68,9 @@ final class PhabricatorProjectEditPictureController
'This server only supports these image formats: %s.',
implode(', ', $supported_formats));
} else {
- $xformer = new PhabricatorImageTransformer();
- $xformed = $xformer->executeProfileTransform(
- $file,
- $width = 50,
- $min_height = 50,
- $max_height = 50);
+ $xform = PhabricatorFileTransform::getTransformByKey(
+ PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE);
+ $xformed = $xform->executeTransform($file);
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php
index 9679ddab5c..cb8fe9e87f 100644
--- a/src/applications/project/controller/PhabricatorProjectProfileController.php
+++ b/src/applications/project/controller/PhabricatorProjectProfileController.php
@@ -67,6 +67,7 @@ final class PhabricatorProjectProfileController
$nav,
array(
'title' => $project->getName(),
+ 'pageObjects' => array($project->getPHID()),
));
}
diff --git a/src/applications/project/icon/PhabricatorProjectIcon.php b/src/applications/project/icon/PhabricatorProjectIcon.php
index ae6efa9b6e..9411baa6a9 100644
--- a/src/applications/project/icon/PhabricatorProjectIcon.php
+++ b/src/applications/project/icon/PhabricatorProjectIcon.php
@@ -44,7 +44,7 @@ final class PhabricatorProjectIcon extends Phobject {
}
public static function renderIconForChooser($icon) {
- $project_icons = PhabricatorProjectIcon::getIconMap();
+ $project_icons = self::getIconMap();
return phutil_tag(
'span',
diff --git a/src/applications/project/storage/PhabricatorProjectColumn.php b/src/applications/project/storage/PhabricatorProjectColumn.php
index 8fcb6ade9c..63b8a8748f 100644
--- a/src/applications/project/storage/PhabricatorProjectColumn.php
+++ b/src/applications/project/storage/PhabricatorProjectColumn.php
@@ -111,7 +111,7 @@ final class PhabricatorProjectColumn
->setMetadata(
array(
'tip' => $text,
- ));;
+ ));
}
return null;
diff --git a/src/applications/project/storage/PhabricatorProjectColumnTransaction.php b/src/applications/project/storage/PhabricatorProjectColumnTransaction.php
index dd4f95d5f6..ed4bfed8a6 100644
--- a/src/applications/project/storage/PhabricatorProjectColumnTransaction.php
+++ b/src/applications/project/storage/PhabricatorProjectColumnTransaction.php
@@ -21,7 +21,7 @@ final class PhabricatorProjectColumnTransaction
$author_handle = $this->renderHandleLink($this->getAuthorPHID());
switch ($this->getTransactionType()) {
- case PhabricatorProjectColumnTransaction::TYPE_NAME:
+ case self::TYPE_NAME:
if ($old === null) {
return pht(
'%s created this column.',
@@ -44,7 +44,7 @@ final class PhabricatorProjectColumnTransaction
$author_handle);
}
}
- case PhabricatorProjectColumnTransaction::TYPE_LIMIT:
+ case self::TYPE_LIMIT:
if (!$old) {
return pht(
'%s set the point limit for this column to %s.',
@@ -62,7 +62,7 @@ final class PhabricatorProjectColumnTransaction
$new);
}
- case PhabricatorProjectColumnTransaction::TYPE_STATUS:
+ case self::TYPE_STATUS:
switch ($new) {
case PhabricatorProjectColumn::STATUS_ACTIVE:
return pht(
diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php
index 9b0ff0c340..3dceb0934c 100644
--- a/src/applications/project/storage/PhabricatorProjectTransaction.php
+++ b/src/applications/project/storage/PhabricatorProjectTransaction.php
@@ -28,12 +28,12 @@ final class PhabricatorProjectTransaction
$req_phids = array();
switch ($this->getTransactionType()) {
- case PhabricatorProjectTransaction::TYPE_MEMBERS:
+ case self::TYPE_MEMBERS:
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
$req_phids = array_merge($add, $rem);
break;
- case PhabricatorProjectTransaction::TYPE_IMAGE:
+ case self::TYPE_IMAGE:
$req_phids[] = $old;
$req_phids[] = $new;
break;
@@ -48,7 +48,7 @@ final class PhabricatorProjectTransaction
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
- case PhabricatorProjectTransaction::TYPE_STATUS:
+ case self::TYPE_STATUS:
if ($old == 0) {
return 'red';
} else {
@@ -64,25 +64,25 @@ final class PhabricatorProjectTransaction
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
- case PhabricatorProjectTransaction::TYPE_STATUS:
+ case self::TYPE_STATUS:
if ($old == 0) {
return 'fa-ban';
} else {
return 'fa-check';
}
- case PhabricatorProjectTransaction::TYPE_LOCKED:
+ case self::TYPE_LOCKED:
if ($new) {
return 'fa-lock';
} else {
return 'fa-unlock';
}
- case PhabricatorProjectTransaction::TYPE_ICON:
+ case self::TYPE_ICON:
return $new;
- case PhabricatorProjectTransaction::TYPE_IMAGE:
+ case self::TYPE_IMAGE:
return 'fa-photo';
- case PhabricatorProjectTransaction::TYPE_MEMBERS:
+ case self::TYPE_MEMBERS:
return 'fa-user';
- case PhabricatorProjectTransaction::TYPE_SLUGS:
+ case self::TYPE_SLUGS:
return 'fa-tag';
}
return parent::getIcon();
@@ -94,7 +94,7 @@ final class PhabricatorProjectTransaction
$author_handle = $this->renderHandleLink($this->getAuthorPHID());
switch ($this->getTransactionType()) {
- case PhabricatorProjectTransaction::TYPE_NAME:
+ case self::TYPE_NAME:
if ($old === null) {
return pht(
'%s created this project.',
@@ -106,7 +106,7 @@ final class PhabricatorProjectTransaction
$old,
$new);
}
- case PhabricatorProjectTransaction::TYPE_STATUS:
+ case self::TYPE_STATUS:
if ($old == 0) {
return pht(
'%s archived this project.',
@@ -116,7 +116,7 @@ final class PhabricatorProjectTransaction
'%s activated this project.',
$author_handle);
}
- case PhabricatorProjectTransaction::TYPE_IMAGE:
+ case self::TYPE_IMAGE:
// TODO: Some day, it would be nice to show the images.
if (!$old) {
return pht(
@@ -135,19 +135,19 @@ final class PhabricatorProjectTransaction
$this->renderHandleLink($new));
}
- case PhabricatorProjectTransaction::TYPE_ICON:
+ case self::TYPE_ICON:
return pht(
'%s set this project\'s icon to %s.',
$author_handle,
PhabricatorProjectIcon::getLabel($new));
- case PhabricatorProjectTransaction::TYPE_COLOR:
+ case self::TYPE_COLOR:
return pht(
'%s set this project\'s color to %s.',
$author_handle,
PHUITagView::getShadeName($new));
- case PhabricatorProjectTransaction::TYPE_LOCKED:
+ case self::TYPE_LOCKED:
if ($new) {
return pht(
'%s locked this project\'s membership.',
@@ -158,7 +158,7 @@ final class PhabricatorProjectTransaction
$author_handle);
}
- case PhabricatorProjectTransaction::TYPE_SLUGS:
+ case self::TYPE_SLUGS:
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
@@ -184,7 +184,7 @@ final class PhabricatorProjectTransaction
$this->renderSlugList($rem));
}
- case PhabricatorProjectTransaction::TYPE_MEMBERS:
+ case self::TYPE_MEMBERS:
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
diff --git a/src/applications/releeph/storage/ReleephRequest.php b/src/applications/releeph/storage/ReleephRequest.php
index 7f3f190a6d..7f2487107a 100644
--- a/src/applications/releeph/storage/ReleephRequest.php
+++ b/src/applications/releeph/storage/ReleephRequest.php
@@ -127,8 +127,8 @@ final class ReleephRequest extends ReleephDAO
if ($this->getInBranch()) {
return ReleephRequestStatus::STATUS_NEEDS_REVERT;
} else {
- $intent_pass = ReleephRequest::INTENT_PASS;
- $intent_want = ReleephRequest::INTENT_WANT;
+ $intent_pass = self::INTENT_PASS;
+ $intent_want = self::INTENT_WANT;
$has_been_in_branch = $this->getCommitIdentifier();
// Regardless of why we reverted something, always say reverted if it
diff --git a/src/applications/releeph/storage/ReleephRequestTransaction.php b/src/applications/releeph/storage/ReleephRequestTransaction.php
index bd17ad43c9..f4a4720c78 100644
--- a/src/applications/releeph/storage/ReleephRequestTransaction.php
+++ b/src/applications/releeph/storage/ReleephRequestTransaction.php
@@ -38,12 +38,12 @@ final class ReleephRequestTransaction
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
- case ReleephRequestTransaction::TYPE_REQUEST:
- case ReleephRequestTransaction::TYPE_DISCOVERY:
+ case self::TYPE_REQUEST:
+ case self::TYPE_DISCOVERY:
$phids[] = $new;
break;
- case ReleephRequestTransaction::TYPE_EDIT_FIELD:
+ case self::TYPE_EDIT_FIELD:
self::searchForPHIDs($this->getOldValue(), $phids);
self::searchForPHIDs($this->getNewValue(), $phids);
break;
@@ -60,18 +60,18 @@ final class ReleephRequestTransaction
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
- case ReleephRequestTransaction::TYPE_REQUEST:
+ case self::TYPE_REQUEST:
return pht(
'%s requested %s',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($new));
break;
- case ReleephRequestTransaction::TYPE_USER_INTENT:
+ case self::TYPE_USER_INTENT:
return $this->getIntentTitle();
break;
- case ReleephRequestTransaction::TYPE_EDIT_FIELD:
+ case self::TYPE_EDIT_FIELD:
$field = newv($this->getMetadataValue('fieldClass'), array());
$name = $field->getName();
@@ -89,7 +89,7 @@ final class ReleephRequestTransaction
$field->normalizeForTransactionView($this, $new));
break;
- case ReleephRequestTransaction::TYPE_PICK_STATUS:
+ case self::TYPE_PICK_STATUS:
switch ($new) {
case ReleephRequest::PICK_OK:
return pht('%s found this request picks without error',
@@ -109,7 +109,7 @@ final class ReleephRequestTransaction
}
break;
- case ReleephRequestTransaction::TYPE_COMMIT:
+ case self::TYPE_COMMIT:
$action_type = $this->getMetadataValue('action');
switch ($action_type) {
case 'pick':
@@ -126,7 +126,7 @@ final class ReleephRequestTransaction
}
break;
- case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH:
+ case self::TYPE_MANUAL_IN_BRANCH:
$action = $new ? pht('picked') : pht('reverted');
return pht(
'%s marked this request as manually %s',
@@ -134,7 +134,7 @@ final class ReleephRequestTransaction
$action);
break;
- case ReleephRequestTransaction::TYPE_DISCOVERY:
+ case self::TYPE_DISCOVERY:
return pht('%s discovered this commit as %s',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($new));
@@ -173,7 +173,7 @@ final class ReleephRequestTransaction
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
- case ReleephRequestTransaction::TYPE_USER_INTENT:
+ case self::TYPE_USER_INTENT:
switch ($new) {
case ReleephRequest::INTENT_WANT:
return PhabricatorTransactions::COLOR_GREEN;
@@ -243,7 +243,7 @@ final class ReleephRequestTransaction
public function shouldHide() {
$type = $this->getTransactionType();
- if ($type === ReleephRequestTransaction::TYPE_USER_INTENT &&
+ if ($type === self::TYPE_USER_INTENT &&
$this->getMetadataValue('isRQCreate')) {
return true;
@@ -255,7 +255,7 @@ final class ReleephRequestTransaction
// ReleephSummaryFieldSpecification is usually blank when an RQ is created,
// creating a transaction change from null to "". Hide these!
- if ($type === ReleephRequestTransaction::TYPE_EDIT_FIELD) {
+ if ($type === self::TYPE_EDIT_FIELD) {
if ($this->getOldValue() === null && $this->getNewValue() === '') {
return true;
}
@@ -265,7 +265,7 @@ final class ReleephRequestTransaction
public function isBoringPickStatus() {
$type = $this->getTransactionType();
- if ($type === ReleephRequestTransaction::TYPE_PICK_STATUS) {
+ if ($type === self::TYPE_PICK_STATUS) {
$new = $this->getNewValue();
if ($new === ReleephRequest::PICK_OK ||
$new === ReleephRequest::REVERT_OK) {
diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php
index 3131f9b9e6..21c7fe38ab 100644
--- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php
+++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php
@@ -201,7 +201,7 @@ final class PhabricatorRepositorySearchEngine
array $handles) {
assert_instances_of($repositories, 'PhabricatorRepository');
- $viewer = $this->requireViewer();;
+ $viewer = $this->requireViewer();
$list = new PHUIObjectItemListView();
foreach ($repositories as $repository) {
diff --git a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php
index d5fb87fa8d..f16784bbf1 100644
--- a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php
+++ b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php
@@ -54,13 +54,13 @@ final class PhabricatorRepositoryPushLog
public static function getHeraldChangeFlagConditionOptions() {
return array(
- PhabricatorRepositoryPushLog::CHANGEFLAG_ADD =>
+ self::CHANGEFLAG_ADD =>
pht('change creates ref'),
- PhabricatorRepositoryPushLog::CHANGEFLAG_DELETE =>
+ self::CHANGEFLAG_DELETE =>
pht('change deletes ref'),
- PhabricatorRepositoryPushLog::CHANGEFLAG_REWRITE =>
+ self::CHANGEFLAG_REWRITE =>
pht('change rewrites ref'),
- PhabricatorRepositoryPushLog::CHANGEFLAG_DANGEROUS =>
+ self::CHANGEFLAG_DANGEROUS =>
pht('dangerous change'),
);
}
diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php
index d3ba1cf9ae..ac67585268 100644
--- a/src/applications/search/controller/PhabricatorApplicationSearchController.php
+++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php
@@ -55,18 +55,12 @@ final class PhabricatorApplicationSearchController
$engine = $this->getSearchEngine();
if (!$engine) {
- throw new Exception(
- pht(
- 'Call %s before delegating to this controller!',
- 'setEngine()'));
+ throw new PhutilInvalidStateException('setEngine');
}
$nav = $this->getNavigation();
if (!$nav) {
- throw new Exception(
- pht(
- 'Call %s before delegating to this controller!',
- 'setNavigation()'));
+ throw new PhutilInvalidStateException('setNavigation');
}
$engine->setViewer($this->getRequest()->getUser());
diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
index a0e7245a76..cd0a9655fa 100644
--- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
+++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
@@ -33,7 +33,7 @@ abstract class PhabricatorApplicationSearchEngine {
protected function requireViewer() {
if (!$this->viewer) {
- throw new Exception('Call setViewer() before using an engine!');
+ throw new PhutilInvalidStateException('setViewer');
}
return $this->viewer;
}
diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php
index e07a045dc2..3248dc8b2a 100644
--- a/src/applications/settings/storage/PhabricatorUserPreferences.php
+++ b/src/applications/settings/storage/PhabricatorUserPreferences.php
@@ -74,14 +74,14 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO {
}
public function getPinnedApplications(array $apps, PhabricatorUser $viewer) {
- $pref_pinned = PhabricatorUserPreferences::PREFERENCE_APP_PINNED;
+ $pref_pinned = self::PREFERENCE_APP_PINNED;
$pinned = $this->getPreference($pref_pinned);
if ($pinned) {
return $pinned;
}
- $pref_tiles = PhabricatorUserPreferences::PREFERENCE_APP_TILES;
+ $pref_tiles = self::PREFERENCE_APP_TILES;
$tiles = $this->getPreference($pref_tiles, array());
$full_tile = 'full';
diff --git a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php
index abb73fd588..9abf1f5801 100644
--- a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php
+++ b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php
@@ -26,10 +26,10 @@ final class PhabricatorSlowvoteTransaction
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
- case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION:
- case PhabricatorSlowvoteTransaction::TYPE_RESPONSES:
- case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE:
- case PhabricatorSlowvoteTransaction::TYPE_CLOSE:
+ case self::TYPE_DESCRIPTION:
+ case self::TYPE_RESPONSES:
+ case self::TYPE_SHUFFLE:
+ case self::TYPE_CLOSE:
return ($old === null);
}
@@ -43,7 +43,7 @@ final class PhabricatorSlowvoteTransaction
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
- case PhabricatorSlowvoteTransaction::TYPE_QUESTION:
+ case self::TYPE_QUESTION:
if ($old === null) {
return pht(
'%s created this poll.',
@@ -56,16 +56,16 @@ final class PhabricatorSlowvoteTransaction
$new);
}
break;
- case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION:
+ case self::TYPE_DESCRIPTION:
return pht(
'%s updated the description for this poll.',
$this->renderHandleLink($author_phid));
- case PhabricatorSlowvoteTransaction::TYPE_RESPONSES:
+ case self::TYPE_RESPONSES:
// TODO: This could be more detailed
return pht(
'%s changed who can see the responses.',
$this->renderHandleLink($author_phid));
- case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE:
+ case self::TYPE_SHUFFLE:
if ($new) {
return pht(
'%s made poll responses appear in a random order.',
@@ -76,7 +76,7 @@ final class PhabricatorSlowvoteTransaction
$this->renderHandleLink($author_phid));
}
break;
- case PhabricatorSlowvoteTransaction::TYPE_CLOSE:
+ case self::TYPE_CLOSE:
if ($new) {
return pht(
'%s closed this poll.',
@@ -98,18 +98,18 @@ final class PhabricatorSlowvoteTransaction
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
- case PhabricatorSlowvoteTransaction::TYPE_QUESTION:
+ case self::TYPE_QUESTION:
if ($old === null) {
return 'fa-plus';
} else {
return 'fa-pencil';
}
- case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION:
- case PhabricatorSlowvoteTransaction::TYPE_RESPONSES:
+ case self::TYPE_DESCRIPTION:
+ case self::TYPE_RESPONSES:
return 'fa-pencil';
- case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE:
+ case self::TYPE_SHUFFLE:
return 'fa-refresh';
- case PhabricatorSlowvoteTransaction::TYPE_CLOSE:
+ case self::TYPE_CLOSE:
if ($new) {
return 'fa-ban';
} else {
@@ -126,11 +126,11 @@ final class PhabricatorSlowvoteTransaction
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
- case PhabricatorSlowvoteTransaction::TYPE_QUESTION:
- case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION:
- case PhabricatorSlowvoteTransaction::TYPE_RESPONSES:
- case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE:
- case PhabricatorSlowvoteTransaction::TYPE_CLOSE:
+ case self::TYPE_QUESTION:
+ case self::TYPE_DESCRIPTION:
+ case self::TYPE_RESPONSES:
+ case self::TYPE_SHUFFLE:
+ case self::TYPE_CLOSE:
return PhabricatorTransactions::COLOR_BLUE;
}
@@ -139,7 +139,7 @@ final class PhabricatorSlowvoteTransaction
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
- case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION:
+ case self::TYPE_DESCRIPTION:
return true;
}
return parent::hasChangeDetails();
diff --git a/src/applications/slowvote/view/SlowvoteEmbedView.php b/src/applications/slowvote/view/SlowvoteEmbedView.php
index 79e4c0bb1d..4ff920bb7d 100644
--- a/src/applications/slowvote/view/SlowvoteEmbedView.php
+++ b/src/applications/slowvote/view/SlowvoteEmbedView.php
@@ -22,7 +22,7 @@ final class SlowvoteEmbedView extends AphrontView {
public function render() {
if (!$this->poll) {
- throw new Exception('Call setPoll() before render()!');
+ throw new PhutilInvalidStateException('setPoll');
}
$poll = $this->poll;
diff --git a/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php b/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php
index 498b01a04f..3ee4b5f6e8 100644
--- a/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php
+++ b/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php
@@ -56,7 +56,7 @@ final class PhabricatorSubscriptionsEditor extends PhabricatorEditor {
public function save() {
if (!$this->object) {
- throw new Exception('Call setObject() before save()!');
+ throw new PhutilInvalidStateException('setObject');
}
$actor = $this->requireActor();
diff --git a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php
index 380c8380ce..fb7c32892f 100644
--- a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php
+++ b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php
@@ -21,6 +21,7 @@ final class PhabricatorSubscriptionsUIEventListener
private function handleActionEvent($event) {
$user = $event->getUser();
+ $user_phid = $user->getPHID();
$object = $event->getValue('object');
if (!$object || !$object->getPHID()) {
@@ -33,12 +34,12 @@ final class PhabricatorSubscriptionsUIEventListener
return;
}
- if (!$object->shouldAllowSubscription($user->getPHID())) {
+ if (!$object->shouldAllowSubscription($user_phid)) {
// This object doesn't allow the viewer to subscribe.
return;
}
- if ($object->isAutomaticallySubscribed($user->getPHID())) {
+ if ($user_phid && $object->isAutomaticallySubscribed($user_phid)) {
$sub_action = id(new PhabricatorActionView())
->setWorkflow(true)
->setDisabled(true)
@@ -50,15 +51,14 @@ final class PhabricatorSubscriptionsUIEventListener
$subscribed = false;
if ($user->isLoggedIn()) {
$src_phid = $object->getPHID();
- $dst_phid = $user->getPHID();
$edge_type = PhabricatorObjectHasSubscriberEdgeType::EDGECONST;
$edges = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($src_phid))
->withEdgeTypes(array($edge_type))
- ->withDestinationPHIDs(array($user->getPHID()))
+ ->withDestinationPHIDs(array($user_phid))
->execute();
- $subscribed = isset($edges[$src_phid][$edge_type][$dst_phid]);
+ $subscribed = isset($edges[$src_phid][$edge_type][$user_phid]);
}
if ($subscribed) {
diff --git a/src/applications/system/engine/PhabricatorDestructionEngine.php b/src/applications/system/engine/PhabricatorDestructionEngine.php
index e1f1045132..b55b336052 100644
--- a/src/applications/system/engine/PhabricatorDestructionEngine.php
+++ b/src/applications/system/engine/PhabricatorDestructionEngine.php
@@ -46,6 +46,9 @@ final class PhabricatorDestructionEngine extends Phobject {
$template = $object->getApplicationTransactionTemplate();
$this->destroyTransactions($template, $object_phid);
}
+
+ $this->destroyWorkerTasks($object_phid);
+ $this->destroyNotifications($object_phid);
}
// Nuke any Herald transcripts of the object, because they may contain
@@ -94,7 +97,28 @@ final class PhabricatorDestructionEngine extends Phobject {
foreach ($xactions as $xaction) {
$this->destroyObject($xaction);
}
+ }
+ private function destroyWorkerTasks($object_phid) {
+ $tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere(
+ 'objectPHID = %s',
+ $object_phid);
+
+ foreach ($tasks as $task) {
+ $task->archiveTask(
+ PhabricatorWorkerArchiveTask::RESULT_CANCELLED,
+ 0);
+ }
+ }
+
+ private function destroyNotifications($object_phid) {
+ $notifications = id(new PhabricatorFeedStoryNotification())->loadAllWhere(
+ 'primaryObjectPHID = %s',
+ $object_phid);
+
+ foreach ($notifications as $notification) {
+ $notification->delete();
+ }
}
}
diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php
index ee09a76eda..23b77fbd1b 100644
--- a/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php
+++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php
@@ -133,8 +133,7 @@ final class PhabricatorApplicationTransactionCommentEditor
}
if (!$this->getContentSource()) {
- throw new Exception(
- 'Call setContentSource() before applyEdit()!');
+ throw new PhutilInvalidStateException('applyEdit');
}
$actor = $this->requireActor();
diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
index c28fb135ee..dfd4bba286 100644
--- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
+++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
@@ -569,6 +569,11 @@ abstract class PhabricatorApplicationTransactionEditor
return $xaction;
}
+ protected function didApplyInternalEffects(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+ return $xactions;
+ }
protected function applyFinalEffects(
PhabricatorLiskDAO $object,
@@ -731,6 +736,8 @@ abstract class PhabricatorApplicationTransactionEditor
$this->applyInternalEffects($object, $xaction);
}
+ $xactions = $this->didApplyInternalEffects($object, $xactions);
+
$object->save();
foreach ($xactions as $xaction) {
@@ -963,8 +970,7 @@ abstract class PhabricatorApplicationTransactionEditor
array $xactions) {
if (!$this->getContentSource()) {
- throw new Exception(
- 'Call setContentSource() before applyTransactions()!');
+ throw new PhutilInvalidStateException('setContentSource');
}
// Do a bunch of sanity checks that the incoming transactions are fresh.
diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php
index ac6f713eeb..4c1e3ccd17 100644
--- a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php
+++ b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php
@@ -126,10 +126,8 @@ abstract class PhabricatorApplicationTransactionQuery
$handles = array();
$merged = array_mergev($phids);
if ($merged) {
- $handles = id(new PhabricatorHandleQuery())
- ->setViewer($this->getViewer())
- ->withPHIDs($merged)
- ->execute();
+ $handles = $this->getViewer()->loadHandles($merged);
+ $handles = iterator_to_array($handles);
}
foreach ($xactions as $xaction) {
$xaction->setHandles(
diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
index c6755ccfcc..355f15c2a7 100644
--- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
+++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
@@ -1073,7 +1073,7 @@ abstract class PhabricatorApplicationTransaction
}
public function attachTransactionGroup(array $group) {
- assert_instances_of($group, 'PhabricatorApplicationTransaction');
+ assert_instances_of($group, __CLASS__);
$this->transactionGroup = $group;
return $this;
}
@@ -1165,7 +1165,7 @@ abstract class PhabricatorApplicationTransaction
}
$old_target = $xaction->getRenderingTarget();
- $new_target = PhabricatorApplicationTransaction::TARGET_TEXT;
+ $new_target = self::TARGET_TEXT;
$xaction->setRenderingTarget($new_target);
if ($publisher->getRenderWithImpliedContext()) {
diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
index 77cc6a6371..311c7e8b9f 100644
--- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
+++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
@@ -149,7 +149,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
}
if (!$this->getObjectPHID()) {
- throw new Exception('Call setObjectPHID() before render()!');
+ throw new PhutilInvalidStateException('setObjectPHID', 'render');
}
return id(new AphrontFormView())
diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php
index ec5a02337e..28110e7334 100644
--- a/src/infrastructure/customfield/field/PhabricatorCustomField.php
+++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php
@@ -65,7 +65,7 @@ abstract class PhabricatorCustomField {
"object of class '{$obj_class}'.");
}
- $fields = PhabricatorCustomField::buildFieldList(
+ $fields = self::buildFieldList(
$base_class,
$spec,
$object);
diff --git a/src/infrastructure/daemon/bot/PhabricatorBot.php b/src/infrastructure/daemon/bot/PhabricatorBot.php
index 12ab9e663a..ef949cb9ff 100644
--- a/src/infrastructure/daemon/bot/PhabricatorBot.php
+++ b/src/infrastructure/daemon/bot/PhabricatorBot.php
@@ -19,7 +19,11 @@ final class PhabricatorBot extends PhabricatorDaemon {
protected function run() {
$argv = $this->getArgv();
if (count($argv) !== 1) {
- throw new Exception('usage: PhabricatorBot ');
+ throw new Exception(
+ pht(
+ 'Usage: %s %s',
+ __CLASS__,
+ ''));
}
$json_raw = Filesystem::readFile($argv[0]);
@@ -53,8 +57,7 @@ final class PhabricatorBot extends PhabricatorDaemon {
$conduit_uri = idx($config, 'conduit.uri');
if ($conduit_uri) {
- $conduit_user = idx($config, 'conduit.user');
- $conduit_cert = idx($config, 'conduit.cert');
+ $conduit_token = idx($config, 'conduit.token');
// Normalize the path component of the URI so users can enter the
// domain without the "/api/" part.
@@ -64,16 +67,23 @@ final class PhabricatorBot extends PhabricatorDaemon {
$conduit_uri = (string)$conduit_uri->setPath('/api/');
$conduit = new ConduitClient($conduit_uri);
- $response = $conduit->callMethodSynchronous(
- 'conduit.connect',
- array(
- 'client' => 'PhabricatorBot',
- 'clientVersion' => '1.0',
- 'clientDescription' => php_uname('n').':'.$nick,
- 'host' => $conduit_host,
- 'user' => $conduit_user,
- 'certificate' => $conduit_cert,
- ));
+ if ($conduit_token) {
+ $conduit->setConduitToken($conduit_token);
+ } else {
+ $conduit_user = idx($config, 'conduit.user');
+ $conduit_cert = idx($config, 'conduit.cert');
+
+ $response = $conduit->callMethodSynchronous(
+ 'conduit.connect',
+ array(
+ 'client' => __CLASS__,
+ 'clientVersion' => '1.0',
+ 'clientDescription' => php_uname('n').':'.$nick,
+ 'host' => $conduit_host,
+ 'user' => $conduit_user,
+ 'certificate' => $conduit_cert,
+ ));
+ }
$this->conduit = $conduit;
}
diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php
index cffa6faf35..f226d0ad76 100644
--- a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php
+++ b/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php
@@ -26,7 +26,7 @@ final class PhabricatorBotObjectNameHandler extends PhabricatorBotHandler {
$pattern =
'@'.
- '(?getConduit()->callMethodSynchronous(
- 'diffusion.getcommits',
+ 'diffusion.querycommits',
array(
- 'commits' => $commit_names,
+ 'names' => $commit_names,
));
- foreach ($commits as $commit) {
- if (isset($commit['error'])) {
- continue;
- }
- $output[$commit['commitPHID']] = $commit['uri'];
+ foreach ($commits['data'] as $commit) {
+ $output[$commit['phid']] = $commit['uri'];
}
}
diff --git a/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php b/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php
index 34cab99afa..c569f0c695 100644
--- a/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php
+++ b/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php
@@ -150,7 +150,7 @@ final class PhabricatorWorkerTriggerQuery
if (($this->nextEpochMin !== null) ||
($this->nextEpochMax !== null) ||
- ($this->order == PhabricatorWorkerTriggerQuery::ORDER_EXECUTION)) {
+ ($this->order == self::ORDER_EXECUTION)) {
$joins[] = qsprintf(
$conn_r,
'JOIN %T e ON e.triggerID = t.id',
diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php
index bc4358ba55..4608ebeffa 100644
--- a/src/infrastructure/diff/PhabricatorInlineCommentController.php
+++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php
@@ -266,6 +266,8 @@ abstract class PhabricatorInlineCommentController
// comment appears on. This is expected in the case of ghost comments.
// We currently put the new comment on the visible changeset, not the
// original comment's changeset.
+
+ $this->isNewFile = $reply_comment->getIsNewFile();
}
}
diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php
index 6634c397f9..3a9bc616d9 100644
--- a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php
+++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php
@@ -76,10 +76,10 @@ final class PHUIDiffInlineCommentEditView
public function render() {
if (!$this->uri) {
- throw new Exception('Call setSubmitURI() before render()!');
+ throw new PhutilInvalidStateException('setSubmitURI');
}
if (!$this->user) {
- throw new Exception('Call setUser() before render()!');
+ throw new PhutilInvalidStateException('setUser');
}
$content = phabricator_form(
diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php
index 05ab7c677d..39f911e3c5 100644
--- a/src/infrastructure/env/PhabricatorEnv.php
+++ b/src/infrastructure/env/PhabricatorEnv.php
@@ -88,7 +88,7 @@ final class PhabricatorEnv {
// Force a valid timezone. If both PHP and Phabricator configuration are
// invalid, use UTC.
- $tz = PhabricatorEnv::getEnvConfig('phabricator.timezone');
+ $tz = self::getEnvConfig('phabricator.timezone');
if ($tz) {
@date_default_timezone_set($tz);
}
@@ -102,7 +102,7 @@ final class PhabricatorEnv {
$phabricator_path = dirname(phutil_get_library_root('phabricator'));
$support_path = $phabricator_path.'/support/bin';
$env_path = $support_path.PATH_SEPARATOR.$env_path;
- $append_dirs = PhabricatorEnv::getEnvConfig('environment.append-paths');
+ $append_dirs = self::getEnvConfig('environment.append-paths');
if (!empty($append_dirs)) {
$append_path = implode(PATH_SEPARATOR, $append_dirs);
$env_path = $env_path.PATH_SEPARATOR.$append_path;
@@ -116,7 +116,7 @@ final class PhabricatorEnv {
// If an instance identifier is defined, write it into the environment so
// it's available to subprocesses.
- $instance = PhabricatorEnv::getEnvConfig('cluster.instance');
+ $instance = self::getEnvConfig('cluster.instance');
if (strlen($instance)) {
putenv('PHABRICATOR_INSTANCE='.$instance);
$_ENV['PHABRICATOR_INSTANCE'] = $instance;
@@ -139,7 +139,7 @@ final class PhabricatorEnv {
$translations = PhutilTranslation::getTranslationMapForLocale(
$locale_code);
- $override = PhabricatorEnv::getEnvConfig('translation.override');
+ $override = self::getEnvConfig('translation.override');
if (!is_array($override)) {
$override = array();
}
@@ -178,7 +178,7 @@ final class PhabricatorEnv {
// If the install overrides the database adapter, we might need to load
// the database adapter class before we can push on the database config.
// This config is locked and can't be edited from the web UI anyway.
- foreach (PhabricatorEnv::getEnvConfig('load-libraries') as $library) {
+ foreach (self::getEnvConfig('load-libraries') as $library) {
phutil_load_library($library);
}
@@ -809,7 +809,7 @@ final class PhabricatorEnv {
}
public static function isClusterAddress($address) {
- $cluster_addresses = PhabricatorEnv::getEnvConfig('cluster.addresses');
+ $cluster_addresses = self::getEnvConfig('cluster.addresses');
if (!$cluster_addresses) {
throw new Exception(
pht(
diff --git a/src/infrastructure/events/PhabricatorExampleEventListener.php b/src/infrastructure/events/PhabricatorExampleEventListener.php
index 0a7bd699df..fb85678da1 100644
--- a/src/infrastructure/events/PhabricatorExampleEventListener.php
+++ b/src/infrastructure/events/PhabricatorExampleEventListener.php
@@ -21,8 +21,11 @@ final class PhabricatorExampleEventListener extends PhabricatorEventListener {
$console = PhutilConsole::getConsole();
$console->writeOut(
- "PhabricatorExampleEventListener got test event at %d\n",
- $event->getValue('time'));
+ "%s\n",
+ pht(
+ '% got test event at %d',
+ __CLASS__,
+ $event->getValue('time')));
}
}
diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php
index 491940d23f..c29dddd821 100644
--- a/src/infrastructure/markup/PhabricatorMarkupEngine.php
+++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php
@@ -42,7 +42,7 @@ final class PhabricatorMarkupEngine {
private $objects = array();
private $viewer;
private $contextObject;
- private $version = 14;
+ private $version = 15;
/* -( Markup Pipeline )---------------------------------------------------- */
@@ -337,6 +337,7 @@ final class PhabricatorMarkupEngine {
public static function newPhameMarkupEngine() {
return self::newMarkupEngine(array(
'macros' => false,
+ 'uri.full' => true,
));
}
@@ -349,7 +350,6 @@ final class PhabricatorMarkupEngine {
array(
'macros' => false,
'youtube' => false,
-
));
}
@@ -437,6 +437,7 @@ final class PhabricatorMarkupEngine {
'macros' => true,
'uri.allowed-protocols' => PhabricatorEnv::getEnvConfig(
'uri.allowed-protocols'),
+ 'uri.full' => false,
'syntax-highlighter.engine' => PhabricatorEnv::getEnvConfig(
'syntax-highlighter.engine'),
'preserve-linebreaks' => true,
@@ -464,6 +465,8 @@ final class PhabricatorMarkupEngine {
'syntax-highlighter.engine',
$options['syntax-highlighter.engine']);
+ $engine->setConfig('uri.full', $options['uri.full']);
+
$rules = array();
$rules[] = new PhutilRemarkupEscapeRemarkupRule();
$rules[] = new PhutilRemarkupMonospaceRule();
@@ -586,7 +589,7 @@ final class PhabricatorMarkupEngine {
// - Hopefully don't return too much text. We don't explicitly limit
// this right now.
- $blocks = preg_split("/\n *\n\s*/", trim($corpus));
+ $blocks = preg_split("/\n *\n\s*/", $corpus);
$best = null;
foreach ($blocks as $block) {
diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php
index 0b4471d811..8f776716ea 100644
--- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php
+++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php
@@ -28,10 +28,9 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
protected function loadHandles(array $objects) {
$phids = mpull($objects, 'getPHID');
- $handles = id(new PhabricatorHandleQuery($phids))
- ->withPHIDs($phids)
- ->setViewer($this->getEngine()->getConfig('viewer'))
- ->execute();
+ $viewer = $this->getEngine()->getConfig('viewer');
+ $handles = $viewer->loadHandles($phids);
+ $handles = iterator_to_array($handles);
$result = array();
foreach ($objects as $id => $object) {
@@ -45,7 +44,13 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
PhabricatorObjectHandle $handle,
$id) {
- return $handle->getURI();
+ $uri = $handle->getURI();
+
+ if ($this->getEngine()->getConfig('uri.full')) {
+ $uri = PhabricatorEnv::getURI($uri);
+ }
+
+ return $uri;
}
protected function renderObjectRefForAnyMedia (
diff --git a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php
index 1f353d7dfb..e1b2028a19 100644
--- a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php
+++ b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php
@@ -33,6 +33,7 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery {
private $rawResultLimit;
private $capabilities;
private $workspace = array();
+ private $inFlightPHIDs = array();
private $policyFilteredPHIDs = array();
private $canUseApplication;
@@ -468,6 +469,39 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery {
}
+ /**
+ * Mark PHIDs as in flight.
+ *
+ * PHIDs which are "in flight" are actively being queried for. Using this
+ * list can prevent infinite query loops by aborting queries which cycle.
+ *
+ * @param list List of PHIDs which are now in flight.
+ * @return this
+ */
+ public function putPHIDsInFlight(array $phids) {
+ foreach ($phids as $phid) {
+ $this->inFlightPHIDs[$phid] = $phid;
+ }
+ return $this;
+ }
+
+
+ /**
+ * Get PHIDs which are currently in flight.
+ *
+ * PHIDs which are "in flight" are actively being queried for.
+ *
+ * @return map PHIDs currently in flight.
+ */
+ public function getPHIDsInFlight() {
+ $results = $this->inFlightPHIDs;
+ if ($this->getParentQuery()) {
+ $results += $this->getParentQuery()->getPHIDsInFlight();
+ }
+ return $results;
+ }
+
+
/* -( Policy Query Implementation )---------------------------------------- */
diff --git a/src/infrastructure/sms/storage/PhabricatorSMS.php b/src/infrastructure/sms/storage/PhabricatorSMS.php
index c17d8f16da..7b6c85f159 100644
--- a/src/infrastructure/sms/storage/PhabricatorSMS.php
+++ b/src/infrastructure/sms/storage/PhabricatorSMS.php
@@ -38,8 +38,8 @@ final class PhabricatorSMS
// and ProviderSMSID are totally garbage data before a send it attempted.
return id(new PhabricatorSMS())
->setBody($body)
- ->setSendStatus(PhabricatorSMS::STATUS_UNSENT)
- ->setProviderShortName(PhabricatorSMS::SHORTNAME_PLACEHOLDER)
+ ->setSendStatus(self::STATUS_UNSENT)
+ ->setProviderShortName(self::SHORTNAME_PLACEHOLDER)
->setProviderSMSID(Filesystem::readRandomCharacters(40));
}
@@ -70,6 +70,6 @@ final class PhabricatorSMS
public function hasBeenSentAtLeastOnce() {
return ($this->getProviderShortName() !=
- PhabricatorSMS::SHORTNAME_PLACEHOLDER);
+ self::SHORTNAME_PLACEHOLDER);
}
}
diff --git a/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php b/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php
index 0eefe68069..390d591a07 100644
--- a/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php
+++ b/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php
@@ -38,7 +38,7 @@ abstract class PhabricatorSQLPatchList {
final public static function buildAllPatches() {
$patch_lists = id(new PhutilSymbolLoader())
- ->setAncestorClass('PhabricatorSQLPatchList')
+ ->setAncestorClass(__CLASS__)
->setConcreteOnly(true)
->selectAndLoadSymbols();
diff --git a/src/infrastructure/time/PhabricatorTime.php b/src/infrastructure/time/PhabricatorTime.php
index d2866d99c2..03495d8412 100644
--- a/src/infrastructure/time/PhabricatorTime.php
+++ b/src/infrastructure/time/PhabricatorTime.php
@@ -25,7 +25,10 @@ final class PhabricatorTime {
public static function popTime($key) {
if ($key !== last_key(self::$stack)) {
- throw new Exception('PhabricatorTime::popTime with bad key.');
+ throw new Exception(
+ pht(
+ '%s with bad key.',
+ __METHOD__));
}
array_pop(self::$stack);
@@ -49,7 +52,7 @@ final class PhabricatorTime {
$old_zone = date_default_timezone_get();
date_default_timezone_set($user->getTimezoneIdentifier());
- $timestamp = (int)strtotime($time, PhabricatorTime::getNow());
+ $timestamp = (int)strtotime($time, self::getNow());
if ($timestamp <= 0) {
$timestamp = null;
}
diff --git a/src/infrastructure/util/PhabricatorHash.php b/src/infrastructure/util/PhabricatorHash.php
index dd158d1168..df1fbfa08b 100644
--- a/src/infrastructure/util/PhabricatorHash.php
+++ b/src/infrastructure/util/PhabricatorHash.php
@@ -35,7 +35,7 @@ final class PhabricatorHash extends Phobject {
}
for ($ii = 0; $ii < 1000; $ii++) {
- $result = PhabricatorHash::digest($result, $salt);
+ $result = self::digest($result, $salt);
}
return $result;
@@ -113,7 +113,7 @@ final class PhabricatorHash extends Phobject {
// who can control the inputs from intentionally using the hashed form
// of a string to cause a collision.
- $hash = PhabricatorHash::digestForIndex($string);
+ $hash = self::digestForIndex($string);
$prefix = substr($string, 0, ($length - ($min_length - 1)));
diff --git a/src/infrastructure/util/password/PhabricatorPasswordHasher.php b/src/infrastructure/util/password/PhabricatorPasswordHasher.php
index 4409163509..3b136b4ec3 100644
--- a/src/infrastructure/util/password/PhabricatorPasswordHasher.php
+++ b/src/infrastructure/util/password/PhabricatorPasswordHasher.php
@@ -213,7 +213,7 @@ abstract class PhabricatorPasswordHasher extends Phobject {
*/
public static function getAllHashers() {
$objects = id(new PhutilSymbolLoader())
- ->setAncestorClass('PhabricatorPasswordHasher')
+ ->setAncestorClass(__CLASS__)
->loadObjects();
$map = array();
@@ -404,7 +404,7 @@ abstract class PhabricatorPasswordHasher extends Phobject {
}
try {
- $current_hasher = PhabricatorPasswordHasher::getHasherForHash($hash);
+ $current_hasher = self::getHasherForHash($hash);
return $current_hasher->getHumanReadableName();
} catch (Exception $ex) {
$info = self::parseHashFromStorage($hash);
@@ -421,7 +421,7 @@ abstract class PhabricatorPasswordHasher extends Phobject {
*/
public static function getBestAlgorithmName() {
try {
- $best_hasher = PhabricatorPasswordHasher::getBestHasher();
+ $best_hasher = self::getBestHasher();
return $best_hasher->getHumanReadableName();
} catch (Exception $ex) {
return pht('Unknown');
diff --git a/src/view/AphrontDialogView.php b/src/view/AphrontDialogView.php
index ac946969d5..408a92d94d 100644
--- a/src/view/AphrontDialogView.php
+++ b/src/view/AphrontDialogView.php
@@ -215,7 +215,10 @@ final class AphrontDialogView extends AphrontView {
if (!$this->user) {
throw new Exception(
- pht('You must call setUser() when rendering an AphrontDialogView.'));
+ pht(
+ 'You must call %s when rendering an %s.',
+ 'setUser()',
+ __CLASS__));
}
$more = $this->class;
diff --git a/src/view/control/AphrontCursorPagerView.php b/src/view/control/AphrontCursorPagerView.php
index a683eb3d6d..81ab33df81 100644
--- a/src/view/control/AphrontCursorPagerView.php
+++ b/src/view/control/AphrontCursorPagerView.php
@@ -92,8 +92,7 @@ final class AphrontCursorPagerView extends AphrontView {
public function getFirstPageURI() {
if (!$this->uri) {
- throw new Exception(
- pht('You must call setURI() before you can call getFirstPageURI().'));
+ throw new PhutilInvalidStateException('setURI');
}
if (!$this->afterID && !($this->beforeID && $this->moreResults)) {
@@ -107,8 +106,7 @@ final class AphrontCursorPagerView extends AphrontView {
public function getPrevPageURI() {
if (!$this->uri) {
- throw new Exception(
- pht('You must call setURI() before you can call getPrevPageURI().'));
+ throw new PhutilInvalidStateException('getPrevPageURI');
}
if (!$this->prevPageID) {
@@ -122,8 +120,7 @@ final class AphrontCursorPagerView extends AphrontView {
public function getNextPageURI() {
if (!$this->uri) {
- throw new Exception(
- pht('You must call setURI() before you can call getNextPageURI().'));
+ throw new PhutilInvalidStateException('setURI');
}
if (!$this->nextPageID) {
@@ -137,8 +134,7 @@ final class AphrontCursorPagerView extends AphrontView {
public function render() {
if (!$this->uri) {
- throw new Exception(
- pht('You must call setURI() before you can call render().'));
+ throw new PhutilInvalidStateException('setURI');
}
$links = array();
diff --git a/src/view/control/AphrontPagerView.php b/src/view/control/AphrontPagerView.php
index ba6a98736c..8b08b15c5a 100644
--- a/src/view/control/AphrontPagerView.php
+++ b/src/view/control/AphrontPagerView.php
@@ -109,8 +109,7 @@ final class AphrontPagerView extends AphrontView {
public function render() {
if (!$this->uri) {
- throw new Exception(
- pht('You must call setURI() before you can call render().'));
+ throw new PhutilInvalidStateException('setURI');
}
require_celerity_resource('aphront-pager-view-css');
diff --git a/src/view/control/AphrontTableView.php b/src/view/control/AphrontTableView.php
index 9edd851c19..a6814a030e 100644
--- a/src/view/control/AphrontTableView.php
+++ b/src/view/control/AphrontTableView.php
@@ -124,6 +124,7 @@ final class AphrontTableView extends AphrontView {
$visibility = array_values($this->columnVisibility);
$device_visibility = array_values($this->deviceVisibility);
+
$headers = $this->headers;
$short_headers = $this->shortHeaders;
$sort_values = $this->sortValues;
@@ -235,12 +236,16 @@ final class AphrontTableView extends AphrontView {
if ($data) {
$row_num = 0;
foreach ($data as $row) {
+ $row_size = count($row);
while (count($row) > count($col_classes)) {
$col_classes[] = null;
}
while (count($row) > count($visibility)) {
$visibility[] = true;
}
+ while (count($row) > count($device_visibility)) {
+ $device_visibility[] = true;
+ }
$tr = array();
// NOTE: Use of a separate column counter is to allow this to work
// correctly if the row data has string or non-sequential keys.
diff --git a/src/view/form/AphrontFormView.php b/src/view/form/AphrontFormView.php
index 6b30656131..f9f281ff20 100644
--- a/src/view/form/AphrontFormView.php
+++ b/src/view/form/AphrontFormView.php
@@ -126,7 +126,10 @@ final class AphrontFormView extends AphrontView {
$layout = $this->buildLayoutView();
if (!$this->user) {
- throw new Exception(pht('You must pass the user to AphrontFormView.'));
+ throw new Exception(
+ pht(
+ 'You must pass the user to %s.',
+ __CLASS__));
}
$sigils = $this->sigils;
diff --git a/src/view/form/PHUIFormLayoutView.php b/src/view/form/PHUIFormLayoutView.php
index fa05a72a59..1c5ccbb7f2 100644
--- a/src/view/form/PHUIFormLayoutView.php
+++ b/src/view/form/PHUIFormLayoutView.php
@@ -7,6 +7,7 @@
*/
final class PHUIFormLayoutView extends AphrontView {
+ private $classes = array();
private $fullWidth;
public function setFullWidth($width) {
@@ -14,6 +15,11 @@ final class PHUIFormLayoutView extends AphrontView {
return $this;
}
+ public function addClass($class) {
+ $this->classes[] = $class;
+ return $this;
+ }
+
public function appendInstructions($text) {
return $this->appendChild(
phutil_tag(
@@ -26,8 +32,7 @@ final class PHUIFormLayoutView extends AphrontView {
public function appendRemarkupInstructions($remarkup) {
if ($this->getUser() === null) {
- throw new Exception(
- 'Call `setUser` before appending Remarkup to PHUIFormLayoutView.');
+ throw new PhutilInvalidStateException('setUser');
}
return $this->appendInstructions(
@@ -38,7 +43,8 @@ final class PHUIFormLayoutView extends AphrontView {
}
public function render() {
- $classes = array('phui-form-view');
+ $classes = $this->classes;
+ $classes[] = 'phui-form-view';
if ($this->fullWidth) {
$classes[] = 'phui-form-full-width';
diff --git a/src/view/form/PHUIInfoView.php b/src/view/form/PHUIInfoView.php
index a079e3f6ca..497fcf3f78 100644
--- a/src/view/form/PHUIInfoView.php
+++ b/src/view/form/PHUIInfoView.php
@@ -41,7 +41,6 @@ final class PHUIInfoView extends AphrontView {
}
final public function render() {
-
require_celerity_resource('phui-info-view-css');
$errors = $this->errors;
diff --git a/src/view/form/PHUIPagedFormView.php b/src/view/form/PHUIPagedFormView.php
index 82e58c7f58..ff72f67ca0 100644
--- a/src/view/form/PHUIPagedFormView.php
+++ b/src/view/form/PHUIPagedFormView.php
@@ -152,7 +152,7 @@ final class PHUIPagedFormView extends AphrontView {
$is_attempt_complete = false;
if ($this->prevPage) {
- $prev_index = $this->getPageIndex($selected->getKey()) - 1;;
+ $prev_index = $this->getPageIndex($selected->getKey()) - 1;
$index = max(0, $prev_index);
$selected = $this->getPageByIndex($index);
} else if ($this->nextPage) {
diff --git a/src/view/form/control/AphrontFormCheckboxControl.php b/src/view/form/control/AphrontFormCheckboxControl.php
index 673c1c6d82..0d89e0a6ad 100644
--- a/src/view/form/control/AphrontFormCheckboxControl.php
+++ b/src/view/form/control/AphrontFormCheckboxControl.php
@@ -4,12 +4,18 @@ final class AphrontFormCheckboxControl extends AphrontFormControl {
private $boxes = array();
- public function addCheckbox($name, $value, $label, $checked = false) {
+ public function addCheckbox(
+ $name,
+ $value,
+ $label,
+ $checked = false,
+ $id = null) {
$this->boxes[] = array(
'name' => $name,
'value' => $value,
'label' => $label,
'checked' => $checked,
+ 'id' => $id,
);
return $this;
}
@@ -21,7 +27,10 @@ final class AphrontFormCheckboxControl extends AphrontFormControl {
protected function renderInput() {
$rows = array();
foreach ($this->boxes as $box) {
- $id = celerity_generate_unique_node_id();
+ $id = idx($box, 'id');
+ if ($id === null) {
+ $id = celerity_generate_unique_node_id();
+ }
$checkbox = phutil_tag(
'input',
array(
diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php
index ed79392fbb..84de3b3215 100644
--- a/src/view/form/control/AphrontFormDateControl.php
+++ b/src/view/form/control/AphrontFormDateControl.php
@@ -11,6 +11,7 @@ final class AphrontFormDateControl extends AphrontFormControl {
private $valueTime;
private $allowNull;
private $continueOnInvalidDate = false;
+ private $isTimeDisabled;
private $isDisabled;
public function setAllowNull($allow_null) {
@@ -18,6 +19,11 @@ final class AphrontFormDateControl extends AphrontFormControl {
return $this;
}
+ public function setIsTimeDisabled($is_disabled) {
+ $this->isTimeDisabled = $is_disabled;
+ return $this;
+ }
+
const TIME_START_OF_DAY = 'start-of-day';
const TIME_END_OF_DAY = 'end-of-day';
const TIME_START_OF_BUSINESS = 'start-of-business';
@@ -282,6 +288,9 @@ final class AphrontFormDateControl extends AphrontFormControl {
if ($disabled) {
$classes[] = 'datepicker-disabled';
}
+ if ($this->isTimeDisabled) {
+ $classes[] = 'no-time';
+ }
return javelin_tag(
'div',
@@ -291,6 +300,7 @@ final class AphrontFormDateControl extends AphrontFormControl {
'meta' => array(
'disabled' => (bool)$disabled,
),
+ 'id' => $this->getID(),
),
array(
$checkbox,
diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php
index 7d5451a1b2..f384dd3ba3 100644
--- a/src/view/form/control/PhabricatorRemarkupControl.php
+++ b/src/view/form/control/PhabricatorRemarkupControl.php
@@ -24,8 +24,7 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl {
$viewer = $this->getUser();
if (!$viewer) {
- throw new Exception(
- pht('Call setUser() before rendering a PhabricatorRemarkupControl!'));
+ throw new PhutilInvalidStateException('setUser');
}
// We need to have this if previews render images, since Ajax can not
diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php
index b0f8b5d0be..7c5dcaf1cb 100644
--- a/src/view/page/PhabricatorStandardPageView.php
+++ b/src/view/page/PhabricatorStandardPageView.php
@@ -3,7 +3,6 @@
/**
* This is a standard Phabricator page with menus, Javelin, DarkConsole, and
* basic styles.
- *
*/
final class PhabricatorStandardPageView extends PhabricatorBarePageView {
@@ -160,7 +159,8 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
if (!$this->getRequest()) {
throw new Exception(
pht(
- 'You must set the Request to render a PhabricatorStandardPageView.'));
+ 'You must set the Request to render a %s.',
+ __CLASS__));
}
$console = $this->getConsole();
@@ -377,15 +377,6 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
}
}
- if (!$this->isQuicksandBlacklistURI()) {
- Javelin::initBehavior(
- 'scrollbar',
- array(
- 'nodeID' => 'phabricator-standard-page',
- 'isMainContent' => true,
- ));
- }
-
$main_page = phutil_tag(
'div',
array(
diff --git a/src/view/phui/PHUIDocumentView.php b/src/view/phui/PHUIDocumentView.php
index 5140d0aa7b..2d4689b8bf 100644
--- a/src/view/phui/PHUIDocumentView.php
+++ b/src/view/phui/PHUIDocumentView.php
@@ -74,7 +74,7 @@ final class PHUIDocumentView extends AphrontTagView {
if ($this->offset) {
$classes[] = 'phui-document-offset';
- };
+ }
if ($this->fluid) {
$classes[] = 'phui-document-fluid';
diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php
index ad3561d930..14159e500c 100644
--- a/src/view/phui/PHUIHeaderView.php
+++ b/src/view/phui/PHUIHeaderView.php
@@ -1,6 +1,6 @@
image) {
+ $classes[] = 'phui-header-has-image';
+ }
+
+ return array(
+ 'class' => $classes,
+ );
+ }
+
+ protected function getTagContent() {
$image = null;
if ($this->image) {
$image = phutil_tag(
@@ -156,7 +170,6 @@ final class PHUIHeaderView extends AphrontView {
'style' => 'background-image: url('.$this->image.')',
),
' ');
- $classes[] = 'phui-header-has-image';
}
$header = array();
@@ -243,20 +256,15 @@ final class PHUIHeaderView extends AphrontView {
$property_list);
}
- return phutil_tag(
- 'div',
- array(
- 'class' => implode(' ', $classes),
- ),
- array(
- $image,
- phutil_tag(
- 'h1',
- array(
- 'class' => 'phui-header-view grouped',
- ),
- $header),
- ));
+ return array(
+ $image,
+ phutil_tag(
+ 'h1',
+ array(
+ 'class' => 'phui-header-view grouped',
+ ),
+ $header),
+ );
}
private function renderPolicyProperty(PhabricatorPolicyInterface $object) {
diff --git a/src/view/phui/PHUIRemarkupPreviewPanel.php b/src/view/phui/PHUIRemarkupPreviewPanel.php
index 8fa02f983c..9ca2f620f9 100644
--- a/src/view/phui/PHUIRemarkupPreviewPanel.php
+++ b/src/view/phui/PHUIRemarkupPreviewPanel.php
@@ -42,8 +42,11 @@ final class PHUIRemarkupPreviewPanel extends AphrontTagView {
);
if (empty($skins[$skin])) {
- $valid = implode(', ', array_keys($skins));
- throw new Exception("Invalid skin '{$skin}'. Valid skins are: {$valid}.");
+ throw new Exception(
+ pht(
+ "Invalid skin '%s'. Valid skins are: %s.",
+ $skin,
+ implode(', ', array_keys($skins))));
}
$this->skin = $skin;
@@ -69,10 +72,10 @@ final class PHUIRemarkupPreviewPanel extends AphrontTagView {
protected function getTagContent() {
if ($this->previewURI === null) {
- throw new Exception('Call setPreviewURI() before rendering!');
+ throw new PhutilInvalidStateException('setPreviewURI');
}
if ($this->controlID === null) {
- throw new Exception('Call setControlID() before rendering!');
+ throw new PhutilInvalidStateException('setControlID');
}
$preview_id = celerity_generate_unique_node_id();
diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php
index 6d42fdcec3..21c893125d 100644
--- a/src/view/phui/calendar/PHUICalendarDayView.php
+++ b/src/view/phui/calendar/PHUICalendarDayView.php
@@ -1,12 +1,17 @@
events[] = $event;
@@ -21,7 +26,16 @@ final class PHUICalendarDayView extends AphrontView {
return $this->browseURI;
}
- public function __construct($year, $month, $day = null) {
+ public function __construct(
+ $range_start,
+ $range_end,
+ $year,
+ $month,
+ $day = null) {
+
+ $this->rangeStart = $range_start;
+ $this->rangeEnd = $range_end;
+
$this->day = $day;
$this->month = $month;
$this->year = $year;
@@ -32,53 +46,91 @@ final class PHUICalendarDayView extends AphrontView {
$hours = $this->getHoursOfDay();
$hourly_events = array();
- $rows = array();
- // sort events into buckets by their start time
- // pretend no events overlap
+ $first_event_hour = null;
+
+ $all_day_events = $this->getAllDayEvents();
+ $today_all_day_events = array();
+
+ $day_start = $this->getDateTime();
+ $day_end = id(clone $day_start)->modify('+1 day');
+
+ $day_start_epoch = $day_start->format('U');
+ $day_end_epoch = $day_end->format('U') - 1;
+
+ foreach ($all_day_events as $all_day_event) {
+ $all_day_start = $all_day_event->getEpochStart();
+ $all_day_end = $all_day_event->getEpochEnd();
+
+ if ($all_day_start < $day_end_epoch && $all_day_end > $day_start_epoch) {
+ $today_all_day_events[] = $all_day_event;
+ }
+ }
+
foreach ($hours as $hour) {
- $events = array();
+ $current_hour_events = array();
$hour_start = $hour->format('U');
$hour_end = id(clone $hour)->modify('+1 hour')->format('U');
+
foreach ($this->events as $event) {
- if ($event->getEpochStart() >= $hour_start
- && $event->getEpochStart() < $hour_end) {
- $events[] = $event;
+ if ($event->getIsAllDay()) {
+ continue;
+ }
+ if (($hour == $day_start &&
+ $event->getEpochStart() <= $hour_start &&
+ $event->getEpochEnd() > $day_start_epoch) ||
+ ($event->getEpochStart() >= $hour_start
+ && $event->getEpochStart() < $hour_end)) {
+ $current_hour_events[] = $event;
+ $this->todayEvents[] = $event;
}
}
- $count_events = count($events);
- $n = 0;
- foreach ($events as $event) {
- $event_start = $event->getEpochStart();
- $event_end = $event->getEpochEnd();
+ foreach ($current_hour_events as $event) {
+ $day_start_epoch = $this->getDateTime()->format('U');
+ $event_start = max($event->getEpochStart(), $day_start_epoch);
+ $event_end = min($event->getEpochEnd(), $day_end_epoch);
- $top = ((($event_start - $hour_start) / ($hour_end - $hour_start))
- * 100).'%';
- $height = ((($event_end - $event_start) / ($hour_end - $hour_start))
- * 100).'%';
+ $top = (($event_start - $hour_start) / ($hour_end - $hour_start))
+ * 100;
+ $top = max(0, $top);
+
+ $height = (($event_end - $event_start) / ($hour_end - $hour_start))
+ * 100;
+ $height = min(2400, $height);
+
+ if ($first_event_hour === null) {
+ $first_event_hour = $hour;
+ }
$hourly_events[$event->getEventID()] = array(
'hour' => $hour,
'event' => $event,
'offset' => '0',
'width' => '100%',
- 'top' => $top,
- 'height' => $height,
+ 'top' => $top.'%',
+ 'height' => $height.'%',
);
-
- $n++;
}
}
- $clusters = $this->findClusters();
+ $clusters = $this->findTodayClusters();
foreach ($clusters as $cluster) {
$hourly_events = $this->updateEventsFromCluster(
$cluster,
$hourly_events);
}
- // actually construct table
+ $rows = array();
+
foreach ($hours as $hour) {
+ $early_hours = array(8);
+ if ($first_event_hour) {
+ $early_hours[] = $first_event_hour->format('G');
+ }
+ if ($hour->format('G') < min($early_hours)) {
+ continue;
+ }
+
$drawn_hourly_events = array();
$cell_time = phutil_tag(
'td',
@@ -87,6 +139,7 @@ final class PHUICalendarDayView extends AphrontView {
foreach ($hourly_events as $hourly_event) {
if ($hourly_event['hour'] == $hour) {
+
$drawn_hourly_events[] = $this->drawEvent(
$hourly_event['event'],
$hourly_event['offset'],
@@ -111,17 +164,23 @@ final class PHUICalendarDayView extends AphrontView {
$table = phutil_tag(
'table',
array('class' => 'phui-calendar-day-view'),
- array(
- '',
- $rows,
- ));
+ $rows);
+
+ $all_day_event_box = new PHUIBoxView();
+ foreach ($today_all_day_events as $all_day_event) {
+ $all_day_event_box->appendChild(
+ $this->drawAllDayEvent($all_day_event));
+ }
$header = $this->renderDayViewHeader();
$sidebar = $this->renderSidebar();
+ $warnings = $this->getQueryRangeWarning();
$table_box = id(new PHUIObjectBoxView())
->setHeader($header)
+ ->appendChild($all_day_event_box)
->appendChild($table)
+ ->setFormErrors($warnings)
->setFlush(true);
$layout = id(new AphrontMultiColumnView())
@@ -138,33 +197,83 @@ final class PHUICalendarDayView extends AphrontView {
$layout);
}
+ private function getAllDayEvents() {
+ $all_day_events = array();
+
+ foreach ($this->events as $event) {
+ if ($event->getIsAllDay()) {
+ $all_day_events[] = $event;
+ }
+ }
+
+ $all_day_events = array_values(msort($all_day_events, 'getEpochStart'));
+ return $all_day_events;
+ }
+
+ private function getQueryRangeWarning() {
+ $errors = array();
+
+ $range_start_epoch = $this->rangeStart->getEpoch();
+ $range_end_epoch = $this->rangeEnd->getEpoch();
+
+ $day_start = $this->getDateTime();
+ $day_end = id(clone $day_start)->modify('+1 day');
+
+ $day_start = $day_start->format('U');
+ $day_end = $day_end->format('U') - 1;
+
+ if (($range_start_epoch != null &&
+ $range_start_epoch < $day_end &&
+ $range_start_epoch > $day_start) ||
+ ($range_end_epoch != null &&
+ $range_end_epoch < $day_end &&
+ $range_end_epoch > $day_start)) {
+ $errors[] = pht('Part of the day is out of range');
+ }
+
+ if (($this->rangeEnd->getEpoch() != null &&
+ $this->rangeEnd->getEpoch() < $day_start) ||
+ ($this->rangeStart->getEpoch() != null &&
+ $this->rangeStart->getEpoch() > $day_end)) {
+ $errors[] = pht('Day is out of query range');
+ }
+ return $errors;
+ }
+
private function renderSidebar() {
$this->events = msort($this->events, 'getEpochStart');
$week_of_boxes = $this->getWeekOfBoxes();
$filled_boxes = array();
- foreach ($week_of_boxes as $weekly_box) {
- $start = $weekly_box['start'];
- $end = id(clone $start)->modify('+1 day');
+ foreach ($week_of_boxes as $day_box) {
+ $box_start = $day_box['start'];
+ $box_end = id(clone $box_start)->modify('+1 day');
+
+ $box_start = $box_start->format('U');
+ $box_end = $box_end->format('U');
$box_events = array();
foreach ($this->events as $event) {
- if ($event->getEpochStart() >= $start->format('U') &&
- $event->getEpochStart() < $end->format('U')) {
+ $event_start = $event->getEpochStart();
+ $event_end = $event->getEpochEnd();
+
+ if ($event_start < $box_end && $event_end > $box_start) {
$box_events[] = $event;
}
}
+
$filled_boxes[] = $this->renderSidebarBox(
$box_events,
- $weekly_box['title']);
+ $day_box['title']);
}
return $filled_boxes;
}
private function renderSidebarBox($events, $title) {
- $widget = new PHUICalendarWidgetView();
+ $widget = id(new PHUICalendarWidgetView())
+ ->addClass('calendar-day-view-sidebar');
$list = id(new PHUICalendarListView())
->setUser($this->user);
@@ -172,7 +281,8 @@ final class PHUICalendarDayView extends AphrontView {
if (count($events) == 0) {
$list->showBlankState(true);
} else {
- foreach ($events as $event) {
+ $sorted_events = msort($events, 'getEpochStart');
+ foreach ($sorted_events as $event) {
$list->addEvent($event);
}
}
@@ -216,8 +326,6 @@ final class PHUICalendarDayView extends AphrontView {
private function renderDayViewHeader() {
$button_bar = null;
-
- // check for a browseURI, which means we need "fancy" prev / next UI
$uri = $this->getBrowseURI();
if ($uri) {
list($prev_year, $prev_month, $prev_day) = $this->getPrevDay();
@@ -251,9 +359,8 @@ final class PHUICalendarDayView extends AphrontView {
}
- $day_of_week = $this->getDayOfWeek();
- $header_text = $this->getDateTime()->format('F j, Y');
- $header_text = $day_of_week.', '.$header_text;
+ $display_day = $this->getDateTime();
+ $header_text = $display_day->format('l, F j, Y');
$header = id(new PHUIHeaderView())
->setHeader($header_text);
@@ -267,7 +374,6 @@ final class PHUICalendarDayView extends AphrontView {
private function updateEventsFromCluster($cluster, $hourly_events) {
$cluster_size = count($cluster);
-
$n = 0;
foreach ($cluster as $cluster_member) {
$event_id = $cluster_member->getEventID();
@@ -284,6 +390,35 @@ final class PHUICalendarDayView extends AphrontView {
return $hourly_events;
}
+ private function drawAllDayEvent(AphrontCalendarEventView $event) {
+ $name = phutil_tag(
+ 'a',
+ array(
+ 'class' => 'day-view-all-day',
+ 'href' => $event->getURI(),
+ ),
+ $event->getName());
+
+ $all_day_label = phutil_tag(
+ 'span',
+ array(
+ 'class' => 'phui-calendar-all-day-label',
+ ),
+ pht('All Day'));
+
+ $div = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phui-calendar-day-event',
+ ),
+ array(
+ $all_day_label,
+ $name,
+ ));
+
+ return $div;
+ }
+
private function drawEvent(
AphrontCalendarEventView $event,
$offset,
@@ -313,12 +448,6 @@ final class PHUICalendarDayView extends AphrontView {
return $div;
}
- private function getDayOfWeek() {
- $date = $this->getDateTime();
- $day_of_week = $date->format('l');
- return $day_of_week;
- }
-
// returns DateTime of each hour in the day
private function getHoursOfDay() {
$included_datetimes = array();
@@ -375,14 +504,14 @@ final class PHUICalendarDayView extends AphrontView {
return $date;
}
- private function findClusters() {
- $events = msort($this->events, 'getEpochStart');
+ private function findTodayClusters() {
+ $events = msort($this->todayEvents, 'getEpochStart');
$clusters = array();
foreach ($events as $event) {
$destination_cluster_key = null;
- $event_start = $event->getEpochStart();
- $event_end = $event->getEpochEnd();
+ $event_start = $event->getEpochStart() - (30 * 60);
+ $event_end = $event->getEpochEnd() + (30 * 60);
foreach ($clusters as $key => $cluster) {
foreach ($cluster as $clustered_event) {
diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php
index fd6a7a0e73..fc40d87745 100644
--- a/src/view/phui/calendar/PHUICalendarListView.php
+++ b/src/view/phui/calendar/PHUICalendarListView.php
@@ -22,7 +22,7 @@ final class PHUICalendarListView extends AphrontTagView {
protected function getTagAttributes() {
require_celerity_resource('phui-calendar-css');
require_celerity_resource('phui-calendar-list-css');
- return array('class' => 'phui-calendar-day-list');
+ return array('class' => 'phui-calendar-event-list');
}
protected function getTagContent() {
@@ -30,27 +30,28 @@ final class PHUICalendarListView extends AphrontTagView {
return '';
}
- $events = msort($this->events, 'getEpochStart');
-
$singletons = array();
$allday = false;
- foreach ($events as $event) {
+ foreach ($this->events as $event) {
$color = $event->getColor();
+ $start_epoch = $event->getEpochStart();
- if ($event->getAllDay()) {
+ if ($event->getIsAllDay()) {
$timelabel = pht('All Day');
+ $dot = null;
} else {
$timelabel = phabricator_time(
$event->getEpochStart(),
$this->getUser());
+
+ $dot = phutil_tag(
+ 'span',
+ array(
+ 'class' => 'phui-calendar-list-dot',
+ ),
+ '');
}
- $dot = phutil_tag(
- 'span',
- array(
- 'class' => 'phui-calendar-list-dot',
- ),
- '');
$title = phutil_tag(
'span',
array(
@@ -64,10 +65,15 @@ final class PHUICalendarListView extends AphrontTagView {
),
$timelabel);
+ $class = 'phui-calendar-list-item phui-calendar-'.$color;
+ if ($event->getIsAllDay()) {
+ $class = $class.' all-day';
+ }
+
$singletons[] = phutil_tag(
'li',
array(
- 'class' => 'phui-calendar-list-item phui-calendar-'.$color,
+ 'class' => $class,
),
array(
$dot,
@@ -112,11 +118,13 @@ final class PHUICalendarListView extends AphrontTagView {
$description = pht('(%s)', $event->getName());
}
+ $class = 'phui-calendar-item-link';
+
$anchor = javelin_tag(
'a',
array(
'sigil' => 'has-tooltip',
- 'class' => 'phui-calendar-item-link',
+ 'class' => $class,
'href' => '/E'.$event->getEventID(),
'meta' => array(
'tip' => $tip,
diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php
index 089d98144c..7eb35f76a5 100644
--- a/src/view/phui/calendar/PHUICalendarMonthView.php
+++ b/src/view/phui/calendar/PHUICalendarMonthView.php
@@ -1,11 +1,12 @@
holidays = mpull($holidays, null, 'getDay');
- return $this;
- }
+ public function __construct(
+ $range_start,
+ $range_end,
+ $month,
+ $year,
+ $day = null) {
+
+ $this->rangeStart = $range_start;
+ $this->rangeEnd = $range_end;
- public function __construct($month, $year, $day = null) {
$this->day = $day;
$this->month = $month;
$this->year = $year;
@@ -48,126 +52,237 @@ final class PHUICalendarMonthView extends AphrontView {
public function render() {
if (empty($this->user)) {
- throw new Exception('Call setUser() before render()!');
+ throw new PhutilInvalidStateException('setUser');
}
$events = msort($this->events, 'getEpochStart');
-
$days = $this->getDatesInMonth();
+ $cell_lists = array();
+ $empty_cell = array(
+ 'list' => null,
+ 'date' => null,
+ 'uri' => null,
+ 'count' => 0,
+ 'class' => null,
+ );
+
require_celerity_resource('phui-calendar-month-css');
$first = reset($days);
+ $start_of_week = 0;
+
$empty = $first->format('w');
- $markup = array();
-
- $empty_box = phutil_tag(
- 'div',
- array('class' => 'phui-calendar-day phui-calendar-empty'),
- '');
-
for ($ii = 0; $ii < $empty; $ii++) {
- $markup[] = $empty_box;
+ $cell_lists[] = $empty_cell;
}
- $show_events = array();
-
foreach ($days as $day) {
$day_number = $day->format('j');
- $holiday = idx($this->holidays, $day->format('Y-m-d'));
- $class = 'phui-calendar-day';
+ $class = 'phui-calendar-month-day';
$weekday = $day->format('w');
- if ($day_number == $this->day) {
- $class .= ' phui-calendar-today';
- }
-
- if ($holiday || $weekday == 0 || $weekday == 6) {
- $class .= ' phui-calendar-not-work-day';
- }
-
$day->setTime(0, 0, 0);
- $epoch_start = $day->format('U');
-
- $day->modify('+1 day');
- $epoch_end = $day->format('U');
-
- if ($weekday == 0) {
- $show_events = array();
- } else {
- $show_events = array_fill_keys(
- array_keys($show_events),
- phutil_tag_div(
- 'phui-calendar-event phui-calendar-event-empty',
- "\xC2\xA0")); //
- }
+ $day_start_epoch = $day->format('U');
+ $day_end_epoch = id(clone $day)->modify('+1 day')->format('U');
$list_events = array();
+ $all_day_events = array();
+
foreach ($events as $event) {
- if ($event->getEpochStart() >= $epoch_end) {
- // This list is sorted, so we can stop looking.
+ if ($event->getEpochStart() >= $day_end_epoch) {
break;
}
- if ($event->getEpochStart() < $epoch_end &&
- $event->getEpochEnd() > $epoch_start) {
- $list_events[] = $event;
+ if ($event->getEpochStart() < $day_end_epoch &&
+ $event->getEpochEnd() > $day_start_epoch) {
+ if ($event->getIsAllDay()) {
+ $all_day_events[] = $event;
+ } else {
+ $list_events[] = $event;
+ }
}
}
$list = new PHUICalendarListView();
$list->setUser($this->user);
+ foreach ($all_day_events as $item) {
+ $list->addEvent($item);
+ }
foreach ($list_events as $item) {
$list->addEvent($item);
}
- $holiday_markup = null;
- if ($holiday) {
- $name = $holiday->getName();
- $holiday_markup = phutil_tag(
- 'div',
- array(
- 'class' => 'phui-calendar-holiday',
- 'title' => $name,
- ),
- $name);
- }
+ $uri = $this->getBrowseURI();
+ $uri = $uri.$day->format('Y').'/'.
+ $day->format('m').'/'.
+ $day->format('d').'/';
- $markup[] = phutil_tag_div(
- $class,
- array(
- phutil_tag_div('phui-calendar-date-number', $day_number),
- $holiday_markup,
- $list,
- ));
+ $cell_lists[] = array(
+ 'list' => $list,
+ 'date' => $day,
+ 'uri' => $uri,
+ 'count' => count($all_day_events) + count($list_events),
+ 'class' => $class,
+ );
}
- $table = array();
- $rows = array_chunk($markup, 7);
- foreach ($rows as $row) {
+ $rows = array();
+ $cell_lists_by_week = array_chunk($cell_lists, 7);
+
+ foreach ($cell_lists_by_week as $week_of_cell_lists) {
$cells = array();
- while (count($row) < 7) {
- $row[] = $empty_box;
+ while (count($week_of_cell_lists) < 7) {
+ $week_of_cell_lists[] = $empty_cell;
}
- $j = 0;
- foreach ($row as $cell) {
- if ($j == 0) {
- $cells[] = phutil_tag(
- 'td',
- array(
- 'class' => 'phui-calendar-month-weekstart',
- ),
- $cell);
- } else {
- $cells[] = phutil_tag('td', array(), $cell);
- }
- $j++;
+ foreach ($week_of_cell_lists as $cell_list) {
+ $cells[] = $this->getEventListCell($cell_list);
}
- $table[] = phutil_tag('tr', array(), $cells);
+ $rows[] = phutil_tag('tr', array(), $cells);
+
+ $cells = array();
+ foreach ($week_of_cell_lists as $cell_list) {
+ $cells[] = $this->getDayNumberCell($cell_list);
+ }
+ $rows[] = phutil_tag('tr', array(), $cells);
}
- $header = phutil_tag(
+ $header = $this->getDayNamesHeader();
+
+ $table = phutil_tag(
+ 'table',
+ array('class' => 'phui-calendar-view'),
+ array(
+ $header,
+ $rows,
+ ));
+
+ $warnings = $this->getQueryRangeWarning();
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeader($this->renderCalendarHeader($first))
+ ->appendChild($table)
+ ->setFormErrors($warnings);
+ if ($this->error) {
+ $box->setInfoView($this->error);
+
+ }
+
+ return $box;
+ }
+
+ private function getEventListCell($event_list) {
+ $list = $event_list['list'];
+ $class = $event_list['class'];
+ $uri = $event_list['uri'];
+ $count = $event_list['count'];
+
+ $event_count_badge = $this->getEventCountBadge($count);
+ $cell_day_secret_link = $this->getHiddenDayLink($uri);
+
+ $cell_data_div = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phui-calendar-month-cell-div',
+ ),
+ array(
+ $cell_day_secret_link,
+ $event_count_badge,
+ $list,
+ ));
+
+ return phutil_tag(
+ 'td',
+ array(
+ 'class' => 'phui-calendar-month-event-list '.$class,
+ ),
+ $cell_data_div);
+ }
+
+ private function getDayNumberCell($event_list) {
+ $class = $event_list['class'];
+ $date = $event_list['date'];
+ $cell_day_secret_link = null;
+
+ if ($date) {
+ $uri = $event_list['uri'];
+ $cell_day_secret_link = $this->getHiddenDayLink($uri);
+
+ $cell_day = phutil_tag(
+ 'a',
+ array(
+ 'class' => 'phui-calendar-date-number',
+ 'href' => $uri,
+ ),
+ $date->format('j'));
+ } else {
+ $cell_day = null;
+ }
+
+ if ($date && $date->format('j') == $this->day) {
+ $today_class = 'phui-calendar-today-slot phui-calendar-today';
+ } else {
+ $today_class = 'phui-calendar-today-slot';
+ }
+
+ $today_slot = phutil_tag (
+ 'div',
+ array(
+ 'class' => $today_class,
+ ),
+ null);
+
+ $cell_div = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phui-calendar-month-cell-div',
+ ),
+ array(
+ $cell_day_secret_link,
+ $cell_day,
+ $today_slot,
+ ));
+
+ return phutil_tag(
+ 'td',
+ array(
+ 'class' => 'phui-calendar-date-number-container '.$class,
+ ),
+ $cell_div);
+ }
+
+ private function getEventCountBadge($count) {
+ $event_count = null;
+ if ($count > 0) {
+ $event_count = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phui-calendar-month-count-badge',
+ ),
+ $count);
+ }
+
+ return phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phui-calendar-month-event-count',
+ ),
+ $event_count);
+ }
+
+ private function getHiddenDayLink($uri) {
+ return phutil_tag(
+ 'a',
+ array(
+ 'class' => 'phui-calendar-month-secret-link',
+ 'href' => $uri,
+ ),
+ null);
+ }
+
+ private function getDayNamesHeader() {
+ return phutil_tag(
'tr',
array('class' => 'phui-calendar-day-of-week-header'),
array(
@@ -179,24 +294,6 @@ final class PHUICalendarMonthView extends AphrontView {
phutil_tag('th', array(), pht('Fri')),
phutil_tag('th', array(), pht('Sat')),
));
-
- $table = phutil_tag(
- 'table',
- array('class' => 'phui-calendar-view'),
- array(
- $header,
- phutil_implode_html("\n", $table),
- ));
-
- $box = id(new PHUIObjectBoxView())
- ->setHeader($this->renderCalendarHeader($first))
- ->appendChild($table);
- if ($this->error) {
- $box->setInfoView($this->error);
-
- }
-
- return $box;
}
private function renderCalendarHeader(DateTime $date) {
@@ -250,6 +347,37 @@ final class PHUICalendarMonthView extends AphrontView {
return $header;
}
+ private function getQueryRangeWarning() {
+ $errors = array();
+
+ $range_start_epoch = $this->rangeStart->getEpoch();
+ $range_end_epoch = $this->rangeEnd->getEpoch();
+
+ $month_start = $this->getDateTime();
+ $month_end = id(clone $month_start)->modify('+1 month');
+
+ $month_start = $month_start->format('U');
+ $month_end = $month_end->format('U') - 1;
+
+ if (($range_start_epoch != null &&
+ $range_start_epoch < $month_end &&
+ $range_start_epoch > $month_start) ||
+ ($range_end_epoch != null &&
+ $range_end_epoch < $month_end &&
+ $range_end_epoch > $month_start)) {
+ $errors[] = pht('Part of the month is out of range');
+ }
+
+ if (($this->rangeEnd->getEpoch() != null &&
+ $this->rangeEnd->getEpoch() < $month_start) ||
+ ($this->rangeStart->getEpoch() != null &&
+ $this->rangeStart->getEpoch() > $month_end)) {
+ $errors[] = pht('Month is out of query range');
+ }
+
+ return $errors;
+ }
+
private function getNextYearAndMonth() {
$next = $this->getDateTime();
$next->modify('+1 month');
@@ -282,22 +410,41 @@ final class PHUICalendarMonthView extends AphrontView {
$month = $this->month;
$year = $this->year;
- // Get the year and month numbers of the following month, so we can
- // determine when this month ends.
list($next_year, $next_month) = $this->getNextYearAndMonth();
-
$end_date = new DateTime("{$next_year}-{$next_month}-01", $timezone);
- $end_epoch = $end_date->format('U');
+
+ $start_of_week = 0;
+ $end_of_week = 6 - $start_of_week;
+ $days_in_month = id(clone $end_date)->modify('-1 day')->format('d');
+
+ $first_month_day_date = new DateTime("{$year}-{$month}-01", $timezone);
+ $last_month_day_date = id(clone $end_date)->modify('-1 day');
+
+ $first_weekday_of_month = $first_month_day_date->format('w');
+ $last_weekday_of_month = $last_month_day_date->format('w');
+
+ $num_days_display = $days_in_month;
+ if ($start_of_week < $first_weekday_of_month) {
+ $num_days_display += $first_weekday_of_month;
+ }
+ if ($end_of_week > $last_weekday_of_month) {
+ $num_days_display += (6 - $last_weekday_of_month);
+ $end_date->modify('+'.(6 - $last_weekday_of_month).' days');
+ }
$days = array();
- for ($day = 1; $day <= 31; $day++) {
- $day_date = new DateTime("{$year}-{$month}-{$day}", $timezone);
+ $day_date = id(clone $first_month_day_date)
+ ->modify('-'.$first_weekday_of_month.' days');
+
+ for ($day = 1; $day <= $num_days_display; $day++) {
$day_epoch = $day_date->format('U');
+ $end_epoch = $end_date->format('U');
if ($day_epoch >= $end_epoch) {
break;
} else {
- $days[] = $day_date;
+ $days[] = clone $day_date;
}
+ $day_date->modify('+1 day');
}
return $days;
diff --git a/src/view/widget/hovercard/PhabricatorHovercardView.php b/src/view/widget/hovercard/PhabricatorHovercardView.php
index 40cb6fa30e..58131a6a6b 100644
--- a/src/view/widget/hovercard/PhabricatorHovercardView.php
+++ b/src/view/widget/hovercard/PhabricatorHovercardView.php
@@ -62,7 +62,7 @@ final class PhabricatorHovercardView extends AphrontView {
public function render() {
if (!$this->handle) {
- throw new Exception('Call setObjectHandle() before calling render()!');
+ throw new PhutilInvalidStateException('setObjectHandle');
}
$handle = $this->handle;
diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php
index 3945049ffb..30e8f3c937 100644
--- a/support/PhabricatorStartup.php
+++ b/support/PhabricatorStartup.php
@@ -255,7 +255,7 @@ final class PhabricatorStartup {
static $initialized;
if (!$initialized) {
declare(ticks=1);
- register_tick_function(array('PhabricatorStartup', 'onDebugTick'));
+ register_tick_function(array(__CLASS__, 'onDebugTick'));
}
}
@@ -647,7 +647,7 @@ final class PhabricatorStartup {
// populated into $_POST, but it wasn't.
$config = ini_get('post_max_size');
- PhabricatorStartup::didFatal(
+ self::didFatal(
"As received by the server, this request had a nonzero content length ".
"but no POST data.\n\n".
"Normally, this indicates that it exceeds the 'post_max_size' setting ".
diff --git a/webroot/rsrc/css/application/auth/auth.css b/webroot/rsrc/css/application/auth/auth.css
index 219c8795a3..07eaccd46c 100644
--- a/webroot/rsrc/css/application/auth/auth.css
+++ b/webroot/rsrc/css/application/auth/auth.css
@@ -26,11 +26,21 @@
.auth-account-view {
background-color: #fff;
border: 1px solid {$lightblueborder};
- background-repeat: no-repeat;
- background-position: 4px 4px;
- padding: 4px 4px 4px 62px;
- min-height: 50px;
border-radius: 2px;
+ min-height: 50px;
+ position: relative;
+ padding: 4px 4px 4px 64px;
+}
+
+.auth-account-view-profile-image {
+ width: 50px;
+ height: 50px;
+ top: 4px;
+ left: 4px;
+
+ background-repeat: no-repeat;
+ background-size: 100%;
+ position: absolute;
}
.auth-account-view-name {
diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css
index d31594852c..93a9364412 100644
--- a/webroot/rsrc/css/application/base/standard-page-view.css
+++ b/webroot/rsrc/css/application/base/standard-page-view.css
@@ -114,27 +114,6 @@ a.handle-disabled {
margin: 2px 2px -2px 0;
}
-.main-page-frame {
- position: absolute;
- top: 0;
- bottom: 0;
- right: 0;
- left: 0;
- overflow: hidden;
-}
-
-.phabricator-standard-page {
- /* If we don't activate JX.Scrollbar because the default scrollbars are
- satisfactory, make sure the page still has sensible behavior. These
- settings will be overwritten by .jx-scrollbar-frame if JX.Scrollbar
- activates. */
- position: relative;
- height: 100%;
- overflow-y: scroll;
-
- -webkit-overflow-scrolling: touch;
-}
-
.jx-scrollbar-frame {
position: relative;
overflow: hidden;
diff --git a/webroot/rsrc/css/application/conduit/conduit-api.css b/webroot/rsrc/css/application/conduit/conduit-api.css
new file mode 100644
index 0000000000..fbe577e908
--- /dev/null
+++ b/webroot/rsrc/css/application/conduit/conduit-api.css
@@ -0,0 +1,16 @@
+/**
+ * @provides conduit-api-css
+ */
+.conduit-api-example-code {
+ margin: 16px;
+ white-space: pre;
+ color: {$darkgreytext};
+}
+
+.conduit-api-example-code strong {
+ color: {$red};
+}
+
+.conduit-api-example-code strong.real {
+ color: {$blue};
+}
diff --git a/webroot/rsrc/css/application/conpherence/durable-column.css b/webroot/rsrc/css/application/conpherence/durable-column.css
index 324d76c27c..1dbb829565 100644
--- a/webroot/rsrc/css/application/conpherence/durable-column.css
+++ b/webroot/rsrc/css/application/conpherence/durable-column.css
@@ -2,10 +2,22 @@
* @provides conpherence-durable-column-view
*/
-.with-durable-column .phabricator-standard-page {
+.with-durable-column .phabricator-standard-page-body {
margin-right: 300px;
}
+.with-durable-margin .phabricator-standard-page-body {
+ margin-right: 312px;
+}
+
+.with-durable-column .phabricator-main-menu {
+ padding-right: 304px;
+}
+
+.with-durable-margin .phabricator-main-menu {
+ padding-right: 316px;
+}
+
.with-durable-column
.phabricator-global-upload-instructions {
font-size: 28px;
@@ -20,8 +32,12 @@
right: 300px;
}
+.with-durable-margin .global-upload-mask {
+ right: 312px;
+}
+
.conpherence-durable-column {
- position: absolute;
+ position: fixed;
top: 0;
bottom: 0;
right: 0;
@@ -29,6 +45,11 @@
background: #fff;
}
+.with-durable-margin .conpherence-durable-column {
+ right: 12px;
+ box-shadow: 1px 0px 2px rgba(0, 0, 0, 0.10);
+}
+
.conpherence-durable-column .loading-mask {
position: absolute;
top: 90px;
@@ -126,6 +147,10 @@
border-left: 1px solid {$lightblueborder};
}
+.with-durable-margin .conpherence-durable-column-body {
+ border-right: 1px solid {$lightblueborder};
+}
+
.conpherence-durable-column-main {
position: absolute;
top: 46px;
diff --git a/webroot/rsrc/css/application/conpherence/message-pane.css b/webroot/rsrc/css/application/conpherence/message-pane.css
index b450703d96..41815d086f 100644
--- a/webroot/rsrc/css/application/conpherence/message-pane.css
+++ b/webroot/rsrc/css/application/conpherence/message-pane.css
@@ -113,6 +113,14 @@
right: 241px;
}
+.conpherence-message-pane .phui-form-view.login-to-participate {
+ height: 28px;
+}
+
+.conpherence-message-pane .login-to-participate a.button {
+ float: right;
+}
+
.conpherence-message-pane .aphront-form-control-submit button,
.conpherence-message-pane .aphront-form-control-submit a.button {
margin-top: 6px;
@@ -154,7 +162,7 @@
.conpherence-message-pane .conpherence-transaction-view {
padding: 2px 0px;
margin: 4px 12px;
- background-size: 35px;
+ background-size: 100%;
min-height: auto;
}
diff --git a/webroot/rsrc/css/application/conpherence/notification.css b/webroot/rsrc/css/application/conpherence/notification.css
index 75403f991d..e5bd40274a 100644
--- a/webroot/rsrc/css/application/conpherence/notification.css
+++ b/webroot/rsrc/css/application/conpherence/notification.css
@@ -26,7 +26,7 @@
position: absolute;
width: 30px;
height: 30px;
- background-size: 30px;
+ background-size: 100%;
}
.phabricator-notification .conpherence-menu-item-view
diff --git a/webroot/rsrc/css/application/conpherence/transaction.css b/webroot/rsrc/css/application/conpherence/transaction.css
index d67d31692e..6ebd53b2ee 100644
--- a/webroot/rsrc/css/application/conpherence/transaction.css
+++ b/webroot/rsrc/css/application/conpherence/transaction.css
@@ -25,3 +25,22 @@
color: {$darkbluetext};
font-weight: bold;
}
+
+.conpherence-fulltext-results {
+ margin: 0 8px 8px;
+ background: {$lightgreybackground};
+ border: 1px solid {$lightgreyborder};
+}
+
+.conpherence-fulltext-result {
+ margin: 0 0 1px;
+ padding: 8px;
+}
+
+.conpherence-fulltext-match {
+ background: {$lightyellow};
+}
+
+.conpherence-fulltext-results .epoch-link {
+ float: right;
+}
diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css
index 7333d87994..4bb08ea3df 100644
--- a/webroot/rsrc/css/core/core.css
+++ b/webroot/rsrc/css/core/core.css
@@ -54,6 +54,10 @@ body {
breaks lots of things and prevents you from using landscape to see more
columns in source code views. */
-webkit-text-size-adjust: none;
+
+ /* Prevent content from resizing abruptly when shifting between scrollable
+ and unscrollable pages. */
+ overflow-y: scroll;
}
textarea {
diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css
index 4c2c5d457a..c69638eede 100644
--- a/webroot/rsrc/css/core/z-index.css
+++ b/webroot/rsrc/css/core/z-index.css
@@ -81,7 +81,6 @@
z-index: 5;
}
-.conpherence-durable-column-header,
.phabricator-main-menu {
z-index: 6;
}
@@ -90,10 +89,14 @@
z-index: 6;
}
-.jx-scrollbar-bar {
+.conpherence-durable-column {
z-index: 7;
}
+.jx-scrollbar-bar {
+ z-index: 8;
+}
+
.differential-haunt-mode-1 .differential-add-comment-panel,
.differential-haunt-mode-2 .differential-add-comment-panel {
z-index: 8;
diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css
index bb07f8b8f8..11f4045fe1 100644
--- a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css
+++ b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css
@@ -48,3 +48,25 @@
text-decoration: none;
color: {$greytext};
}
+
+.day-view-all-day {
+ border: 1px solid {$blueborder};
+ height: 12px;
+ margin: 0;
+ display: block;
+ padding: 8px;
+ background-color: {$bluebackground};
+ text-decoration: none;
+ color: {$greytext};
+}
+
+.phui-calendar-day-event + .phui-calendar-day-event .day-view-all-day {
+ border-top-style: none;
+ border-top-width: 0;
+}
+
+.phui-calendar-all-day-label {
+ color: {$greytext};
+ float: right;
+ margin: 8px 8px 0 0;
+}
diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css
index c3abbb131c..3f53e89b25 100644
--- a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css
+++ b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css
@@ -17,52 +17,91 @@ tr.phui-calendar-day-of-week-header th {
}
table.phui-calendar-view td {
- border: 1px solid #dfdfdf;
+ border: solid #dfdfdf;
+ border-width: 1px 1px 0 1px;
width: 14.2857%; /* This is one seventh, approximately. */
}
-table.phui-calendar-view td div.phui-calendar-day {
- min-height: 125px;
+.phui-calendar-month-cell-div {
position: relative;
}
-.phui-calendar-holiday {
- color: {$greytext};
- padding: .5em;
- max-height: 1em;
- overflow: hidden;
+.phui-calendar-month-event-list .phui-calendar-month-cell-div {
+ min-height: 125px;
}
-table.phui-calendar-view td.phui-calendar-month-weekstart {
- border-left: none;
+.device .phui-calendar-month-event-list .phui-calendar-month-cell-div {
+ min-height: 60px;
}
-.phui-calendar-date-number {
+a.phui-calendar-month-secret-link {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ outline: 0;
+}
+
+table.phui-calendar-view tr td:first-child {
+ border-left-width: 0px;
+}
+
+.device table.phui-calendar-view .phui-calendar-event-list {
+ display: none;
+}
+
+.phui-calendar-month-event-count {
+ display: none;
+}
+
+.device .phui-calendar-month-event-count {
+ display: block;
+ text-align: center;
+ padding-top: 12px;
+}
+
+.phui-calendar-month-event-count .phui-calendar-month-count-badge {
+ border: 1px solid {$lightgreyborder};
+ color: {$lightgreytext};
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ text-align: center;
+ vertical-align: middle;
+ padding: 5px 3px 0px 3px;
+ margin: 0 auto;
+}
+
+table.phui-calendar-view a.phui-calendar-date-number {
+ color: {$lightgreytext};
+ padding: 0 4px;
+ display: inline-block;
+ min-width: 16px;
+ text-align: center;
+}
+
+table.phui-calendar-view td.phui-calendar-date-number-container {
font-weight: normal;
color: {$lightgreytext};
- padding: 4px;
- border-color: {$thinblueborder};
- border-style: solid;
- border-width: 0 0 1px 1px;
- position: absolute;
- background: #ffffff;
- width: 16px;
- height: 16px;
- text-align: center;
- top: 0;
- right: 0;
+ border-width: 0 1px 0 1px;
+ text-align: right;
}
.phui-calendar-not-work-day {
background-color: {$lightgreybackground};
}
-.phui-calendar-today {
- background-color: {$lightgreen};
+.phui-calendar-today-slot {
+ display: block;
+ width: 100%;
+ height: 4px;
+ padding: 0;
+ margin: 0;
}
-.phui-calendar-empty {
- background-color: {$greybackground};
+.phui-calendar-today-slot.phui-calendar-today {
+ background-color: {$lightblueborder};
}
.phui-calendar-event-empty {
@@ -71,11 +110,35 @@ table.phui-calendar-view td.phui-calendar-month-weekstart {
}
.phui-calendar-view .phui-calendar-list {
- padding: 8px;
+ padding: 1px;
+}
+
+.phui-calendar-list-item.all-day span {
+ padding: 0;
+ margin: 0;
+}
+
+.phui-calendar-view .phui-calendar-list li.phui-calendar-list-item.all-day {
+ margin: 0;
+ padding: 4px 8px;
+ background-color: {$lightpink};
+ display: block;
+ float: none;
+}
+
+li.phui-calendar-list-item.all-day:first-child {
+ margin-top: 0;
+}
+
+.phui-calendar-view .phui-calendar-list li {
+ margin: 0 8px;
+ display: inline-block;
+ float: left;
+ clear: both;
}
.phui-calendar-view .phui-calendar-list li:first-child {
- margin-right: 16px;
+ margin-top: 8px;
}
.phui-calendar-view .phui-calendar-list-dot {
@@ -95,6 +158,10 @@ table.phui-calendar-view td.phui-calendar-month-weekstart {
word-break: break-word;
}
+li.phui-calendar-list-item.all-day .phui-calendar-list-title a{
+ color: {$pink};
+}
+
.phui-calendar-view .phui-calendar-list-time {
display: none;
}
diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css
index c9026c3980..2f7462cf11 100644
--- a/webroot/rsrc/css/phui/phui-form-view.css
+++ b/webroot/rsrc/css/phui/phui-form-view.css
@@ -453,6 +453,10 @@ properly, and submit values. */
opacity: 0.5;
}
+.aphront-form-date-container.no-time .aphront-form-date-time-input{
+ display: none;
+}
+
.login-to-comment {
margin: 12px;
diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css
index 6352681156..a270536711 100644
--- a/webroot/rsrc/css/phui/phui-header-view.css
+++ b/webroot/rsrc/css/phui/phui-header-view.css
@@ -93,6 +93,7 @@ body.device-phone .phui-header-view {
.phui-header-image {
display: inline-block;
background-repeat: no-repeat;
+ background-size: 100%;
border: 2px solid white;
width: 50px;
height: 50px;
diff --git a/webroot/rsrc/css/phui/phui-object-item-list-view.css b/webroot/rsrc/css/phui/phui-object-item-list-view.css
index a8ba8d5c01..a6251754ef 100644
--- a/webroot/rsrc/css/phui/phui-object-item-list-view.css
+++ b/webroot/rsrc/css/phui/phui-object-item-list-view.css
@@ -545,7 +545,7 @@ ul.phui-object-item-icons {
.phui-object-item-image {
width: 40px;
height: 40px;
- background-size: 40px;
+ background-size: 100%;
margin: 6px;
position: absolute;
background-color: {$lightbluebackground};
diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css
index b82c9385a7..d35a5458ee 100644
--- a/webroot/rsrc/css/phui/phui-timeline-view.css
+++ b/webroot/rsrc/css/phui/phui-timeline-view.css
@@ -92,6 +92,7 @@
.phui-timeline-image {
background-repeat: no-repeat;
+ background-size: 100%;
position: absolute;
border-radius: 3px;
}
diff --git a/webroot/rsrc/externals/javelin/lib/Scrollbar.js b/webroot/rsrc/externals/javelin/lib/Scrollbar.js
index 684fb4ce08..7596939581 100644
--- a/webroot/rsrc/externals/javelin/lib/Scrollbar.js
+++ b/webroot/rsrc/externals/javelin/lib/Scrollbar.js
@@ -100,6 +100,7 @@ JX.install('Scrollbar', {
statics: {
_controlWidth: null,
+
/**
* Compute the width of the browser's scrollbar control, in pixels.
*/
@@ -118,8 +119,35 @@ JX.install('Scrollbar', {
}
return self._controlWidth;
+ },
+
+
+ /**
+ * Get the margin width required to avoid double scrollbars.
+ *
+ * For most browsers which render a real scrollbar control, this is 0.
+ * Adjacent elements may touch the edge of the content directly without
+ * overlapping.
+ *
+ * On OSX with a trackpad, scrollbars are only drawn when content is
+ * scrolled. Content panes with internal scrollbars may overlap adjacent
+ * scrollbars if they are not laid out with a margin.
+ *
+ * @return int Control margin width in pixels.
+ */
+ getScrollbarControlMargin: function() {
+ var self = JX.Scrollbar;
+
+ // If this browser and OS don't render a real scrollbar control, we
+ // need to leave a margin. Generally, this is OSX with no mouse attached.
+ if (self._getScrollbarControlWidth() === 0) {
+ return 12;
+ }
+
+ return 0;
}
+
},
members: {
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/default.p100.png b/webroot/rsrc/image/icon/fatcow/thumbnails/default.p100.png
deleted file mode 100644
index f713c2398b..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/default.p100.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/default160x120.png b/webroot/rsrc/image/icon/fatcow/thumbnails/default160x120.png
deleted file mode 100644
index 16d6fd4f90..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/default160x120.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/default280x210.png b/webroot/rsrc/image/icon/fatcow/thumbnails/default280x210.png
deleted file mode 100644
index 7288c81954..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/default280x210.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/default60x45.png b/webroot/rsrc/image/icon/fatcow/thumbnails/default60x45.png
deleted file mode 100644
index 145ea1eb63..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/default60x45.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/image.p100.png b/webroot/rsrc/image/icon/fatcow/thumbnails/image.p100.png
deleted file mode 100644
index f5fa35ab08..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/image.p100.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/image160x120.png b/webroot/rsrc/image/icon/fatcow/thumbnails/image160x120.png
deleted file mode 100644
index 90cc11c02d..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/image160x120.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/image280x210.png b/webroot/rsrc/image/icon/fatcow/thumbnails/image280x210.png
deleted file mode 100644
index efdf733f8e..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/image280x210.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/image60x45.png b/webroot/rsrc/image/icon/fatcow/thumbnails/image60x45.png
deleted file mode 100644
index 9077e69586..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/image60x45.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf.p100.png b/webroot/rsrc/image/icon/fatcow/thumbnails/pdf.p100.png
deleted file mode 100644
index ad3a39b490..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf.p100.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf160x120.png b/webroot/rsrc/image/icon/fatcow/thumbnails/pdf160x120.png
deleted file mode 100644
index 20f08f955b..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf160x120.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf280x210.png b/webroot/rsrc/image/icon/fatcow/thumbnails/pdf280x210.png
deleted file mode 100644
index 8036981aca..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf280x210.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf60x45.png b/webroot/rsrc/image/icon/fatcow/thumbnails/pdf60x45.png
deleted file mode 100644
index 8a16eaf488..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf60x45.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/zip.p100.png b/webroot/rsrc/image/icon/fatcow/thumbnails/zip.p100.png
deleted file mode 100644
index 86fa739b3b..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/zip.p100.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/zip160x120.png b/webroot/rsrc/image/icon/fatcow/thumbnails/zip160x120.png
deleted file mode 100644
index fbe19e59f6..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/zip160x120.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/zip280x210.png b/webroot/rsrc/image/icon/fatcow/thumbnails/zip280x210.png
deleted file mode 100644
index 8db127b282..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/zip280x210.png and /dev/null differ
diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/zip60x45.png b/webroot/rsrc/image/icon/fatcow/thumbnails/zip60x45.png
deleted file mode 100644
index c66416c318..0000000000
Binary files a/webroot/rsrc/image/icon/fatcow/thumbnails/zip60x45.png and /dev/null differ
diff --git a/webroot/rsrc/js/application/aphlict/Aphlict.js b/webroot/rsrc/js/application/aphlict/Aphlict.js
index d4886a12a0..78d0958c81 100644
--- a/webroot/rsrc/js/application/aphlict/Aphlict.js
+++ b/webroot/rsrc/js/application/aphlict/Aphlict.js
@@ -49,6 +49,10 @@ JX.install('Aphlict', {
JX.Leader.call(JX.bind(this, this._begin));
},
+ getSubscriptions: function() {
+ return this._subscriptions;
+ },
+
setSubscriptions: function(subscriptions) {
this._subscriptions = subscriptions;
JX.Leader.broadcast(
diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js
index 042855ee4e..d1ebc33d9f 100644
--- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js
+++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js
@@ -93,23 +93,34 @@ JX.behavior('aphlict-dropdown', function(config, statics) {
if (!data.fromServer) {
return;
}
- var updated = false;
var new_data = data.newResponse.aphlictDropdownData;
- for (var ii = 0; ii < new_data.length; ii++) {
- if (new_data[ii].countType != config.countType) {
- continue;
- }
- if (!new_data[ii].isInstalled) {
- continue;
- }
- updated = true;
- _updateCount(parseInt(new_data[ii].count));
- }
- if (updated) {
- dirty = true;
- }
+ update_counts(new_data);
});
+ JX.Stratcom.listen(
+ 'conpherence-redraw-aphlict',
+ null,
+ function (e) {
+ update_counts(e.getData());
+ });
+
+ function update_counts(new_data) {
+ var updated = false;
+ for (var ii = 0; ii < new_data.length; ii++) {
+ if (new_data[ii].countType != config.countType) {
+ continue;
+ }
+ if (!new_data[ii].isInstalled) {
+ continue;
+ }
+ updated = true;
+ _updateCount(parseInt(new_data[ii].count));
+ }
+ if (updated) {
+ dirty = true;
+ }
+ }
+
function set_visible(menu, icon) {
if (menu) {
statics.visible = {menu: menu, icon: icon};
diff --git a/webroot/rsrc/js/application/calendar/event-all-day.js b/webroot/rsrc/js/application/calendar/event-all-day.js
new file mode 100644
index 0000000000..a8bd7da7a8
--- /dev/null
+++ b/webroot/rsrc/js/application/calendar/event-all-day.js
@@ -0,0 +1,16 @@
+/**
+ * @provides javelin-behavior-event-all-day
+ */
+
+
+JX.behavior('event-all-day', function(config) {
+ var checkbox = JX.$(config.allDayID);
+ JX.DOM.listen(checkbox, 'change', null, function() {
+ var start = JX.$(config.startDateID);
+ var end = JX.$(config.endDateID);
+
+ JX.DOM.alterClass(start, 'no-time', checkbox.checked);
+ JX.DOM.alterClass(end, 'no-time', checkbox.checked);
+ });
+
+});
diff --git a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js
index b1694e19ca..c1c47c5c78 100644
--- a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js
+++ b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js
@@ -4,6 +4,7 @@
* javelin-util
* javelin-stratcom
* javelin-install
+ * javelin-aphlict
* javelin-workflow
* javelin-router
* javelin-behavior-device
@@ -26,9 +27,12 @@ JX.install('ConpherenceThreadManager', {
_loadedThreadID: null,
_loadedThreadPHID: null,
_latestTransactionID: null,
+ _transactionIDMap: null,
+ _transactionCache: null,
_canEditLoadedThread: null,
_updating: null,
_minimalDisplay: false,
+ _messagesRootCallback: JX.bag,
_willLoadThreadCallback: JX.bag,
_didLoadThreadCallback: JX.bag,
_didUpdateThreadCallback: JX.bag,
@@ -81,6 +85,59 @@ JX.install('ConpherenceThreadManager', {
return this;
},
+ _updateTransactionIDMap: function(transactions) {
+ var loaded_id = this.getLoadedThreadID();
+ if (!this._transactionIDMap[loaded_id]) {
+ this._transactionIDMap[this._loadedThreadID] = {};
+ }
+ var loaded_transaction_ids = this._transactionIDMap[loaded_id];
+ var transaction;
+ for (var ii = 0; ii < transactions.length; ii++) {
+ transaction = transactions[ii];
+ loaded_transaction_ids[JX.Stratcom.getData(transaction).id] = 1;
+ }
+ this._transactionIDMap[this._loadedThreadID] = loaded_transaction_ids;
+ return this;
+ },
+
+ _updateTransactionCache: function(transactions) {
+ var transaction;
+ for (var ii = 0; ii < transactions.length; ii++) {
+ transaction = transactions[ii];
+ this._transactionCache[JX.Stratcom.getData(transaction).id] =
+ transaction;
+ }
+ return this;
+ },
+
+ _getLoadedTransactions: function() {
+ var loaded_id = this.getLoadedThreadID();
+ var loaded_tx_ids = JX.keys(this._transactionIDMap[loaded_id]);
+ loaded_tx_ids.sort(function (a, b) {
+ var x = parseFloat(a);
+ var y = parseFloat(b);
+ if (x > y) {
+ return 1;
+ }
+ if (x < y) {
+ return -1;
+ }
+ return 0;
+ });
+ var transactions = [];
+ for (var ii = 0; ii < loaded_tx_ids.length; ii++) {
+ transactions.push(this._transactionCache[loaded_tx_ids[ii]]);
+ }
+ return transactions;
+ },
+
+ _deleteTransactionCaches: function(id) {
+ delete this._transactionCache[id];
+ delete this._transactionIDMap[this._loadedThreadID][id];
+
+ return this;
+ },
+
setCanEditLoadedThread: function(bool) {
this._canEditLoadedThread = bool;
return this;
@@ -98,6 +155,11 @@ JX.install('ConpherenceThreadManager', {
return this;
},
+ setMessagesRootCallback: function(callback) {
+ this._messagesRootCallback = callback;
+ return this;
+ },
+
setWillLoadThreadCallback: function(callback) {
this._willLoadThreadCallback = callback;
return this;
@@ -144,6 +206,10 @@ JX.install('ConpherenceThreadManager', {
},
start: function() {
+
+ this._transactionIDMap = {};
+ this._transactionCache = {};
+
JX.Stratcom.listen(
'aphlict-server-message',
null,
@@ -164,20 +230,66 @@ JX.install('ConpherenceThreadManager', {
// Message event for something we already know about.
return;
}
- // If we're currently updating, wait for the update to complete.
+
// If this notification tells us about a message which is newer than
- // the newest one we know to exist, keep track of it so we can
- // update once the in-flight update finishes.
+ // the newest one we know to exist, update our latest knownID so we
+ // can properly update later.
if (this._updating &&
this._updating.threadPHID == this._loadedThreadPHID) {
if (message.messageID > this._updating.knownID) {
this._updating.knownID = message.messageID;
- return;
+ // We're currently updating, so wait for the update to complete.
+ // this.syncWorkflow has us covered in this case.
+ if (this._updating.active) {
+ return;
+ }
}
}
this._updateThread();
}));
+
+ JX.Stratcom.listen(
+ 'click',
+ 'show-older-messages',
+ JX.bind(this, function(e) {
+ e.kill();
+ var data = e.getNodeData('show-older-messages');
+
+ var node = e.getNode('show-older-messages');
+ JX.DOM.setContent(node, 'Loading...');
+ JX.DOM.alterClass(
+ node,
+ 'conpherence-show-more-messages-loading',
+ true);
+
+ new JX.Workflow(this._getMoreMessagesURI(), data)
+ .setHandler(JX.bind(this, function(r) {
+ this._deleteTransactionCaches(JX.Stratcom.getData(node).id);
+ JX.DOM.remove(node);
+ this._updateTransactions(r);
+ })).start();
+ }));
+ JX.Stratcom.listen(
+ 'click',
+ 'show-newer-messages',
+ JX.bind(this, function(e) {
+ e.kill();
+ var data = e.getNodeData('show-newer-messages');
+ var node = e.getNode('show-newer-messages');
+ JX.DOM.setContent(node, 'Loading...');
+ JX.DOM.alterClass(
+ node,
+ 'conpherence-show-more-messages-loading',
+ true);
+
+ new JX.Workflow(this._getMoreMessagesURI(), data)
+ .setHandler(JX.bind(this, function(r) {
+ this._deleteTransactionCaches(JX.Stratcom.getData(node).id);
+ JX.DOM.remove(node);
+ this._updateTransactions(r);
+ })).start();
+ }));
},
_shouldUpdateDOM: function(r) {
@@ -197,10 +309,25 @@ JX.install('ConpherenceThreadManager', {
return true;
},
- _markUpdated: function(r) {
+ _updateDOM: function(r) {
+ this._updateTransactions(r);
+
this._updating.knownID = r.latest_transaction_id;
this._latestTransactionID = r.latest_transaction_id;
- JX.Stratcom.invoke('notification-panel-update', null, {});
+ JX.Stratcom.invoke(
+ 'conpherence-redraw-aphlict',
+ null,
+ r.aphlictDropdownData);
+ },
+
+ _updateTransactions: function(r) {
+ var new_transactions = JX.$H(r.transactions).getFragment().childNodes;
+ this._updateTransactionIDMap(new_transactions);
+ this._updateTransactionCache(new_transactions);
+
+ var transactions = this._getLoadedTransactions();
+
+ JX.DOM.setContent(this._messagesRootCallback(), transactions);
},
_updateThread: function() {
@@ -208,14 +335,11 @@ JX.install('ConpherenceThreadManager', {
action: 'load',
});
- var uri = '/conpherence/update/' + this._loadedThreadID + '/';
-
- var workflow = new JX.Workflow(uri)
+ var workflow = new JX.Workflow(this._getUpdateURI())
.setData(params)
.setHandler(JX.bind(this, function(r) {
if (this._shouldUpdateDOM(r)) {
- this._markUpdated(r);
-
+ this._updateDOM(r);
this._didUpdateThreadCallback(r);
}
}));
@@ -226,7 +350,8 @@ JX.install('ConpherenceThreadManager', {
syncWorkflow: function(workflow, stage) {
this._updating = {
threadPHID: this._loadedThreadPHID,
- knownID: this._latestTransactionID
+ knownID: this._latestTransactionID,
+ active: true
};
workflow.listen(stage, JX.bind(this, function() {
// TODO - do we need to handle if we switch threads somehow?
@@ -235,6 +360,7 @@ JX.install('ConpherenceThreadManager', {
if (need_sync) {
return this._updateThread();
}
+ this._updating.active = false;
}));
workflow.start();
},
@@ -246,8 +372,7 @@ JX.install('ConpherenceThreadManager', {
.setData(params)
.setHandler(JX.bind(this, function(r) {
if (this._shouldUpdateDOM(r)) {
- this._markUpdated(r);
-
+ this._updateDOM(r);
this._didUpdateWorkflowCallback(r);
}
}));
@@ -271,13 +396,39 @@ JX.install('ConpherenceThreadManager', {
params = this._getParams(params);
var handler = JX.bind(this, function(r) {
+ var client = JX.Aphlict.getInstance();
+ if (client) {
+ var old_subs = client.getSubscriptions();
+ var new_subs = [];
+ for (var ii = 0; ii < old_subs.length; ii++) {
+ if (old_subs[ii] == this._loadedThreadPHID) {
+ continue;
+ } else {
+ new_subs.push(old_subs[ii]);
+ }
+ }
+ new_subs.push(r.threadPHID);
+ client.clearSubscriptions(client.getSubscriptions());
+ client.setSubscriptions(new_subs);
+ }
this._loadedThreadID = r.threadID;
this._loadedThreadPHID = r.threadPHID;
this._latestTransactionID = r.latestTransactionID;
this._canEditLoadedThread = r.canEdit;
- JX.Stratcom.invoke('notification-panel-update', null, {});
+
+ JX.Stratcom.invoke(
+ 'conpherence-redraw-aphlict',
+ null,
+ r.aphlictDropdownData);
this._didLoadThreadCallback(r);
+ var messages_root = this._messagesRootCallback();
+ var messages = JX.DOM.scry(
+ messages_root,
+ 'div',
+ 'conpherence-transaction-view');
+ this._updateTransactionIDMap(messages);
+ this._updateTransactionCache(messages);
if (force_reload) {
JX.Stratcom.invoke('hashchange');
@@ -304,8 +455,7 @@ JX.install('ConpherenceThreadManager', {
var workflow = JX.Workflow.newFromForm(form, params, keep_enabled)
.setHandler(JX.bind(this, function(r) {
if (this._shouldUpdateDOM(r)) {
- this._markUpdated(r);
-
+ this._updateDOM(r);
this._didSendMessageCallback(r);
} else if (r.non_update) {
this._didSendMessageCallback(r, true);
@@ -321,9 +471,8 @@ JX.install('ConpherenceThreadManager', {
var data = e.getNodeData('tag:form');
if (!data.preview) {
- var uri = '/conpherence/update/' + this._loadedThreadID + '/';
data.preview = new JX.PhabricatorShapedRequest(
- uri,
+ this._getUpdateURI(),
JX.bag,
JX.bind(this, function () {
var data = JX.DOM.convertFormToDictionary(form);
@@ -333,6 +482,14 @@ JX.install('ConpherenceThreadManager', {
}));
}
data.preview.trigger();
+ },
+
+ _getUpdateURI: function() {
+ return '/conpherence/update/' + this._loadedThreadID + '/';
+ },
+
+ _getMoreMessagesURI: function() {
+ return '/conpherence/' + this._loadedThreadID + '/';
}
},
diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js
index adcaa382a3..4a9eac1eaf 100644
--- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js
+++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js
@@ -29,9 +29,11 @@ JX.behavior('durable-column', function(config, statics) {
var loadThreadID = null;
var scrollbar = null;
- var columnWidth = 300;
+ var margin = JX.Scrollbar.getScrollbarControlMargin();
+
+ var columnWidth = (300 + margin);
// This is the smallest window size where we'll enable the column.
- var minimumViewportWidth = 768;
+ var minimumViewportWidth = (768 - margin);
var quick = JX.$('phabricator-standard-page-body');
@@ -71,7 +73,15 @@ JX.behavior('durable-column', function(config, statics) {
}
function _drawColumn(visible) {
- JX.DOM.alterClass(document.body, 'with-durable-column', visible);
+ JX.DOM.alterClass(
+ document.body,
+ 'with-durable-column',
+ visible);
+ JX.DOM.alterClass(
+ document.body,
+ 'with-durable-margin',
+ visible && !!margin);
+
var column = _getColumnNode();
if (visible) {
JX.DOM.show(column);
@@ -109,6 +119,9 @@ JX.behavior('durable-column', function(config, statics) {
var threadManager = new JX.ConpherenceThreadManager();
threadManager.setMinimalDisplay(true);
+ threadManager.setMessagesRootCallback(function() {
+ return _getColumnMessagesNode();
+ });
threadManager.setLoadThreadURI('/conpherence/columnview/');
threadManager.setWillLoadThreadCallback(function() {
_markLoading(true);
@@ -146,7 +159,6 @@ JX.behavior('durable-column', function(config, statics) {
return;
}
var messages = _getColumnMessagesNode();
- JX.DOM.appendContent(messages, JX.$H(r.transactions));
scrollbar.scrollTo(messages.scrollHeight);
});
@@ -155,7 +167,6 @@ JX.behavior('durable-column', function(config, statics) {
});
threadManager.setDidUpdateWorkflowCallback(function(r) {
var messages = _getColumnMessagesNode();
- JX.DOM.appendContent(messages, JX.$H(r.transactions));
scrollbar.scrollTo(messages.scrollHeight);
JX.DOM.setContent(_getColumnTitleNode(), r.conpherence_title);
});
diff --git a/webroot/rsrc/js/application/conpherence/behavior-menu.js b/webroot/rsrc/js/application/conpherence/behavior-menu.js
index 26e184813e..2233b3ff28 100644
--- a/webroot/rsrc/js/application/conpherence/behavior-menu.js
+++ b/webroot/rsrc/js/application/conpherence/behavior-menu.js
@@ -28,12 +28,15 @@ JX.behavior('conpherence-menu', function(config) {
// TODO - move more logic into the ThreadManager
var threadManager = new JX.ConpherenceThreadManager();
+ threadManager.setMessagesRootCallback(function() {
+ return scrollbar.getContentNode();
+ });
threadManager.setWillLoadThreadCallback(function() {
markThreadLoading(true);
});
threadManager.setDidLoadThreadCallback(function(r) {
var header = JX.$H(r.header);
- var messages = JX.$H(r.messages);
+ var messages = JX.$H(r.transactions);
var form = JX.$H(r.form);
var root = JX.DOM.find(document, 'div', 'conpherence-layout');
var header_root = JX.DOM.find(root, 'div', 'conpherence-header-pane');
@@ -48,7 +51,6 @@ JX.behavior('conpherence-menu', function(config) {
});
threadManager.setDidUpdateThreadCallback(function(r) {
- JX.DOM.appendContent(scrollbar.getContentNode(), JX.$H(r.transactions));
_scrollMessageWindow();
});
@@ -70,14 +72,13 @@ JX.behavior('conpherence-menu', function(config) {
} catch (ex) {
// Ignore; maybe no files widget
}
- JX.DOM.appendContent(scrollbar.getContentNode(), JX.$H(r.transactions));
- _scrollMessageWindow();
-
if (fileWidget) {
JX.DOM.setContent(
fileWidget,
JX.$H(r.file_widget));
}
+
+ _scrollMessageWindow();
textarea.value = '';
}
markThreadLoading(false);
@@ -415,56 +416,6 @@ JX.behavior('conpherence-menu', function(config) {
.start();
});
- var _oldLoadingTransactionID = null;
- JX.Stratcom.listen('click', 'show-older-messages', function(e) {
- e.kill();
- var data = e.getNodeData('show-older-messages');
- if (data.oldest_transaction_id == _oldLoadingTransactionID) {
- return;
- }
- _oldLoadingTransactionID = data.oldest_transaction_id;
-
- var node = e.getNode('show-older-messages');
- JX.DOM.setContent(node, 'Loading...');
- JX.DOM.alterClass(node, 'conpherence-show-more-messages-loading', true);
-
- var conf_id = _thread.selected;
- var messages_root = scrollbar.getContentNode();
- new JX.Workflow(config.baseURI + conf_id + '/', data)
- .setHandler(function(r) {
- JX.DOM.remove(node);
- var messages = JX.$H(r.messages);
- JX.DOM.prependContent(
- messages_root,
- JX.$H(messages));
- }).start();
- });
-
- var _newLoadingTransactionID = null;
- JX.Stratcom.listen('click', 'show-newer-messages', function(e) {
- e.kill();
- var data = e.getNodeData('show-newer-messages');
- if (data.newest_transaction_id == _newLoadingTransactionID) {
- return;
- }
- _newLoadingTransactionID = data.newest_transaction_id;
-
- var node = e.getNode('show-newer-messages');
- JX.DOM.setContent(node, 'Loading...');
- JX.DOM.alterClass(node, 'conpherence-show-more-messages-loading', true);
-
- var conf_id = _thread.selected;
- var messages_root = scrollbar.getContentNode();
- new JX.Workflow(config.baseURI + conf_id + '/', data)
- .setHandler(function(r) {
- JX.DOM.remove(node);
- var messages = JX.$H(r.messages);
- JX.DOM.appendContent(
- messages_root,
- JX.$H(messages));
- }).start();
- });
-
/**
* On devices, we just show a thread list, so we don't want to automatically
* select or load any threads. On desktop, we automatically select the first
diff --git a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js b/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js
index 049e3f1db1..9842da11f5 100644
--- a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js
+++ b/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js
@@ -33,7 +33,7 @@ JX.install('DifferentialInlineCommentEditor', {
on_right : this.getOnRight(),
id : this.getID(),
number : this.getLineNumber(),
- is_new : this.getIsNew(),
+ is_new : (this.getIsNew() ? 1 : 0),
length : this.getLength(),
changesetID : this.getChangesetID(),
text : this.getText() || '',
diff --git a/webroot/rsrc/js/application/projects/behavior-project-boards.js b/webroot/rsrc/js/application/projects/behavior-project-boards.js
index 20a78d0963..04e99fde28 100644
--- a/webroot/rsrc/js/application/projects/behavior-project-boards.js
+++ b/webroot/rsrc/js/application/projects/behavior-project-boards.js
@@ -249,6 +249,9 @@ JX.behavior('project-boards', function(config, statics) {
for (ii = 0; ii < lists.length; ii++) {
lists[ii].setGroup(lists);
}
+ }
+
+ function setup() {
JX.Stratcom.listen(
'click',
@@ -336,6 +339,9 @@ JX.behavior('project-boards', function(config, statics) {
statics.boardID = new_config.boardID;
}
update_statics(new_config);
+ if (data.fromServer) {
+ init_board();
+ }
});
return true;
}
@@ -345,7 +351,8 @@ JX.behavior('project-boards', function(config, statics) {
var current_page_id = JX.Quicksand.getCurrentPageID();
statics.boardConfigCache = {};
statics.boardConfigCache[current_page_id] = config;
- statics.setup = init_board();
+ init_board();
+ statics.setup = setup();
}
});
diff --git a/webroot/rsrc/js/core/Hovercard.js b/webroot/rsrc/js/core/Hovercard.js
index e83b0fcba0..15149eceeb 100644
--- a/webroot/rsrc/js/core/Hovercard.js
+++ b/webroot/rsrc/js/core/Hovercard.js
@@ -80,6 +80,7 @@ JX.install('Hovercard', {
var p = JX.$V(root);
var d = JX.Vector.getDim(root);
var n = JX.Vector.getDim(child);
+ var v = JX.Vector.getViewport();
// Move the tip so it's nicely aligned.
// I'm just doing north/south alignment for now
@@ -89,8 +90,12 @@ JX.install('Hovercard', {
var x = parseInt(p.x, 10) - margin / 2;
var y = parseInt(p.y - n.y, 10) - margin;
+ // If running off the edge of the viewport, make it margin / 2 away
+ // from the far right edge of the viewport instead
+ if ((x + n.x) > (v.x)) {
+ x = x - parseInt(x + n.x - v.x + margin / 2, 10);
// If more in the center, we can safely center
- if (x > (n.x / 2) + margin) {
+ } else if (x > (n.x / 2) + margin) {
x = parseInt(p.x - (n.x / 2) + d.x, 10);
}