mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 14:52:41 +01:00
Merge branch 'master' into redesign-2015
This commit is contained in:
commit
b1b97dddc7
239 changed files with 4573 additions and 1660 deletions
BIN
resources/builtin/image-100x100.png
Normal file
BIN
resources/builtin/image-100x100.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 993 B |
BIN
resources/builtin/image-220x220.png
Normal file
BIN
resources/builtin/image-220x220.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
resources/builtin/image-280x210.png
Normal file
BIN
resources/builtin/image-280x210.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -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',
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
|
||||
ADD isAllDay BOOL NOT NULL;
|
2
resources/sql/autopatches/20150513.user.cache.1.sql
Normal file
2
resources/sql/autopatches/20150513.user.cache.1.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_user.user
|
||||
ADD profileImageCache VARCHAR(255) COLLATE {$COLLATE_TEXT};
|
|
@ -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',
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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()'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -154,7 +154,7 @@ abstract class AphrontResponse {
|
|||
|
||||
array_walk_recursive(
|
||||
$object,
|
||||
array('AphrontResponse', 'processValueForJSONEncoding'));
|
||||
array(__CLASS__, 'processValueForJSONEncoding'));
|
||||
|
||||
$response = json_encode($object);
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
2
src/applications/cache/PhabricatorCaches.php
vendored
2
src/applications/cache/PhabricatorCaches.php
vendored
|
@ -270,7 +270,7 @@ final class PhabricatorCaches {
|
|||
}
|
||||
|
||||
private static function addNamespaceToCaches(array $caches) {
|
||||
$namespace = PhabricatorCaches::getNamespace();
|
||||
$namespace = self::getNamespace();
|
||||
if (!$namespace) {
|
||||
return $caches;
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -150,5 +150,9 @@ final class ConduitCall {
|
|||
return $method;
|
||||
}
|
||||
|
||||
public function getMethodImplementation() {
|
||||
return $this->handler;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -113,7 +113,7 @@ abstract class PhabricatorSetupCheck {
|
|||
|
||||
final public static function runAllChecks() {
|
||||
$symbols = id(new PhutilSymbolLoader())
|
||||
->setAncestorClass('PhabricatorSetupCheck')
|
||||
->setAncestorClass(__CLASS__)
|
||||
->setConcreteOnly(true)
|
||||
->selectAndLoadSymbols();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
final class ConpherenceRoomListController extends ConpherenceController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$user = $request->getUser();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -12,7 +12,7 @@ final class DarkConsoleErrorLogPluginAPI {
|
|||
// reenter autoloaders).
|
||||
PhutilReadableSerializer::printableValue(null);
|
||||
PhutilErrorHandler::setErrorListener(
|
||||
array('DarkConsoleErrorLogPluginAPI', 'handleErrors'));
|
||||
array(__CLASS__, 'handleErrors'));
|
||||
}
|
||||
|
||||
public static function enableDiscardMode() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.')';
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -278,6 +278,7 @@ final class DifferentialChangesetTwoUpRenderer
|
|||
|
||||
$scaffold->addInlineView($companion);
|
||||
unset($new_comments[$n_num][$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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.');
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
32
src/applications/differential/storage/__tests__/map/add.diff
Normal file
32
src/applications/differential/storage/__tests__/map/add.diff
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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'));
|
||||
|
||||
|
|
|
@ -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 )--------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -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 )-------------------------------------------------- */
|
||||
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue