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

Merge branch 'master' into redesign-2015

This commit is contained in:
epriestley 2015-05-14 09:50:12 -07:00
commit b1b97dddc7
239 changed files with 4573 additions and 1660 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

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

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
ADD isAllDay BOOL NOT NULL;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_user.user
ADD profileImageCache VARCHAR(255) COLLATE {$COLLATE_TEXT};

View file

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

View file

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

View file

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

View file

@ -154,7 +154,7 @@ abstract class AphrontResponse {
array_walk_recursive(
$object,
array('AphrontResponse', 'processValueForJSONEncoding'));
array(__CLASS__, 'processValueForJSONEncoding'));
$response = json_encode($object);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -270,7 +270,7 @@ final class PhabricatorCaches {
}
private static function addNamespaceToCaches(array $caches) {
$namespace = PhabricatorCaches::getNamespace();
$namespace = self::getNamespace();
if (!$namespace) {
return $caches;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -150,5 +150,9 @@ final class ConduitCall {
return $method;
}
public function getMethodImplementation() {
return $this->handler;
}
}

View file

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

View file

@ -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(
'<li><strong>%s:</strong> %s</li>',
$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(
'<p class="aphront-form-instructions">Enter parameters using '.
'<strong>JSON</strong>. For instance, to enter a list, type: '.
'<tt>["apple", "banana", "cherry"]</tt>'));
->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(
'<li><strong>%s:</strong> %s</li>',
$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;
}
}

View file

@ -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(), '<json-parameters>');
} 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(), '<conduit-token>');
$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[] = '<?php';
$parts[] = "\n\n";
$parts[] = 'require_once ';
$parts[] = phutil_var_export($libphutil_path, true);
$parts[] = ";\n\n";
$parts[] = '$api_token = "';
$parts[] = phutil_tag('strong', array(), pht('<api-token>'));
$parts[] = "\";\n";
$parts[] = '$api_parameters = ';
if ($params === null) {
$parts[] = 'array(';
$parts[] = phutil_tag('strong', array(), pht('<parameters>'));
$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;
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -113,7 +113,7 @@ abstract class PhabricatorSetupCheck {
final public static function runAllChecks() {
$symbols = id(new PhutilSymbolLoader())
->setAncestorClass('PhabricatorSetupCheck')
->setAncestorClass(__CLASS__)
->setConcreteOnly(true)
->selectAndLoadSymbols();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,10 @@
final class ConpherenceRoomListController extends ConpherenceController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$user = $request->getUser();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,7 +12,7 @@ final class DarkConsoleErrorLogPluginAPI {
// reenter autoloaders).
PhutilReadableSerializer::printableValue(null);
PhutilErrorHandler::setErrorListener(
array('DarkConsoleErrorLogPluginAPI', 'handleErrors'));
array(__CLASS__, 'handleErrors'));
}
public static function enableDiscardMode() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,376 @@
<?php
/**
* Datastructure which follows lines of code across source changes.
*
* This map is used to update the positions of inline comments after diff
* updates. For example, if a inline comment appeared on line 30 of a diff
* but the next update adds 15 more lines above it, the comment should move
* down to line 45.
*
*/
final class DifferentialLineAdjustmentMap extends Phobject {
private $map;
private $nearestMap;
private $isInverse;
private $finalOffset;
private $nextMapInChain;
/**
* Get the raw adjustment map.
*/
public function getMap() {
return $this->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.')';
}
}

View file

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

View file

@ -278,6 +278,7 @@ final class DifferentialChangesetTwoUpRenderer
$scaffold->addInlineView($companion);
unset($new_comments[$n_num][$key]);
break;
}
}
}

View file

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

View file

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

View file

@ -0,0 +1,294 @@
<?php
final class DifferentialAdjustmentMapTestCase extends ArcanistPhutilTestCase {
public function testBasicMaps() {
$change_map = array(
1 => 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();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -91,6 +91,8 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
'(?P<phid>[^/]+)/'.
'(?P<key>[^/]+)/'
=> 'PhabricatorFileTransformController',
'transforms/(?P<id>[1-9]\d*)/' =>
'PhabricatorFileTransformListController',
'uploaddialog/' => 'PhabricatorFileUploadDialogController',
'download/(?P<phid>[^/]+)/' => 'PhabricatorFileDialogController',
),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,382 @@
<?php
abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform {
private $file;
private $data;
private $image;
private $imageX;
private $imageY;
/**
* Get an estimate of the transformed dimensions of a file.
*
* @param PhabricatorFile File to transform.
* @return list<int, int>|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<int, int> 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;
}
}

View file

@ -0,0 +1,225 @@
<?php
final class PhabricatorFileThumbnailTransform
extends PhabricatorFileImageTransform {
const TRANSFORM_PROFILE = 'profile';
const TRANSFORM_PINBOARD = 'pinboard';
const TRANSFORM_THUMBGRID = 'thumbgrid';
const TRANSFORM_PREVIEW = 'preview';
private $name;
private $key;
private $dstX;
private $dstY;
private $scaleUp;
public function setName($name) {
$this->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);
}
}

View file

@ -0,0 +1,74 @@
<?php
abstract class PhabricatorFileTransform extends Phobject {
abstract public function getTransformName();
abstract public function getTransformKey();
abstract public function canApplyTransform(PhabricatorFile $file);
abstract public function applyTransform(PhabricatorFile $file);
public function getDefaultTransform(PhabricatorFile $file) {
return null;
}
public function generateTransforms() {
return array($this);
}
public function executeTransform(PhabricatorFile $file) {
if ($this->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;
}
}

View file

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

View file

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

View file

@ -4,7 +4,7 @@ abstract class HarbormasterBuildStepImplementation {
public static function getImplementations() {
return id(new PhutilSymbolLoader())
->setAncestorClass('HarbormasterBuildStepImplementation')
->setAncestorClass(__CLASS__)
->loadObjects();
}

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