mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-27 09:12:41 +01:00
(stable) Promote 2016 Week 47
This commit is contained in:
commit
10c4dedd18
126 changed files with 3139 additions and 1185 deletions
|
@ -9,8 +9,8 @@ return array(
|
|||
'names' => array(
|
||||
'conpherence.pkg.css' => '0b64e988',
|
||||
'conpherence.pkg.js' => '6249a1cf',
|
||||
'core.pkg.css' => 'a729d20e',
|
||||
'core.pkg.js' => '1a77dddf',
|
||||
'core.pkg.css' => '347113ea',
|
||||
'core.pkg.js' => '40e98735',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => 'a4ba74b5',
|
||||
'differential.pkg.js' => '634399e9',
|
||||
|
@ -21,8 +21,7 @@ return array(
|
|||
'maniphest.pkg.js' => '949a7498',
|
||||
'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
|
||||
'rsrc/css/aphront/dark-console.css' => 'f54bf286',
|
||||
'rsrc/css/aphront/dialog-view.css' => 'ea3745f5',
|
||||
'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d',
|
||||
'rsrc/css/aphront/dialog-view.css' => '49b2a8a3',
|
||||
'rsrc/css/aphront/list-filter-view.css' => '5d6f0526',
|
||||
'rsrc/css/aphront/multi-column.css' => '84cc6640',
|
||||
'rsrc/css/aphront/notification.css' => '3f6c89c9',
|
||||
|
@ -84,7 +83,7 @@ return array(
|
|||
'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b',
|
||||
'rsrc/css/application/paste/paste.css' => '1898e534',
|
||||
'rsrc/css/application/people/people-profile.css' => '2473d929',
|
||||
'rsrc/css/application/phame/phame.css' => '8efb0729',
|
||||
'rsrc/css/application/phame/phame.css' => '654dd9ef',
|
||||
'rsrc/css/application/pholio/pholio-edit.css' => '07676f51',
|
||||
'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49',
|
||||
'rsrc/css/application/pholio/pholio.css' => 'ca89d380',
|
||||
|
@ -133,14 +132,15 @@ return array(
|
|||
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
|
||||
'rsrc/css/phui/phui-cms.css' => 'be43c8a8',
|
||||
'rsrc/css/phui/phui-comment-form.css' => '4ecc56ef',
|
||||
'rsrc/css/phui/phui-comment-panel.css' => '85113e6a',
|
||||
'rsrc/css/phui/phui-crumbs-view.css' => '195ac419',
|
||||
'rsrc/css/phui/phui-curtain-view.css' => '947bf1a4',
|
||||
'rsrc/css/phui/phui-document-pro.css' => 'ca1fed81',
|
||||
'rsrc/css/phui/phui-document-pro.css' => 'c354e312',
|
||||
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
|
||||
'rsrc/css/phui/phui-document.css' => 'c32e8dec',
|
||||
'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9',
|
||||
'rsrc/css/phui/phui-fontkit.css' => '9cda225e',
|
||||
'rsrc/css/phui/phui-form-view.css' => '91adabe4',
|
||||
'rsrc/css/phui/phui-form-view.css' => '3fadd537',
|
||||
'rsrc/css/phui/phui-form.css' => 'b8fb087a',
|
||||
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
|
||||
'rsrc/css/phui/phui-header-view.css' => '6ec8f155',
|
||||
|
@ -149,8 +149,9 @@ return array(
|
|||
'rsrc/css/phui/phui-icon.css' => '417f80fb',
|
||||
'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c',
|
||||
'rsrc/css/phui/phui-info-panel.css' => '27ea50a1',
|
||||
'rsrc/css/phui/phui-info-view.css' => '28efab79',
|
||||
'rsrc/css/phui/phui-info-view.css' => 'ec92802a',
|
||||
'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0',
|
||||
'rsrc/css/phui/phui-lightbox.css' => 'e17ce2bd',
|
||||
'rsrc/css/phui/phui-list.css' => '9da2aa00',
|
||||
'rsrc/css/phui/phui-object-box.css' => '6b487c57',
|
||||
'rsrc/css/phui/phui-object-item-list-view.css' => '87278fa0',
|
||||
|
@ -165,8 +166,8 @@ return array(
|
|||
'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2',
|
||||
'rsrc/css/phui/phui-timeline-view.css' => 'bc523970',
|
||||
'rsrc/css/phui/phui-two-column-view.css' => 'bbe32c23',
|
||||
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7',
|
||||
'rsrc/css/phui/workboards/phui-workboard.css' => 'e09eb53a',
|
||||
'rsrc/css/phui/workboards/phui-workboard-color.css' => '207828dd',
|
||||
'rsrc/css/phui/workboards/phui-workboard.css' => '60d09514',
|
||||
'rsrc/css/phui/workboards/phui-workcard.css' => '0c62d7c5',
|
||||
'rsrc/css/phui/workboards/phui-workpanel.css' => '92197373',
|
||||
'rsrc/css/sprite-login.css' => '6dbbbd97',
|
||||
|
@ -261,7 +262,7 @@ return array(
|
|||
'rsrc/externals/javelin/lib/behavior.js' => '61cbc29a',
|
||||
'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '8d3bc1b2',
|
||||
'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => '70baed2f',
|
||||
'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => 'e6e25838',
|
||||
'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => '185bbd53',
|
||||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd',
|
||||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '013ffff9',
|
||||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0',
|
||||
|
@ -323,12 +324,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/lightbox/close-2.png' => 'cc40e7c8',
|
||||
'rsrc/image/icon/lightbox/close-hover-2.png' => 'fb5d6d9e',
|
||||
'rsrc/image/icon/lightbox/left-arrow-2.png' => '8426133b',
|
||||
'rsrc/image/icon/lightbox/left-arrow-hover-2.png' => '701e5ee3',
|
||||
'rsrc/image/icon/lightbox/right-arrow-2.png' => '6d5519a0',
|
||||
'rsrc/image/icon/lightbox/right-arrow-hover-2.png' => '3a04aa21',
|
||||
'rsrc/image/icon/subscribe.png' => 'd03ed5a5',
|
||||
'rsrc/image/icon/tango/attachment.png' => 'ecc8022e',
|
||||
'rsrc/image/icon/tango/edit.png' => '929a1363',
|
||||
|
@ -477,7 +472,7 @@ return array(
|
|||
'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5',
|
||||
'rsrc/js/core/Busy.js' => '59a7976a',
|
||||
'rsrc/js/core/DragAndDropFileUpload.js' => '58dea2fa',
|
||||
'rsrc/js/core/DraggableList.js' => '5a13c79f',
|
||||
'rsrc/js/core/DraggableList.js' => 'bea6e7f4',
|
||||
'rsrc/js/core/Favicon.js' => '1fe2510c',
|
||||
'rsrc/js/core/FileUpload.js' => '680ea2c8',
|
||||
'rsrc/js/core/Hovercard.js' => '1bd28176',
|
||||
|
@ -500,7 +495,7 @@ return array(
|
|||
'rsrc/js/core/behavior-device.js' => 'bb1dd507',
|
||||
'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22',
|
||||
'rsrc/js/core/behavior-error-log.js' => '6882e80a',
|
||||
'rsrc/js/core/behavior-fancy-datepicker.js' => '568931f3',
|
||||
'rsrc/js/core/behavior-fancy-datepicker.js' => 'a9210d03',
|
||||
'rsrc/js/core/behavior-file-tree.js' => '88236f00',
|
||||
'rsrc/js/core/behavior-form.js' => '5c54cbf3',
|
||||
'rsrc/js/core/behavior-gesture.js' => '3ab51e2c',
|
||||
|
@ -510,7 +505,7 @@ return array(
|
|||
'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64',
|
||||
'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0',
|
||||
'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0',
|
||||
'rsrc/js/core/behavior-lightbox-attachments.js' => 'f8ba29d7',
|
||||
'rsrc/js/core/behavior-lightbox-attachments.js' => 'ec949017',
|
||||
'rsrc/js/core/behavior-line-linker.js' => '1499a8cb',
|
||||
'rsrc/js/core/behavior-more.js' => 'a80d0378',
|
||||
'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f',
|
||||
|
@ -550,7 +545,7 @@ return array(
|
|||
'almanac-css' => 'dbb9b3af',
|
||||
'aphront-bars' => '231ac33c',
|
||||
'aphront-dark-console-css' => 'f54bf286',
|
||||
'aphront-dialog-view-css' => 'ea3745f5',
|
||||
'aphront-dialog-view-css' => '49b2a8a3',
|
||||
'aphront-list-filter-view-css' => '5d6f0526',
|
||||
'aphront-multi-column-view-css' => '84cc6640',
|
||||
'aphront-panel-view-css' => '8427b78d',
|
||||
|
@ -649,14 +644,14 @@ return array(
|
|||
'javelin-behavior-editengine-reorder-fields' => 'b59e1e96',
|
||||
'javelin-behavior-error-log' => '6882e80a',
|
||||
'javelin-behavior-event-all-day' => 'b41537c9',
|
||||
'javelin-behavior-fancy-datepicker' => '568931f3',
|
||||
'javelin-behavior-fancy-datepicker' => 'a9210d03',
|
||||
'javelin-behavior-global-drag-and-drop' => '960f6a39',
|
||||
'javelin-behavior-herald-rule-editor' => '7ebaeed3',
|
||||
'javelin-behavior-high-security-warning' => 'a464fe03',
|
||||
'javelin-behavior-history-install' => '7ee2b591',
|
||||
'javelin-behavior-icon-composer' => '8499b6ab',
|
||||
'javelin-behavior-launch-icon-composer' => '48086888',
|
||||
'javelin-behavior-lightbox-attachments' => 'f8ba29d7',
|
||||
'javelin-behavior-lightbox-attachments' => 'ec949017',
|
||||
'javelin-behavior-line-chart' => 'e4232876',
|
||||
'javelin-behavior-load-blame' => '42126667',
|
||||
'javelin-behavior-maniphest-batch-editor' => '782ab6e7',
|
||||
|
@ -753,7 +748,7 @@ return array(
|
|||
'javelin-tokenizer' => '8d3bc1b2',
|
||||
'javelin-typeahead' => '70baed2f',
|
||||
'javelin-typeahead-composite-source' => '503e17fd',
|
||||
'javelin-typeahead-normalizer' => 'e6e25838',
|
||||
'javelin-typeahead-normalizer' => '185bbd53',
|
||||
'javelin-typeahead-ondemand-source' => '013ffff9',
|
||||
'javelin-typeahead-preloaded-source' => '54f314a0',
|
||||
'javelin-typeahead-source' => '0fcf201c',
|
||||
|
@ -772,7 +767,6 @@ return array(
|
|||
'javelin-workboard-column' => '21df4ff5',
|
||||
'javelin-workboard-controller' => '55baf5ed',
|
||||
'javelin-workflow' => '1e911d0f',
|
||||
'lightbox-attachment-css' => '7acac05d',
|
||||
'maniphest-batch-editor' => 'b0f0b6d5',
|
||||
'maniphest-report-css' => '9b9580b7',
|
||||
'maniphest-task-edit-css' => 'fda62a9b',
|
||||
|
@ -792,7 +786,7 @@ return array(
|
|||
'phabricator-countdown-css' => '16c52f5c',
|
||||
'phabricator-dashboard-css' => 'bc6f2127',
|
||||
'phabricator-drag-and-drop-file-upload' => '58dea2fa',
|
||||
'phabricator-draggable-list' => '5a13c79f',
|
||||
'phabricator-draggable-list' => 'bea6e7f4',
|
||||
'phabricator-fatal-config-template-css' => '8f18fa41',
|
||||
'phabricator-favicon' => '1fe2510c',
|
||||
'phabricator-feed-css' => 'ecd4ec57',
|
||||
|
@ -830,7 +824,7 @@ return array(
|
|||
'phabricator-uiexample-reactor-sendclass' => '1def2711',
|
||||
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
|
||||
'phabricator-zindex-css' => 'd1270942',
|
||||
'phame-css' => '8efb0729',
|
||||
'phame-css' => '654dd9ef',
|
||||
'pholio-css' => 'ca89d380',
|
||||
'pholio-edit-css' => '07676f51',
|
||||
'pholio-inline-comments-css' => '8e545e49',
|
||||
|
@ -853,16 +847,17 @@ return array(
|
|||
'phui-chart-css' => '6bf6f78e',
|
||||
'phui-cms-css' => 'be43c8a8',
|
||||
'phui-comment-form-css' => '4ecc56ef',
|
||||
'phui-comment-panel-css' => '85113e6a',
|
||||
'phui-crumbs-view-css' => '195ac419',
|
||||
'phui-curtain-view-css' => '947bf1a4',
|
||||
'phui-document-summary-view-css' => '9ca48bdf',
|
||||
'phui-document-view-css' => 'c32e8dec',
|
||||
'phui-document-view-pro-css' => 'ca1fed81',
|
||||
'phui-document-view-pro-css' => 'c354e312',
|
||||
'phui-feed-story-css' => '44a9c8e9',
|
||||
'phui-font-icon-base-css' => '870a7360',
|
||||
'phui-fontkit-css' => '9cda225e',
|
||||
'phui-form-css' => 'b8fb087a',
|
||||
'phui-form-view-css' => '91adabe4',
|
||||
'phui-form-view-css' => '3fadd537',
|
||||
'phui-head-thing-view-css' => 'fd311e5f',
|
||||
'phui-header-view-css' => '6ec8f155',
|
||||
'phui-hovercard' => '1bd28176',
|
||||
|
@ -871,9 +866,10 @@ return array(
|
|||
'phui-icon-view-css' => '417f80fb',
|
||||
'phui-image-mask-css' => 'a8498f9c',
|
||||
'phui-info-panel-css' => '27ea50a1',
|
||||
'phui-info-view-css' => '28efab79',
|
||||
'phui-info-view-css' => 'ec92802a',
|
||||
'phui-inline-comment-view-css' => '5953c28e',
|
||||
'phui-invisible-character-view-css' => '6993d9f0',
|
||||
'phui-lightbox-css' => 'e17ce2bd',
|
||||
'phui-list-view-css' => '9da2aa00',
|
||||
'phui-object-box-css' => '6b487c57',
|
||||
'phui-object-item-list-view-css' => '87278fa0',
|
||||
|
@ -889,8 +885,8 @@ return array(
|
|||
'phui-theme-css' => '798c69b8',
|
||||
'phui-timeline-view-css' => 'bc523970',
|
||||
'phui-two-column-view-css' => 'bbe32c23',
|
||||
'phui-workboard-color-css' => 'ac6fe6a7',
|
||||
'phui-workboard-view-css' => 'e09eb53a',
|
||||
'phui-workboard-color-css' => '207828dd',
|
||||
'phui-workboard-view-css' => '60d09514',
|
||||
'phui-workcard-view-css' => '0c62d7c5',
|
||||
'phui-workpanel-view-css' => '92197373',
|
||||
'phuix-action-list-view' => 'b5c256b8',
|
||||
|
@ -1043,6 +1039,9 @@ return array(
|
|||
'javelin-workflow',
|
||||
'javelin-workboard-controller',
|
||||
),
|
||||
'185bbd53' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
'1aa4c968' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1353,13 +1352,6 @@ return array(
|
|||
'phabricator-drag-and-drop-file-upload',
|
||||
'javelin-workboard-board',
|
||||
),
|
||||
'568931f3' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-vector',
|
||||
),
|
||||
'58dea2fa' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
|
@ -1379,14 +1371,6 @@ return array(
|
|||
'javelin-vector',
|
||||
'javelin-dom',
|
||||
),
|
||||
'5a13c79f' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'5c54cbf3' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1597,6 +1581,9 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
),
|
||||
'85113e6a' => array(
|
||||
'phui-timeline-view-css',
|
||||
),
|
||||
'85ee8ce6' => array(
|
||||
'aphront-dialog-view-css',
|
||||
),
|
||||
|
@ -1808,6 +1795,13 @@ return array(
|
|||
'javelin-uri',
|
||||
'phabricator-keyboard-shortcut',
|
||||
),
|
||||
'a9210d03' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-vector',
|
||||
),
|
||||
'a9f88de2' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1939,6 +1933,14 @@ return array(
|
|||
'javelin-util',
|
||||
'javelin-request',
|
||||
),
|
||||
'bea6e7f4' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'bee502c8' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -2118,9 +2120,6 @@ return array(
|
|||
'javelin-workflow',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'e6e25838' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
'e9581f08' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -2128,6 +2127,15 @@ return array(
|
|||
'javelin-dom',
|
||||
'phabricator-draggable-list',
|
||||
),
|
||||
'ec949017' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
'javelin-mask',
|
||||
'javelin-util',
|
||||
'phuix-icon-view',
|
||||
'phabricator-busy',
|
||||
),
|
||||
'edd1ba66' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -2188,14 +2196,6 @@ return array(
|
|||
'javelin-install',
|
||||
'javelin-dom',
|
||||
),
|
||||
'f8ba29d7' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
'javelin-mask',
|
||||
'javelin-util',
|
||||
'phabricator-busy',
|
||||
),
|
||||
'fb20ac8d' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-aphlict',
|
||||
|
@ -2288,7 +2288,8 @@ return array(
|
|||
'phabricator-main-menu-view',
|
||||
'phabricator-notification-css',
|
||||
'phabricator-notification-menu-css',
|
||||
'lightbox-attachment-css',
|
||||
'phui-lightbox-css',
|
||||
'phui-comment-panel-css',
|
||||
'phui-header-view-css',
|
||||
'phabricator-nav-view-css',
|
||||
'phui-basic-nav-view-css',
|
||||
|
|
|
@ -111,7 +111,8 @@ return array(
|
|||
'phabricator-main-menu-view',
|
||||
'phabricator-notification-css',
|
||||
'phabricator-notification-menu-css',
|
||||
'lightbox-attachment-css',
|
||||
'phui-lightbox-css',
|
||||
'phui-comment-panel-css',
|
||||
'phui-header-view-css',
|
||||
'phabricator-nav-view-css',
|
||||
'phui-basic-nav-view-css',
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_phame.phame_post
|
||||
ADD subtitle VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_phame.phame_post
|
||||
ADD headerImagePHID VARBINARY(64);
|
|
@ -34,7 +34,13 @@ try {
|
|||
'name' => 'host',
|
||||
'param' => 'hostname',
|
||||
'help' => pht(
|
||||
'Connect to __host__ instead of the default host.'),
|
||||
'Operate on the database server identified by __hostname__.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'ref',
|
||||
'param' => 'ref',
|
||||
'help' => pht(
|
||||
'Operate on the database identified by __ref__.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'user',
|
||||
|
@ -81,39 +87,64 @@ try {
|
|||
// First, test that the Phabricator configuration is set up correctly. After
|
||||
// we know this works we'll test any administrative credentials specifically.
|
||||
|
||||
$refs = PhabricatorDatabaseRef::getActiveDatabaseRefs();
|
||||
if (!$refs) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('No databases are configured.'));
|
||||
}
|
||||
|
||||
$host = $args->getArg('host');
|
||||
if (strlen($host)) {
|
||||
$ref = null;
|
||||
$ref_key = $args->getArg('ref');
|
||||
if (strlen($host) || strlen($ref_key)) {
|
||||
if ($host && $ref_key) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Use "--host" or "--ref" to select a database, but not both.'));
|
||||
}
|
||||
|
||||
$refs = PhabricatorDatabaseRef::getLiveRefs();
|
||||
|
||||
// Include the master in case the user is just specifying a redundant
|
||||
// "--host" flag for no reason and does not actually have a database
|
||||
// cluster configured.
|
||||
$refs[] = PhabricatorDatabaseRef::getMasterDatabaseRef();
|
||||
$refs = PhabricatorDatabaseRef::getActiveDatabaseRefs();
|
||||
|
||||
$possible_refs = array();
|
||||
foreach ($refs as $possible_ref) {
|
||||
if ($possible_ref->getHost() == $host) {
|
||||
$ref = $possible_ref;
|
||||
if ($host && ($possible_ref->getHost() == $host)) {
|
||||
$possible_refs[] = $possible_ref;
|
||||
break;
|
||||
}
|
||||
if ($ref_key && ($possible_ref->getRefKey() == $ref_key)) {
|
||||
$possible_refs[] = $possible_ref;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$ref) {
|
||||
if (!$possible_refs) {
|
||||
if ($host) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'There is no configured database on host "%s". This command can '.
|
||||
'only interact with configured databases.',
|
||||
$host));
|
||||
}
|
||||
} else {
|
||||
$ref = PhabricatorDatabaseRef::getMasterDatabaseRef();
|
||||
if (!$ref) {
|
||||
throw new Exception(
|
||||
pht('No database master is configured.'));
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'There is no configured database with ref "%s". This command can '.
|
||||
'only interact with configured databases.',
|
||||
$ref_key));
|
||||
}
|
||||
}
|
||||
|
||||
if (count($possible_refs) > 1) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Host "%s" identifies more than one database. Use "--ref" to select '.
|
||||
'a specific database.',
|
||||
$host));
|
||||
}
|
||||
|
||||
$refs = $possible_refs;
|
||||
}
|
||||
|
||||
$apis = array();
|
||||
foreach ($refs as $ref) {
|
||||
$default_user = $ref->getUser();
|
||||
$default_host = $ref->getHost();
|
||||
$default_port = $ref->getPort();
|
||||
|
@ -195,6 +226,10 @@ try {
|
|||
exit(1);
|
||||
}
|
||||
|
||||
$api->setRef($ref);
|
||||
$apis[] = $api;
|
||||
}
|
||||
|
||||
$workflows = id(new PhutilClassMapQuery())
|
||||
->setAncestorClass('PhabricatorStorageManagementWorkflow')
|
||||
->execute();
|
||||
|
@ -202,7 +237,7 @@ $workflows = id(new PhutilClassMapQuery())
|
|||
$patches = PhabricatorSQLPatchList::buildAllPatches();
|
||||
|
||||
foreach ($workflows as $workflow) {
|
||||
$workflow->setAPI($api);
|
||||
$workflow->setAPIs($apis);
|
||||
$workflow->setPatches($patches);
|
||||
}
|
||||
|
||||
|
|
|
@ -1917,6 +1917,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuthProviderConfigQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigQuery.php',
|
||||
'PhabricatorAuthProviderConfigTransaction' => 'applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php',
|
||||
'PhabricatorAuthProviderConfigTransactionQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigTransactionQuery.php',
|
||||
'PhabricatorAuthProvidersGuidanceContext' => 'applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php',
|
||||
'PhabricatorAuthProvidersGuidanceEngineExtension' => 'applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php',
|
||||
'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthQueryPublicKeysConduitAPIMethod.php',
|
||||
'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php',
|
||||
'PhabricatorAuthRevokeTokenController' => 'applications/auth/controller/PhabricatorAuthRevokeTokenController.php',
|
||||
|
@ -1941,6 +1943,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuthSessionEngineExtension' => 'applications/auth/engine/PhabricatorAuthSessionEngineExtension.php',
|
||||
'PhabricatorAuthSessionEngineExtensionModule' => 'applications/auth/engine/PhabricatorAuthSessionEngineExtensionModule.php',
|
||||
'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php',
|
||||
'PhabricatorAuthSessionInfo' => 'applications/auth/data/PhabricatorAuthSessionInfo.php',
|
||||
'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php',
|
||||
'PhabricatorAuthSetupCheck' => 'applications/config/check/PhabricatorAuthSetupCheck.php',
|
||||
'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php',
|
||||
|
@ -2158,6 +2161,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCalendarImportTriggerLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportTriggerLogType.php',
|
||||
'PhabricatorCalendarImportUpdateLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportUpdateLogType.php',
|
||||
'PhabricatorCalendarImportViewController' => 'applications/calendar/controller/PhabricatorCalendarImportViewController.php',
|
||||
'PhabricatorCalendarInviteeDatasource' => 'applications/calendar/typeahead/PhabricatorCalendarInviteeDatasource.php',
|
||||
'PhabricatorCalendarInviteeUserDatasource' => 'applications/calendar/typeahead/PhabricatorCalendarInviteeUserDatasource.php',
|
||||
'PhabricatorCalendarInviteeViewerFunctionDatasource' => 'applications/calendar/typeahead/PhabricatorCalendarInviteeViewerFunctionDatasource.php',
|
||||
'PhabricatorCalendarManagementNotifyWorkflow' => 'applications/calendar/management/PhabricatorCalendarManagementNotifyWorkflow.php',
|
||||
'PhabricatorCalendarManagementReloadWorkflow' => 'applications/calendar/management/PhabricatorCalendarManagementReloadWorkflow.php',
|
||||
'PhabricatorCalendarManagementWorkflow' => 'applications/calendar/management/PhabricatorCalendarManagementWorkflow.php',
|
||||
|
@ -2647,6 +2653,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileImageProxyController' => 'applications/files/controller/PhabricatorFileImageProxyController.php',
|
||||
'PhabricatorFileImageTransform' => 'applications/files/transform/PhabricatorFileImageTransform.php',
|
||||
'PhabricatorFileInfoController' => 'applications/files/controller/PhabricatorFileInfoController.php',
|
||||
'PhabricatorFileLightboxController' => 'applications/files/controller/PhabricatorFileLightboxController.php',
|
||||
'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php',
|
||||
'PhabricatorFileListController' => 'applications/files/controller/PhabricatorFileListController.php',
|
||||
'PhabricatorFileQuery' => 'applications/files/query/PhabricatorFileQuery.php',
|
||||
|
@ -2728,6 +2735,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorGlobalLock' => 'infrastructure/util/PhabricatorGlobalLock.php',
|
||||
'PhabricatorGlobalUploadTargetView' => 'applications/files/view/PhabricatorGlobalUploadTargetView.php',
|
||||
'PhabricatorGoogleAuthProvider' => 'applications/auth/provider/PhabricatorGoogleAuthProvider.php',
|
||||
'PhabricatorGuidanceContext' => 'applications/guides/guidance/PhabricatorGuidanceContext.php',
|
||||
'PhabricatorGuidanceEngine' => 'applications/guides/guidance/PhabricatorGuidanceEngine.php',
|
||||
'PhabricatorGuidanceEngineExtension' => 'applications/guides/guidance/PhabricatorGuidanceEngineExtension.php',
|
||||
'PhabricatorGuidanceMessage' => 'applications/guides/guidance/PhabricatorGuidanceMessage.php',
|
||||
'PhabricatorGuideApplication' => 'applications/guides/application/PhabricatorGuideApplication.php',
|
||||
'PhabricatorGuideController' => 'applications/guides/controller/PhabricatorGuideController.php',
|
||||
'PhabricatorGuideInstallModule' => 'applications/guides/module/PhabricatorGuideInstallModule.php',
|
||||
|
@ -2887,6 +2898,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorManiphestTaskTestDataGenerator' => 'applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php',
|
||||
'PhabricatorMarkupCache' => 'applications/cache/storage/PhabricatorMarkupCache.php',
|
||||
'PhabricatorMarkupEngine' => 'infrastructure/markup/PhabricatorMarkupEngine.php',
|
||||
'PhabricatorMarkupEngineTestCase' => 'infrastructure/markup/__tests__/PhabricatorMarkupEngineTestCase.php',
|
||||
'PhabricatorMarkupInterface' => 'infrastructure/markup/PhabricatorMarkupInterface.php',
|
||||
'PhabricatorMarkupOneOff' => 'infrastructure/markup/PhabricatorMarkupOneOff.php',
|
||||
'PhabricatorMarkupPreviewController' => 'infrastructure/markup/PhabricatorMarkupPreviewController.php',
|
||||
|
@ -3218,6 +3230,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php',
|
||||
'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php',
|
||||
'PhabricatorPeopleCreateController' => 'applications/people/controller/PhabricatorPeopleCreateController.php',
|
||||
'PhabricatorPeopleCreateGuidanceContext' => 'applications/people/guidance/PhabricatorPeopleCreateGuidanceContext.php',
|
||||
'PhabricatorPeopleDatasource' => 'applications/people/typeahead/PhabricatorPeopleDatasource.php',
|
||||
'PhabricatorPeopleDeleteController' => 'applications/people/controller/PhabricatorPeopleDeleteController.php',
|
||||
'PhabricatorPeopleDetailsProfilePanel' => 'applications/people/profilepanel/PhabricatorPeopleDetailsProfilePanel.php',
|
||||
|
@ -4075,6 +4088,7 @@ phutil_register_library_map(array(
|
|||
'PhamePostEditEngine' => 'applications/phame/editor/PhamePostEditEngine.php',
|
||||
'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php',
|
||||
'PhamePostFulltextEngine' => 'applications/phame/search/PhamePostFulltextEngine.php',
|
||||
'PhamePostHeaderPictureController' => 'applications/phame/controller/post/PhamePostHeaderPictureController.php',
|
||||
'PhamePostHistoryController' => 'applications/phame/controller/post/PhamePostHistoryController.php',
|
||||
'PhamePostListController' => 'applications/phame/controller/post/PhamePostListController.php',
|
||||
'PhamePostListView' => 'applications/phame/view/PhamePostListView.php',
|
||||
|
@ -6436,7 +6450,7 @@ phutil_register_library_map(array(
|
|||
'PHUIInfoExample' => 'PhabricatorUIExample',
|
||||
'PHUIInfoPanelExample' => 'PhabricatorUIExample',
|
||||
'PHUIInfoPanelView' => 'AphrontView',
|
||||
'PHUIInfoView' => 'AphrontView',
|
||||
'PHUIInfoView' => 'AphrontTagView',
|
||||
'PHUIInvisibleCharacterTestCase' => 'PhabricatorTestCase',
|
||||
'PHUIInvisibleCharacterView' => 'AphrontView',
|
||||
'PHUIListExample' => 'PhabricatorUIExample',
|
||||
|
@ -6730,6 +6744,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuthProviderConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorAuthProviderConfigTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorAuthProvidersGuidanceContext' => 'PhabricatorGuidanceContext',
|
||||
'PhabricatorAuthProvidersGuidanceEngineExtension' => 'PhabricatorGuidanceEngineExtension',
|
||||
'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod',
|
||||
'PhabricatorAuthRegisterController' => 'PhabricatorAuthController',
|
||||
'PhabricatorAuthRevokeTokenController' => 'PhabricatorAuthController',
|
||||
|
@ -6762,6 +6778,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAuthSessionEngineExtension' => 'Phobject',
|
||||
'PhabricatorAuthSessionEngineExtensionModule' => 'PhabricatorConfigModule',
|
||||
'PhabricatorAuthSessionGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'PhabricatorAuthSessionInfo' => 'Phobject',
|
||||
'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorAuthSetupCheck' => 'PhabricatorSetupCheck',
|
||||
'PhabricatorAuthStartController' => 'PhabricatorAuthController',
|
||||
|
@ -7032,6 +7049,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCalendarImportTriggerLogType' => 'PhabricatorCalendarImportLogType',
|
||||
'PhabricatorCalendarImportUpdateLogType' => 'PhabricatorCalendarImportLogType',
|
||||
'PhabricatorCalendarImportViewController' => 'PhabricatorCalendarController',
|
||||
'PhabricatorCalendarInviteeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorCalendarInviteeUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorCalendarInviteeViewerFunctionDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'PhabricatorCalendarManagementNotifyWorkflow' => 'PhabricatorCalendarManagementWorkflow',
|
||||
'PhabricatorCalendarManagementReloadWorkflow' => 'PhabricatorCalendarManagementWorkflow',
|
||||
'PhabricatorCalendarManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||
|
@ -7604,6 +7624,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileImageProxyController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileImageTransform' => 'PhabricatorFileTransform',
|
||||
'PhabricatorFileInfoController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileLightboxController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileLinkView' => 'AphrontView',
|
||||
'PhabricatorFileListController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
|
@ -7687,6 +7708,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorGlobalLock' => 'PhutilLock',
|
||||
'PhabricatorGlobalUploadTargetView' => 'AphrontView',
|
||||
'PhabricatorGoogleAuthProvider' => 'PhabricatorOAuth2AuthProvider',
|
||||
'PhabricatorGuidanceContext' => 'Phobject',
|
||||
'PhabricatorGuidanceEngine' => 'Phobject',
|
||||
'PhabricatorGuidanceEngineExtension' => 'Phobject',
|
||||
'PhabricatorGuidanceMessage' => 'Phobject',
|
||||
'PhabricatorGuideApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorGuideController' => 'PhabricatorController',
|
||||
'PhabricatorGuideInstallModule' => 'PhabricatorGuideModule',
|
||||
|
@ -7851,6 +7876,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorManiphestTaskTestDataGenerator' => 'PhabricatorTestDataGenerator',
|
||||
'PhabricatorMarkupCache' => 'PhabricatorCacheDAO',
|
||||
'PhabricatorMarkupEngine' => 'Phobject',
|
||||
'PhabricatorMarkupEngineTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorMarkupOneOff' => array(
|
||||
'Phobject',
|
||||
'PhabricatorMarkupInterface',
|
||||
|
@ -8256,6 +8282,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleController' => 'PhabricatorController',
|
||||
'PhabricatorPeopleCreateController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleCreateGuidanceContext' => 'PhabricatorGuidanceContext',
|
||||
'PhabricatorPeopleDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'PhabricatorPeopleDeleteController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleDetailsProfilePanel' => 'PhabricatorProfilePanel',
|
||||
|
@ -9304,6 +9331,7 @@ phutil_register_library_map(array(
|
|||
'PhamePostEditEngine' => 'PhabricatorEditEngine',
|
||||
'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhamePostFulltextEngine' => 'PhabricatorFulltextEngine',
|
||||
'PhamePostHeaderPictureController' => 'PhamePostController',
|
||||
'PhamePostHistoryController' => 'PhamePostController',
|
||||
'PhamePostListController' => 'PhamePostController',
|
||||
'PhamePostListView' => 'AphrontTagView',
|
||||
|
|
|
@ -94,58 +94,12 @@ final class PhabricatorAuthListController
|
|||
$crumbs->addTextCrumb(pht('Auth Providers'));
|
||||
$crumbs->setBorder(true);
|
||||
|
||||
$domains_key = 'auth.email-domains';
|
||||
$domains_link = $this->renderConfigLink($domains_key);
|
||||
$domains_value = PhabricatorEnv::getEnvConfig($domains_key);
|
||||
$guidance_context = new PhabricatorAuthProvidersGuidanceContext();
|
||||
|
||||
$approval_key = 'auth.require-approval';
|
||||
$approval_link = $this->renderConfigLink($approval_key);
|
||||
$approval_value = PhabricatorEnv::getEnvConfig($approval_key);
|
||||
|
||||
$issues = array();
|
||||
if ($domains_value) {
|
||||
$issues[] = pht(
|
||||
'Phabricator is configured with an email domain whitelist (in %s), so '.
|
||||
'only users with a verified email address at one of these %s '.
|
||||
'allowed domain(s) will be able to register an account: %s',
|
||||
$domains_link,
|
||||
phutil_count($domains_value),
|
||||
phutil_tag('strong', array(), implode(', ', $domains_value)));
|
||||
} else {
|
||||
$issues[] = pht(
|
||||
'Anyone who can browse to this Phabricator install will be able to '.
|
||||
'register an account. To add email domain restrictions, configure '.
|
||||
'%s.',
|
||||
$domains_link);
|
||||
}
|
||||
|
||||
if ($approval_value) {
|
||||
$issues[] = pht(
|
||||
'Administrative approvals are enabled (in %s), so all new users must '.
|
||||
'have their accounts approved by an administrator.',
|
||||
$approval_link);
|
||||
} else {
|
||||
$issues[] = pht(
|
||||
'Administrative approvals are disabled, so users who register will '.
|
||||
'be able to use their accounts immediately. To enable approvals, '.
|
||||
'configure %s.',
|
||||
$approval_link);
|
||||
}
|
||||
|
||||
if (!$domains_value && !$approval_value) {
|
||||
$severity = PHUIInfoView::SEVERITY_WARNING;
|
||||
$issues[] = pht(
|
||||
'You can safely ignore this warning if the install itself has '.
|
||||
'access controls (for example, it is deployed on a VPN) or if all of '.
|
||||
'the configured providers have access controls (for example, they are '.
|
||||
'all private LDAP or OAuth servers).');
|
||||
} else {
|
||||
$severity = PHUIInfoView::SEVERITY_NOTICE;
|
||||
}
|
||||
|
||||
$warning = id(new PHUIInfoView())
|
||||
->setSeverity($severity)
|
||||
->setErrors($issues);
|
||||
$guidance = id(new PhabricatorGuidanceEngine())
|
||||
->setViewer($viewer)
|
||||
->setGuidanceContext($guidance_context)
|
||||
->newInfoView();
|
||||
|
||||
$button = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
|
@ -170,7 +124,7 @@ final class PhabricatorAuthListController
|
|||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setFooter(array(
|
||||
$warning,
|
||||
$guidance,
|
||||
$list,
|
||||
));
|
||||
|
||||
|
@ -180,14 +134,4 @@ final class PhabricatorAuthListController
|
|||
->appendChild($view);
|
||||
}
|
||||
|
||||
private function renderConfigLink($key) {
|
||||
return phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/config/edit/'.$key.'/',
|
||||
'target' => '_blank',
|
||||
),
|
||||
$key);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
36
src/applications/auth/data/PhabricatorAuthSessionInfo.php
Normal file
36
src/applications/auth/data/PhabricatorAuthSessionInfo.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthSessionInfo extends Phobject {
|
||||
|
||||
private $sessionType;
|
||||
private $identityPHID;
|
||||
private $isPartial;
|
||||
|
||||
public function setSessionType($session_type) {
|
||||
$this->sessionType = $session_type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSessionType() {
|
||||
return $this->sessionType;
|
||||
}
|
||||
|
||||
public function setIdentityPHID($identity_phid) {
|
||||
$this->identityPHID = $identity_phid;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIdentityPHID() {
|
||||
return $this->identityPHID;
|
||||
}
|
||||
|
||||
public function setIsPartial($is_partial) {
|
||||
$this->isPartial = $is_partial;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsPartial() {
|
||||
return $this->isPartial;
|
||||
}
|
||||
|
||||
}
|
|
@ -270,6 +270,16 @@ final class PhabricatorAuthSessionEngine extends Phobject {
|
|||
$log->save();
|
||||
unset($unguarded);
|
||||
|
||||
$info = id(new PhabricatorAuthSessionInfo())
|
||||
->setSessionType($session_type)
|
||||
->setIdentityPHID($identity_phid)
|
||||
->setIsPartial($partial);
|
||||
|
||||
$extensions = PhabricatorAuthSessionEngineExtension::getAllExtensions();
|
||||
foreach ($extensions as $extension) {
|
||||
$extension->didEstablishSession($info);
|
||||
}
|
||||
|
||||
return $session_key;
|
||||
}
|
||||
|
||||
|
@ -837,6 +847,11 @@ final class PhabricatorAuthSessionEngine extends Phobject {
|
|||
|
||||
// Switch to the user's translation.
|
||||
PhabricatorEnv::setLocaleCode($user->getTranslation());
|
||||
|
||||
$extensions = PhabricatorAuthSessionEngineExtension::getAllExtensions();
|
||||
foreach ($extensions as $extension) {
|
||||
$extension->willServeRequestForUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,14 @@ abstract class PhabricatorAuthSessionEngineExtension
|
|||
|
||||
abstract public function getExtensionName();
|
||||
|
||||
public function didEstablishSession(PhabricatorAuthSessionInfo $info) {
|
||||
return;
|
||||
}
|
||||
|
||||
public function willServeRequestForUser(PhabricatorUser $user) {
|
||||
return;
|
||||
}
|
||||
|
||||
public function didLogout(PhabricatorUser $user, array $sessions) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthProvidersGuidanceContext
|
||||
extends PhabricatorGuidanceContext {}
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAuthProvidersGuidanceEngineExtension
|
||||
extends PhabricatorGuidanceEngineExtension {
|
||||
|
||||
const GUIDANCEKEY = 'core.auth.providers';
|
||||
|
||||
public function canGenerateGuidance(PhabricatorGuidanceContext $context) {
|
||||
return ($context instanceof PhabricatorAuthProvidersGuidanceContext);
|
||||
}
|
||||
|
||||
public function generateGuidance(PhabricatorGuidanceContext $context) {
|
||||
$domains_key = 'auth.email-domains';
|
||||
$domains_link = $this->renderConfigLink($domains_key);
|
||||
$domains_value = PhabricatorEnv::getEnvConfig($domains_key);
|
||||
|
||||
$approval_key = 'auth.require-approval';
|
||||
$approval_link = $this->renderConfigLink($approval_key);
|
||||
$approval_value = PhabricatorEnv::getEnvConfig($approval_key);
|
||||
|
||||
$results = array();
|
||||
|
||||
if ($domains_value) {
|
||||
$message = pht(
|
||||
'Phabricator is configured with an email domain whitelist (in %s), so '.
|
||||
'only users with a verified email address at one of these %s '.
|
||||
'allowed domain(s) will be able to register an account: %s',
|
||||
$domains_link,
|
||||
phutil_count($domains_value),
|
||||
phutil_tag('strong', array(), implode(', ', $domains_value)));
|
||||
|
||||
$results[] = $this->newGuidance('core.auth.email-domains.on')
|
||||
->setMessage($message);
|
||||
} else {
|
||||
$message = pht(
|
||||
'Anyone who can browse to this Phabricator install will be able to '.
|
||||
'register an account. To add email domain restrictions, configure '.
|
||||
'%s.',
|
||||
$domains_link);
|
||||
|
||||
$results[] = $this->newGuidance('core.auth.email-domains.off')
|
||||
->setMessage($message);
|
||||
}
|
||||
|
||||
if ($approval_value) {
|
||||
$message = pht(
|
||||
'Administrative approvals are enabled (in %s), so all new users must '.
|
||||
'have their accounts approved by an administrator.',
|
||||
$approval_link);
|
||||
|
||||
$results[] = $this->newGuidance('core.auth.require-approval.on')
|
||||
->setMessage($message);
|
||||
} else {
|
||||
$message = pht(
|
||||
'Administrative approvals are disabled, so users who register will '.
|
||||
'be able to use their accounts immediately. To enable approvals, '.
|
||||
'configure %s.',
|
||||
$approval_link);
|
||||
|
||||
$results[] = $this->newGuidance('core.auth.require-approval.off')
|
||||
->setMessage($message);
|
||||
}
|
||||
|
||||
if (!$domains_value && !$approval_value) {
|
||||
$message = pht(
|
||||
'You can safely ignore these warnings if the install itself has '.
|
||||
'access controls (for example, it is deployed on a VPN) or if all of '.
|
||||
'the configured providers have access controls (for example, they are '.
|
||||
'all private LDAP or OAuth servers).');
|
||||
|
||||
$results[] = $this->newWarning('core.auth.warning')
|
||||
->setMessage($message);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function renderConfigLink($key) {
|
||||
return phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/config/edit/'.$key.'/',
|
||||
'target' => '_blank',
|
||||
),
|
||||
$key);
|
||||
}
|
||||
|
||||
}
|
|
@ -309,16 +309,48 @@ final class PhabricatorCalendarEventViewController
|
|||
$status_declined => 'red',
|
||||
);
|
||||
|
||||
$viewer_phid = $viewer->getPHID();
|
||||
$is_rsvp_invited = $event->isRSVPInvited($viewer_phid);
|
||||
$type_user = PhabricatorPeopleUserPHIDType::TYPECONST;
|
||||
|
||||
$head = array();
|
||||
$tail = array();
|
||||
foreach ($invitees as $invitee) {
|
||||
$item = new PHUIStatusItemView();
|
||||
$invitee_phid = $invitee->getInviteePHID();
|
||||
$status = $invitee->getStatus();
|
||||
$target = $viewer->renderHandle($invitee_phid);
|
||||
$is_user = (phid_get_type($invitee_phid) == $type_user);
|
||||
|
||||
if (!$is_user) {
|
||||
$icon = 'fa-users';
|
||||
$icon_color = 'blue';
|
||||
} else {
|
||||
$icon = $icon_map[$status];
|
||||
$icon_color = $icon_color_map[$status];
|
||||
}
|
||||
|
||||
// Highlight invited groups which you're a member of if you have
|
||||
// not RSVP'd to an event yet.
|
||||
if ($is_rsvp_invited) {
|
||||
if ($invitee_phid != $viewer_phid) {
|
||||
if ($event->hasRSVPAuthority($viewer_phid, $invitee_phid)) {
|
||||
$item->setHighlighted(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$item->setIcon($icon, $icon_color)
|
||||
->setTarget($target);
|
||||
|
||||
if ($is_user) {
|
||||
$tail[] = $item;
|
||||
} else {
|
||||
$head[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_merge($head, $tail) as $item) {
|
||||
$invitee_list->addItem($item);
|
||||
}
|
||||
} else {
|
||||
|
@ -511,6 +543,7 @@ final class PhabricatorCalendarEventViewController
|
|||
$event = id(new PhabricatorCalendarEventQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->needRSVPs(array($viewer->getPHID()))
|
||||
->executeOne();
|
||||
if (!$event) {
|
||||
return null;
|
||||
|
@ -586,10 +619,8 @@ final class PhabricatorCalendarEventViewController
|
|||
$viewer = $this->getViewer();
|
||||
$id = $event->getID();
|
||||
|
||||
$invite_status = $event->getUserInviteStatus($viewer->getPHID());
|
||||
$status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED;
|
||||
$is_invite_pending = ($invite_status == $status_invited);
|
||||
if (!$is_invite_pending) {
|
||||
$is_pending = $event->isRSVPInvited($viewer->getPHID());
|
||||
if (!$is_pending) {
|
||||
return array();
|
||||
}
|
||||
|
||||
|
|
|
@ -82,6 +82,9 @@ final class PhabricatorCalendarExportICSController
|
|||
$saved->setParameter('rangeEnd', null);
|
||||
$saved->setParameter('upcoming', null);
|
||||
|
||||
// The "month" and "day" display modes imply time ranges.
|
||||
$saved->setParameter('display', 'list');
|
||||
|
||||
$query = $engine->buildQueryFromSavedQuery($saved);
|
||||
|
||||
$events = $query
|
||||
|
|
|
@ -108,6 +108,8 @@ final class PhabricatorCalendarEventEditEngine
|
|||
->setConduitTypeDescription(pht('New event name.'))
|
||||
->setValue($object->getName()),
|
||||
id(new PhabricatorBoolEditField())
|
||||
->setIsLockable(false)
|
||||
->setIsDefaultable(false)
|
||||
->setKey('isAllDay')
|
||||
->setOptions(pht('Normal Event'), pht('All Day Event'))
|
||||
->setAsCheckbox(true)
|
||||
|
@ -151,6 +153,8 @@ final class PhabricatorCalendarEventEditEngine
|
|||
->setConduitTypeDescription(pht('True to cancel the event.'))
|
||||
->setValue($object->getIsCancelled()),
|
||||
id(new PhabricatorUsersEditField())
|
||||
->setIsLockable(false)
|
||||
->setIsDefaultable(false)
|
||||
->setKey('hostPHID')
|
||||
->setAliases(array('host'))
|
||||
->setLabel(pht('Host'))
|
||||
|
@ -162,6 +166,8 @@ final class PhabricatorCalendarEventEditEngine
|
|||
->setConduitTypeDescription(pht('New event host.'))
|
||||
->setSingleValue($object->getHostPHID()),
|
||||
id(new PhabricatorDatasourceEditField())
|
||||
->setIsLockable(false)
|
||||
->setIsDefaultable(false)
|
||||
->setIsHidden($is_future)
|
||||
->setKey('inviteePHIDs')
|
||||
->setAliases(array('invite', 'invitee', 'invitees', 'inviteePHID'))
|
||||
|
@ -203,6 +209,8 @@ final class PhabricatorCalendarEventEditEngine
|
|||
|
||||
id(new PhabricatorBoolEditField())
|
||||
->setIsHidden(true)
|
||||
->setIsLockable(false)
|
||||
->setIsDefaultable(false)
|
||||
->setKey('isRecurring')
|
||||
->setLabel(pht('Recurring'))
|
||||
->setOptions(pht('One-Time Event'), pht('Recurring Event'))
|
||||
|
@ -213,6 +221,8 @@ final class PhabricatorCalendarEventEditEngine
|
|||
->setConduitTypeDescription(pht('Mark the event as a recurring event.'))
|
||||
->setValue(true),
|
||||
id(new PhabricatorSelectEditField())
|
||||
->setIsLockable(false)
|
||||
->setIsDefaultable(false)
|
||||
->setKey('frequency')
|
||||
->setLabel(pht('Frequency'))
|
||||
->setOptions($frequency_options)
|
||||
|
|
|
@ -43,7 +43,7 @@ final class PhabricatorCalendarEventInviteesPolicyRule
|
|||
if (!isset($this->sourcePHIDs[$viewer_phid])) {
|
||||
$source_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$viewer_phid,
|
||||
PhabricatorProjectMemberOfProjectEdgeType::EDGECONST);
|
||||
PhabricatorProjectMaterializedMemberEdgeType::EDGECONST);
|
||||
$source_phids[] = $viewer_phid;
|
||||
$this->sourcePHIDs[$viewer_phid] = $source_phids;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ final class PhabricatorCalendarEventQuery
|
|||
private $utcInitialEpochMin;
|
||||
private $utcInitialEpochMax;
|
||||
private $isImported;
|
||||
private $needRSVPs;
|
||||
|
||||
private $generateGhosts = false;
|
||||
|
||||
|
@ -109,6 +110,11 @@ final class PhabricatorCalendarEventQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function needRSVPs(array $phids) {
|
||||
$this->needRSVPs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getDefaultOrderVector() {
|
||||
return array('start', 'id');
|
||||
}
|
||||
|
@ -166,7 +172,6 @@ final class PhabricatorCalendarEventQuery
|
|||
}
|
||||
|
||||
$raw_limit = $this->getRawResultLimit();
|
||||
|
||||
if (!$raw_limit && !$this->rangeEnd) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
@ -614,6 +619,70 @@ final class PhabricatorCalendarEventQuery
|
|||
|
||||
$events = msort($events, 'getStartDateTimeEpoch');
|
||||
|
||||
if ($this->needRSVPs) {
|
||||
$rsvp_phids = $this->needRSVPs;
|
||||
$project_type = PhabricatorProjectProjectPHIDType::TYPECONST;
|
||||
|
||||
$project_phids = array();
|
||||
foreach ($events as $event) {
|
||||
foreach ($event->getInvitees() as $invitee) {
|
||||
$invitee_phid = $invitee->getInviteePHID();
|
||||
if (phid_get_type($invitee_phid) == $project_type) {
|
||||
$project_phids[] = $invitee_phid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($project_phids) {
|
||||
$member_type = PhabricatorProjectMaterializedMemberEdgeType::EDGECONST;
|
||||
|
||||
$query = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs($project_phids)
|
||||
->withEdgeTypes(array($member_type))
|
||||
->withDestinationPHIDs($rsvp_phids);
|
||||
|
||||
$edges = $query->execute();
|
||||
|
||||
$project_map = array();
|
||||
foreach ($edges as $src => $types) {
|
||||
foreach ($types as $type => $dsts) {
|
||||
foreach ($dsts as $dst => $edge) {
|
||||
$project_map[$dst][] = $src;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$project_map = array();
|
||||
}
|
||||
|
||||
$membership_map = array();
|
||||
foreach ($rsvp_phids as $rsvp_phid) {
|
||||
$membership_map[$rsvp_phid] = array();
|
||||
$membership_map[$rsvp_phid][] = $rsvp_phid;
|
||||
|
||||
$project_phids = idx($project_map, $rsvp_phid);
|
||||
if ($project_phids) {
|
||||
foreach ($project_phids as $project_phid) {
|
||||
$membership_map[$rsvp_phid][] = $project_phid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($events as $event) {
|
||||
$invitees = $event->getInvitees();
|
||||
$invitees = mpull($invitees, null, 'getInviteePHID');
|
||||
|
||||
$rsvp_map = array();
|
||||
foreach ($rsvp_phids as $rsvp_phid) {
|
||||
$membership_phids = $membership_map[$rsvp_phid];
|
||||
$rsvps = array_select_keys($invitees, $membership_phids);
|
||||
$rsvp_map[$rsvp_phid] = $rsvps;
|
||||
}
|
||||
|
||||
$event->attachRSVPs($rsvp_map);
|
||||
}
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,10 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
}
|
||||
|
||||
public function newQuery() {
|
||||
return new PhabricatorCalendarEventQuery();
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
return id(new PhabricatorCalendarEventQuery())
|
||||
->needRSVPs(array($viewer->getPHID()));
|
||||
}
|
||||
|
||||
protected function shouldShowOrderField() {
|
||||
|
@ -33,7 +36,7 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
id(new PhabricatorSearchDatasourceField())
|
||||
->setLabel(pht('Invited'))
|
||||
->setKey('invitedPHIDs')
|
||||
->setDatasource(new PhabricatorPeopleUserFunctionDatasource()),
|
||||
->setDatasource(new PhabricatorCalendarInviteeDatasource()),
|
||||
id(new PhabricatorSearchDateControlField())
|
||||
->setLabel(pht('Occurs After'))
|
||||
->setKey('rangeStart'),
|
||||
|
@ -79,6 +82,18 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
);
|
||||
}
|
||||
|
||||
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
|
||||
$query = parent::buildQueryFromSavedQuery($saved);
|
||||
|
||||
// If this is an export query for generating an ".ics" file, don't
|
||||
// build ghost events.
|
||||
if ($saved->getParameter('export')) {
|
||||
$query->setGenerateGhosts(false);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
$viewer = $this->requireViewer();
|
||||
|
@ -122,13 +137,7 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
$query->withImportSourcePHIDs($map['importSourcePHIDs']);
|
||||
}
|
||||
|
||||
// Generate ghosts (and ignore stub events) if we aren't querying for
|
||||
// specific events or exporting.
|
||||
if (!empty($map['export'])) {
|
||||
// This is a specific mode enabled by event exports.
|
||||
$query
|
||||
->withIsStub(false);
|
||||
} else if (!$map['ids'] && !$map['phids']) {
|
||||
if (!$map['ids'] && !$map['phids']) {
|
||||
$query
|
||||
->withIsStub(false)
|
||||
->setGenerateGhosts(true);
|
||||
|
@ -361,10 +370,14 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
|
||||
$month_view->setUser($viewer);
|
||||
|
||||
$viewer_phid = $viewer->getPHID();
|
||||
foreach ($events as $event) {
|
||||
$epoch_min = $event->getStartDateTimeEpoch();
|
||||
$epoch_max = $event->getEndDateTimeEpoch();
|
||||
|
||||
$is_invited = $event->isRSVPInvited($viewer_phid);
|
||||
$is_attending = $event->getIsUserAttending($viewer_phid);
|
||||
|
||||
$event_view = id(new AphrontCalendarEventView())
|
||||
->setHostPHID($event->getHostPHID())
|
||||
->setEpochRange($epoch_min, $epoch_max)
|
||||
|
@ -373,6 +386,8 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
->setURI($event->getURI())
|
||||
->setIsAllDay($event->getIsAllDay())
|
||||
->setIcon($event->getDisplayIcon($viewer))
|
||||
->setViewerIsInvited($is_invited || $is_attending)
|
||||
->setDatetimeSummary($event->renderEventDate($viewer, true))
|
||||
->setIconColor($event->getDisplayIconColor($viewer));
|
||||
|
||||
$month_view->addEvent($event_view);
|
||||
|
@ -441,6 +456,7 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
->setIconColor($status_color)
|
||||
->setName($event->getName())
|
||||
->setURI($event->getURI())
|
||||
->setDatetimeSummary($event->renderEventDate($viewer, true))
|
||||
->setIsCancelled($event->getIsCancelled());
|
||||
|
||||
$day_view->addEvent($event_view);
|
||||
|
|
|
@ -50,6 +50,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
private $parentEvent = self::ATTACHABLE;
|
||||
private $invitees = self::ATTACHABLE;
|
||||
private $importSource = self::ATTACHABLE;
|
||||
private $rsvps = self::ATTACHABLE;
|
||||
|
||||
private $viewerTimezone;
|
||||
|
||||
|
@ -537,16 +538,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
return $is_attending;
|
||||
}
|
||||
|
||||
public function getIsUserInvited($phid) {
|
||||
$uninvited_status = PhabricatorCalendarEventInvitee::STATUS_UNINVITED;
|
||||
$declined_status = PhabricatorCalendarEventInvitee::STATUS_DECLINED;
|
||||
$status = $this->getUserInviteStatus($phid);
|
||||
if ($status == $uninvited_status || $status == $declined_status) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getIsGhostEvent() {
|
||||
return $this->isGhostEvent;
|
||||
}
|
||||
|
@ -590,13 +581,15 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
$start = $this->newStartDateTime();
|
||||
$end = $this->newEndDateTime();
|
||||
|
||||
if ($show_end) {
|
||||
$min_date = $start->newPHPDateTime();
|
||||
$max_date = $end->newPHPDateTime();
|
||||
|
||||
if ($this->getIsAllDay()) {
|
||||
// Subtract one second since the stored date is exclusive.
|
||||
$max_date = $max_date->modify('-1 second');
|
||||
}
|
||||
|
||||
if ($show_end) {
|
||||
$min_day = $min_date->format('Y m d');
|
||||
$max_day = $max_date->format('Y m d');
|
||||
|
||||
|
@ -605,8 +598,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
$show_end_date = false;
|
||||
}
|
||||
|
||||
$min_epoch = $start->getEpoch();
|
||||
$max_epoch = $end->getEpoch();
|
||||
$min_epoch = $min_date->format('U');
|
||||
$max_epoch = $max_date->format('U');
|
||||
|
||||
if ($this->getIsAllDay()) {
|
||||
if ($show_end_date) {
|
||||
|
@ -643,14 +636,19 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
}
|
||||
|
||||
if ($viewer->isLoggedIn()) {
|
||||
$status = $this->getUserInviteStatus($viewer->getPHID());
|
||||
$viewer_phid = $viewer->getPHID();
|
||||
if ($this->isRSVPInvited($viewer_phid)) {
|
||||
return 'fa-users';
|
||||
} else {
|
||||
$status = $this->getUserInviteStatus($viewer_phid);
|
||||
switch ($status) {
|
||||
case PhabricatorCalendarEventInvitee::STATUS_ATTENDING:
|
||||
return 'fa-check-circle';
|
||||
case PhabricatorCalendarEventInvitee::STATUS_INVITED:
|
||||
return 'fa-user-plus';
|
||||
case PhabricatorCalendarEventInvitee::STATUS_DECLINED:
|
||||
return 'fa-times';
|
||||
return 'fa-times-circle';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -671,7 +669,12 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
}
|
||||
|
||||
if ($viewer->isLoggedIn()) {
|
||||
$status = $this->getUserInviteStatus($viewer->getPHID());
|
||||
$viewer_phid = $viewer->getPHID();
|
||||
if ($this->isRSVPInvited($viewer_phid)) {
|
||||
return 'green';
|
||||
}
|
||||
|
||||
$status = $this->getUserInviteStatus($viewer_phid);
|
||||
switch ($status) {
|
||||
case PhabricatorCalendarEventInvitee::STATUS_ATTENDING:
|
||||
return 'green';
|
||||
|
@ -1121,6 +1124,52 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
return $phids;
|
||||
}
|
||||
|
||||
public function getRSVPs($phid) {
|
||||
return $this->assertAttachedKey($this->rsvps, $phid);
|
||||
}
|
||||
|
||||
public function attachRSVPs(array $rsvps) {
|
||||
$this->rsvps = $rsvps;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isRSVPInvited($phid) {
|
||||
$status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED;
|
||||
return ($this->getRSVPStatus($phid) == $status_invited);
|
||||
}
|
||||
|
||||
public function hasRSVPAuthority($phid, $other_phid) {
|
||||
foreach ($this->getRSVPs($phid) as $rsvp) {
|
||||
if ($rsvp->getInviteePHID() == $other_phid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getRSVPStatus($phid) {
|
||||
// Check for an individual invitee record first.
|
||||
$invitees = $this->invitees;
|
||||
$invitees = mpull($invitees, null, 'getInviteePHID');
|
||||
$invitee = idx($invitees, $phid);
|
||||
if ($invitee) {
|
||||
return $invitee->getStatus();
|
||||
}
|
||||
|
||||
// If we don't have one, try to find an invited status for the user's
|
||||
// projects.
|
||||
$status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED;
|
||||
foreach ($this->getRSVPs($phid) as $rsvp) {
|
||||
if ($rsvp->getStatus() == $status_invited) {
|
||||
return $status_invited;
|
||||
}
|
||||
}
|
||||
|
||||
return PhabricatorCalendarEventInvitee::STATUS_UNINVITED;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -( Markup Interface )--------------------------------------------------- */
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarInviteeDatasource
|
||||
extends PhabricatorTypeaheadCompositeDatasource {
|
||||
|
||||
public function getBrowseTitle() {
|
||||
return pht('Browse Invitees');
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type a user or project name, or function...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorCalendarApplication';
|
||||
}
|
||||
|
||||
public function getComponentDatasources() {
|
||||
return array(
|
||||
new PhabricatorCalendarInviteeUserDatasource(),
|
||||
new PhabricatorCalendarInviteeViewerFunctionDatasource(),
|
||||
new DifferentialExactUserFunctionDatasource(),
|
||||
new PhabricatorProjectDatasource(),
|
||||
);
|
||||
}
|
||||
|
||||
public static function expandInvitees(
|
||||
PhabricatorUser $viewer,
|
||||
array $values) {
|
||||
|
||||
$phids = array();
|
||||
foreach ($values as $value) {
|
||||
if (phid_get_type($value) == PhabricatorPeopleUserPHIDType::TYPECONST) {
|
||||
$phids[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$phids) {
|
||||
return $values;
|
||||
}
|
||||
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withMemberPHIDs($phids)
|
||||
->execute();
|
||||
foreach ($projects as $project) {
|
||||
$values[] = $project->getPHID();
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarInviteeUserDatasource
|
||||
extends PhabricatorTypeaheadCompositeDatasource {
|
||||
|
||||
public function getBrowseTitle() {
|
||||
return pht('Browse Users');
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type a user name...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorCalendarApplication';
|
||||
}
|
||||
|
||||
public function getComponentDatasources() {
|
||||
return array(
|
||||
new PhabricatorPeopleDatasource(),
|
||||
);
|
||||
}
|
||||
|
||||
protected function evaluateValues(array $values) {
|
||||
return PhabricatorCalendarInviteeDatasource::expandInvitees(
|
||||
$this->getViewer(),
|
||||
$values);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarInviteeViewerFunctionDatasource
|
||||
extends PhabricatorTypeaheadDatasource {
|
||||
|
||||
public function getBrowseTitle() {
|
||||
return pht('Browse Viewer');
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type viewer()...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorPeopleApplication';
|
||||
}
|
||||
|
||||
public function getDatasourceFunctions() {
|
||||
return array(
|
||||
'viewer' => array(
|
||||
'name' => pht('Current Viewer'),
|
||||
'summary' => pht('Use the current viewing user.'),
|
||||
'description' => pht(
|
||||
'Show invites the current viewer is invited to. This function '.
|
||||
'includes events the user is invited to because a project they '.
|
||||
'are a member of is invited.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function loadResults() {
|
||||
if ($this->getViewer()->getPHID()) {
|
||||
$results = array($this->renderViewerFunctionToken());
|
||||
} else {
|
||||
$results = array();
|
||||
}
|
||||
|
||||
return $this->filterResultsAgainstTokens($results);
|
||||
}
|
||||
|
||||
protected function canEvaluateFunction($function) {
|
||||
if (!$this->getViewer()->getPHID()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::canEvaluateFunction($function);
|
||||
}
|
||||
|
||||
protected function evaluateFunction($function, array $argv_list) {
|
||||
$results = array();
|
||||
foreach ($argv_list as $argv) {
|
||||
$results[] = $this->getViewer()->getPHID();
|
||||
}
|
||||
|
||||
return PhabricatorCalendarInviteeDatasource::expandInvitees(
|
||||
$this->getViewer(),
|
||||
$results);
|
||||
}
|
||||
|
||||
public function renderFunctionTokens($function, array $argv_list) {
|
||||
$tokens = array();
|
||||
foreach ($argv_list as $argv) {
|
||||
$tokens[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult(
|
||||
$this->renderViewerFunctionToken());
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
private function renderViewerFunctionToken() {
|
||||
return $this->newFunctionResult()
|
||||
->setName(pht('Current Viewer'))
|
||||
->setPHID('viewer()')
|
||||
->setIcon('fa-user')
|
||||
->setUnique(true);
|
||||
}
|
||||
|
||||
}
|
|
@ -15,6 +15,7 @@ final class AphrontCalendarEventView extends AphrontView {
|
|||
private $iconColor;
|
||||
private $canEdit;
|
||||
private $isCancelled;
|
||||
private $datetimeSummary;
|
||||
|
||||
public function setIconColor($icon_color) {
|
||||
$this->iconColor = $icon_color;
|
||||
|
@ -135,6 +136,15 @@ final class AphrontCalendarEventView extends AphrontView {
|
|||
return false;
|
||||
}
|
||||
|
||||
public function setDatetimeSummary($datetime_summary) {
|
||||
$this->datetimeSummary = $datetime_summary;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDatetimeSummary() {
|
||||
return $this->datetimeSummary;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
throw new Exception(pht('Events are only rendered indirectly.'));
|
||||
}
|
||||
|
|
|
@ -45,9 +45,10 @@ final class PhabricatorConfigApplication extends PhabricatorApplication {
|
|||
'group/(?P<key>[^/]+)/' => 'PhabricatorConfigGroupController',
|
||||
'version/' => 'PhabricatorConfigVersionController',
|
||||
'database/'.
|
||||
'(?:(?P<ref>[^/]+)/'.
|
||||
'(?:(?P<database>[^/]+)/'.
|
||||
'(?:(?P<table>[^/]+)/'.
|
||||
'(?:(?:col/(?P<column>[^/]+)|key/(?P<key>[^/]+))/)?)?)?'
|
||||
'(?:(?:col/(?P<column>[^/]+)|key/(?P<key>[^/]+))/)?)?)?)?'
|
||||
=> 'PhabricatorConfigDatabaseStatusController',
|
||||
'dbissue/' => 'PhabricatorConfigDatabaseIssueController',
|
||||
'(?P<verb>ignore|unignore)/(?P<key>[^/]+)/'
|
||||
|
|
|
@ -12,89 +12,6 @@ final class PhabricatorDatabaseSetupCheck extends PhabricatorSetupCheck {
|
|||
}
|
||||
|
||||
protected function executeChecks() {
|
||||
$master = PhabricatorDatabaseRef::getMasterDatabaseRef();
|
||||
if (!$master) {
|
||||
// If we're implicitly in read-only mode during disaster recovery,
|
||||
// don't bother with these setup checks.
|
||||
return;
|
||||
}
|
||||
|
||||
$conn_raw = $master->newManagementConnection();
|
||||
|
||||
try {
|
||||
queryfx($conn_raw, 'SELECT 1');
|
||||
$database_exception = null;
|
||||
} catch (AphrontInvalidCredentialsQueryException $ex) {
|
||||
$database_exception = $ex;
|
||||
} catch (AphrontConnectionQueryException $ex) {
|
||||
$database_exception = $ex;
|
||||
}
|
||||
|
||||
if ($database_exception) {
|
||||
$issue = PhabricatorSetupIssue::newDatabaseConnectionIssue(
|
||||
$database_exception);
|
||||
$this->addIssue($issue);
|
||||
return;
|
||||
}
|
||||
|
||||
$engines = queryfx_all($conn_raw, 'SHOW ENGINES');
|
||||
$engines = ipull($engines, 'Support', 'Engine');
|
||||
|
||||
$innodb = idx($engines, 'InnoDB');
|
||||
if ($innodb != 'YES' && $innodb != 'DEFAULT') {
|
||||
$message = pht(
|
||||
"The 'InnoDB' engine is not available in MySQL. Enable InnoDB in ".
|
||||
"your MySQL configuration.".
|
||||
"\n\n".
|
||||
"(If you aleady created tables, MySQL incorrectly used some other ".
|
||||
"engine to create them. You need to convert them or drop and ".
|
||||
"reinitialize them.)");
|
||||
|
||||
$this->newIssue('mysql.innodb')
|
||||
->setName(pht('MySQL InnoDB Engine Not Available'))
|
||||
->setMessage($message)
|
||||
->setIsFatal(true);
|
||||
return;
|
||||
}
|
||||
|
||||
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
|
||||
|
||||
$databases = queryfx_all($conn_raw, 'SHOW DATABASES');
|
||||
$databases = ipull($databases, 'Database', 'Database');
|
||||
|
||||
if (empty($databases[$namespace.'_meta_data'])) {
|
||||
$message = pht(
|
||||
"Run the storage upgrade script to setup Phabricator's database ".
|
||||
"schema.");
|
||||
|
||||
$this->newIssue('storage.upgrade')
|
||||
->setName(pht('Setup MySQL Schema'))
|
||||
->setMessage($message)
|
||||
->setIsFatal(true)
|
||||
->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade'));
|
||||
} else {
|
||||
$conn_meta = $master->newApplicationConnection(
|
||||
$namespace.'_meta_data');
|
||||
|
||||
$applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status');
|
||||
$applied = ipull($applied, 'patch', 'patch');
|
||||
|
||||
$all = PhabricatorSQLPatchList::buildAllPatches();
|
||||
$diff = array_diff_key($all, $applied);
|
||||
|
||||
if ($diff) {
|
||||
$this->newIssue('storage.patch')
|
||||
->setName(pht('Upgrade MySQL Schema'))
|
||||
->setMessage(
|
||||
pht(
|
||||
"Run the storage upgrade script to upgrade Phabricator's ".
|
||||
"database schema. Missing patches:<br />%s<br />",
|
||||
phutil_implode_html(phutil_tag('br'), array_keys($diff))))
|
||||
->addCommand(
|
||||
hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade'));
|
||||
}
|
||||
}
|
||||
|
||||
$host = PhabricatorEnv::getEnvConfig('mysql.host');
|
||||
$matches = null;
|
||||
if (preg_match('/^([^:]+):(\d+)$/', $host, $matches)) {
|
||||
|
@ -126,5 +43,97 @@ final class PhabricatorDatabaseSetupCheck extends PhabricatorSetupCheck {
|
|||
$port));
|
||||
}
|
||||
|
||||
$masters = PhabricatorDatabaseRef::getMasterDatabaseRefs();
|
||||
if (!$masters) {
|
||||
// If we're implicitly in read-only mode during disaster recovery,
|
||||
// don't bother with these setup checks.
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($masters as $master) {
|
||||
if ($this->checkMasterDatabase($master)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function checkMasterDatabase(PhabricatorDatabaseRef $master) {
|
||||
$conn_raw = $master->newManagementConnection();
|
||||
|
||||
try {
|
||||
queryfx($conn_raw, 'SELECT 1');
|
||||
$database_exception = null;
|
||||
} catch (AphrontInvalidCredentialsQueryException $ex) {
|
||||
$database_exception = $ex;
|
||||
} catch (AphrontConnectionQueryException $ex) {
|
||||
$database_exception = $ex;
|
||||
}
|
||||
|
||||
if ($database_exception) {
|
||||
$issue = PhabricatorSetupIssue::newDatabaseConnectionIssue(
|
||||
$database_exception);
|
||||
$this->addIssue($issue);
|
||||
return true;
|
||||
}
|
||||
|
||||
$engines = queryfx_all($conn_raw, 'SHOW ENGINES');
|
||||
$engines = ipull($engines, 'Support', 'Engine');
|
||||
|
||||
$innodb = idx($engines, 'InnoDB');
|
||||
if ($innodb != 'YES' && $innodb != 'DEFAULT') {
|
||||
$message = pht(
|
||||
"The 'InnoDB' engine is not available in MySQL. Enable InnoDB in ".
|
||||
"your MySQL configuration.".
|
||||
"\n\n".
|
||||
"(If you aleady created tables, MySQL incorrectly used some other ".
|
||||
"engine to create them. You need to convert them or drop and ".
|
||||
"reinitialize them.)");
|
||||
|
||||
$this->newIssue('mysql.innodb')
|
||||
->setName(pht('MySQL InnoDB Engine Not Available'))
|
||||
->setMessage($message)
|
||||
->setIsFatal(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
|
||||
|
||||
$databases = queryfx_all($conn_raw, 'SHOW DATABASES');
|
||||
$databases = ipull($databases, 'Database', 'Database');
|
||||
|
||||
if (empty($databases[$namespace.'_meta_data'])) {
|
||||
$message = pht(
|
||||
"Run the storage upgrade script to setup Phabricator's database ".
|
||||
"schema.");
|
||||
|
||||
$this->newIssue('storage.upgrade')
|
||||
->setName(pht('Setup MySQL Schema'))
|
||||
->setMessage($message)
|
||||
->setIsFatal(true)
|
||||
->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade'));
|
||||
return true;
|
||||
}
|
||||
|
||||
$conn_meta = $master->newApplicationConnection(
|
||||
$namespace.'_meta_data');
|
||||
|
||||
$applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status');
|
||||
$applied = ipull($applied, 'patch', 'patch');
|
||||
|
||||
$all = PhabricatorSQLPatchList::buildAllPatches();
|
||||
$diff = array_diff_key($all, $applied);
|
||||
|
||||
if ($diff) {
|
||||
$this->newIssue('storage.patch')
|
||||
->setName(pht('Upgrade MySQL Schema'))
|
||||
->setMessage(
|
||||
pht(
|
||||
"Run the storage upgrade script to upgrade Phabricator's ".
|
||||
"database schema. Missing patches:<br />%s<br />",
|
||||
phutil_implode_html(phutil_tag('br'), array_keys($diff))))
|
||||
->addCommand(
|
||||
hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade'));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,22 +3,6 @@
|
|||
abstract class PhabricatorConfigDatabaseController
|
||||
extends PhabricatorConfigController {
|
||||
|
||||
protected function buildSchemaQuery() {
|
||||
$ref = PhabricatorDatabaseRef::getMasterDatabaseRef();
|
||||
|
||||
$api = id(new PhabricatorStorageManagementAPI())
|
||||
->setUser($ref->getUser())
|
||||
->setHost($ref->getHost())
|
||||
->setPort($ref->getPort())
|
||||
->setNamespace(PhabricatorLiskDAO::getDefaultStorageNamespace())
|
||||
->setPassword($ref->getPass());
|
||||
|
||||
$query = id(new PhabricatorConfigSchemaQuery())
|
||||
->setAPI($api);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function renderIcon($status) {
|
||||
switch ($status) {
|
||||
case PhabricatorConfigStorageSchema::STATUS_OKAY:
|
||||
|
|
|
@ -6,11 +6,11 @@ final class PhabricatorConfigDatabaseIssueController
|
|||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$query = $this->buildSchemaQuery();
|
||||
$query = new PhabricatorConfigSchemaQuery();
|
||||
|
||||
$actual = $query->loadActualSchema();
|
||||
$expect = $query->loadExpectedSchema();
|
||||
$comp = $query->buildComparisonSchema($expect, $actual);
|
||||
$actual = $query->loadActualSchemata();
|
||||
$expect = $query->loadExpectedSchemata();
|
||||
$comp_servers = $query->buildComparisonSchemata($expect, $actual);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Database Issues'));
|
||||
|
@ -18,9 +18,11 @@ final class PhabricatorConfigDatabaseIssueController
|
|||
|
||||
// Collect all open issues.
|
||||
$issues = array();
|
||||
foreach ($comp_servers as $ref_name => $comp) {
|
||||
foreach ($comp->getDatabases() as $database_name => $database) {
|
||||
foreach ($database->getLocalIssues() as $issue) {
|
||||
$issues[] = array(
|
||||
$ref_name,
|
||||
$database_name,
|
||||
null,
|
||||
null,
|
||||
|
@ -31,6 +33,7 @@ final class PhabricatorConfigDatabaseIssueController
|
|||
foreach ($database->getTables() as $table_name => $table) {
|
||||
foreach ($table->getLocalIssues() as $issue) {
|
||||
$issues[] = array(
|
||||
$ref_name,
|
||||
$database_name,
|
||||
$table_name,
|
||||
null,
|
||||
|
@ -41,6 +44,7 @@ final class PhabricatorConfigDatabaseIssueController
|
|||
foreach ($table->getColumns() as $column_name => $column) {
|
||||
foreach ($column->getLocalIssues() as $issue) {
|
||||
$issues[] = array(
|
||||
$ref_name,
|
||||
$database_name,
|
||||
$table_name,
|
||||
'column',
|
||||
|
@ -52,6 +56,7 @@ final class PhabricatorConfigDatabaseIssueController
|
|||
foreach ($table->getKeys() as $key_name => $key) {
|
||||
foreach ($key->getLocalIssues() as $issue) {
|
||||
$issues[] = array(
|
||||
$ref_name,
|
||||
$database_name,
|
||||
$table_name,
|
||||
'key',
|
||||
|
@ -62,21 +67,21 @@ final class PhabricatorConfigDatabaseIssueController
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Sort all open issues so that the most severe issues appear first.
|
||||
$order = array();
|
||||
$counts = array();
|
||||
foreach ($issues as $key => $issue) {
|
||||
$const = $issue[4];
|
||||
$const = $issue[5];
|
||||
$status = PhabricatorConfigStorageSchema::getIssueStatus($const);
|
||||
$severity = PhabricatorConfigStorageSchema::getStatusSeverity($status);
|
||||
$order[$key] = sprintf(
|
||||
'~%d~%s%s%s',
|
||||
9 - $severity,
|
||||
$issue[0],
|
||||
$issue[1],
|
||||
$issue[3]);
|
||||
$issue[2],
|
||||
$issue[4]);
|
||||
|
||||
if (empty($counts[$status])) {
|
||||
$counts[$status] = 0;
|
||||
|
@ -91,22 +96,25 @@ final class PhabricatorConfigDatabaseIssueController
|
|||
// Render the issues.
|
||||
$rows = array();
|
||||
foreach ($issues as $issue) {
|
||||
$const = $issue[4];
|
||||
$const = $issue[5];
|
||||
|
||||
$uri = $this->getApplicationURI('/database/'.$issue[0].'/'.$issue[1].'/');
|
||||
|
||||
$database_link = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $this->getApplicationURI('/database/'.$issue[0].'/'),
|
||||
'href' => $uri,
|
||||
),
|
||||
$issue[0]);
|
||||
$issue[1]);
|
||||
|
||||
$rows[] = array(
|
||||
$this->renderIcon(
|
||||
PhabricatorConfigStorageSchema::getIssueStatus($const)),
|
||||
$issue[0],
|
||||
$database_link,
|
||||
$issue[1],
|
||||
$issue[2],
|
||||
$issue[3],
|
||||
$issue[4],
|
||||
PhabricatorConfigStorageSchema::getIssueDescription($const),
|
||||
);
|
||||
}
|
||||
|
@ -117,6 +125,7 @@ final class PhabricatorConfigDatabaseIssueController
|
|||
->setHeaders(
|
||||
array(
|
||||
null,
|
||||
pht('Server'),
|
||||
pht('Database'),
|
||||
pht('Table'),
|
||||
pht('Type'),
|
||||
|
@ -130,6 +139,7 @@ final class PhabricatorConfigDatabaseIssueController
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'wide',
|
||||
));
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ final class PhabricatorConfigDatabaseStatusController
|
|||
private $table;
|
||||
private $column;
|
||||
private $key;
|
||||
private $ref;
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
@ -14,49 +15,60 @@ final class PhabricatorConfigDatabaseStatusController
|
|||
$this->table = $request->getURIData('table');
|
||||
$this->column = $request->getURIData('column');
|
||||
$this->key = $request->getURIData('key');
|
||||
$this->ref = $request->getURIData('ref');
|
||||
|
||||
$query = $this->buildSchemaQuery();
|
||||
$query = new PhabricatorConfigSchemaQuery();
|
||||
|
||||
$actual = $query->loadActualSchema();
|
||||
$expect = $query->loadExpectedSchema();
|
||||
$comp = $query->buildComparisonSchema($expect, $actual);
|
||||
$actual = $query->loadActualSchemata();
|
||||
$expect = $query->loadExpectedSchemata();
|
||||
$comp = $query->buildComparisonSchemata($expect, $actual);
|
||||
|
||||
if ($this->ref !== null) {
|
||||
$server_actual = idx($actual, $this->ref);
|
||||
if (!$server_actual) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$server_comparison = $comp[$this->ref];
|
||||
$server_expect = $expect[$this->ref];
|
||||
|
||||
if ($this->column) {
|
||||
return $this->renderColumn(
|
||||
$comp,
|
||||
$expect,
|
||||
$actual,
|
||||
$server_comparison,
|
||||
$server_expect,
|
||||
$server_actual,
|
||||
$this->database,
|
||||
$this->table,
|
||||
$this->column);
|
||||
} else if ($this->key) {
|
||||
return $this->renderKey(
|
||||
$comp,
|
||||
$expect,
|
||||
$actual,
|
||||
$server_comparison,
|
||||
$server_expect,
|
||||
$server_actual,
|
||||
$this->database,
|
||||
$this->table,
|
||||
$this->key);
|
||||
} else if ($this->table) {
|
||||
return $this->renderTable(
|
||||
$comp,
|
||||
$expect,
|
||||
$actual,
|
||||
$server_comparison,
|
||||
$server_expect,
|
||||
$server_actual,
|
||||
$this->database,
|
||||
$this->table);
|
||||
} else if ($this->database) {
|
||||
return $this->renderDatabase(
|
||||
$comp,
|
||||
$expect,
|
||||
$actual,
|
||||
$server_comparison,
|
||||
$server_expect,
|
||||
$server_actual,
|
||||
$this->database);
|
||||
} else {
|
||||
return $this->renderServer(
|
||||
}
|
||||
}
|
||||
|
||||
return $this->renderServers(
|
||||
$comp,
|
||||
$expect,
|
||||
$actual);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildResponse($title, $body) {
|
||||
$nav = $this->buildSideNavView();
|
||||
|
@ -66,34 +78,57 @@ final class PhabricatorConfigDatabaseStatusController
|
|||
$title = pht('Database Status');
|
||||
}
|
||||
|
||||
$ref = $this->ref;
|
||||
$database = $this->database;
|
||||
$table = $this->table;
|
||||
$column = $this->column;
|
||||
$key = $this->key;
|
||||
|
||||
$links = array();
|
||||
$links[] = array(
|
||||
pht('Database Status'),
|
||||
'database/',
|
||||
);
|
||||
|
||||
if ($database) {
|
||||
$links[] = array(
|
||||
$database,
|
||||
"database/{$ref}/{$database}/",
|
||||
);
|
||||
}
|
||||
|
||||
if ($table) {
|
||||
$links[] = array(
|
||||
$table,
|
||||
"database/{$ref}/{$database}/{$table}/",
|
||||
);
|
||||
}
|
||||
|
||||
if ($column) {
|
||||
$links[] = array(
|
||||
$column,
|
||||
"database/{$ref}/{$database}/{$table}/col/{$column}/",
|
||||
);
|
||||
}
|
||||
|
||||
if ($key) {
|
||||
$links[] = array(
|
||||
$key,
|
||||
"database/{$ref}/{$database}/{$table}/key/{$key}/",
|
||||
);
|
||||
}
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->setBorder(true);
|
||||
if ($this->database) {
|
||||
$crumbs->addTextCrumb(
|
||||
pht('Database Status'),
|
||||
$this->getApplicationURI('database/'));
|
||||
if ($this->table) {
|
||||
$crumbs->addTextCrumb(
|
||||
$this->database,
|
||||
$this->getApplicationURI('database/'.$this->database.'/'));
|
||||
if ($this->column || $this->key) {
|
||||
$crumbs->addTextCrumb(
|
||||
$this->table,
|
||||
$this->getApplicationURI(
|
||||
'database/'.$this->database.'/'.$this->table.'/'));
|
||||
if ($this->column) {
|
||||
$crumbs->addTextCrumb($this->column);
|
||||
|
||||
$last_key = last_key($links);
|
||||
foreach ($links as $link_key => $link) {
|
||||
list($name, $href) = $link;
|
||||
if ($link_key == $last_key) {
|
||||
$crumbs->addTextCrumb($name);
|
||||
} else {
|
||||
$crumbs->addTextCrumb($this->key);
|
||||
$crumbs->addTextCrumb($name, $this->getApplicationURI($href));
|
||||
}
|
||||
} else {
|
||||
$crumbs->addTextCrumb($this->table);
|
||||
}
|
||||
} else {
|
||||
$crumbs->addTextCrumb($this->database);
|
||||
}
|
||||
} else {
|
||||
$crumbs->addTextCrumb(pht('Database Status'));
|
||||
}
|
||||
|
||||
$doc_link = PhabricatorEnv::getDoclink('Managing Storage Adjustments');
|
||||
|
@ -121,15 +156,18 @@ final class PhabricatorConfigDatabaseStatusController
|
|||
}
|
||||
|
||||
|
||||
private function renderServer(
|
||||
PhabricatorConfigServerSchema $comp,
|
||||
PhabricatorConfigServerSchema $expect,
|
||||
PhabricatorConfigServerSchema $actual) {
|
||||
private function renderServers(
|
||||
array $comp_servers,
|
||||
array $expect_servers,
|
||||
array $actual_servers) {
|
||||
|
||||
$charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
|
||||
$collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
|
||||
|
||||
$rows = array();
|
||||
foreach ($comp_servers as $ref_key => $comp) {
|
||||
$actual = $actual_servers[$ref_key];
|
||||
$expect = $expect_servers[$ref_key];
|
||||
foreach ($comp->getDatabases() as $database_name => $database) {
|
||||
$actual_database = $actual->getDatabase($database_name);
|
||||
if ($actual_database) {
|
||||
|
@ -143,30 +181,39 @@ final class PhabricatorConfigDatabaseStatusController
|
|||
$status = $database->getStatus();
|
||||
$issues = $database->getIssues();
|
||||
|
||||
$uri = $this->getURI(
|
||||
array(
|
||||
'ref' => $ref_key,
|
||||
'database' => $database_name,
|
||||
));
|
||||
|
||||
$rows[] = array(
|
||||
$this->renderIcon($status),
|
||||
$ref_key,
|
||||
phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $this->getApplicationURI(
|
||||
'/database/'.$database_name.'/'),
|
||||
'href' => $uri,
|
||||
),
|
||||
$database_name),
|
||||
$this->renderAttr($charset, $database->hasIssue($charset_issue)),
|
||||
$this->renderAttr($collation, $database->hasIssue($collation_issue)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$table = id(new AphrontTableView($rows))
|
||||
->setHeaders(
|
||||
array(
|
||||
null,
|
||||
pht('Server'),
|
||||
pht('Database'),
|
||||
pht('Charset'),
|
||||
pht('Collation'),
|
||||
))
|
||||
->setColumnClasses(
|
||||
array(
|
||||
null,
|
||||
null,
|
||||
'wide pri',
|
||||
null,
|
||||
|
@ -200,13 +247,17 @@ final class PhabricatorConfigDatabaseStatusController
|
|||
foreach ($database->getTables() as $table_name => $table) {
|
||||
$status = $table->getStatus();
|
||||
|
||||
$uri = $this->getURI(
|
||||
array(
|
||||
'table' => $table_name,
|
||||
));
|
||||
|
||||
$rows[] = array(
|
||||
$this->renderIcon($status),
|
||||
phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $this->getApplicationURI(
|
||||
'/database/'.$database_name.'/'.$table_name.'/'),
|
||||
'href' => $uri,
|
||||
),
|
||||
$table_name),
|
||||
$this->renderAttr(
|
||||
|
@ -251,6 +302,10 @@ final class PhabricatorConfigDatabaseStatusController
|
|||
|
||||
$properties = $this->buildProperties(
|
||||
array(
|
||||
array(
|
||||
pht('Server'),
|
||||
$this->ref,
|
||||
),
|
||||
array(
|
||||
pht('Character Set'),
|
||||
$actual_charset,
|
||||
|
@ -325,17 +380,17 @@ final class PhabricatorConfigDatabaseStatusController
|
|||
$data_type = $expect_column->getDataType();
|
||||
}
|
||||
|
||||
$uri = $this->getURI(
|
||||
array(
|
||||
'column' => $column_name,
|
||||
));
|
||||
|
||||
$rows[] = array(
|
||||
$this->renderIcon($status),
|
||||
phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $this->getApplicationURI(
|
||||
'database/'.
|
||||
$database_name.'/'.
|
||||
$table_name.'/'.
|
||||
'col/'.
|
||||
$column_name.'/'),
|
||||
'href' => $uri,
|
||||
),
|
||||
$column_name),
|
||||
$data_type,
|
||||
|
@ -407,17 +462,17 @@ final class PhabricatorConfigDatabaseStatusController
|
|||
$key->hasIssue($longkey_issue));
|
||||
}
|
||||
|
||||
$uri = $this->getURI(
|
||||
array(
|
||||
'key' => $key_name,
|
||||
));
|
||||
|
||||
$key_rows[] = array(
|
||||
$this->renderIcon($status),
|
||||
phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $this->getApplicationURI(
|
||||
'database/'.
|
||||
$database_name.'/'.
|
||||
$table_name.'/'.
|
||||
'key/'.
|
||||
$key_name.'/'),
|
||||
'href' => $uri,
|
||||
),
|
||||
$key_name),
|
||||
$this->renderAttr(
|
||||
|
@ -464,6 +519,10 @@ final class PhabricatorConfigDatabaseStatusController
|
|||
|
||||
$properties = $this->buildProperties(
|
||||
array(
|
||||
array(
|
||||
pht('Server'),
|
||||
$this->ref,
|
||||
),
|
||||
array(
|
||||
pht('Collation'),
|
||||
$actual_collation,
|
||||
|
@ -561,6 +620,10 @@ final class PhabricatorConfigDatabaseStatusController
|
|||
|
||||
$properties = $this->buildProperties(
|
||||
array(
|
||||
array(
|
||||
pht('Server'),
|
||||
$this->ref,
|
||||
),
|
||||
array(
|
||||
pht('Data Type'),
|
||||
$data_type,
|
||||
|
@ -678,6 +741,10 @@ final class PhabricatorConfigDatabaseStatusController
|
|||
|
||||
$properties = $this->buildProperties(
|
||||
array(
|
||||
array(
|
||||
pht('Server'),
|
||||
$this->ref,
|
||||
),
|
||||
array(
|
||||
pht('Unique'),
|
||||
$this->renderBoolean($actual_unique),
|
||||
|
@ -745,4 +812,40 @@ final class PhabricatorConfigDatabaseStatusController
|
|||
return phutil_tag_div('config-page-property', $view);
|
||||
}
|
||||
|
||||
private function getURI(array $properties) {
|
||||
$defaults = array(
|
||||
'ref' => $this->ref,
|
||||
'database' => $this->database,
|
||||
'table' => $this->table,
|
||||
'column' => $this->column,
|
||||
'key' => $this->key,
|
||||
);
|
||||
|
||||
$properties = $properties + $defaults;
|
||||
$properties = array_select_keys($properties, array_keys($defaults));
|
||||
|
||||
$parts = array();
|
||||
foreach ($properties as $key => $property) {
|
||||
if (!strlen($property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($key == 'column') {
|
||||
$parts[] = 'col';
|
||||
} else if ($key == 'key') {
|
||||
$parts[] = 'key';
|
||||
}
|
||||
|
||||
$parts[] = $property;
|
||||
}
|
||||
|
||||
if ($parts) {
|
||||
$parts = implode('/', $parts).'/';
|
||||
} else {
|
||||
$parts = null;
|
||||
}
|
||||
|
||||
return $this->getApplicationURI('/database/'.$parts);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,36 +2,70 @@
|
|||
|
||||
final class PhabricatorConfigSchemaQuery extends Phobject {
|
||||
|
||||
private $api;
|
||||
private $refs;
|
||||
private $apis;
|
||||
|
||||
public function setAPI(PhabricatorStorageManagementAPI $api) {
|
||||
$this->api = $api;
|
||||
public function setRefs(array $refs) {
|
||||
$this->refs = $refs;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getAPI() {
|
||||
if (!$this->api) {
|
||||
throw new PhutilInvalidStateException('setAPI');
|
||||
public function getRefs() {
|
||||
if (!$this->refs) {
|
||||
return PhabricatorDatabaseRef::getMasterDatabaseRefs();
|
||||
}
|
||||
return $this->api;
|
||||
return $this->refs;
|
||||
}
|
||||
|
||||
protected function getConn() {
|
||||
return $this->getAPI()->getConn(null);
|
||||
public function setAPIs(array $apis) {
|
||||
$map = array();
|
||||
foreach ($apis as $api) {
|
||||
$map[$api->getRef()->getRefKey()] = $api;
|
||||
}
|
||||
$this->apis = $map;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function getDatabaseNames() {
|
||||
$api = $this->getAPI();
|
||||
private function getDatabaseNames(PhabricatorDatabaseRef $ref) {
|
||||
$api = $this->getAPI($ref);
|
||||
$patches = PhabricatorSQLPatchList::buildAllPatches();
|
||||
return $api->getDatabaseList(
|
||||
$patches,
|
||||
$only_living = true);
|
||||
}
|
||||
|
||||
public function loadActualSchema() {
|
||||
$databases = $this->getDatabaseNames();
|
||||
private function getAPI(PhabricatorDatabaseRef $ref) {
|
||||
$key = $ref->getRefKey();
|
||||
|
||||
if (isset($this->apis[$key])) {
|
||||
return $this->apis[$key];
|
||||
}
|
||||
|
||||
return id(new PhabricatorStorageManagementAPI())
|
||||
->setUser($ref->getUser())
|
||||
->setHost($ref->getHost())
|
||||
->setPort($ref->getPort())
|
||||
->setNamespace(PhabricatorLiskDAO::getDefaultStorageNamespace())
|
||||
->setPassword($ref->getPass());
|
||||
}
|
||||
|
||||
public function loadActualSchemata() {
|
||||
$refs = $this->getRefs();
|
||||
|
||||
$schemata = array();
|
||||
foreach ($refs as $ref) {
|
||||
$schema = $this->loadActualSchemaForServer($ref);
|
||||
$schemata[$schema->getRef()->getRefKey()] = $schema;
|
||||
}
|
||||
|
||||
return $schemata;
|
||||
}
|
||||
|
||||
private function loadActualSchemaForServer(PhabricatorDatabaseRef $ref) {
|
||||
$databases = $this->getDatabaseNames($ref);
|
||||
|
||||
$conn = $ref->newManagementConnection();
|
||||
|
||||
$conn = $this->getConn();
|
||||
$tables = queryfx_all(
|
||||
$conn,
|
||||
'SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COLLATION
|
||||
|
@ -92,7 +126,8 @@ final class PhabricatorConfigSchemaQuery extends Phobject {
|
|||
// primary, unique, and foreign keys, so we can't use them here. We pull
|
||||
// indexes later on using SHOW INDEXES.
|
||||
|
||||
$server_schema = new PhabricatorConfigServerSchema();
|
||||
$server_schema = id(new PhabricatorConfigServerSchema())
|
||||
->setRef($ref);
|
||||
|
||||
$tables = igroup($tables, 'TABLE_SCHEMA');
|
||||
foreach ($tables as $database_name => $database_tables) {
|
||||
|
@ -177,15 +212,29 @@ final class PhabricatorConfigSchemaQuery extends Phobject {
|
|||
return $server_schema;
|
||||
}
|
||||
|
||||
public function loadExpectedSchema() {
|
||||
$databases = $this->getDatabaseNames();
|
||||
$info = $this->getAPI()->getCharsetInfo();
|
||||
public function loadExpectedSchemata() {
|
||||
$refs = $this->getRefs();
|
||||
|
||||
$schemata = array();
|
||||
foreach ($refs as $ref) {
|
||||
$schema = $this->loadExpectedSchemaForServer($ref);
|
||||
$schemata[$schema->getRef()->getRefKey()] = $schema;
|
||||
}
|
||||
|
||||
return $schemata;
|
||||
}
|
||||
|
||||
public function loadExpectedSchemaForServer(PhabricatorDatabaseRef $ref) {
|
||||
$databases = $this->getDatabaseNames($ref);
|
||||
$info = $this->getAPI($ref)->getCharsetInfo();
|
||||
|
||||
$specs = id(new PhutilClassMapQuery())
|
||||
->setAncestorClass('PhabricatorConfigSchemaSpec')
|
||||
->execute();
|
||||
|
||||
$server_schema = new PhabricatorConfigServerSchema();
|
||||
$server_schema = id(new PhabricatorConfigServerSchema())
|
||||
->setRef($ref);
|
||||
|
||||
foreach ($specs as $spec) {
|
||||
$spec
|
||||
->setUTF8Charset(
|
||||
|
@ -201,7 +250,21 @@ final class PhabricatorConfigSchemaQuery extends Phobject {
|
|||
return $server_schema;
|
||||
}
|
||||
|
||||
public function buildComparisonSchema(
|
||||
public function buildComparisonSchemata(
|
||||
array $expect_servers,
|
||||
array $actual_servers) {
|
||||
|
||||
$schemata = array();
|
||||
foreach ($actual_servers as $key => $actual_server) {
|
||||
$schemata[$key] = $this->buildComparisonSchemaForServer(
|
||||
$expect_servers[$key],
|
||||
$actual_server);
|
||||
}
|
||||
|
||||
return $schemata;
|
||||
}
|
||||
|
||||
private function buildComparisonSchemaForServer(
|
||||
PhabricatorConfigServerSchema $expect,
|
||||
PhabricatorConfigServerSchema $actual) {
|
||||
|
||||
|
|
|
@ -3,8 +3,18 @@
|
|||
final class PhabricatorConfigServerSchema
|
||||
extends PhabricatorConfigStorageSchema {
|
||||
|
||||
private $ref;
|
||||
private $databases = array();
|
||||
|
||||
public function setRef(PhabricatorDatabaseRef $ref) {
|
||||
$this->ref = $ref;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRef() {
|
||||
return $this->ref;
|
||||
}
|
||||
|
||||
public function addDatabase(PhabricatorConfigDatabaseSchema $database) {
|
||||
$key = $database->getName();
|
||||
if (isset($this->databases[$key])) {
|
||||
|
|
|
@ -29,18 +29,13 @@ final class DifferentialParseCommitMessageConduitAPIMethod
|
|||
$corpus = $request->getValue('corpus');
|
||||
$is_partial = $request->getValue('partial');
|
||||
|
||||
$revision = new DifferentialRevision();
|
||||
|
||||
$field_list = PhabricatorCustomField::getObjectFields(
|
||||
$revision,
|
||||
new DifferentialRevision(),
|
||||
DifferentialCustomField::ROLE_COMMITMESSAGE);
|
||||
$field_list->setViewer($viewer);
|
||||
$field_map = mpull($field_list->getFields(), null, 'getFieldKeyForConduit');
|
||||
|
||||
$this->errors = array();
|
||||
|
||||
$label_map = $this->buildLabelMap($field_list);
|
||||
$corpus_map = $this->parseCommitMessage($corpus, $label_map);
|
||||
$corpus_map = $this->parseCommitMessage($corpus);
|
||||
|
||||
$values = array();
|
||||
foreach ($corpus_map as $field_key => $text_value) {
|
||||
|
@ -94,44 +89,12 @@ final class DifferentialParseCommitMessageConduitAPIMethod
|
|||
);
|
||||
}
|
||||
|
||||
private function buildLabelMap(PhabricatorCustomFieldList $field_list) {
|
||||
$label_map = array();
|
||||
|
||||
foreach ($field_list->getFields() as $key => $field) {
|
||||
$labels = $field->getCommitMessageLabels();
|
||||
$key = $field->getFieldKeyForConduit();
|
||||
|
||||
foreach ($labels as $label) {
|
||||
$normal_label = DifferentialCommitMessageParser::normalizeFieldLabel(
|
||||
$label);
|
||||
if (!empty($label_map[$normal_label])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Field label "%s" is parsed by two custom fields: "%s" and '.
|
||||
'"%s". Each label must be parsed by only one field.',
|
||||
$label,
|
||||
$key,
|
||||
$label_map[$normal_label]));
|
||||
}
|
||||
$label_map[$normal_label] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return $label_map;
|
||||
}
|
||||
|
||||
|
||||
private function parseCommitMessage($corpus, array $label_map) {
|
||||
$key_title = id(new DifferentialTitleField())->getFieldKeyForConduit();
|
||||
$key_summary = id(new DifferentialSummaryField())->getFieldKeyForConduit();
|
||||
|
||||
$parser = id(new DifferentialCommitMessageParser())
|
||||
->setLabelMap($label_map)
|
||||
->setTitleKey($key_title)
|
||||
->setSummaryKey($key_summary);
|
||||
|
||||
private function parseCommitMessage($corpus) {
|
||||
$viewer = $this->getViewer();
|
||||
$parser = DifferentialCommitMessageParser::newStandardParser($viewer);
|
||||
$result = $parser->parseCorpus($corpus);
|
||||
|
||||
$this->errors = array();
|
||||
foreach ($parser->getErrors() as $error) {
|
||||
$this->errors[] = $error;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ abstract class DifferentialCoreCustomField
|
|||
|
||||
private $value;
|
||||
private $fieldError;
|
||||
private $fieldParser;
|
||||
|
||||
abstract protected function readValueFromRevision(
|
||||
DifferentialRevision $revision);
|
||||
|
@ -60,6 +61,32 @@ abstract class DifferentialCoreCustomField
|
|||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
$this->setFieldError(pht('Required'));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$parser = $this->getFieldParser();
|
||||
$result = $parser->parseCorpus($value);
|
||||
|
||||
unset($result['__title__']);
|
||||
unset($result['__summary__']);
|
||||
|
||||
if ($result) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht(
|
||||
'The value you have entered in "%s" can not be parsed '.
|
||||
'unambiguously when rendered in a commit message. Edit the '.
|
||||
'message so that keywords like "Summary:" and "Test Plan:" do '.
|
||||
'not appear at the beginning of lines. Parsed keys: %s.',
|
||||
$this->getFieldName(),
|
||||
implode(', ', array_keys($result))),
|
||||
$xaction);
|
||||
$errors[] = $error;
|
||||
$this->setFieldError(pht('Invalid'));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,6 +94,22 @@ abstract class DifferentialCoreCustomField
|
|||
return $errors;
|
||||
}
|
||||
|
||||
private function getFieldParser() {
|
||||
if (!$this->fieldParser) {
|
||||
$viewer = $this->getViewer();
|
||||
$parser = DifferentialCommitMessageParser::newStandardParser($viewer);
|
||||
|
||||
// Set custom title and summary keys so we can detect the presence of
|
||||
// "Summary:" in, e.g., a test plan.
|
||||
$parser->setTitleKey('__title__');
|
||||
$parser->setSummaryKey('__summary__');
|
||||
|
||||
$this->fieldParser = $parser;
|
||||
}
|
||||
|
||||
return $this->fieldParser;
|
||||
}
|
||||
|
||||
public function canDisableField() {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,45 @@ final class DifferentialCommitMessageParser extends Phobject {
|
|||
private $errors;
|
||||
|
||||
|
||||
public static function newStandardParser(PhabricatorUser $viewer) {
|
||||
|
||||
$key_title = id(new DifferentialTitleField())->getFieldKeyForConduit();
|
||||
$key_summary = id(new DifferentialSummaryField())->getFieldKeyForConduit();
|
||||
|
||||
$field_list = PhabricatorCustomField::getObjectFields(
|
||||
new DifferentialRevision(),
|
||||
DifferentialCustomField::ROLE_COMMITMESSAGE);
|
||||
$field_list->setViewer($viewer);
|
||||
|
||||
$label_map = array();
|
||||
|
||||
foreach ($field_list->getFields() as $field) {
|
||||
$labels = $field->getCommitMessageLabels();
|
||||
$key = $field->getFieldKeyForConduit();
|
||||
|
||||
foreach ($labels as $label) {
|
||||
$normal_label = self::normalizeFieldLabel(
|
||||
$label);
|
||||
if (!empty($label_map[$normal_label])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Field label "%s" is parsed by two custom fields: "%s" and '.
|
||||
'"%s". Each label must be parsed by only one field.',
|
||||
$label,
|
||||
$key,
|
||||
$label_map[$normal_label]));
|
||||
}
|
||||
$label_map[$normal_label] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return id(new self())
|
||||
->setLabelMap($label_map)
|
||||
->setTitleKey($key_title)
|
||||
->setSummaryKey($key_summary);
|
||||
}
|
||||
|
||||
|
||||
/* -( Configuring the Parser )--------------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ final class DifferentialExactUserFunctionDatasource
|
|||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorDifferentialApplication';
|
||||
return 'PhabricatorPeopleApplication';
|
||||
}
|
||||
|
||||
public function getComponentDatasources() {
|
||||
|
|
|
@ -46,30 +46,9 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
|
|||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/(?:'.
|
||||
'r(?P<repositoryCallsign>[A-Z]+)'.
|
||||
'|'.
|
||||
'R(?P<repositoryID>[1-9]\d*):'.
|
||||
')(?P<commit>[a-f0-9]+)'
|
||||
=> 'DiffusionCommitController',
|
||||
|
||||
'/diffusion/' => array(
|
||||
$this->getQueryRoutePattern()
|
||||
=> 'DiffusionRepositoryListController',
|
||||
$this->getEditRoutePattern('edit/') =>
|
||||
'DiffusionRepositoryEditController',
|
||||
'pushlog/' => array(
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DiffusionPushLogListController',
|
||||
'view/(?P<id>\d+)/' => 'DiffusionPushEventViewController',
|
||||
),
|
||||
'(?:'.
|
||||
'(?P<repositoryCallsign>[A-Z]+)'.
|
||||
'|'.
|
||||
'(?P<repositoryID>[1-9]\d*)'.
|
||||
')/' => array(
|
||||
$repository_routes = array(
|
||||
'/' => array(
|
||||
'' => 'DiffusionRepositoryController',
|
||||
|
||||
'repository/(?P<dblob>.*)' => 'DiffusionRepositoryController',
|
||||
'change/(?P<dblob>.*)' => 'DiffusionChangeController',
|
||||
'history/(?P<dblob>.*)' => 'DiffusionHistoryController',
|
||||
|
@ -107,13 +86,35 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
|
|||
'pathtree/(?P<dblob>.*)' => 'DiffusionPathTreeController',
|
||||
),
|
||||
|
||||
// NOTE: This must come after the rule above; it just gives us a
|
||||
// catch-all for serving repositories over HTTP. We must accept
|
||||
// requests without the trailing "/" because SVN commands don't
|
||||
// necessarily include it.
|
||||
'(?:(?P<repositoryCallsign>[A-Z]+)|(?P<repositoryID>[1-9]\d*))'.
|
||||
'(?:/.*)?'
|
||||
=> 'DiffusionRepositoryDefaultController',
|
||||
// NOTE: This must come after the rules above; it just gives us a
|
||||
// catch-all for serving repositories over HTTP. We must accept requests
|
||||
// without the trailing "/" because SVN commands don't necessarily
|
||||
// include it.
|
||||
'(?:/.*)?' => 'DiffusionRepositoryDefaultController',
|
||||
);
|
||||
|
||||
return array(
|
||||
'/(?:'.
|
||||
'r(?P<repositoryCallsign>[A-Z]+)'.
|
||||
'|'.
|
||||
'R(?P<repositoryID>[1-9]\d*):'.
|
||||
')(?P<commit>[a-f0-9]+)'
|
||||
=> 'DiffusionCommitController',
|
||||
|
||||
'/source/(?P<repositoryShortName>[^/.]+)(?P<dotgit>\.git)?'
|
||||
=> $repository_routes,
|
||||
|
||||
'/diffusion/' => array(
|
||||
$this->getQueryRoutePattern()
|
||||
=> 'DiffusionRepositoryListController',
|
||||
$this->getEditRoutePattern('edit/') =>
|
||||
'DiffusionRepositoryEditController',
|
||||
'pushlog/' => array(
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DiffusionPushLogListController',
|
||||
'view/(?P<id>\d+)/' => 'DiffusionPushEventViewController',
|
||||
),
|
||||
'(?P<repositoryCallsign>[A-Z]+)' => $repository_routes,
|
||||
'(?P<repositoryID>[1-9]\d*)' => $repository_routes,
|
||||
|
||||
'inline/' => array(
|
||||
'edit/(?P<phid>[^/]+)/' => 'DiffusionInlineCommentController',
|
||||
|
|
|
@ -90,6 +90,11 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
protected function getRepositoryIdentifierFromRequest(
|
||||
AphrontRequest $request) {
|
||||
|
||||
$short_name = $request->getURIData('repositoryShortName');
|
||||
if (strlen($short_name)) {
|
||||
return $short_name;
|
||||
}
|
||||
|
||||
$identifier = $request->getURIData('repositoryCallsign');
|
||||
if (strlen($identifier)) {
|
||||
return $identifier;
|
||||
|
|
|
@ -16,6 +16,7 @@ final class DiffusionLastModifiedController extends DiffusionController {
|
|||
$drequest = $this->getDiffusionRequest();
|
||||
|
||||
$paths = $request->getStr('paths');
|
||||
|
||||
try {
|
||||
$paths = phutil_json_decode($paths);
|
||||
} catch (PhutilJSONParserException $ex) {
|
||||
|
|
|
@ -88,6 +88,13 @@ final class DiffusionServeController extends DiffusionController {
|
|||
}
|
||||
}
|
||||
|
||||
// If the request was for a path like "/source/libphutil.git" but the
|
||||
// repository is not a Git repository, reject the request.
|
||||
$type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
|
||||
if ($request->getURIData('dotgit') && ($vcs !== $type_git)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $vcs;
|
||||
}
|
||||
|
||||
|
@ -607,7 +614,9 @@ final class DiffusionServeController extends DiffusionController {
|
|||
$request = $this->getRequest();
|
||||
$request_path = $request->getRequestURI()->getPath();
|
||||
|
||||
$info = PhabricatorRepository::parseRepositoryServicePath($request_path);
|
||||
$info = PhabricatorRepository::parseRepositoryServicePath(
|
||||
$request_path,
|
||||
$repository->getVersionControlSystem());
|
||||
$base_path = $info['path'];
|
||||
|
||||
// For Git repositories, strip an optional directory component if it
|
||||
|
|
|
@ -15,7 +15,9 @@ final class DiffusionGitLFSAuthenticateWorkflow
|
|||
}
|
||||
|
||||
protected function identifyRepository() {
|
||||
return $this->loadRepositoryWithPath($this->getLFSPathArgument());
|
||||
return $this->loadRepositoryWithPath(
|
||||
$this->getLFSPathArgument(),
|
||||
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
|
||||
}
|
||||
|
||||
private function getLFSPathArgument() {
|
||||
|
|
|
@ -168,8 +168,7 @@ final class DiffusionRepositoryBasicsManagementPanel
|
|||
|
||||
$short_name = $repository->getRepositorySlug();
|
||||
if ($short_name === null) {
|
||||
$short_name = $repository->getCloneName();
|
||||
$short_name = phutil_tag('em', array(), $short_name);
|
||||
$short_name = phutil_tag('em', array(), pht('No Short Name'));
|
||||
}
|
||||
$view->addProperty(pht('Short Name'), $short_name);
|
||||
|
||||
|
|
|
@ -17,7 +17,9 @@ abstract class DiffusionGitSSHWorkflow
|
|||
protected function identifyRepository() {
|
||||
$args = $this->getArgs();
|
||||
$path = head($args->getArg('dir'));
|
||||
return $this->loadRepositoryWithPath($path);
|
||||
return $this->loadRepositoryWithPath(
|
||||
$path,
|
||||
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
|
||||
}
|
||||
|
||||
protected function waitForGitClient() {
|
||||
|
|
|
@ -27,7 +27,9 @@ final class DiffusionMercurialServeSSHWorkflow
|
|||
protected function identifyRepository() {
|
||||
$args = $this->getArgs();
|
||||
$path = $args->getArg('repository');
|
||||
return $this->loadRepositoryWithPath($path);
|
||||
return $this->loadRepositoryWithPath(
|
||||
$path,
|
||||
PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL);
|
||||
}
|
||||
|
||||
protected function executeRepositoryOperations() {
|
||||
|
|
|
@ -161,18 +161,19 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
|
|||
}
|
||||
}
|
||||
|
||||
protected function loadRepositoryWithPath($path) {
|
||||
protected function loadRepositoryWithPath($path, $vcs) {
|
||||
$viewer = $this->getUser();
|
||||
|
||||
$info = PhabricatorRepository::parseRepositoryServicePath($path);
|
||||
$info = PhabricatorRepository::parseRepositoryServicePath($path, $vcs);
|
||||
if ($info === null) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unrecognized repository path "%s". Expected a path like "%s" '.
|
||||
'or "%s".',
|
||||
'Unrecognized repository path "%s". Expected a path like "%s", '.
|
||||
'"%s", or "%s".',
|
||||
$path,
|
||||
'/diffusion/X/',
|
||||
'/diffusion/123/'));
|
||||
'/diffusion/123/',
|
||||
'/source/thaumaturgy.git'));
|
||||
}
|
||||
|
||||
$identifier = $info['identifier'];
|
||||
|
|
|
@ -117,7 +117,9 @@ final class DiffusionSubversionServeSSHWorkflow
|
|||
$uri = $struct[2]['value'];
|
||||
$path = $this->getPathFromSubversionURI($uri);
|
||||
|
||||
return $this->loadRepositoryWithPath($path);
|
||||
return $this->loadRepositoryWithPath(
|
||||
$path,
|
||||
PhabricatorRepositoryType::REPOSITORY_TYPE_SVN);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -76,6 +76,7 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
|
|||
'dropupload/' => 'PhabricatorFileDropUploadController',
|
||||
'compose/' => 'PhabricatorFileComposeController',
|
||||
'comment/(?P<id>[1-9]\d*)/' => 'PhabricatorFileCommentController',
|
||||
'thread/(?P<phid>[^/]+)/' => 'PhabricatorFileLightboxController',
|
||||
'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorFileDeleteController',
|
||||
'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorFileEditController',
|
||||
'info/(?P<phid>[^/]+)/' => 'PhabricatorFileInfoController',
|
||||
|
@ -129,4 +130,10 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getQuicksandURIPatternBlacklist() {
|
||||
return array(
|
||||
'/file/data/.*',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFileLightboxController
|
||||
extends PhabricatorFileController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$phid = $request->getURIData('phid');
|
||||
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($phid))
|
||||
->executeOne();
|
||||
if (!$file) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$transactions = id(new PhabricatorFileTransactionQuery())
|
||||
->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT));
|
||||
$timeline = $this->buildTransactionTimeline($file, $transactions);
|
||||
|
||||
if ($timeline->isTimelineEmpty()) {
|
||||
$timeline = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-comment-panel-empty',
|
||||
),
|
||||
pht('No comments.'));
|
||||
}
|
||||
|
||||
require_celerity_resource('phui-comment-panel-css');
|
||||
$content = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-comment-panel',
|
||||
),
|
||||
$timeline);
|
||||
|
||||
return id(new AphrontAjaxResponse())
|
||||
->setContent($content);
|
||||
}
|
||||
|
||||
}
|
|
@ -98,7 +98,7 @@ final class PhabricatorEmbedFileRemarkupRule
|
|||
PhabricatorObjectHandle $handle,
|
||||
array $options) {
|
||||
|
||||
require_celerity_resource('lightbox-attachment-css');
|
||||
require_celerity_resource('phui-lightbox-css');
|
||||
|
||||
$attrs = array();
|
||||
$image_class = 'phabricator-remarkup-embed-image';
|
||||
|
@ -176,6 +176,7 @@ final class PhabricatorEmbedFileRemarkupRule
|
|||
'uri' => $file->getBestURI(),
|
||||
'dUri' => $file->getDownloadURI(),
|
||||
'viewable' => true,
|
||||
'monogram' => $file->getMonogram(),
|
||||
),
|
||||
),
|
||||
$img);
|
||||
|
@ -279,7 +280,8 @@ final class PhabricatorEmbedFileRemarkupRule
|
|||
->setFileName($this->assertFlatText($options['name']))
|
||||
->setFileDownloadURI($file->getDownloadURI())
|
||||
->setFileViewURI($file->getBestURI())
|
||||
->setFileViewable((bool)$options['viewable']);
|
||||
->setFileViewable((bool)$options['viewable'])
|
||||
->setFileMonogram($file->getMonogram());
|
||||
}
|
||||
|
||||
private function parseDimension($string) {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorGuidanceContext
|
||||
extends Phobject {}
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorGuidanceEngine
|
||||
extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $guidanceContext;
|
||||
|
||||
public function setGuidanceContext(
|
||||
PhabricatorGuidanceContext $guidance_context) {
|
||||
$this->guidanceContext = $guidance_context;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getGuidanceContext() {
|
||||
return $this->guidanceContext;
|
||||
}
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
public function newInfoView() {
|
||||
$extensions = PhabricatorGuidanceEngineExtension::getAllExtensions();
|
||||
$context = $this->getGuidanceContext();
|
||||
|
||||
$keep = array();
|
||||
foreach ($extensions as $key => $extension) {
|
||||
if (!$extension->canGenerateGuidance($context)) {
|
||||
continue;
|
||||
}
|
||||
$keep[$key] = id(clone $extension);
|
||||
}
|
||||
|
||||
$guidance_map = array();
|
||||
foreach ($keep as $extension) {
|
||||
$guidance_list = $extension->generateGuidance($context);
|
||||
foreach ($guidance_list as $guidance) {
|
||||
$key = $guidance->getKey();
|
||||
|
||||
if (isset($guidance_map[$key])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Two guidance extensions generated guidance with the same '.
|
||||
'key ("%s"). Each piece of guidance must have a unique key.',
|
||||
$key));
|
||||
}
|
||||
|
||||
$guidance_map[$key] = $guidance;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($keep as $extension) {
|
||||
$guidance_map = $extension->didGenerateGuidance($context, $guidance_map);
|
||||
}
|
||||
|
||||
if (!$guidance_map) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$guidance_map = msortv($guidance_map, 'getSortVector');
|
||||
|
||||
$severity = PhabricatorGuidanceMessage::SEVERITY_NOTICE;
|
||||
$strength = null;
|
||||
foreach ($guidance_map as $guidance) {
|
||||
if ($strength !== null) {
|
||||
if ($guidance->getSeverityStrength() <= $strength) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$strength = $guidance->getSeverityStrength();
|
||||
$severity = $guidance->getSeverity();
|
||||
}
|
||||
|
||||
$severity_map = array(
|
||||
PhabricatorGuidanceMessage::SEVERITY_NOTICE
|
||||
=> PHUIInfoView::SEVERITY_NOTICE,
|
||||
PhabricatorGuidanceMessage::SEVERITY_WARNING
|
||||
=> PHUIInfoView::SEVERITY_WARNING,
|
||||
);
|
||||
|
||||
$messages = mpull($guidance_map, 'getMessage', 'getKey');
|
||||
|
||||
return id(new PHUIInfoView())
|
||||
->setViewer($this->getViewer())
|
||||
->setSeverity(idx($severity_map, $severity, $severity))
|
||||
->setErrors($messages);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorGuidanceEngineExtension
|
||||
extends Phobject {
|
||||
|
||||
final public function getExtensionKey() {
|
||||
return $this->getPhobjectClassConstant('GUIDANCEKEY', 64);
|
||||
}
|
||||
|
||||
abstract public function canGenerateGuidance(
|
||||
PhabricatorGuidanceContext $context);
|
||||
|
||||
abstract public function generateGuidance(
|
||||
PhabricatorGuidanceContext $context);
|
||||
|
||||
public function didGenerateGuidance(
|
||||
PhabricatorGuidanceContext $context,
|
||||
array $guidance) {
|
||||
return $guidance;
|
||||
}
|
||||
|
||||
final protected function newGuidance($key) {
|
||||
return id(new PhabricatorGuidanceMessage())
|
||||
->setKey($key);
|
||||
}
|
||||
|
||||
final protected function newWarning($key) {
|
||||
return $this->newGuidance($key)
|
||||
->setSeverity(PhabricatorGuidanceMessage::SEVERITY_WARNING);
|
||||
}
|
||||
|
||||
final public static function getAllExtensions() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->setUniqueMethod('getExtensionKey')
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorGuidanceMessage
|
||||
extends Phobject {
|
||||
|
||||
private $key;
|
||||
private $message;
|
||||
private $severity = self::SEVERITY_NOTICE;
|
||||
private $priority = 1000;
|
||||
|
||||
const SEVERITY_NOTICE = 'notice';
|
||||
const SEVERITY_WARNING = 'warning';
|
||||
|
||||
public function setSeverity($severity) {
|
||||
$this->severity = $severity;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSeverity() {
|
||||
return $this->severity;
|
||||
}
|
||||
|
||||
public function setKey($key) {
|
||||
$this->key = $key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getKey() {
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function setMessage($message) {
|
||||
$this->message = $message;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMessage() {
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function getSortVector() {
|
||||
return id(new PhutilSortVector())
|
||||
->addInt($this->getPriority());
|
||||
}
|
||||
|
||||
public function setPriority($priority) {
|
||||
$this->priority = $priority;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPriority() {
|
||||
return $this->priority;
|
||||
}
|
||||
|
||||
public function getSeverityStrength() {
|
||||
$map = array(
|
||||
self::SEVERITY_NOTICE => 1,
|
||||
self::SEVERITY_WARNING => 2,
|
||||
);
|
||||
|
||||
return idx($map, $this->getSeverity(), 0);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -16,6 +16,10 @@ abstract class HarbormasterArtifact extends Phobject {
|
|||
abstract public function getArtifactParameterDescriptions();
|
||||
abstract public function willCreateArtifact(PhabricatorUser $actor);
|
||||
|
||||
public function readArtifactHTTPParameter($key, $value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function validateArtifactData(array $artifact_data) {
|
||||
$artifact_spec = $this->getArtifactParameterSpecification();
|
||||
PhutilTypeSpec::checkMap($artifact_data, $artifact_spec);
|
||||
|
|
|
@ -27,6 +27,16 @@ final class HarbormasterURIArtifact extends HarbormasterArtifact {
|
|||
);
|
||||
}
|
||||
|
||||
public function readArtifactHTTPParameter($key, $value) {
|
||||
// TODO: This is hacky and artifact parameters should be replaced more
|
||||
// broadly, likely with EditFields. See T11887.
|
||||
switch ($key) {
|
||||
case 'ui.external':
|
||||
return (bool)$value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getArtifactParameterDescriptions() {
|
||||
return array(
|
||||
'uri' => pht('The URI to store.'),
|
||||
|
|
|
@ -115,11 +115,28 @@ final class HarbormasterCreateArtifactConduitAPIMethod
|
|||
$build_target_phid));
|
||||
}
|
||||
|
||||
$artifact_type = $request->getValue('artifactType');
|
||||
|
||||
// Cast "artifactData" parameters to acceptable types if this request
|
||||
// is submitting raw HTTP parameters. This is not ideal. See T11887 for
|
||||
// discussion.
|
||||
$artifact_data = $request->getValue('artifactData');
|
||||
if (!$request->getIsStrictlyTyped()) {
|
||||
$impl = HarbormasterArtifact::getArtifactType($artifact_type);
|
||||
if ($impl) {
|
||||
foreach ($artifact_data as $key => $value) {
|
||||
$artifact_data[$key] = $impl->readArtifactHTTPParameter(
|
||||
$key,
|
||||
$value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$artifact = $build_target->createArtifact(
|
||||
$viewer,
|
||||
$request->getValue('artifactKey'),
|
||||
$request->getValue('artifactType'),
|
||||
$request->getValue('artifactData'));
|
||||
$artifact_type,
|
||||
$artifact_data);
|
||||
|
||||
return array(
|
||||
'data' => $this->returnArtifactList(array($artifact)),
|
||||
|
|
|
@ -510,15 +510,23 @@ final class ManiphestTaskDetailController extends ManiphestController {
|
|||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$in_handles = $viewer->loadHandles($in_phids);
|
||||
$out_handles = $viewer->loadHandles($out_phids);
|
||||
|
||||
$in_handles = $this->getCompleteHandles($in_handles);
|
||||
$out_handles = $this->getCompleteHandles($out_handles);
|
||||
|
||||
if (!count($in_handles) && !count($out_handles)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$view = new PHUIPropertyListView();
|
||||
|
||||
if ($in_phids) {
|
||||
$in_handles = $viewer->loadHandles($in_phids);
|
||||
if (count($in_handles)) {
|
||||
$view->addProperty(pht('Mentioned In'), $in_handles->renderList());
|
||||
}
|
||||
|
||||
if ($out_phids) {
|
||||
$out_handles = $viewer->loadHandles($out_phids);
|
||||
if (count($out_handles)) {
|
||||
$view->addProperty(pht('Mentioned Here'), $out_handles->renderList());
|
||||
}
|
||||
|
||||
|
@ -528,4 +536,18 @@ final class ManiphestTaskDetailController extends ManiphestController {
|
|||
->appendChild($view);
|
||||
}
|
||||
|
||||
private function getCompleteHandles(PhabricatorHandleList $handles) {
|
||||
$phids = array();
|
||||
|
||||
foreach ($handles as $phid => $handle) {
|
||||
if (!$handle->isComplete()) {
|
||||
continue;
|
||||
}
|
||||
$phids[] = $phid;
|
||||
}
|
||||
|
||||
return $handles->newSublist($phids);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ final class PassphraseQueryConduitAPIMethod
|
|||
|
||||
switch ($credential->getCredentialType()) {
|
||||
case PassphraseSSHPrivateKeyFileCredentialType::CREDENTIAL_TYPE:
|
||||
if ($secret) {
|
||||
if ($secret !== null) {
|
||||
$material['file'] = $secret;
|
||||
}
|
||||
if ($public_key) {
|
||||
|
@ -91,7 +91,7 @@ final class PassphraseQueryConduitAPIMethod
|
|||
break;
|
||||
case PassphraseSSHGeneratedKeyCredentialType::CREDENTIAL_TYPE:
|
||||
case PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE:
|
||||
if ($secret) {
|
||||
if ($secret !== null) {
|
||||
$material['privateKey'] = $secret;
|
||||
}
|
||||
if ($public_key) {
|
||||
|
@ -99,10 +99,15 @@ final class PassphraseQueryConduitAPIMethod
|
|||
}
|
||||
break;
|
||||
case PassphrasePasswordCredentialType::CREDENTIAL_TYPE:
|
||||
if ($secret) {
|
||||
if ($secret !== null) {
|
||||
$material['password'] = $secret;
|
||||
}
|
||||
break;
|
||||
case PassphraseTokenCredentialType::CREDENTIAL_TYPE:
|
||||
if ($secret !== null) {
|
||||
$material['token'] = $secret;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$allow_api) {
|
||||
|
|
|
@ -101,9 +101,20 @@ final class PhabricatorPeopleCreateController
|
|||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setForm($form);
|
||||
|
||||
$guidance_context = new PhabricatorPeopleCreateGuidanceContext();
|
||||
|
||||
$guidance = id(new PhabricatorGuidanceEngine())
|
||||
->setViewer($admin)
|
||||
->setGuidanceContext($guidance_context)
|
||||
->newInfoView();
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setFooter($box);
|
||||
->setFooter(
|
||||
array(
|
||||
$guidance,
|
||||
$box,
|
||||
));
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
|
|
|
@ -194,11 +194,12 @@ final class PhabricatorPeopleProfileViewController
|
|||
->withDateRange($range_start, $range_end)
|
||||
->withInvitedPHIDs(array($user->getPHID()))
|
||||
->withIsCancelled(false)
|
||||
->needRSVPs(array($viewer->getPHID()))
|
||||
->execute();
|
||||
|
||||
$event_views = array();
|
||||
foreach ($events as $event) {
|
||||
$viewer_is_invited = $event->getIsUserInvited($viewer->getPHID());
|
||||
$viewer_is_invited = $event->isRSVPInvited($viewer->getPHID());
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
|
@ -216,6 +217,7 @@ final class PhabricatorPeopleProfileViewController
|
|||
->setIcon($event->getIcon())
|
||||
->setViewerIsInvited($viewer_is_invited)
|
||||
->setName($event->getName())
|
||||
->setDatetimeSummary($event->renderEventDate($viewer, true))
|
||||
->setURI($event->getURI());
|
||||
|
||||
$event_views[] = $event_view;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorPeopleCreateGuidanceContext
|
||||
extends PhabricatorGuidanceContext {}
|
|
@ -53,6 +53,7 @@ final class PhabricatorPhameApplication extends PhabricatorApplication {
|
|||
'preview/' => 'PhabricatorMarkupPreviewController',
|
||||
'move/(?P<id>\d+)/' => 'PhamePostMoveController',
|
||||
'archive/(?P<id>\d+)/' => 'PhamePostArchiveController',
|
||||
'header/(?P<id>[1-9]\d*)/' => 'PhamePostHeaderPictureController',
|
||||
),
|
||||
'blog/' => array(
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhameBlogListController',
|
||||
|
|
|
@ -90,6 +90,7 @@ abstract class PhameLiveController extends PhameController {
|
|||
if (strlen($post_id)) {
|
||||
$post_query = id(new PhamePostQuery())
|
||||
->setViewer($viewer)
|
||||
->needHeaderImage(true)
|
||||
->withIDs(array($post_id));
|
||||
|
||||
if ($blog) {
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
final class PhamePostHeaderPictureController
|
||||
extends PhamePostController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$post = id(new PhamePostQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->needHeaderImage(true)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$post) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$post_uri = '/phame/post/view/'.$id;
|
||||
|
||||
$supported_formats = PhabricatorFile::getTransformableImageFormats();
|
||||
$e_file = true;
|
||||
$errors = array();
|
||||
$delete_header = ($request->getInt('delete') == 1);
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
if ($request->getFileExists('header')) {
|
||||
$file = PhabricatorFile::newFromPHPUpload(
|
||||
$_FILES['header'],
|
||||
array(
|
||||
'authorPHID' => $viewer->getPHID(),
|
||||
'canCDN' => true,
|
||||
));
|
||||
} else if (!$delete_header) {
|
||||
$e_file = pht('Required');
|
||||
$errors[] = pht(
|
||||
'You must choose a file when uploading a new post header.');
|
||||
}
|
||||
|
||||
if (!$errors && !$delete_header) {
|
||||
if (!$file->isTransformableImage()) {
|
||||
$e_file = pht('Not Supported');
|
||||
$errors[] = pht(
|
||||
'This server only supports these image formats: %s.',
|
||||
implode(', ', $supported_formats));
|
||||
}
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
if ($delete_header) {
|
||||
$new_value = null;
|
||||
} else {
|
||||
$file->attachToObject($post->getPHID());
|
||||
$new_value = $file->getPHID();
|
||||
}
|
||||
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PhamePostTransaction())
|
||||
->setTransactionType(PhamePostTransaction::TYPE_HEADERIMAGE)
|
||||
->setNewValue($new_value);
|
||||
|
||||
$editor = id(new PhamePostEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContinueOnNoEffect(true);
|
||||
|
||||
$editor->applyTransactions($post, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($post_uri);
|
||||
}
|
||||
}
|
||||
|
||||
$title = pht('Edit Post Header');
|
||||
|
||||
$upload_form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->setEncType('multipart/form-data')
|
||||
->appendChild(
|
||||
id(new AphrontFormFileControl())
|
||||
->setName('header')
|
||||
->setLabel(pht('Upload Header'))
|
||||
->setError($e_file)
|
||||
->setCaption(
|
||||
pht('Supported formats: %s', implode(', ', $supported_formats))))
|
||||
->appendChild(
|
||||
id(new AphrontFormCheckboxControl())
|
||||
->setName('delete')
|
||||
->setLabel(pht('Delete Header'))
|
||||
->addCheckbox(
|
||||
'delete',
|
||||
1,
|
||||
null,
|
||||
null))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->addCancelButton($post_uri)
|
||||
->setValue(pht('Upload Header')));
|
||||
|
||||
$upload_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Upload New Header'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setForm($upload_form);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(
|
||||
$post->getTitle(),
|
||||
$this->getApplicationURI('post/view/'.$id));
|
||||
$crumbs->addTextCrumb(pht('Post Header'));
|
||||
$crumbs->setBorder(true);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Edit Post Header'))
|
||||
->setHeaderIcon('fa-camera');
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setFooter(array(
|
||||
$upload_box,
|
||||
));
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild(
|
||||
array(
|
||||
$view,
|
||||
));
|
||||
|
||||
}
|
||||
}
|
|
@ -19,9 +19,11 @@ final class PhamePostViewController
|
|||
$is_external = $this->getIsExternal();
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($post->getTitle())
|
||||
->addClass('phame-header-bar')
|
||||
->setUser($viewer);
|
||||
|
||||
$hero = $this->buildPhamePostHeader($post);
|
||||
|
||||
if (!$is_external) {
|
||||
$actions = $this->renderActions($post);
|
||||
$header->setPolicyObject($post);
|
||||
|
@ -167,6 +169,7 @@ final class PhamePostViewController
|
|||
->setCrumbs($crumbs)
|
||||
->appendChild(
|
||||
array(
|
||||
$hero,
|
||||
$document,
|
||||
$about,
|
||||
$properties,
|
||||
|
@ -204,6 +207,13 @@ final class PhamePostViewController
|
|||
->setName(pht('Edit Post'))
|
||||
->setDisabled(!$can_edit));
|
||||
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-camera-retro')
|
||||
->setHref($this->getApplicationURI('post/header/'.$id.'/'))
|
||||
->setName(pht('Edit Header Image'))
|
||||
->setDisabled(!$can_edit));
|
||||
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-arrows')
|
||||
|
@ -307,4 +317,33 @@ final class PhamePostViewController
|
|||
return array(head($prev), head($next));
|
||||
}
|
||||
|
||||
private function buildPhamePostHeader(
|
||||
PhamePost $post) {
|
||||
|
||||
$image = null;
|
||||
if ($post->getHeaderImagePHID()) {
|
||||
$image = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phame-header-hero',
|
||||
),
|
||||
phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $post->getHeaderImageURI(),
|
||||
'class' => 'phame-header-image',
|
||||
)));
|
||||
}
|
||||
|
||||
$title = phutil_tag_div('phame-header-title', $post->getTitle());
|
||||
$subtitle = null;
|
||||
if ($post->getSubtitle()) {
|
||||
$subtitle = phutil_tag_div('phame-header-subtitle', $post->getSubtitle());
|
||||
}
|
||||
|
||||
return phutil_tag_div(
|
||||
'phame-mega-header', array($image, $title, $subtitle));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -99,6 +99,14 @@ final class PhamePostEditEngine
|
|||
->setConduitTypeDescription(pht('New post title.'))
|
||||
->setTransactionType(PhamePostTransaction::TYPE_TITLE)
|
||||
->setValue($object->getTitle()),
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('subtitle')
|
||||
->setLabel(pht('Subtitle'))
|
||||
->setDescription(pht('Post subtitle.'))
|
||||
->setConduitDescription(pht('Change the post subtitle.'))
|
||||
->setConduitTypeDescription(pht('New post subtitle.'))
|
||||
->setTransactionType(PhamePostTransaction::TYPE_SUBTITLE)
|
||||
->setValue($object->getSubtitle()),
|
||||
id(new PhabricatorSelectEditField())
|
||||
->setKey('visibility')
|
||||
->setLabel(pht('Visibility'))
|
||||
|
|
|
@ -16,8 +16,10 @@ final class PhamePostEditor
|
|||
|
||||
$types[] = PhamePostTransaction::TYPE_BLOG;
|
||||
$types[] = PhamePostTransaction::TYPE_TITLE;
|
||||
$types[] = PhamePostTransaction::TYPE_SUBTITLE;
|
||||
$types[] = PhamePostTransaction::TYPE_BODY;
|
||||
$types[] = PhamePostTransaction::TYPE_VISIBILITY;
|
||||
$types[] = PhamePostTransaction::TYPE_HEADERIMAGE;
|
||||
$types[] = PhabricatorTransactions::TYPE_COMMENT;
|
||||
|
||||
return $types;
|
||||
|
@ -32,10 +34,14 @@ final class PhamePostEditor
|
|||
return $object->getBlogPHID();
|
||||
case PhamePostTransaction::TYPE_TITLE:
|
||||
return $object->getTitle();
|
||||
case PhamePostTransaction::TYPE_SUBTITLE:
|
||||
return $object->getSubtitle();
|
||||
case PhamePostTransaction::TYPE_BODY:
|
||||
return $object->getBody();
|
||||
case PhamePostTransaction::TYPE_VISIBILITY:
|
||||
return $object->getVisibility();
|
||||
case PhamePostTransaction::TYPE_HEADERIMAGE:
|
||||
return $object->getHeaderImagePHID();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,8 +51,10 @@ final class PhamePostEditor
|
|||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhamePostTransaction::TYPE_TITLE:
|
||||
case PhamePostTransaction::TYPE_SUBTITLE:
|
||||
case PhamePostTransaction::TYPE_BODY:
|
||||
case PhamePostTransaction::TYPE_VISIBILITY:
|
||||
case PhamePostTransaction::TYPE_HEADERIMAGE:
|
||||
case PhamePostTransaction::TYPE_BLOG:
|
||||
return $xaction->getNewValue();
|
||||
}
|
||||
|
@ -59,10 +67,14 @@ final class PhamePostEditor
|
|||
switch ($xaction->getTransactionType()) {
|
||||
case PhamePostTransaction::TYPE_TITLE:
|
||||
return $object->setTitle($xaction->getNewValue());
|
||||
case PhamePostTransaction::TYPE_SUBTITLE:
|
||||
return $object->setSubtitle($xaction->getNewValue());
|
||||
case PhamePostTransaction::TYPE_BODY:
|
||||
return $object->setBody($xaction->getNewValue());
|
||||
case PhamePostTransaction::TYPE_BLOG:
|
||||
return $object->setBlogPHID($xaction->getNewValue());
|
||||
case PhamePostTransaction::TYPE_HEADERIMAGE:
|
||||
return $object->setHeaderImagePHID($xaction->getNewValue());
|
||||
case PhamePostTransaction::TYPE_VISIBILITY:
|
||||
if ($xaction->getNewValue() == PhameConstants::VISIBILITY_DRAFT) {
|
||||
$object->setDatePublished(0);
|
||||
|
@ -84,8 +96,10 @@ final class PhamePostEditor
|
|||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhamePostTransaction::TYPE_TITLE:
|
||||
case PhamePostTransaction::TYPE_SUBTITLE:
|
||||
case PhamePostTransaction::TYPE_BODY:
|
||||
case PhamePostTransaction::TYPE_VISIBILITY:
|
||||
case PhamePostTransaction::TYPE_HEADERIMAGE:
|
||||
case PhamePostTransaction::TYPE_BLOG:
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
private $publishedAfter;
|
||||
private $phids;
|
||||
|
||||
private $needHeaderImage;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
|
@ -39,6 +41,11 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function needHeaderImage($need) {
|
||||
$this->needHeaderImage = $need;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new PhamePost();
|
||||
}
|
||||
|
@ -71,6 +78,28 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
$post->attachBlog($blog);
|
||||
}
|
||||
|
||||
if ($this->needHeaderImage) {
|
||||
$file_phids = mpull($posts, 'getHeaderImagePHID');
|
||||
$file_phids = array_filter($file_phids);
|
||||
if ($file_phids) {
|
||||
$files = id(new PhabricatorFileQuery())
|
||||
->setParentQuery($this)
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs($file_phids)
|
||||
->execute();
|
||||
$files = mpull($files, null, 'getPHID');
|
||||
} else {
|
||||
$files = array();
|
||||
}
|
||||
|
||||
foreach ($posts as $post) {
|
||||
$file = idx($files, $post->getHeaderImagePHID());
|
||||
if ($file) {
|
||||
$post->attachHeaderImageFile($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ final class PhamePost extends PhameDAO
|
|||
|
||||
protected $bloggerPHID;
|
||||
protected $title;
|
||||
protected $subtitle;
|
||||
protected $phameTitle;
|
||||
protected $body;
|
||||
protected $visibility;
|
||||
|
@ -25,8 +26,10 @@ final class PhamePost extends PhameDAO
|
|||
protected $datePublished;
|
||||
protected $blogPHID;
|
||||
protected $mailKey;
|
||||
protected $headerImagePHID;
|
||||
|
||||
private $blog = self::ATTACHABLE;
|
||||
private $headerImageFile = self::ATTACHABLE;
|
||||
|
||||
public static function initializePost(
|
||||
PhabricatorUser $blogger,
|
||||
|
@ -122,9 +125,11 @@ final class PhamePost extends PhameDAO
|
|||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'title' => 'text255',
|
||||
'subtitle' => 'text64',
|
||||
'phameTitle' => 'sort64?',
|
||||
'visibility' => 'uint32',
|
||||
'mailKey' => 'bytes20',
|
||||
'headerImagePHID' => 'phid?',
|
||||
|
||||
// T6203/NULLABILITY
|
||||
// These seem like they should always be non-null?
|
||||
|
@ -170,6 +175,19 @@ final class PhamePost extends PhameDAO
|
|||
return PhabricatorSlug::normalizeProjectSlug($this->getTitle(), true);
|
||||
}
|
||||
|
||||
public function getHeaderImageURI() {
|
||||
return $this->getHeaderImageFile()->getBestURI();
|
||||
}
|
||||
|
||||
public function attachHeaderImageFile(PhabricatorFile $file) {
|
||||
$this->headerImageFile = $file;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHeaderImageFile() {
|
||||
return $this->assertAttached($this->headerImageFile);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
|
||||
|
||||
|
|
|
@ -4,8 +4,10 @@ final class PhamePostTransaction
|
|||
extends PhabricatorApplicationTransaction {
|
||||
|
||||
const TYPE_TITLE = 'phame.post.title';
|
||||
const TYPE_SUBTITLE = 'phame.post.subtitle';
|
||||
const TYPE_BODY = 'phame.post.body';
|
||||
const TYPE_VISIBILITY = 'phame.post.visibility';
|
||||
const TYPE_HEADERIMAGE = 'phame.post.headerimage';
|
||||
const TYPE_BLOG = 'phame.post.blog';
|
||||
|
||||
const MAILTAG_CONTENT = 'phame-post-content';
|
||||
|
@ -70,6 +72,9 @@ final class PhamePostTransaction
|
|||
case PhabricatorTransactions::TYPE_CREATE:
|
||||
return 'fa-plus';
|
||||
break;
|
||||
case self::TYPE_HEADERIMAGE:
|
||||
return 'fa-camera-retro';
|
||||
break;
|
||||
case self::TYPE_VISIBILITY:
|
||||
if ($new == PhameConstants::VISIBILITY_PUBLISHED) {
|
||||
return 'fa-globe';
|
||||
|
@ -94,6 +99,7 @@ final class PhamePostTransaction
|
|||
$tags[] = self::MAILTAG_SUBSCRIBERS;
|
||||
break;
|
||||
case self::TYPE_TITLE:
|
||||
case self::TYPE_SUBTITLE:
|
||||
case self::TYPE_BODY:
|
||||
$tags[] = self::MAILTAG_CONTENT;
|
||||
break;
|
||||
|
@ -136,11 +142,29 @@ final class PhamePostTransaction
|
|||
$new);
|
||||
}
|
||||
break;
|
||||
case self::TYPE_SUBTITLE:
|
||||
if ($old === null) {
|
||||
return pht(
|
||||
'%s set the post\'s subtitle to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$new);
|
||||
} else {
|
||||
return pht(
|
||||
'%s updated the post\'s subtitle to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$new);
|
||||
}
|
||||
break;
|
||||
case self::TYPE_BODY:
|
||||
return pht(
|
||||
'%s updated the blog post.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
break;
|
||||
case self::TYPE_HEADERIMAGE:
|
||||
return pht(
|
||||
'%s updated the header image.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
break;
|
||||
case self::TYPE_VISIBILITY:
|
||||
if ($new == PhameConstants::VISIBILITY_DRAFT) {
|
||||
return pht(
|
||||
|
@ -195,12 +219,24 @@ final class PhamePostTransaction
|
|||
$this->renderHandleLink($object_phid));
|
||||
}
|
||||
break;
|
||||
case self::TYPE_SUBTITLE:
|
||||
return pht(
|
||||
'%s updated the subtitle for %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
break;
|
||||
case self::TYPE_BODY:
|
||||
return pht(
|
||||
'%s updated the blog post %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
break;
|
||||
case self::TYPE_HEADERIMAGE:
|
||||
return pht(
|
||||
'%s updated the header image for post %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
break;
|
||||
case self::TYPE_VISIBILITY:
|
||||
if ($new == PhameConstants::VISIBILITY_DRAFT) {
|
||||
return pht(
|
||||
|
|
|
@ -74,6 +74,24 @@ final class PhabricatorHandleList
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new list with a subset of the PHIDs in this list.
|
||||
*/
|
||||
public function newSublist(array $phids) {
|
||||
foreach ($phids as $phid) {
|
||||
if (!isset($this[$phid])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Trying to create a new sublist of an existsing handle list, '.
|
||||
'but PHID "%s" does not appear in the parent list.',
|
||||
$phid));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->handlePool->newHandleList($phids);
|
||||
}
|
||||
|
||||
|
||||
/* -( Rendering )---------------------------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -205,11 +205,7 @@ final class PhabricatorPhurlURLEditor
|
|||
protected function getMailTo(PhabricatorLiskDAO $object) {
|
||||
$phids = array();
|
||||
|
||||
if ($object->getPHID()) {
|
||||
$phids[] = $object->getPHID();
|
||||
}
|
||||
$phids[] = $this->getActingAsPHID();
|
||||
$phids = array_unique($phids);
|
||||
|
||||
return $phids;
|
||||
}
|
||||
|
|
|
@ -416,7 +416,6 @@ final class PhabricatorProjectBoardViewController
|
|||
->appendChild($board)
|
||||
->addClass('project-board-wrapper');
|
||||
|
||||
$nav = $this->getProfileMenu();
|
||||
$divider = id(new PHUIListItemView())
|
||||
->setType(PHUIListItemView::TYPE_DIVIDER);
|
||||
$fullscreen = $this->buildFullscreenMenu();
|
||||
|
@ -439,7 +438,6 @@ final class PhabricatorProjectBoardViewController
|
|||
))
|
||||
->setPageObjectPHIDs(array($project->getPHID()))
|
||||
->setShowFooter(false)
|
||||
->setNavigation($nav)
|
||||
->setCrumbs($crumbs)
|
||||
->addQuicksandConfig(
|
||||
array(
|
||||
|
|
|
@ -119,7 +119,8 @@ abstract class PhabricatorProjectController extends PhabricatorController {
|
|||
foreach ($ancestors as $ancestor) {
|
||||
$crumbs->addTextCrumb(
|
||||
$ancestor->getName(),
|
||||
$ancestor->getURI());
|
||||
$ancestor->getProfileURI()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -367,6 +367,11 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||
return "/project/view/{$id}/";
|
||||
}
|
||||
|
||||
public function getProfileURI() {
|
||||
$id = $this->getID();
|
||||
return "/project/profile/{$id}/";
|
||||
}
|
||||
|
||||
public function save() {
|
||||
if (!$this->getMailKey()) {
|
||||
$this->setMailKey(Filesystem::readRandomCharacters(20));
|
||||
|
|
|
@ -103,15 +103,10 @@ final class PhabricatorProjectDatasource
|
|||
|
||||
$all_strings = array();
|
||||
$all_strings[] = $proj->getDisplayName();
|
||||
|
||||
// Add an extra space after the name so that the original project
|
||||
// sorts ahead of milestones. This is kind of a hack but ehh?
|
||||
$all_strings[] = null;
|
||||
|
||||
foreach ($proj->getSlugs() as $project_slug) {
|
||||
$all_strings[] = $project_slug->getSlug();
|
||||
}
|
||||
$all_strings = implode(' ', $all_strings);
|
||||
$all_strings = implode("\n", $all_strings);
|
||||
|
||||
$proj_result = id(new PhabricatorTypeaheadResult())
|
||||
->setName($all_strings)
|
||||
|
@ -135,7 +130,7 @@ final class PhabricatorProjectDatasource
|
|||
|
||||
$description = idx($descriptions, $phid);
|
||||
if (strlen($description)) {
|
||||
$summary = PhabricatorMarkupEngine::summarize($description);
|
||||
$summary = PhabricatorMarkupEngine::summarizeSentence($description);
|
||||
$proj_result->addAttribute($summary);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -563,6 +563,11 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
}
|
||||
|
||||
public function getURI() {
|
||||
$short_name = $this->getRepositorySlug();
|
||||
if (strlen($short_name)) {
|
||||
return "/source/{$short_name}/";
|
||||
}
|
||||
|
||||
$callsign = $this->getCallsign();
|
||||
if (strlen($callsign)) {
|
||||
return "/diffusion/{$callsign}/";
|
||||
|
@ -573,7 +578,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
}
|
||||
|
||||
public function getPathURI($path) {
|
||||
return $this->getURI().$path;
|
||||
return $this->getURI().ltrim($path, '/');
|
||||
}
|
||||
|
||||
public function getCommitURI($identifier) {
|
||||
|
@ -586,14 +591,22 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
return "/R{$id}:{$identifier}";
|
||||
}
|
||||
|
||||
public static function parseRepositoryServicePath($request_path) {
|
||||
public static function parseRepositoryServicePath($request_path, $vcs) {
|
||||
|
||||
// NOTE: In Mercurial over SSH, the path will begin without a leading "/",
|
||||
// so we're matching it optionally.
|
||||
|
||||
if ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) {
|
||||
$maybe_git = '(?:\\.git)?';
|
||||
} else {
|
||||
$maybe_git = null;
|
||||
}
|
||||
|
||||
$patterns = array(
|
||||
'(^'.
|
||||
'(?P<base>/?diffusion/(?P<identifier>[A-Z]+|[0-9]\d*))'.
|
||||
'(?P<path>(?:/.*)?)'.
|
||||
'(?P<base>/?(?:diffusion|source)/(?P<identifier>[^/.]+))'.
|
||||
$maybe_git.
|
||||
'(?P<path>(?:/|.*)?)'.
|
||||
'\z)',
|
||||
);
|
||||
|
||||
|
@ -624,28 +637,15 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
public function getCanonicalPath($request_path) {
|
||||
$standard_pattern =
|
||||
'(^'.
|
||||
'(?P<prefix>/diffusion/)'.
|
||||
'(?P<prefix>/(?:diffusion|source)/)'.
|
||||
'(?P<identifier>[^/]+)'.
|
||||
'(?P<suffix>(?:/.*)?)'.
|
||||
'\z)';
|
||||
|
||||
$matches = null;
|
||||
if (preg_match($standard_pattern, $request_path, $matches)) {
|
||||
$prefix = $matches['prefix'];
|
||||
|
||||
$callsign = $this->getCallsign();
|
||||
if ($callsign) {
|
||||
$identifier = $callsign;
|
||||
} else {
|
||||
$identifier = $this->getID();
|
||||
}
|
||||
|
||||
$suffix = $matches['suffix'];
|
||||
if (!strlen($suffix)) {
|
||||
$suffix = '/';
|
||||
}
|
||||
|
||||
return $prefix.$identifier.$suffix;
|
||||
return $this->getPathURI($suffix);
|
||||
}
|
||||
|
||||
$commit_pattern =
|
||||
|
@ -724,18 +724,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
return $this->getCommitURI($commit);
|
||||
}
|
||||
|
||||
|
||||
$identifier = $this->getID();
|
||||
|
||||
$callsign = $this->getCallsign();
|
||||
if ($callsign !== null) {
|
||||
$identifier = $callsign;
|
||||
}
|
||||
|
||||
if (strlen($identifier)) {
|
||||
$identifier = phutil_escape_uri_path_component($identifier);
|
||||
}
|
||||
|
||||
if (strlen($path)) {
|
||||
$path = ltrim($path, '/');
|
||||
$path = str_replace(array(';', '$'), array(';;', '$$'), $path);
|
||||
|
@ -766,13 +754,13 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
case 'lint':
|
||||
case 'pathtree':
|
||||
case 'refs':
|
||||
$uri = "/diffusion/{$identifier}/{$action}/{$path}{$commit}{$line}";
|
||||
$uri = $this->getPathURI("/{$action}/{$path}{$commit}{$line}");
|
||||
break;
|
||||
case 'branch':
|
||||
if (strlen($path)) {
|
||||
$uri = "/diffusion/{$identifier}/repository/{$path}";
|
||||
$uri = $this->getPathURI("/repository/{$path}");
|
||||
} else {
|
||||
$uri = "/diffusion/{$identifier}/";
|
||||
$uri = $this->getPathURI('/');
|
||||
}
|
||||
break;
|
||||
case 'external':
|
||||
|
@ -2108,9 +2096,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
$has_callsign = ($this->getCallsign() !== null);
|
||||
$has_shortname = ($this->getRepositorySlug() !== null);
|
||||
|
||||
// TODO: For now, never enable these because they don't work yet.
|
||||
$has_shortname = false;
|
||||
|
||||
$identifier_map = array(
|
||||
PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_CALLSIGN => $has_callsign,
|
||||
PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_SHORTNAME => $has_shortname,
|
||||
|
|
|
@ -125,8 +125,8 @@ final class PhabricatorRepositoryURI
|
|||
$other_uris = $repository->getURIs();
|
||||
|
||||
$identifier_value = array(
|
||||
self::BUILTIN_IDENTIFIER_CALLSIGN => 3,
|
||||
self::BUILTIN_IDENTIFIER_SHORTNAME => 2,
|
||||
self::BUILTIN_IDENTIFIER_SHORTNAME => 3,
|
||||
self::BUILTIN_IDENTIFIER_CALLSIGN => 2,
|
||||
self::BUILTIN_IDENTIFIER_ID => 1,
|
||||
);
|
||||
|
||||
|
|
|
@ -233,6 +233,10 @@ class PhabricatorApplicationTransactionView extends AphrontView {
|
|||
return $view;
|
||||
}
|
||||
|
||||
public function isTimelineEmpty() {
|
||||
return !count($this->buildEvents(true));
|
||||
}
|
||||
|
||||
protected function getOrBuildEngine() {
|
||||
if (!$this->engine) {
|
||||
$field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT;
|
||||
|
|
|
@ -311,6 +311,11 @@ final class PhabricatorTypeaheadModularDatasourceController
|
|||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setForm($form);
|
||||
|
||||
// Make "\n" delimiters more visible.
|
||||
foreach ($content as $key => $row) {
|
||||
$content[$key][0] = str_replace("\n", '<\n>', $row[0]);
|
||||
}
|
||||
|
||||
$table = new AphrontTableView($content);
|
||||
$table->setHeaders(
|
||||
array(
|
||||
|
|
|
@ -204,8 +204,8 @@ For many linters, you can do this by providing a `severity` map:
|
|||
}
|
||||
```
|
||||
|
||||
Here, the lint message "E221" (which is "multiple spaces before operator") is
|
||||
disabled, so it won't be shown. The message "E401" (which is "multiple imports
|
||||
Here, the lint message `E221` (which is "multiple spaces before operator") is
|
||||
disabled, so it won't be shown. The message `E401` (which is "multiple imports
|
||||
on one line") is set to "warning" severity.
|
||||
|
||||
If you want to remap a large number of messages, you can use `severity.rules`
|
||||
|
|
|
@ -37,6 +37,22 @@ revision or task.
|
|||
You can click through to a user's profile to see more details about their
|
||||
availability.
|
||||
|
||||
Status Icons
|
||||
============
|
||||
|
||||
On the month and day views, Calendar shows an icon next to each event to
|
||||
indicate status. The icons are:
|
||||
|
||||
- {icon user-plus, color=green} **Invited, Individual**: You're personally
|
||||
invited to the event.
|
||||
- {icon users, color=green} **Invited, Group**: A project you are a member
|
||||
of is invited to the event.
|
||||
- {icon check-circle, color=green} **Attending**: You're attending the event.
|
||||
- {icon times-circle, color=grey} **Declined**: You've declined the event.
|
||||
- {icon times, color=red} **Cancelled**: The event has been cancelled.
|
||||
|
||||
If you don't have any special relationship to the event and the event does not
|
||||
have any special status, an event-specific icon is shown instead.
|
||||
|
||||
Importing Events
|
||||
================
|
||||
|
|
|
@ -222,23 +222,92 @@ other Phabricator SSH services.
|
|||
NOTE: The Phabricator `sshd` service **MUST** be 6.2 or newer, because
|
||||
Phabricator relies on the `AuthorizedKeysCommand` option.
|
||||
|
||||
**Choose a Port**: These instructions will configure the alternate `sshd` on
|
||||
Before continuing, you must choose a strategy for which port each copy of
|
||||
`sshd` will run on. The next section lays out various approaches.
|
||||
|
||||
|
||||
SSHD Port Assignment
|
||||
====================
|
||||
|
||||
The normal `sshd` that lets you administrate the host and the special `sshd`
|
||||
which serves repositories can't run on the same port. In particular, only one
|
||||
of them can run on port `22`, which will make it a bit inconvenient to access
|
||||
the other one.
|
||||
|
||||
These instructions will walk you through configuring the alternate `sshd` on
|
||||
port `2222`. This is easy to configure, but if you run the service on this port
|
||||
users will clone and push to URIs like `ssh://git@host.com:2222/`, which is
|
||||
a little ugly.
|
||||
users will clone and push to URIs like `ssh://git@host.com:2222/`, which is a
|
||||
little ugly.
|
||||
|
||||
The easiest way to fix this is to put a load balancer in front of the host and
|
||||
have it forward TCP traffic on port `22` to port `2222`. Then users can clone
|
||||
from `ssh://git@host.com/` without an explicit port number and you don't need
|
||||
to do anything else.
|
||||
There are several different approaches you can use to mitigate or eliminate
|
||||
this problem.
|
||||
|
||||
Alternatively, you can move the administrative `sshd` to a new port, then run
|
||||
Phabricator `sshd` on port 22. This is complicated and risky. See "Moving the
|
||||
sshd Port" below for help.
|
||||
**Run on Port 2222**: You can do nothing, and just run the repository `sshd` on
|
||||
port `2222` and accept the explicit port in the URIs. This is the simplest
|
||||
approach, and you can always start here and clean things up later if you grow
|
||||
tired of dealing with the port number.
|
||||
|
||||
Finally, you can just run on port `2222` and accept the explicit port in the
|
||||
URIs. This is the simplest approach, and you can start here and clean things
|
||||
up later.
|
||||
**Use a Load Balancer**: You can configure a load balancer in front of the host
|
||||
and have it forward TCP traffic on port `22` to port `2222`. Then users can
|
||||
clone from `ssh://git@host.com/` without an explicit port number and you don't
|
||||
need to do anything else.
|
||||
|
||||
This may be very easy to set up, particularly if you are hosted in AWS, and
|
||||
is often the simplest and cleanest approach.
|
||||
|
||||
**Swap Ports**: You can move the administrative `sshd` to a new port, then run
|
||||
Phabricator `sshd` on port 22. This is somewhat complicated and can be a bit
|
||||
risky if you make a mistake. See "Moving the sshd Port" below for help.
|
||||
|
||||
**Change Client Config**: You can run on a nonstandard port, but configure SSH
|
||||
on the client side so that `ssh` automatically defaults to the correct port
|
||||
when connecting to the host. To do this, add a section like this to your
|
||||
`~/.ssh/config`:
|
||||
|
||||
```
|
||||
Host phabricator.corporation.com
|
||||
Port 2222
|
||||
```
|
||||
|
||||
(If you want, you can also add a default `User`.)
|
||||
|
||||
Command line tools like `ssh`, `git` and `hg` will now default to port
|
||||
`2222` when connecting to this host.
|
||||
|
||||
A downside to this approach is that your users will each need to set up their
|
||||
`~/.ssh/config` files individually.
|
||||
|
||||
This file also allows you to define short names for hosts using the `Host` and
|
||||
`HostName` options. If you choose to do this, be aware that Phabricator uses
|
||||
remote/clone URIs to figure out which repository it is operating in, but can
|
||||
not resolve host aliases defined in your `ssh` config. If you create host
|
||||
aliases they may break some features related to repository identification.
|
||||
|
||||
If you use this approach, you will also need to specify a port explicitly when
|
||||
connecting to administrate the host. Any unit tests or other build automation
|
||||
will also need to be configured or use explicit port numbers.
|
||||
|
||||
**Port Multiplexing**: If you have hardware access, you can power down the host
|
||||
and find the network I/O pins on the motherboard (for onboard networking) or
|
||||
network card.
|
||||
|
||||
Carefully strip and solder a short piece of copper wire between the pins for
|
||||
the external interface `22` and internal `2222`, so the external interface can
|
||||
receive traffic for both services.
|
||||
|
||||
(Make sure not to desolder the existing connection between external `22` and
|
||||
internal `22` or you won't be able to connect normally to administrate the
|
||||
host.)
|
||||
|
||||
The obvious downside to this approach is that it requires physical access to
|
||||
the machine, so it won't work if you're hosted on a cloud provider.
|
||||
|
||||
|
||||
SSHD Setup
|
||||
==========
|
||||
|
||||
Now that you've decided how you'll handle port assignment, you're ready to
|
||||
continue `sshd` setup.
|
||||
|
||||
If you plan to connect to a port other than `22`, you should set this port
|
||||
as `diffusion.ssh-port` in your Phabricator config:
|
||||
|
|
|
@ -85,14 +85,6 @@ final class PhabricatorClusterDatabasesConfigOptionType
|
|||
$map[$key] = true;
|
||||
}
|
||||
|
||||
if (count($masters) > 1) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Database cluster configuration is invalid: it describes multiple '.
|
||||
'masters. No more than one host may be a master. Hosts currently '.
|
||||
'configured as masters: %s.',
|
||||
implode(', ', $masters)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -157,6 +157,17 @@ final class PhabricatorDatabaseRef
|
|||
return $this->isIndividual;
|
||||
}
|
||||
|
||||
public function getRefKey() {
|
||||
$host = $this->getHost();
|
||||
|
||||
$port = $this->getPort();
|
||||
if (strlen($port)) {
|
||||
return "{$host}:{$port}";
|
||||
}
|
||||
|
||||
return $host;
|
||||
}
|
||||
|
||||
public static function getConnectionStatusMap() {
|
||||
return array(
|
||||
self::STATUS_OKAY => array(
|
||||
|
@ -212,7 +223,7 @@ final class PhabricatorDatabaseRef
|
|||
);
|
||||
}
|
||||
|
||||
public static function getLiveRefs() {
|
||||
public static function getClusterRefs() {
|
||||
$cache = PhabricatorCaches::getRequestCache();
|
||||
|
||||
$refs = $cache->getKey(self::KEY_REFS);
|
||||
|
@ -446,24 +457,46 @@ final class PhabricatorDatabaseRef
|
|||
return $this->healthRecord;
|
||||
}
|
||||
|
||||
public static function getMasterDatabaseRef() {
|
||||
$refs = self::getLiveRefs();
|
||||
public static function getActiveDatabaseRefs() {
|
||||
$refs = array();
|
||||
|
||||
if (!$refs) {
|
||||
return self::getLiveIndividualRef();
|
||||
foreach (self::getMasterDatabaseRefs() as $ref) {
|
||||
$refs[] = $ref;
|
||||
}
|
||||
|
||||
$master = null;
|
||||
foreach (self::getReplicaDatabaseRefs() as $ref) {
|
||||
$refs[] = $ref;
|
||||
}
|
||||
|
||||
return $refs;
|
||||
}
|
||||
|
||||
public static function getMasterDatabaseRefs() {
|
||||
$refs = self::getClusterRefs();
|
||||
|
||||
if (!$refs) {
|
||||
return array(self::getLiveIndividualRef());
|
||||
}
|
||||
|
||||
$masters = array();
|
||||
foreach ($refs as $ref) {
|
||||
if ($ref->getDisabled()) {
|
||||
continue;
|
||||
}
|
||||
if ($ref->getIsMaster()) {
|
||||
return $ref;
|
||||
$masters[] = $ref;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return $masters;
|
||||
}
|
||||
|
||||
public static function getMasterDatabaseRefForDatabase($database) {
|
||||
$masters = self::getMasterDatabaseRefs();
|
||||
|
||||
// TODO: Actually implement this.
|
||||
|
||||
return head($masters);
|
||||
}
|
||||
|
||||
public static function newIndividualRef() {
|
||||
|
@ -480,18 +513,14 @@ final class PhabricatorDatabaseRef
|
|||
->setIsMaster(true);
|
||||
}
|
||||
|
||||
public static function getReplicaDatabaseRef() {
|
||||
$refs = self::getLiveRefs();
|
||||
public static function getReplicaDatabaseRefs() {
|
||||
$refs = self::getClusterRefs();
|
||||
|
||||
if (!$refs) {
|
||||
return null;
|
||||
return array();
|
||||
}
|
||||
|
||||
// TODO: We may have multiple replicas to choose from, and could make
|
||||
// more of an effort to pick the "best" one here instead of always
|
||||
// picking the first one. Once we've picked one, we should try to use
|
||||
// the same replica for the rest of the request, though.
|
||||
|
||||
$replicas = array();
|
||||
foreach ($refs as $ref) {
|
||||
if ($ref->getDisabled()) {
|
||||
continue;
|
||||
|
@ -499,10 +528,24 @@ final class PhabricatorDatabaseRef
|
|||
if ($ref->getIsMaster()) {
|
||||
continue;
|
||||
}
|
||||
return $ref;
|
||||
|
||||
$replicas[] = $ref;
|
||||
}
|
||||
|
||||
return null;
|
||||
return $replicas;
|
||||
}
|
||||
|
||||
public static function getReplicaDatabaseRefForDatabase($database) {
|
||||
$replicas = self::getReplicaDatabaseRefs();
|
||||
|
||||
// TODO: Actually implement this.
|
||||
|
||||
// TODO: We may have multiple replicas to choose from, and could make
|
||||
// more of an effort to pick the "best" one here instead of always
|
||||
// picking the first one. Once we've picked one, we should try to use
|
||||
// the same replica for the rest of the request, though.
|
||||
|
||||
return head($replicas);
|
||||
}
|
||||
|
||||
private function newConnection(array $options) {
|
||||
|
|
17
src/infrastructure/env/PhabricatorEnv.php
vendored
17
src/infrastructure/env/PhabricatorEnv.php
vendored
|
@ -223,13 +223,24 @@ final class PhabricatorEnv extends Phobject {
|
|||
$stack->pushSource($site_source);
|
||||
}
|
||||
|
||||
$master = PhabricatorDatabaseRef::getMasterDatabaseRef();
|
||||
if (!$master) {
|
||||
$masters = PhabricatorDatabaseRef::getMasterDatabaseRefs();
|
||||
if (!$masters) {
|
||||
self::setReadOnly(true, self::READONLY_MASTERLESS);
|
||||
} else if ($master->isSevered()) {
|
||||
} else {
|
||||
// If any master is severed, we drop to readonly mode. In theory we
|
||||
// could try to continue if we're only missing some applications, but
|
||||
// this is very complex and we're unlikely to get it right.
|
||||
|
||||
foreach ($masters as $master) {
|
||||
// Give severed masters one last chance to get healthy.
|
||||
if ($master->isSevered()) {
|
||||
$master->checkHealth();
|
||||
}
|
||||
|
||||
if ($master->isSevered()) {
|
||||
self::setReadOnly(true, self::READONLY_SEVERED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1580,6 +1580,14 @@ final class PhabricatorUSEnglishTranslation
|
|||
'Restart %s build?',
|
||||
'Restart %s builds?',
|
||||
),
|
||||
|
||||
'%s is starting in %s minute(s), at %s.' => array(
|
||||
array(
|
||||
'%s is starting in one minute, at %3$s.',
|
||||
'%s is starting in %s minutes, at %s.',
|
||||
),
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -607,6 +607,28 @@ final class PhabricatorMarkupEngine extends Phobject {
|
|||
return array_values($files);
|
||||
}
|
||||
|
||||
public static function summarizeSentence($corpus) {
|
||||
$corpus = trim($corpus);
|
||||
$blocks = preg_split('/\n+/', $corpus, 2);
|
||||
$block = head($blocks);
|
||||
|
||||
$sentences = preg_split(
|
||||
'/\b([.?!]+)\B/u',
|
||||
$block,
|
||||
2,
|
||||
PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
if (count($sentences) > 1) {
|
||||
$result = $sentences[0].$sentences[1];
|
||||
} else {
|
||||
$result = head($sentences);
|
||||
}
|
||||
|
||||
return id(new PhutilUTF8StringTruncator())
|
||||
->setMaximumGlyphs(128)
|
||||
->truncateString($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a corpus summary, in a way that shortens the underlying text
|
||||
* without truncating it somewhere awkward.
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMarkupEngineTestCase
|
||||
extends PhabricatorTestCase {
|
||||
|
||||
public function testRemarkupSentenceSummmaries() {
|
||||
$this->assertSentenceSummary(
|
||||
'The quick brown fox. Jumped over the lazy dog.',
|
||||
'The quick brown fox.');
|
||||
|
||||
$this->assertSentenceSummary(
|
||||
'Go to www.help.com for details. Good day.',
|
||||
'Go to www.help.com for details.');
|
||||
|
||||
$this->assertSentenceSummary(
|
||||
'Coxy lummox gives squid who asks for job pen.',
|
||||
'Coxy lummox gives squid who asks for job pen.');
|
||||
|
||||
$this->assertSentenceSummary(
|
||||
'DEPRECATED',
|
||||
'DEPRECATED');
|
||||
|
||||
$this->assertSentenceSummary(
|
||||
'Never use this! It is deadly poison.',
|
||||
'Never use this!');
|
||||
|
||||
$this->assertSentenceSummary(
|
||||
"a short poem\nmeow meow meow\nmeow meow meow\n\n- cat",
|
||||
'a short poem');
|
||||
|
||||
$this->assertSentenceSummary(
|
||||
'WOW!! GREAT PROJECT!',
|
||||
'WOW!!');
|
||||
}
|
||||
|
||||
private function assertSentenceSummary($corpus, $summary) {
|
||||
$this->assertEqual(
|
||||
$summary,
|
||||
PhabricatorMarkupEngine::summarizeSentence($corpus),
|
||||
pht('Summary of: %s', $corpus));
|
||||
}
|
||||
|
||||
}
|
|
@ -114,7 +114,8 @@ abstract class PhabricatorLiskDAO extends LiskDAO {
|
|||
}
|
||||
|
||||
private function newClusterConnection($database, $mode) {
|
||||
$master = PhabricatorDatabaseRef::getMasterDatabaseRef();
|
||||
$master = PhabricatorDatabaseRef::getMasterDatabaseRefForDatabase(
|
||||
$database);
|
||||
|
||||
if ($master && !$master->isSevered()) {
|
||||
$connection = $master->newApplicationConnection($database);
|
||||
|
@ -130,7 +131,8 @@ abstract class PhabricatorLiskDAO extends LiskDAO {
|
|||
}
|
||||
}
|
||||
|
||||
$replica = PhabricatorDatabaseRef::getReplicaDatabaseRef();
|
||||
$replica = PhabricatorDatabaseRef::getReplicaDatabaseRefForDatabase(
|
||||
$database);
|
||||
if ($replica) {
|
||||
$connection = $replica->newApplicationConnection($database);
|
||||
$connection->setReadOnly(true);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
final class PhabricatorStorageManagementAPI extends Phobject {
|
||||
|
||||
private $ref;
|
||||
private $host;
|
||||
private $user;
|
||||
private $port;
|
||||
|
@ -74,6 +75,15 @@ final class PhabricatorStorageManagementAPI extends Phobject {
|
|||
return $this->port;
|
||||
}
|
||||
|
||||
public function setRef(PhabricatorDatabaseRef $ref) {
|
||||
$this->ref = $ref;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRef() {
|
||||
return $this->ref;
|
||||
}
|
||||
|
||||
public function getDatabaseName($fragment) {
|
||||
return $this->namespace.'_'.$fragment;
|
||||
}
|
||||
|
|
|
@ -27,12 +27,19 @@ final class PhabricatorStorageManagementAdjustWorkflow
|
|||
public function didExecute(PhutilArgumentParser $args) {
|
||||
$unsafe = $args->getArg('unsafe');
|
||||
|
||||
$this->requireAllPatchesApplied();
|
||||
return $this->adjustSchemata($unsafe);
|
||||
foreach ($this->getMasterAPIs() as $api) {
|
||||
$this->requireAllPatchesApplied($api);
|
||||
$err = $this->adjustSchemata($api, $unsafe);
|
||||
if ($err) {
|
||||
return $err;
|
||||
}
|
||||
}
|
||||
|
||||
private function requireAllPatchesApplied() {
|
||||
$api = $this->getAPI();
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function requireAllPatchesApplied(
|
||||
PhabricatorStorageManagementAPI $api) {
|
||||
$applied = $api->getAppliedPatches();
|
||||
|
||||
if ($applied === null) {
|
||||
|
|
|
@ -15,7 +15,8 @@ final class PhabricatorStorageManagementDatabasesWorkflow
|
|||
}
|
||||
|
||||
public function didExecute(PhutilArgumentParser $args) {
|
||||
$api = $this->getAPI();
|
||||
$api = $this->getAnyAPI();
|
||||
|
||||
$patches = $this->getPatches();
|
||||
|
||||
$databases = $api->getDatabaseList($patches, true);
|
||||
|
|
|
@ -23,6 +23,8 @@ final class PhabricatorStorageManagementDestroyWorkflow
|
|||
public function didExecute(PhutilArgumentParser $args) {
|
||||
$console = PhutilConsole::getConsole();
|
||||
|
||||
$api = $this->getSingleAPI();
|
||||
|
||||
if (!$this->isDryRun() && !$this->isForce()) {
|
||||
$console->writeOut(
|
||||
phutil_console_wrap(
|
||||
|
@ -42,7 +44,6 @@ final class PhabricatorStorageManagementDestroyWorkflow
|
|||
}
|
||||
}
|
||||
|
||||
$api = $this->getAPI();
|
||||
$patches = $this->getPatches();
|
||||
|
||||
if ($args->getArg('unittest-fixtures')) {
|
||||
|
|
|
@ -44,7 +44,7 @@ final class PhabricatorStorageManagementDumpWorkflow
|
|||
}
|
||||
|
||||
public function didExecute(PhutilArgumentParser $args) {
|
||||
$api = $this->getAPI();
|
||||
$api = $this->getSingleAPI();
|
||||
$patches = $this->getPatches();
|
||||
|
||||
$console = PhutilConsole::getConsole();
|
||||
|
|
|
@ -15,12 +15,13 @@ final class PhabricatorStorageManagementProbeWorkflow
|
|||
}
|
||||
|
||||
public function didExecute(PhutilArgumentParser $args) {
|
||||
$api = $this->getSingleAPI();
|
||||
|
||||
$console = PhutilConsole::getConsole();
|
||||
$console->writeErr(
|
||||
"%s\n",
|
||||
pht('Analyzing table sizes (this may take a moment)...'));
|
||||
|
||||
$api = $this->getAPI();
|
||||
$patches = $this->getPatches();
|
||||
$databases = $api->getDatabaseList($patches, true);
|
||||
|
||||
|
|
|
@ -36,7 +36,14 @@ final class PhabricatorStorageManagementQuickstartWorkflow
|
|||
|
||||
$bin = dirname(phutil_get_library_root('phabricator')).'/bin/storage';
|
||||
|
||||
if (!$this->getAPI()->isCharacterSetAvailable('utf8mb4')) {
|
||||
// We don't care which database we're using to generate a quickstart file,
|
||||
// since all of the schemata should be identical.
|
||||
$api = $this->getAnyAPI();
|
||||
|
||||
$ref = $api->getRef();
|
||||
$ref_key = $ref->getRefKey();
|
||||
|
||||
if (!$api->isCharacterSetAvailable('utf8mb4')) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'You can only generate a new quickstart file if MySQL supports '.
|
||||
|
@ -47,35 +54,39 @@ final class PhabricatorStorageManagementQuickstartWorkflow
|
|||
}
|
||||
|
||||
$err = phutil_passthru(
|
||||
'%s upgrade --force --no-quickstart --namespace %s',
|
||||
'%s upgrade --force --no-quickstart --namespace %s --ref %s',
|
||||
$bin,
|
||||
$namespace);
|
||||
$namespace,
|
||||
$ref_key);
|
||||
if ($err) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$err = phutil_passthru(
|
||||
'%s adjust --force --namespace %s',
|
||||
'%s adjust --force --namespace %s --ref %s',
|
||||
$bin,
|
||||
$namespace);
|
||||
$namespace,
|
||||
$ref_key);
|
||||
if ($err) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$tmp = new TempFile();
|
||||
$err = phutil_passthru(
|
||||
'%s dump --namespace %s > %s',
|
||||
'%s dump --namespace %s --ref %s > %s',
|
||||
$bin,
|
||||
$namespace,
|
||||
$ref_key,
|
||||
$tmp);
|
||||
if ($err) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$err = phutil_passthru(
|
||||
'%s destroy --force --namespace %s',
|
||||
'%s destroy --force --namespace %s --ref %s',
|
||||
$bin,
|
||||
$namespace);
|
||||
$namespace,
|
||||
$ref_key);
|
||||
if ($err) {
|
||||
return $err;
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
|
|||
if (!strlen($input) && !$is_live) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Specify the dumpfile to read with "--in", or use "--live" to '.
|
||||
'Specify the dumpfile to read with "--input", or use "--live" to '.
|
||||
'generate one automatically.'));
|
||||
}
|
||||
|
||||
|
@ -108,11 +108,15 @@ final class PhabricatorStorageManagementRenamespaceWorkflow
|
|||
}
|
||||
|
||||
if ($is_live) {
|
||||
$api = $this->getSingleAPI();
|
||||
$ref_key = $api->getRef()->getRefKey();
|
||||
|
||||
$root = dirname(phutil_get_library_root('phabricator'));
|
||||
|
||||
$future = new ExecFuture(
|
||||
'%R dump',
|
||||
$root.'/bin/storage');
|
||||
'%R dump --ref %s',
|
||||
$root.'/bin/storage',
|
||||
$ref_key);
|
||||
|
||||
$lines = new LinesOfALargeExecFuture($future);
|
||||
} else {
|
||||
|
|
|
@ -15,7 +15,7 @@ final class PhabricatorStorageManagementShellWorkflow
|
|||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$api = $this->getAPI();
|
||||
$api = $this->getSingleAPI();
|
||||
list($host, $port) = $this->getBareHostAndPort($api->getHost());
|
||||
|
||||
$flag_port = $port
|
||||
|
|
|
@ -15,9 +15,8 @@ final class PhabricatorStorageManagementStatusWorkflow
|
|||
}
|
||||
|
||||
public function didExecute(PhutilArgumentParser $args) {
|
||||
$api = $this->getAPI();
|
||||
foreach ($this->getAPIs() as $api) {
|
||||
$patches = $this->getPatches();
|
||||
|
||||
$applied = $api->getAppliedPatches();
|
||||
|
||||
if ($applied === null) {
|
||||
|
@ -29,9 +28,12 @@ final class PhabricatorStorageManagementStatusWorkflow
|
|||
return 1;
|
||||
}
|
||||
|
||||
$ref = $api->getRef();
|
||||
|
||||
$table = id(new PhutilConsoleTable())
|
||||
->setShowHeader(false)
|
||||
->addColumn('id', array('title' => pht('ID')))
|
||||
->addColumn('host', array('title' => pht('Host')))
|
||||
->addColumn('status', array('title' => pht('Status')))
|
||||
->addColumn('duration', array('title' => pht('Duration')))
|
||||
->addColumn('type', array('title' => pht('Type')))
|
||||
|
@ -49,6 +51,7 @@ final class PhabricatorStorageManagementStatusWorkflow
|
|||
|
||||
$table->addRow(array(
|
||||
'id' => $patch->getFullKey(),
|
||||
'host' => $ref->getRefKey(),
|
||||
'status' => in_array($patch->getFullKey(), $applied)
|
||||
? pht('Applied')
|
||||
: pht('Not Applied'),
|
||||
|
@ -59,6 +62,7 @@ final class PhabricatorStorageManagementStatusWorkflow
|
|||
}
|
||||
|
||||
$table->draw();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -73,16 +73,24 @@ final class PhabricatorStorageManagementUpgradeWorkflow
|
|||
$init_only = $args->getArg('init-only');
|
||||
$no_adjust = $args->getArg('no-adjust');
|
||||
|
||||
$this->upgradeSchemata($apply_only, $no_quickstart, $init_only);
|
||||
$apis = $this->getMasterAPIs();
|
||||
|
||||
foreach ($apis as $api) {
|
||||
$this->upgradeSchemata($api, $apply_only, $no_quickstart, $init_only);
|
||||
|
||||
if ($no_adjust || $init_only || $apply_only) {
|
||||
$console->writeOut(
|
||||
"%s\n",
|
||||
pht('Declining to apply storage adjustments.'));
|
||||
return 0;
|
||||
} else {
|
||||
return $this->adjustSchemata(false);
|
||||
$err = $this->adjustSchemata($api, false);
|
||||
if ($err) {
|
||||
return $err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,20 +3,56 @@
|
|||
abstract class PhabricatorStorageManagementWorkflow
|
||||
extends PhabricatorManagementWorkflow {
|
||||
|
||||
private $api;
|
||||
private $apis = array();
|
||||
private $dryRun;
|
||||
private $force;
|
||||
private $patches;
|
||||
|
||||
private $didInitialize;
|
||||
|
||||
final public function getAPI() {
|
||||
return $this->api;
|
||||
final public function setAPIs(array $apis) {
|
||||
$this->apis = $apis;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function setAPI(PhabricatorStorageManagementAPI $api) {
|
||||
$this->api = $api;
|
||||
return $this;
|
||||
final public function getAnyAPI() {
|
||||
return head($this->getAPIs());
|
||||
}
|
||||
|
||||
final public function getMasterAPIs() {
|
||||
$apis = $this->getAPIs();
|
||||
|
||||
$results = array();
|
||||
foreach ($apis as $api) {
|
||||
if ($api->getRef()->getIsMaster()) {
|
||||
$results[] = $api;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$results) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'This command only operates on database masters, but the selected '.
|
||||
'database hosts do not include any masters.'));
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
final public function getSingleAPI() {
|
||||
$apis = $this->getAPIs();
|
||||
if (count($apis) == 1) {
|
||||
return head($apis);
|
||||
}
|
||||
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Phabricator is configured in cluster mode, with multiple database '.
|
||||
'hosts. Use "--host" to specify which host you want to operate on.'));
|
||||
}
|
||||
|
||||
final public function getAPIs() {
|
||||
return $this->apis;
|
||||
}
|
||||
|
||||
final protected function isDryRun() {
|
||||
|
@ -73,22 +109,34 @@ abstract class PhabricatorStorageManagementWorkflow
|
|||
|
||||
public function didExecute(PhutilArgumentParser $args) {}
|
||||
|
||||
private function loadSchemata() {
|
||||
$query = id(new PhabricatorConfigSchemaQuery())
|
||||
->setAPI($this->getAPI());
|
||||
private function loadSchemata(PhabricatorStorageManagementAPI $api) {
|
||||
$query = id(new PhabricatorConfigSchemaQuery());
|
||||
|
||||
$actual = $query->loadActualSchema();
|
||||
$expect = $query->loadExpectedSchema();
|
||||
$comp = $query->buildComparisonSchema($expect, $actual);
|
||||
$ref = $api->getRef();
|
||||
$ref_key = $ref->getRefKey();
|
||||
|
||||
return array($comp, $expect, $actual);
|
||||
$query->setAPIs(array($api));
|
||||
$query->setRefs(array($ref));
|
||||
|
||||
$actual = $query->loadActualSchemata();
|
||||
$expect = $query->loadExpectedSchemata();
|
||||
$comp = $query->buildComparisonSchemata($expect, $actual);
|
||||
|
||||
return array(
|
||||
$comp[$ref_key],
|
||||
$expect[$ref_key],
|
||||
$actual[$ref_key],
|
||||
);
|
||||
}
|
||||
|
||||
final protected function adjustSchemata($unsafe) {
|
||||
$lock = $this->lock();
|
||||
final protected function adjustSchemata(
|
||||
PhabricatorStorageManagementAPI $api,
|
||||
$unsafe) {
|
||||
|
||||
$lock = $this->lock($api);
|
||||
|
||||
try {
|
||||
$err = $this->doAdjustSchemata($unsafe);
|
||||
$err = $this->doAdjustSchemata($api, $unsafe);
|
||||
} catch (Exception $ex) {
|
||||
$lock->unlock();
|
||||
throw $ex;
|
||||
|
@ -99,15 +147,19 @@ abstract class PhabricatorStorageManagementWorkflow
|
|||
return $err;
|
||||
}
|
||||
|
||||
final private function doAdjustSchemata($unsafe) {
|
||||
final private function doAdjustSchemata(
|
||||
PhabricatorStorageManagementAPI $api,
|
||||
$unsafe) {
|
||||
|
||||
$console = PhutilConsole::getConsole();
|
||||
|
||||
$console->writeOut(
|
||||
"%s\n",
|
||||
pht('Verifying database schemata...'));
|
||||
pht(
|
||||
'Verifying database schemata on "%s"...',
|
||||
$api->getRef()->getRefKey()));
|
||||
|
||||
list($adjustments, $errors) = $this->findAdjustments();
|
||||
$api = $this->getAPI();
|
||||
list($adjustments, $errors) = $this->findAdjustments($api);
|
||||
|
||||
if (!$adjustments) {
|
||||
$console->writeOut(
|
||||
|
@ -415,8 +467,9 @@ abstract class PhabricatorStorageManagementWorkflow
|
|||
return $this->printErrors($errors, $err);
|
||||
}
|
||||
|
||||
private function findAdjustments() {
|
||||
list($comp, $expect, $actual) = $this->loadSchemata();
|
||||
private function findAdjustments(
|
||||
PhabricatorStorageManagementAPI $api) {
|
||||
list($comp, $expect, $actual) = $this->loadSchemata($api);
|
||||
|
||||
$issue_charset = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
|
||||
$issue_collation = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
|
||||
|
@ -766,14 +819,15 @@ abstract class PhabricatorStorageManagementWorkflow
|
|||
}
|
||||
|
||||
final protected function upgradeSchemata(
|
||||
PhabricatorStorageManagementAPI $api,
|
||||
$apply_only = null,
|
||||
$no_quickstart = false,
|
||||
$init_only = false) {
|
||||
|
||||
$lock = $this->lock();
|
||||
$lock = $this->lock($api);
|
||||
|
||||
try {
|
||||
$this->doUpgradeSchemata($apply_only, $no_quickstart, $init_only);
|
||||
$this->doUpgradeSchemata($api, $apply_only, $no_quickstart, $init_only);
|
||||
} catch (Exception $ex) {
|
||||
$lock->unlock();
|
||||
throw $ex;
|
||||
|
@ -783,13 +837,12 @@ abstract class PhabricatorStorageManagementWorkflow
|
|||
}
|
||||
|
||||
final private function doUpgradeSchemata(
|
||||
PhabricatorStorageManagementAPI $api,
|
||||
$apply_only,
|
||||
$no_quickstart,
|
||||
$init_only) {
|
||||
|
||||
$api = $this->getAPI();
|
||||
|
||||
$applied = $this->getApi()->getAppliedPatches();
|
||||
$applied = $api->getAppliedPatches();
|
||||
if ($applied === null) {
|
||||
if ($this->dryRun) {
|
||||
echo pht(
|
||||
|
@ -923,11 +976,13 @@ abstract class PhabricatorStorageManagementWorkflow
|
|||
if (count($this->patches)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Some patches could not be applied: %s',
|
||||
'Some patches could not be applied to "%s": %s',
|
||||
$api->getRef()->getRefKey(),
|
||||
implode(', ', array_keys($this->patches))));
|
||||
} else if (!$this->dryRun && !$apply_only) {
|
||||
echo pht(
|
||||
"Storage is up to date. Use '%s' for details.",
|
||||
'Storage is up to date on "%s". Use "%s" for details.',
|
||||
$api->getRef()->getRefKey(),
|
||||
'storage status')."\n";
|
||||
}
|
||||
break;
|
||||
|
@ -955,9 +1010,9 @@ abstract class PhabricatorStorageManagementWorkflow
|
|||
*
|
||||
* @return PhabricatorGlobalLock
|
||||
*/
|
||||
final protected function lock() {
|
||||
final protected function lock(PhabricatorStorageManagementAPI $api) {
|
||||
return PhabricatorGlobalLock::newLock(__CLASS__)
|
||||
->useSpecificConnection($this->getApi()->getConn(null))
|
||||
->useSpecificConnection($api->getConn(null))
|
||||
->lock();
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue