Merge branch 'master' into blender-tweaks
This commit is contained in:
@@ -9,7 +9,7 @@ return array(
|
|||||||
'names' => array(
|
'names' => array(
|
||||||
'conpherence.pkg.css' => '3c8a0668',
|
'conpherence.pkg.css' => '3c8a0668',
|
||||||
'conpherence.pkg.js' => '020aebcf',
|
'conpherence.pkg.js' => '020aebcf',
|
||||||
'core.pkg.css' => 'eef4903d',
|
'core.pkg.css' => '242f9ce6',
|
||||||
'core.pkg.js' => '73a06a9f',
|
'core.pkg.js' => '73a06a9f',
|
||||||
'differential.pkg.css' => '8d8360fb',
|
'differential.pkg.css' => '8d8360fb',
|
||||||
'differential.pkg.js' => '0b037a4f',
|
'differential.pkg.js' => '0b037a4f',
|
||||||
@@ -141,7 +141,7 @@ return array(
|
|||||||
'rsrc/css/phui/phui-big-info-view.css' => '362ad37b',
|
'rsrc/css/phui/phui-big-info-view.css' => '362ad37b',
|
||||||
'rsrc/css/phui/phui-box.css' => '5ed3b8cb',
|
'rsrc/css/phui/phui-box.css' => '5ed3b8cb',
|
||||||
'rsrc/css/phui/phui-bulk-editor.css' => '374d5e30',
|
'rsrc/css/phui/phui-bulk-editor.css' => '374d5e30',
|
||||||
'rsrc/css/phui/phui-chart.css' => '10135a9d',
|
'rsrc/css/phui/phui-chart.css' => '14df9ae3',
|
||||||
'rsrc/css/phui/phui-cms.css' => '8c05c41e',
|
'rsrc/css/phui/phui-cms.css' => '8c05c41e',
|
||||||
'rsrc/css/phui/phui-comment-form.css' => '68a2d99a',
|
'rsrc/css/phui/phui-comment-form.css' => '68a2d99a',
|
||||||
'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0',
|
'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0',
|
||||||
@@ -155,7 +155,7 @@ return array(
|
|||||||
'rsrc/css/phui/phui-form-view.css' => 'a8e0a1ab',
|
'rsrc/css/phui/phui-form-view.css' => 'a8e0a1ab',
|
||||||
'rsrc/css/phui/phui-form.css' => '159e2d9c',
|
'rsrc/css/phui/phui-form.css' => '159e2d9c',
|
||||||
'rsrc/css/phui/phui-head-thing.css' => 'd7f293df',
|
'rsrc/css/phui/phui-head-thing.css' => 'd7f293df',
|
||||||
'rsrc/css/phui/phui-header-view.css' => '285c9139',
|
'rsrc/css/phui/phui-header-view.css' => 'b500eeea',
|
||||||
'rsrc/css/phui/phui-hovercard.css' => '6ca90fa0',
|
'rsrc/css/phui/phui-hovercard.css' => '6ca90fa0',
|
||||||
'rsrc/css/phui/phui-icon-set-selector.css' => '7aa5f3ec',
|
'rsrc/css/phui/phui-icon-set-selector.css' => '7aa5f3ec',
|
||||||
'rsrc/css/phui/phui-icon.css' => '4cbc684a',
|
'rsrc/css/phui/phui-icon.css' => '4cbc684a',
|
||||||
@@ -168,6 +168,7 @@ return array(
|
|||||||
'rsrc/css/phui/phui-object-box.css' => 'f434b6be',
|
'rsrc/css/phui/phui-object-box.css' => 'f434b6be',
|
||||||
'rsrc/css/phui/phui-pager.css' => 'd022c7ad',
|
'rsrc/css/phui/phui-pager.css' => 'd022c7ad',
|
||||||
'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8',
|
'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8',
|
||||||
|
'rsrc/css/phui/phui-policy-section-view.css' => '139fdc64',
|
||||||
'rsrc/css/phui/phui-property-list-view.css' => 'cad62236',
|
'rsrc/css/phui/phui-property-list-view.css' => 'cad62236',
|
||||||
'rsrc/css/phui/phui-remarkup-preview.css' => '91767007',
|
'rsrc/css/phui/phui-remarkup-preview.css' => '91767007',
|
||||||
'rsrc/css/phui/phui-segment-bar-view.css' => '5166b370',
|
'rsrc/css/phui/phui-segment-bar-view.css' => '5166b370',
|
||||||
@@ -178,7 +179,7 @@ return array(
|
|||||||
'rsrc/css/phui/phui-two-column-view.css' => '01e6991e',
|
'rsrc/css/phui/phui-two-column-view.css' => '01e6991e',
|
||||||
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'e86de308',
|
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'e86de308',
|
||||||
'rsrc/css/phui/workboards/phui-workboard.css' => '74fc9d98',
|
'rsrc/css/phui/workboards/phui-workboard.css' => '74fc9d98',
|
||||||
'rsrc/css/phui/workboards/phui-workcard.css' => '9e9eb0df',
|
'rsrc/css/phui/workboards/phui-workcard.css' => '913441b6',
|
||||||
'rsrc/css/phui/workboards/phui-workpanel.css' => '3ae89b20',
|
'rsrc/css/phui/workboards/phui-workpanel.css' => '3ae89b20',
|
||||||
'rsrc/css/sprite-login.css' => '18b368a6',
|
'rsrc/css/sprite-login.css' => '18b368a6',
|
||||||
'rsrc/css/sprite-tokens.css' => 'f1896dc5',
|
'rsrc/css/sprite-tokens.css' => 'f1896dc5',
|
||||||
@@ -391,14 +392,14 @@ return array(
|
|||||||
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123',
|
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123',
|
||||||
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
|
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
|
||||||
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b',
|
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b',
|
||||||
'rsrc/js/application/fact/Chart.js' => 'eec96de0',
|
'rsrc/js/application/fact/Chart.js' => '52e3ff03',
|
||||||
'rsrc/js/application/fact/ChartCurtainView.js' => '86954222',
|
'rsrc/js/application/fact/ChartCurtainView.js' => '86954222',
|
||||||
'rsrc/js/application/fact/ChartFunctionLabel.js' => '81de1dab',
|
'rsrc/js/application/fact/ChartFunctionLabel.js' => '81de1dab',
|
||||||
'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22',
|
'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22',
|
||||||
'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb',
|
'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb',
|
||||||
'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1',
|
'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1',
|
||||||
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => 'b347a301',
|
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => 'b347a301',
|
||||||
'rsrc/js/application/herald/HeraldRuleEditor.js' => '27daef73',
|
'rsrc/js/application/herald/HeraldRuleEditor.js' => '2633bef7',
|
||||||
'rsrc/js/application/herald/PathTypeahead.js' => 'ad486db3',
|
'rsrc/js/application/herald/PathTypeahead.js' => 'ad486db3',
|
||||||
'rsrc/js/application/herald/herald-rule-editor.js' => '0922e81d',
|
'rsrc/js/application/herald/herald-rule-editor.js' => '0922e81d',
|
||||||
'rsrc/js/application/maniphest/behavior-batch-selector.js' => '139ef688',
|
'rsrc/js/application/maniphest/behavior-batch-selector.js' => '139ef688',
|
||||||
@@ -572,7 +573,7 @@ return array(
|
|||||||
'global-drag-and-drop-css' => '1d2713a4',
|
'global-drag-and-drop-css' => '1d2713a4',
|
||||||
'harbormaster-css' => '8dfe16b2',
|
'harbormaster-css' => '8dfe16b2',
|
||||||
'herald-css' => '648d39e2',
|
'herald-css' => '648d39e2',
|
||||||
'herald-rule-editor' => '27daef73',
|
'herald-rule-editor' => '2633bef7',
|
||||||
'herald-test-css' => 'e004176f',
|
'herald-test-css' => 'e004176f',
|
||||||
'inline-comment-summary-css' => '81eb368d',
|
'inline-comment-summary-css' => '81eb368d',
|
||||||
'javelin-aphlict' => '022516b4',
|
'javelin-aphlict' => '022516b4',
|
||||||
@@ -700,7 +701,7 @@ return array(
|
|||||||
'javelin-behavior-user-menu' => '60cd9241',
|
'javelin-behavior-user-menu' => '60cd9241',
|
||||||
'javelin-behavior-view-placeholder' => 'a9942052',
|
'javelin-behavior-view-placeholder' => 'a9942052',
|
||||||
'javelin-behavior-workflow' => '9623adc1',
|
'javelin-behavior-workflow' => '9623adc1',
|
||||||
'javelin-chart' => 'eec96de0',
|
'javelin-chart' => '52e3ff03',
|
||||||
'javelin-chart-curtain-view' => '86954222',
|
'javelin-chart-curtain-view' => '86954222',
|
||||||
'javelin-chart-function-label' => '81de1dab',
|
'javelin-chart-function-label' => '81de1dab',
|
||||||
'javelin-color' => '78f811c9',
|
'javelin-color' => '78f811c9',
|
||||||
@@ -830,7 +831,7 @@ return array(
|
|||||||
'phui-calendar-day-css' => '9597d706',
|
'phui-calendar-day-css' => '9597d706',
|
||||||
'phui-calendar-list-css' => 'ccd7e4e2',
|
'phui-calendar-list-css' => 'ccd7e4e2',
|
||||||
'phui-calendar-month-css' => 'cb758c42',
|
'phui-calendar-month-css' => 'cb758c42',
|
||||||
'phui-chart-css' => '10135a9d',
|
'phui-chart-css' => '14df9ae3',
|
||||||
'phui-cms-css' => '8c05c41e',
|
'phui-cms-css' => '8c05c41e',
|
||||||
'phui-comment-form-css' => '68a2d99a',
|
'phui-comment-form-css' => '68a2d99a',
|
||||||
'phui-comment-panel-css' => 'ec4e31c0',
|
'phui-comment-panel-css' => 'ec4e31c0',
|
||||||
@@ -845,7 +846,7 @@ return array(
|
|||||||
'phui-form-css' => '159e2d9c',
|
'phui-form-css' => '159e2d9c',
|
||||||
'phui-form-view-css' => 'a8e0a1ab',
|
'phui-form-view-css' => 'a8e0a1ab',
|
||||||
'phui-head-thing-view-css' => 'd7f293df',
|
'phui-head-thing-view-css' => 'd7f293df',
|
||||||
'phui-header-view-css' => '285c9139',
|
'phui-header-view-css' => 'b500eeea',
|
||||||
'phui-hovercard' => '074f0783',
|
'phui-hovercard' => '074f0783',
|
||||||
'phui-hovercard-view-css' => '6ca90fa0',
|
'phui-hovercard-view-css' => '6ca90fa0',
|
||||||
'phui-icon-set-selector-css' => '7aa5f3ec',
|
'phui-icon-set-selector-css' => '7aa5f3ec',
|
||||||
@@ -866,6 +867,7 @@ return array(
|
|||||||
'phui-oi-simple-ui-css' => '6a30fa46',
|
'phui-oi-simple-ui-css' => '6a30fa46',
|
||||||
'phui-pager-css' => 'd022c7ad',
|
'phui-pager-css' => 'd022c7ad',
|
||||||
'phui-pinboard-view-css' => '1f08f5d8',
|
'phui-pinboard-view-css' => '1f08f5d8',
|
||||||
|
'phui-policy-section-view-css' => '139fdc64',
|
||||||
'phui-property-list-view-css' => 'cad62236',
|
'phui-property-list-view-css' => 'cad62236',
|
||||||
'phui-remarkup-preview-css' => '91767007',
|
'phui-remarkup-preview-css' => '91767007',
|
||||||
'phui-segment-bar-view-css' => '5166b370',
|
'phui-segment-bar-view-css' => '5166b370',
|
||||||
@@ -877,7 +879,7 @@ return array(
|
|||||||
'phui-two-column-view-css' => '01e6991e',
|
'phui-two-column-view-css' => '01e6991e',
|
||||||
'phui-workboard-color-css' => 'e86de308',
|
'phui-workboard-color-css' => 'e86de308',
|
||||||
'phui-workboard-view-css' => '74fc9d98',
|
'phui-workboard-view-css' => '74fc9d98',
|
||||||
'phui-workcard-view-css' => '9e9eb0df',
|
'phui-workcard-view-css' => '913441b6',
|
||||||
'phui-workpanel-view-css' => '3ae89b20',
|
'phui-workpanel-view-css' => '3ae89b20',
|
||||||
'phuix-action-list-view' => 'c68f183f',
|
'phuix-action-list-view' => 'c68f183f',
|
||||||
'phuix-action-view' => 'aaa08f3b',
|
'phuix-action-view' => 'aaa08f3b',
|
||||||
@@ -1115,7 +1117,7 @@ return array(
|
|||||||
'javelin-json',
|
'javelin-json',
|
||||||
'phabricator-draggable-list',
|
'phabricator-draggable-list',
|
||||||
),
|
),
|
||||||
'27daef73' => array(
|
'2633bef7' => array(
|
||||||
'multirow-row-manager',
|
'multirow-row-manager',
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
@@ -1367,6 +1369,12 @@ return array(
|
|||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
'javelin-fx',
|
'javelin-fx',
|
||||||
),
|
),
|
||||||
|
'52e3ff03' => array(
|
||||||
|
'phui-chart-css',
|
||||||
|
'd3',
|
||||||
|
'javelin-chart-curtain-view',
|
||||||
|
'javelin-chart-function-label',
|
||||||
|
),
|
||||||
'541f81c3' => array(
|
'541f81c3' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
),
|
),
|
||||||
@@ -2128,12 +2136,6 @@ return array(
|
|||||||
'phabricator-keyboard-shortcut',
|
'phabricator-keyboard-shortcut',
|
||||||
'javelin-stratcom',
|
'javelin-stratcom',
|
||||||
),
|
),
|
||||||
'eec96de0' => array(
|
|
||||||
'phui-chart-css',
|
|
||||||
'd3',
|
|
||||||
'javelin-chart-curtain-view',
|
|
||||||
'javelin-chart-function-label',
|
|
||||||
),
|
|
||||||
'ef836bf2' => array(
|
'ef836bf2' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
|
|||||||
3
resources/sql/autopatches/20190909.herald.01.rebuild.php
Normal file
3
resources/sql/autopatches/20190909.herald.01.rebuild.php
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
PhabricatorRebuildIndexesWorker::rebuildObjectsWithQuery('HeraldRuleQuery');
|
||||||
@@ -1021,6 +1021,7 @@ phutil_register_library_map(array(
|
|||||||
'DiffusionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSSHWorkflow.php',
|
'DiffusionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSSHWorkflow.php',
|
||||||
'DiffusionSearchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php',
|
'DiffusionSearchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php',
|
||||||
'DiffusionServeController' => 'applications/diffusion/controller/DiffusionServeController.php',
|
'DiffusionServeController' => 'applications/diffusion/controller/DiffusionServeController.php',
|
||||||
|
'DiffusionServiceRef' => 'applications/diffusion/ref/DiffusionServiceRef.php',
|
||||||
'DiffusionSetPasswordSettingsPanel' => 'applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php',
|
'DiffusionSetPasswordSettingsPanel' => 'applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php',
|
||||||
'DiffusionSetupException' => 'applications/diffusion/exception/DiffusionSetupException.php',
|
'DiffusionSetupException' => 'applications/diffusion/exception/DiffusionSetupException.php',
|
||||||
'DiffusionSourceHyperlinkEngineExtension' => 'applications/diffusion/engineextension/DiffusionSourceHyperlinkEngineExtension.php',
|
'DiffusionSourceHyperlinkEngineExtension' => 'applications/diffusion/engineextension/DiffusionSourceHyperlinkEngineExtension.php',
|
||||||
@@ -3107,6 +3108,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php',
|
'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php',
|
||||||
'PhabricatorDefaultSyntaxStyle' => 'infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php',
|
'PhabricatorDefaultSyntaxStyle' => 'infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php',
|
||||||
'PhabricatorDefaultUnlockEngine' => 'applications/system/engine/PhabricatorDefaultUnlockEngine.php',
|
'PhabricatorDefaultUnlockEngine' => 'applications/system/engine/PhabricatorDefaultUnlockEngine.php',
|
||||||
|
'PhabricatorDemoChartEngine' => 'applications/fact/engine/PhabricatorDemoChartEngine.php',
|
||||||
'PhabricatorDestructibleCodex' => 'applications/system/codex/PhabricatorDestructibleCodex.php',
|
'PhabricatorDestructibleCodex' => 'applications/system/codex/PhabricatorDestructibleCodex.php',
|
||||||
'PhabricatorDestructibleCodexInterface' => 'applications/system/interface/PhabricatorDestructibleCodexInterface.php',
|
'PhabricatorDestructibleCodexInterface' => 'applications/system/interface/PhabricatorDestructibleCodexInterface.php',
|
||||||
'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php',
|
'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php',
|
||||||
@@ -3220,6 +3222,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorEditEngineSettingsPanel' => 'applications/settings/panel/PhabricatorEditEngineSettingsPanel.php',
|
'PhabricatorEditEngineSettingsPanel' => 'applications/settings/panel/PhabricatorEditEngineSettingsPanel.php',
|
||||||
'PhabricatorEditEngineStaticCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineStaticCommentAction.php',
|
'PhabricatorEditEngineStaticCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineStaticCommentAction.php',
|
||||||
'PhabricatorEditEngineSubtype' => 'applications/transactions/editengine/PhabricatorEditEngineSubtype.php',
|
'PhabricatorEditEngineSubtype' => 'applications/transactions/editengine/PhabricatorEditEngineSubtype.php',
|
||||||
|
'PhabricatorEditEngineSubtypeHeraldField' => 'applications/transactions/herald/PhabricatorEditEngineSubtypeHeraldField.php',
|
||||||
'PhabricatorEditEngineSubtypeInterface' => 'applications/transactions/editengine/PhabricatorEditEngineSubtypeInterface.php',
|
'PhabricatorEditEngineSubtypeInterface' => 'applications/transactions/editengine/PhabricatorEditEngineSubtypeInterface.php',
|
||||||
'PhabricatorEditEngineSubtypeMap' => 'applications/transactions/editengine/PhabricatorEditEngineSubtypeMap.php',
|
'PhabricatorEditEngineSubtypeMap' => 'applications/transactions/editengine/PhabricatorEditEngineSubtypeMap.php',
|
||||||
'PhabricatorEditEngineSubtypeTestCase' => 'applications/transactions/editengine/__tests__/PhabricatorEditEngineSubtypeTestCase.php',
|
'PhabricatorEditEngineSubtypeTestCase' => 'applications/transactions/editengine/__tests__/PhabricatorEditEngineSubtypeTestCase.php',
|
||||||
@@ -3441,8 +3444,10 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorFlagDeleteController' => 'applications/flag/controller/PhabricatorFlagDeleteController.php',
|
'PhabricatorFlagDeleteController' => 'applications/flag/controller/PhabricatorFlagDeleteController.php',
|
||||||
'PhabricatorFlagDestructionEngineExtension' => 'applications/flag/engineextension/PhabricatorFlagDestructionEngineExtension.php',
|
'PhabricatorFlagDestructionEngineExtension' => 'applications/flag/engineextension/PhabricatorFlagDestructionEngineExtension.php',
|
||||||
'PhabricatorFlagEditController' => 'applications/flag/controller/PhabricatorFlagEditController.php',
|
'PhabricatorFlagEditController' => 'applications/flag/controller/PhabricatorFlagEditController.php',
|
||||||
|
'PhabricatorFlagHeraldAction' => 'applications/flag/herald/PhabricatorFlagHeraldAction.php',
|
||||||
'PhabricatorFlagListController' => 'applications/flag/controller/PhabricatorFlagListController.php',
|
'PhabricatorFlagListController' => 'applications/flag/controller/PhabricatorFlagListController.php',
|
||||||
'PhabricatorFlagQuery' => 'applications/flag/query/PhabricatorFlagQuery.php',
|
'PhabricatorFlagQuery' => 'applications/flag/query/PhabricatorFlagQuery.php',
|
||||||
|
'PhabricatorFlagRemoveFlagHeraldAction' => 'applications/flag/herald/PhabricatorFlagRemoveFlagHeraldAction.php',
|
||||||
'PhabricatorFlagSearchEngine' => 'applications/flag/query/PhabricatorFlagSearchEngine.php',
|
'PhabricatorFlagSearchEngine' => 'applications/flag/query/PhabricatorFlagSearchEngine.php',
|
||||||
'PhabricatorFlagSelectControl' => 'applications/flag/view/PhabricatorFlagSelectControl.php',
|
'PhabricatorFlagSelectControl' => 'applications/flag/view/PhabricatorFlagSelectControl.php',
|
||||||
'PhabricatorFlaggableInterface' => 'applications/flag/interface/PhabricatorFlaggableInterface.php',
|
'PhabricatorFlaggableInterface' => 'applications/flag/interface/PhabricatorFlaggableInterface.php',
|
||||||
@@ -4196,14 +4201,17 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorPolicyManagementWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementWorkflow.php',
|
'PhabricatorPolicyManagementWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementWorkflow.php',
|
||||||
'PhabricatorPolicyPHIDTypePolicy' => 'applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php',
|
'PhabricatorPolicyPHIDTypePolicy' => 'applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php',
|
||||||
'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php',
|
'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php',
|
||||||
|
'PhabricatorPolicyRef' => 'applications/policy/view/PhabricatorPolicyRef.php',
|
||||||
'PhabricatorPolicyRequestExceptionHandler' => 'aphront/handler/PhabricatorPolicyRequestExceptionHandler.php',
|
'PhabricatorPolicyRequestExceptionHandler' => 'aphront/handler/PhabricatorPolicyRequestExceptionHandler.php',
|
||||||
'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php',
|
'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php',
|
||||||
|
'PhabricatorPolicyRulesView' => 'applications/policy/view/PhabricatorPolicyRulesView.php',
|
||||||
'PhabricatorPolicySearchEngineExtension' => 'applications/policy/engineextension/PhabricatorPolicySearchEngineExtension.php',
|
'PhabricatorPolicySearchEngineExtension' => 'applications/policy/engineextension/PhabricatorPolicySearchEngineExtension.php',
|
||||||
'PhabricatorPolicyStrengthConstants' => 'applications/policy/constants/PhabricatorPolicyStrengthConstants.php',
|
'PhabricatorPolicyStrengthConstants' => 'applications/policy/constants/PhabricatorPolicyStrengthConstants.php',
|
||||||
'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php',
|
'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php',
|
||||||
'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php',
|
'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php',
|
||||||
'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php',
|
'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php',
|
||||||
'PhabricatorPonderApplication' => 'applications/ponder/application/PhabricatorPonderApplication.php',
|
'PhabricatorPonderApplication' => 'applications/ponder/application/PhabricatorPonderApplication.php',
|
||||||
|
'PhabricatorPreambleTestCase' => 'infrastructure/util/__tests__/PhabricatorPreambleTestCase.php',
|
||||||
'PhabricatorPrimaryEmailUserLogType' => 'applications/people/userlog/PhabricatorPrimaryEmailUserLogType.php',
|
'PhabricatorPrimaryEmailUserLogType' => 'applications/people/userlog/PhabricatorPrimaryEmailUserLogType.php',
|
||||||
'PhabricatorProfileMenuEditEngine' => 'applications/search/editor/PhabricatorProfileMenuEditEngine.php',
|
'PhabricatorProfileMenuEditEngine' => 'applications/search/editor/PhabricatorProfileMenuEditEngine.php',
|
||||||
'PhabricatorProfileMenuEditor' => 'applications/search/editor/PhabricatorProfileMenuEditor.php',
|
'PhabricatorProfileMenuEditor' => 'applications/search/editor/PhabricatorProfileMenuEditor.php',
|
||||||
@@ -4220,6 +4228,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorProfileMenuItemView' => 'applications/search/engine/PhabricatorProfileMenuItemView.php',
|
'PhabricatorProfileMenuItemView' => 'applications/search/engine/PhabricatorProfileMenuItemView.php',
|
||||||
'PhabricatorProfileMenuItemViewList' => 'applications/search/engine/PhabricatorProfileMenuItemViewList.php',
|
'PhabricatorProfileMenuItemViewList' => 'applications/search/engine/PhabricatorProfileMenuItemViewList.php',
|
||||||
'PhabricatorProject' => 'applications/project/storage/PhabricatorProject.php',
|
'PhabricatorProject' => 'applications/project/storage/PhabricatorProject.php',
|
||||||
|
'PhabricatorProjectActivityChartEngine' => 'applications/project/chart/PhabricatorProjectActivityChartEngine.php',
|
||||||
'PhabricatorProjectAddHeraldAction' => 'applications/project/herald/PhabricatorProjectAddHeraldAction.php',
|
'PhabricatorProjectAddHeraldAction' => 'applications/project/herald/PhabricatorProjectAddHeraldAction.php',
|
||||||
'PhabricatorProjectApplication' => 'applications/project/application/PhabricatorProjectApplication.php',
|
'PhabricatorProjectApplication' => 'applications/project/application/PhabricatorProjectApplication.php',
|
||||||
'PhabricatorProjectArchiveController' => 'applications/project/controller/PhabricatorProjectArchiveController.php',
|
'PhabricatorProjectArchiveController' => 'applications/project/controller/PhabricatorProjectArchiveController.php',
|
||||||
@@ -4419,6 +4428,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorProjectsWatchersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsWatchersSearchEngineAttachment.php',
|
'PhabricatorProjectsWatchersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsWatchersSearchEngineAttachment.php',
|
||||||
'PhabricatorPronounSetting' => 'applications/settings/setting/PhabricatorPronounSetting.php',
|
'PhabricatorPronounSetting' => 'applications/settings/setting/PhabricatorPronounSetting.php',
|
||||||
'PhabricatorProtocolLog' => 'infrastructure/log/PhabricatorProtocolLog.php',
|
'PhabricatorProtocolLog' => 'infrastructure/log/PhabricatorProtocolLog.php',
|
||||||
|
'PhabricatorPureChartFunction' => 'applications/fact/chart/PhabricatorPureChartFunction.php',
|
||||||
'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php',
|
'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php',
|
||||||
'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php',
|
'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php',
|
||||||
'PhabricatorQueryConstraint' => 'infrastructure/query/constraint/PhabricatorQueryConstraint.php',
|
'PhabricatorQueryConstraint' => 'infrastructure/query/constraint/PhabricatorQueryConstraint.php',
|
||||||
@@ -4657,6 +4667,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorSearchScopeSetting' => 'applications/settings/setting/PhabricatorSearchScopeSetting.php',
|
'PhabricatorSearchScopeSetting' => 'applications/settings/setting/PhabricatorSearchScopeSetting.php',
|
||||||
'PhabricatorSearchSelectField' => 'applications/search/field/PhabricatorSearchSelectField.php',
|
'PhabricatorSearchSelectField' => 'applications/search/field/PhabricatorSearchSelectField.php',
|
||||||
'PhabricatorSearchService' => 'infrastructure/cluster/search/PhabricatorSearchService.php',
|
'PhabricatorSearchService' => 'infrastructure/cluster/search/PhabricatorSearchService.php',
|
||||||
|
'PhabricatorSearchSettingsPanel' => 'applications/settings/panel/PhabricatorSearchSettingsPanel.php',
|
||||||
'PhabricatorSearchStringListField' => 'applications/search/field/PhabricatorSearchStringListField.php',
|
'PhabricatorSearchStringListField' => 'applications/search/field/PhabricatorSearchStringListField.php',
|
||||||
'PhabricatorSearchSubscribersField' => 'applications/search/field/PhabricatorSearchSubscribersField.php',
|
'PhabricatorSearchSubscribersField' => 'applications/search/field/PhabricatorSearchSubscribersField.php',
|
||||||
'PhabricatorSearchTextField' => 'applications/search/field/PhabricatorSearchTextField.php',
|
'PhabricatorSearchTextField' => 'applications/search/field/PhabricatorSearchTextField.php',
|
||||||
@@ -6974,6 +6985,7 @@ phutil_register_library_map(array(
|
|||||||
'DiffusionSSHWorkflow' => 'PhabricatorSSHWorkflow',
|
'DiffusionSSHWorkflow' => 'PhabricatorSSHWorkflow',
|
||||||
'DiffusionSearchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
'DiffusionSearchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||||
'DiffusionServeController' => 'DiffusionController',
|
'DiffusionServeController' => 'DiffusionController',
|
||||||
|
'DiffusionServiceRef' => 'Phobject',
|
||||||
'DiffusionSetPasswordSettingsPanel' => 'PhabricatorSettingsPanel',
|
'DiffusionSetPasswordSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||||
'DiffusionSetupException' => 'Exception',
|
'DiffusionSetupException' => 'Exception',
|
||||||
'DiffusionSourceHyperlinkEngineExtension' => 'PhabricatorRemarkupHyperlinkEngineExtension',
|
'DiffusionSourceHyperlinkEngineExtension' => 'PhabricatorRemarkupHyperlinkEngineExtension',
|
||||||
@@ -8299,7 +8311,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorAccessLog' => 'Phobject',
|
'PhabricatorAccessLog' => 'Phobject',
|
||||||
'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||||
'PhabricatorAccessibilitySetting' => 'PhabricatorSelectSetting',
|
'PhabricatorAccessibilitySetting' => 'PhabricatorSelectSetting',
|
||||||
'PhabricatorAccumulateChartFunction' => 'PhabricatorChartFunction',
|
'PhabricatorAccumulateChartFunction' => 'PhabricatorHigherOrderChartFunction',
|
||||||
'PhabricatorActionListView' => 'AphrontTagView',
|
'PhabricatorActionListView' => 'AphrontTagView',
|
||||||
'PhabricatorActionView' => 'AphrontView',
|
'PhabricatorActionView' => 'AphrontView',
|
||||||
'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel',
|
'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel',
|
||||||
@@ -9157,7 +9169,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorConpherenceWidgetVisibleSetting' => 'PhabricatorInternalSetting',
|
'PhabricatorConpherenceWidgetVisibleSetting' => 'PhabricatorInternalSetting',
|
||||||
'PhabricatorConsoleApplication' => 'PhabricatorApplication',
|
'PhabricatorConsoleApplication' => 'PhabricatorApplication',
|
||||||
'PhabricatorConsoleContentSource' => 'PhabricatorContentSource',
|
'PhabricatorConsoleContentSource' => 'PhabricatorContentSource',
|
||||||
'PhabricatorConstantChartFunction' => 'PhabricatorChartFunction',
|
'PhabricatorConstantChartFunction' => 'PhabricatorPureChartFunction',
|
||||||
'PhabricatorContactNumbersSettingsPanel' => 'PhabricatorSettingsPanel',
|
'PhabricatorContactNumbersSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||||
'PhabricatorContentSource' => 'Phobject',
|
'PhabricatorContentSource' => 'Phobject',
|
||||||
'PhabricatorContentSourceModule' => 'PhabricatorConfigModule',
|
'PhabricatorContentSourceModule' => 'PhabricatorConfigModule',
|
||||||
@@ -9169,7 +9181,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorCoreCreateTransaction' => 'PhabricatorCoreTransactionType',
|
'PhabricatorCoreCreateTransaction' => 'PhabricatorCoreTransactionType',
|
||||||
'PhabricatorCoreTransactionType' => 'PhabricatorModularTransactionType',
|
'PhabricatorCoreTransactionType' => 'PhabricatorModularTransactionType',
|
||||||
'PhabricatorCoreVoidTransaction' => 'PhabricatorModularTransactionType',
|
'PhabricatorCoreVoidTransaction' => 'PhabricatorModularTransactionType',
|
||||||
'PhabricatorCosChartFunction' => 'PhabricatorChartFunction',
|
'PhabricatorCosChartFunction' => 'PhabricatorPureChartFunction',
|
||||||
'PhabricatorCountFact' => 'PhabricatorFact',
|
'PhabricatorCountFact' => 'PhabricatorFact',
|
||||||
'PhabricatorCountdown' => array(
|
'PhabricatorCountdown' => array(
|
||||||
'PhabricatorCountdownDAO',
|
'PhabricatorCountdownDAO',
|
||||||
@@ -9435,6 +9447,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
|
'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
|
||||||
'PhabricatorDefaultSyntaxStyle' => 'PhabricatorSyntaxStyle',
|
'PhabricatorDefaultSyntaxStyle' => 'PhabricatorSyntaxStyle',
|
||||||
'PhabricatorDefaultUnlockEngine' => 'PhabricatorUnlockEngine',
|
'PhabricatorDefaultUnlockEngine' => 'PhabricatorUnlockEngine',
|
||||||
|
'PhabricatorDemoChartEngine' => 'PhabricatorChartEngine',
|
||||||
'PhabricatorDestructibleCodex' => 'Phobject',
|
'PhabricatorDestructibleCodex' => 'Phobject',
|
||||||
'PhabricatorDestructionEngine' => 'Phobject',
|
'PhabricatorDestructionEngine' => 'Phobject',
|
||||||
'PhabricatorDestructionEngineExtension' => 'Phobject',
|
'PhabricatorDestructionEngineExtension' => 'Phobject',
|
||||||
@@ -9553,6 +9566,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorEditEngineSettingsPanel' => 'PhabricatorSettingsPanel',
|
'PhabricatorEditEngineSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||||
'PhabricatorEditEngineStaticCommentAction' => 'PhabricatorEditEngineCommentAction',
|
'PhabricatorEditEngineStaticCommentAction' => 'PhabricatorEditEngineCommentAction',
|
||||||
'PhabricatorEditEngineSubtype' => 'Phobject',
|
'PhabricatorEditEngineSubtype' => 'Phobject',
|
||||||
|
'PhabricatorEditEngineSubtypeHeraldField' => 'HeraldField',
|
||||||
'PhabricatorEditEngineSubtypeMap' => 'Phobject',
|
'PhabricatorEditEngineSubtypeMap' => 'Phobject',
|
||||||
'PhabricatorEditEngineSubtypeTestCase' => 'PhabricatorTestCase',
|
'PhabricatorEditEngineSubtypeTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorEditEngineSubtypeTransaction' => 'PhabricatorEditEngineTransactionType',
|
'PhabricatorEditEngineSubtypeTransaction' => 'PhabricatorEditEngineTransactionType',
|
||||||
@@ -9806,7 +9820,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorFlagDAO',
|
'PhabricatorFlagDAO',
|
||||||
'PhabricatorPolicyInterface',
|
'PhabricatorPolicyInterface',
|
||||||
),
|
),
|
||||||
'PhabricatorFlagAddFlagHeraldAction' => 'HeraldAction',
|
'PhabricatorFlagAddFlagHeraldAction' => 'PhabricatorFlagHeraldAction',
|
||||||
'PhabricatorFlagColor' => 'PhabricatorFlagConstants',
|
'PhabricatorFlagColor' => 'PhabricatorFlagConstants',
|
||||||
'PhabricatorFlagConstants' => 'Phobject',
|
'PhabricatorFlagConstants' => 'Phobject',
|
||||||
'PhabricatorFlagController' => 'PhabricatorController',
|
'PhabricatorFlagController' => 'PhabricatorController',
|
||||||
@@ -9814,8 +9828,10 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorFlagDeleteController' => 'PhabricatorFlagController',
|
'PhabricatorFlagDeleteController' => 'PhabricatorFlagController',
|
||||||
'PhabricatorFlagDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
|
'PhabricatorFlagDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
|
||||||
'PhabricatorFlagEditController' => 'PhabricatorFlagController',
|
'PhabricatorFlagEditController' => 'PhabricatorFlagController',
|
||||||
|
'PhabricatorFlagHeraldAction' => 'HeraldAction',
|
||||||
'PhabricatorFlagListController' => 'PhabricatorFlagController',
|
'PhabricatorFlagListController' => 'PhabricatorFlagController',
|
||||||
'PhabricatorFlagQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhabricatorFlagQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
|
'PhabricatorFlagRemoveFlagHeraldAction' => 'PhabricatorFlagHeraldAction',
|
||||||
'PhabricatorFlagSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
'PhabricatorFlagSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||||
'PhabricatorFlagSelectControl' => 'AphrontFormControl',
|
'PhabricatorFlagSelectControl' => 'AphrontFormControl',
|
||||||
'PhabricatorFlaggableInterface' => 'PhabricatorPHIDInterface',
|
'PhabricatorFlaggableInterface' => 'PhabricatorPHIDInterface',
|
||||||
@@ -10067,7 +10083,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorMarkupInterface',
|
'PhabricatorMarkupInterface',
|
||||||
),
|
),
|
||||||
'PhabricatorMarkupPreviewController' => 'PhabricatorController',
|
'PhabricatorMarkupPreviewController' => 'PhabricatorController',
|
||||||
'PhabricatorMaxChartFunction' => 'PhabricatorChartFunction',
|
'PhabricatorMaxChartFunction' => 'PhabricatorPureChartFunction',
|
||||||
'PhabricatorMemeEngine' => 'Phobject',
|
'PhabricatorMemeEngine' => 'Phobject',
|
||||||
'PhabricatorMemeRemarkupRule' => 'PhutilRemarkupRule',
|
'PhabricatorMemeRemarkupRule' => 'PhutilRemarkupRule',
|
||||||
'PhabricatorMentionRemarkupRule' => 'PhutilRemarkupRule',
|
'PhabricatorMentionRemarkupRule' => 'PhutilRemarkupRule',
|
||||||
@@ -10134,7 +10150,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorMetronome' => 'Phobject',
|
'PhabricatorMetronome' => 'Phobject',
|
||||||
'PhabricatorMetronomeTestCase' => 'PhabricatorTestCase',
|
'PhabricatorMetronomeTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock',
|
'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock',
|
||||||
'PhabricatorMinChartFunction' => 'PhabricatorChartFunction',
|
'PhabricatorMinChartFunction' => 'PhabricatorPureChartFunction',
|
||||||
'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction',
|
'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction',
|
||||||
'PhabricatorModularTransactionType' => 'Phobject',
|
'PhabricatorModularTransactionType' => 'Phobject',
|
||||||
'PhabricatorMonogramDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension',
|
'PhabricatorMonogramDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension',
|
||||||
@@ -10678,8 +10694,10 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorPolicyManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
'PhabricatorPolicyManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||||
'PhabricatorPolicyPHIDTypePolicy' => 'PhabricatorPHIDType',
|
'PhabricatorPolicyPHIDTypePolicy' => 'PhabricatorPHIDType',
|
||||||
'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
|
'PhabricatorPolicyRef' => 'Phobject',
|
||||||
'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
|
'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
|
||||||
'PhabricatorPolicyRule' => 'Phobject',
|
'PhabricatorPolicyRule' => 'Phobject',
|
||||||
|
'PhabricatorPolicyRulesView' => 'AphrontView',
|
||||||
'PhabricatorPolicySearchEngineExtension' => 'PhabricatorSearchEngineExtension',
|
'PhabricatorPolicySearchEngineExtension' => 'PhabricatorSearchEngineExtension',
|
||||||
'PhabricatorPolicyStrengthConstants' => 'PhabricatorPolicyConstants',
|
'PhabricatorPolicyStrengthConstants' => 'PhabricatorPolicyConstants',
|
||||||
'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
|
'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
|
||||||
@@ -10690,6 +10708,7 @@ phutil_register_library_map(array(
|
|||||||
),
|
),
|
||||||
'PhabricatorPolicyType' => 'PhabricatorPolicyConstants',
|
'PhabricatorPolicyType' => 'PhabricatorPolicyConstants',
|
||||||
'PhabricatorPonderApplication' => 'PhabricatorApplication',
|
'PhabricatorPonderApplication' => 'PhabricatorApplication',
|
||||||
|
'PhabricatorPreambleTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorPrimaryEmailUserLogType' => 'PhabricatorUserLogType',
|
'PhabricatorPrimaryEmailUserLogType' => 'PhabricatorUserLogType',
|
||||||
'PhabricatorProfileMenuEditEngine' => 'PhabricatorEditEngine',
|
'PhabricatorProfileMenuEditEngine' => 'PhabricatorEditEngine',
|
||||||
'PhabricatorProfileMenuEditor' => 'PhabricatorApplicationTransactionEditor',
|
'PhabricatorProfileMenuEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
@@ -10726,6 +10745,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorSpacesInterface',
|
'PhabricatorSpacesInterface',
|
||||||
'PhabricatorEditEngineSubtypeInterface',
|
'PhabricatorEditEngineSubtypeInterface',
|
||||||
),
|
),
|
||||||
|
'PhabricatorProjectActivityChartEngine' => 'PhabricatorChartEngine',
|
||||||
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
|
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
|
||||||
'PhabricatorProjectApplication' => 'PhabricatorApplication',
|
'PhabricatorProjectApplication' => 'PhabricatorApplication',
|
||||||
'PhabricatorProjectArchiveController' => 'PhabricatorProjectController',
|
'PhabricatorProjectArchiveController' => 'PhabricatorProjectController',
|
||||||
@@ -10946,6 +10966,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorProjectsWatchersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
|
'PhabricatorProjectsWatchersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
|
||||||
'PhabricatorPronounSetting' => 'PhabricatorSelectSetting',
|
'PhabricatorPronounSetting' => 'PhabricatorSelectSetting',
|
||||||
'PhabricatorProtocolLog' => 'Phobject',
|
'PhabricatorProtocolLog' => 'Phobject',
|
||||||
|
'PhabricatorPureChartFunction' => 'PhabricatorChartFunction',
|
||||||
'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck',
|
'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck',
|
||||||
'PhabricatorQuery' => 'Phobject',
|
'PhabricatorQuery' => 'Phobject',
|
||||||
'PhabricatorQueryConstraint' => 'Phobject',
|
'PhabricatorQueryConstraint' => 'Phobject',
|
||||||
@@ -11204,7 +11225,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorPolicyInterface',
|
'PhabricatorPolicyInterface',
|
||||||
),
|
),
|
||||||
'PhabricatorSavedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhabricatorSavedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhabricatorScaleChartFunction' => 'PhabricatorChartFunction',
|
'PhabricatorScaleChartFunction' => 'PhabricatorPureChartFunction',
|
||||||
'PhabricatorScheduleTaskTriggerAction' => 'PhabricatorTriggerAction',
|
'PhabricatorScheduleTaskTriggerAction' => 'PhabricatorTriggerAction',
|
||||||
'PhabricatorScopedEnv' => 'Phobject',
|
'PhabricatorScopedEnv' => 'Phobject',
|
||||||
'PhabricatorSearchAbstractDocument' => 'Phobject',
|
'PhabricatorSearchAbstractDocument' => 'Phobject',
|
||||||
@@ -11255,9 +11276,10 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorSearchResultBucketGroup' => 'Phobject',
|
'PhabricatorSearchResultBucketGroup' => 'Phobject',
|
||||||
'PhabricatorSearchResultView' => 'AphrontView',
|
'PhabricatorSearchResultView' => 'AphrontView',
|
||||||
'PhabricatorSearchSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
'PhabricatorSearchSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||||
'PhabricatorSearchScopeSetting' => 'PhabricatorInternalSetting',
|
'PhabricatorSearchScopeSetting' => 'PhabricatorSelectSetting',
|
||||||
'PhabricatorSearchSelectField' => 'PhabricatorSearchField',
|
'PhabricatorSearchSelectField' => 'PhabricatorSearchField',
|
||||||
'PhabricatorSearchService' => 'Phobject',
|
'PhabricatorSearchService' => 'Phobject',
|
||||||
|
'PhabricatorSearchSettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
|
||||||
'PhabricatorSearchStringListField' => 'PhabricatorSearchField',
|
'PhabricatorSearchStringListField' => 'PhabricatorSearchField',
|
||||||
'PhabricatorSearchSubscribersField' => 'PhabricatorSearchTokenizerField',
|
'PhabricatorSearchSubscribersField' => 'PhabricatorSearchTokenizerField',
|
||||||
'PhabricatorSearchTextField' => 'PhabricatorSearchField',
|
'PhabricatorSearchTextField' => 'PhabricatorSearchField',
|
||||||
@@ -11295,12 +11317,12 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorSetupIssue' => 'Phobject',
|
'PhabricatorSetupIssue' => 'Phobject',
|
||||||
'PhabricatorSetupIssueUIExample' => 'PhabricatorUIExample',
|
'PhabricatorSetupIssueUIExample' => 'PhabricatorUIExample',
|
||||||
'PhabricatorSetupIssueView' => 'AphrontView',
|
'PhabricatorSetupIssueView' => 'AphrontView',
|
||||||
'PhabricatorShiftChartFunction' => 'PhabricatorChartFunction',
|
'PhabricatorShiftChartFunction' => 'PhabricatorPureChartFunction',
|
||||||
'PhabricatorShortSite' => 'PhabricatorSite',
|
'PhabricatorShortSite' => 'PhabricatorSite',
|
||||||
'PhabricatorShowFiletreeSetting' => 'PhabricatorSelectSetting',
|
'PhabricatorShowFiletreeSetting' => 'PhabricatorSelectSetting',
|
||||||
'PhabricatorSignDocumentsUserLogType' => 'PhabricatorUserLogType',
|
'PhabricatorSignDocumentsUserLogType' => 'PhabricatorUserLogType',
|
||||||
'PhabricatorSimpleEditType' => 'PhabricatorEditType',
|
'PhabricatorSimpleEditType' => 'PhabricatorEditType',
|
||||||
'PhabricatorSinChartFunction' => 'PhabricatorChartFunction',
|
'PhabricatorSinChartFunction' => 'PhabricatorPureChartFunction',
|
||||||
'PhabricatorSite' => 'AphrontSite',
|
'PhabricatorSite' => 'AphrontSite',
|
||||||
'PhabricatorSlackAuthProvider' => 'PhabricatorOAuth2AuthProvider',
|
'PhabricatorSlackAuthProvider' => 'PhabricatorOAuth2AuthProvider',
|
||||||
'PhabricatorSlowvoteApplication' => 'PhabricatorApplication',
|
'PhabricatorSlowvoteApplication' => 'PhabricatorApplication',
|
||||||
|
|||||||
@@ -68,12 +68,42 @@ final class PhabricatorLogoutController
|
|||||||
->setURI('/auth/loggedout/');
|
->setURI('/auth/loggedout/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($viewer->getPHID()) {
|
if ($viewer->getPHID()) {
|
||||||
return $this->newDialog()
|
$dialog = $this->newDialog()
|
||||||
->setTitle(pht('Log Out?'))
|
->setTitle(pht('Log Out?'))
|
||||||
->appendChild(pht('Are you sure you want to log out?'))
|
->appendParagraph(pht('Are you sure you want to log out?'))
|
||||||
->addSubmitButton(pht('Log Out'))
|
|
||||||
->addCancelButton('/');
|
->addCancelButton('/');
|
||||||
|
|
||||||
|
$configs = id(new PhabricatorAuthProviderConfigQuery())
|
||||||
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
->execute();
|
||||||
|
if (!$configs) {
|
||||||
|
$dialog
|
||||||
|
->appendRemarkup(
|
||||||
|
pht(
|
||||||
|
'WARNING: You have not configured any authentication providers '.
|
||||||
|
'yet, so your account has no login credentials. If you log out '.
|
||||||
|
'now, you will not be able to log back in normally.'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'To enable the login flow, follow setup guidance and configure '.
|
||||||
|
'at least one authentication provider, then associate '.
|
||||||
|
'credentials with your account. After completing these steps, '.
|
||||||
|
'you will be able to log out and log back in normally.'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'If you log out now, you can still regain access to your '.
|
||||||
|
'account later by using the account recovery workflow. The '.
|
||||||
|
'login screen will prompt you with recovery instructions.'));
|
||||||
|
|
||||||
|
$button = pht('Log Out Anyway');
|
||||||
|
} else {
|
||||||
|
$button = pht('Log Out');
|
||||||
|
}
|
||||||
|
|
||||||
|
$dialog->addSubmitButton($button);
|
||||||
|
return $dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
return id(new AphrontRedirectResponse())->setURI('/');
|
return id(new AphrontRedirectResponse())->setURI('/');
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ final class PhabricatorAuthListController
|
|||||||
array(
|
array(
|
||||||
'href' => $this->getApplicationURI('config/new/'),
|
'href' => $this->getApplicationURI('config/new/'),
|
||||||
),
|
),
|
||||||
pht('Add Authentication Provider'))));
|
pht('Add Provider'))));
|
||||||
|
|
||||||
$crumbs = $this->buildApplicationCrumbs();
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
$crumbs->addTextCrumb(pht('Login and Registration'));
|
$crumbs->addTextCrumb(pht('Login and Registration'));
|
||||||
|
|||||||
@@ -50,33 +50,50 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
|
|||||||
if (!in_array('STRICT_ALL_TABLES', $modes)) {
|
if (!in_array('STRICT_ALL_TABLES', $modes)) {
|
||||||
$summary = pht(
|
$summary = pht(
|
||||||
'MySQL is not in strict mode (on host "%s"), but using strict mode '.
|
'MySQL is not in strict mode (on host "%s"), but using strict mode '.
|
||||||
'is strongly encouraged.',
|
'is recommended.',
|
||||||
$host_name);
|
$host_name);
|
||||||
|
|
||||||
$message = pht(
|
$message = pht(
|
||||||
"On database host \"%s\", the global %s is not set to %s. ".
|
'On database host "%s", the global "sql_mode" setting does not '.
|
||||||
"It is strongly encouraged that you enable this mode when running ".
|
'include the "STRICT_ALL_TABLES" mode. Enabling this mode is '.
|
||||||
"Phabricator.\n\n".
|
'recommended to generally improve how MySQL handles certain errors.'.
|
||||||
"By default MySQL will silently ignore some types of errors, which ".
|
"\n\n".
|
||||||
"can cause data loss and raise security concerns. Enabling strict ".
|
'Without this mode enabled, MySQL will silently ignore some error '.
|
||||||
"mode makes MySQL raise an explicit error instead, and prevents this ".
|
'conditions, including inserts which attempt to store more data in '.
|
||||||
"entire class of problems from doing any damage.\n\n".
|
'a column than actually fits. This behavior is usually undesirable '.
|
||||||
"You can find more information about this mode (and how to configure ".
|
'and can lead to data corruption (by truncating multibyte characters '.
|
||||||
"it) in the MySQL manual. Usually, it is sufficient to add this to ".
|
'in the middle), data loss (by discarding the data which does not '.
|
||||||
"your %s file (in the %s section) and then restart %s:\n\n".
|
'fit into the column), or security concerns (for example, by '.
|
||||||
"%s\n".
|
'truncating keys or credentials).'.
|
||||||
"(Note that if you run other applications against the same database, ".
|
"\n\n".
|
||||||
"they may not work in strict mode. Be careful about enabling it in ".
|
'Phabricator is developed and tested in "STRICT_ALL_TABLES" mode so '.
|
||||||
"these cases.)",
|
'you should normally never encounter these situations, but may run '.
|
||||||
|
'into them if you interact with the database directly, run '.
|
||||||
|
'third-party code, develop extensions, or just encounter a bug in '.
|
||||||
|
'the software.'.
|
||||||
|
"\n\n".
|
||||||
|
'Enabling "STRICT_ALL_TABLES" makes MySQL raise an explicit error '.
|
||||||
|
'if one of these unusual situations does occur. This is a safer '.
|
||||||
|
'behavior and prevents these situations from causing secret, subtle, '.
|
||||||
|
'and potentially serious issues later on.'.
|
||||||
|
"\n\n".
|
||||||
|
'You can find more information about this mode (and how to configure '.
|
||||||
|
'it) in the MySQL manual. Usually, it is sufficient to add this to '.
|
||||||
|
'your "my.cnf" file (in the "[mysqld]" section) and then '.
|
||||||
|
'restart "mysqld":'.
|
||||||
|
"\n\n".
|
||||||
|
'%s'.
|
||||||
|
"\n".
|
||||||
|
'Note that if you run other applications against the same database, '.
|
||||||
|
'they may not work in strict mode.'.
|
||||||
|
"\n\n".
|
||||||
|
'If you can not or do not want to enable "STRICT_ALL_TABLES", you '.
|
||||||
|
'can safely ignore this warning. Phabricator will work correctly '.
|
||||||
|
'with this mode enabled or disabled.',
|
||||||
$host_name,
|
$host_name,
|
||||||
phutil_tag('tt', array(), 'sql_mode'),
|
|
||||||
phutil_tag('tt', array(), 'STRICT_ALL_TABLES'),
|
|
||||||
phutil_tag('tt', array(), 'my.cnf'),
|
|
||||||
phutil_tag('tt', array(), '[mysqld]'),
|
|
||||||
phutil_tag('tt', array(), 'mysqld'),
|
|
||||||
phutil_tag('pre', array(), 'sql_mode=STRICT_ALL_TABLES'));
|
phutil_tag('pre', array(), 'sql_mode=STRICT_ALL_TABLES'));
|
||||||
|
|
||||||
$this->newIssue('mysql.mode')
|
$this->newIssue('sql_mode.strict')
|
||||||
->setName(pht('MySQL %s Mode Not Set', 'STRICT_ALL_TABLES'))
|
->setName(pht('MySQL %s Mode Not Set', 'STRICT_ALL_TABLES'))
|
||||||
->setSummary($summary)
|
->setSummary($summary)
|
||||||
->setMessage($message)
|
->setMessage($message)
|
||||||
@@ -84,49 +101,6 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
|
|||||||
->addMySQLConfig('sql_mode');
|
->addMySQLConfig('sql_mode');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array('ONLY_FULL_GROUP_BY', $modes)) {
|
|
||||||
$summary = pht(
|
|
||||||
'MySQL is in ONLY_FULL_GROUP_BY mode (on host "%s"), but using this '.
|
|
||||||
'mode is strongly discouraged.',
|
|
||||||
$host_name);
|
|
||||||
|
|
||||||
$message = pht(
|
|
||||||
"On database host \"%s\", the global %s is set to %s. ".
|
|
||||||
"It is strongly encouraged that you disable this mode when running ".
|
|
||||||
"Phabricator.\n\n".
|
|
||||||
"With %s enabled, MySQL rejects queries for which the select list ".
|
|
||||||
"or (as of MySQL 5.0.23) %s list refer to nonaggregated columns ".
|
|
||||||
"that are not named in the %s clause. More importantly, Phabricator ".
|
|
||||||
"does not work properly with this mode enabled.\n\n".
|
|
||||||
"You can find more information about this mode (and how to configure ".
|
|
||||||
"it) in the MySQL manual. Usually, it is sufficient to change the %s ".
|
|
||||||
"in your %s file (in the %s section) and then restart %s:\n\n".
|
|
||||||
"%s\n".
|
|
||||||
"(Note that if you run other applications against the same database, ".
|
|
||||||
"they may not work with %s. Be careful about enabling ".
|
|
||||||
"it in these cases and consider migrating Phabricator to a different ".
|
|
||||||
"database.)",
|
|
||||||
$host_name,
|
|
||||||
phutil_tag('tt', array(), 'sql_mode'),
|
|
||||||
phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY'),
|
|
||||||
phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY'),
|
|
||||||
phutil_tag('tt', array(), 'HAVING'),
|
|
||||||
phutil_tag('tt', array(), 'GROUP BY'),
|
|
||||||
phutil_tag('tt', array(), 'sql_mode'),
|
|
||||||
phutil_tag('tt', array(), 'my.cnf'),
|
|
||||||
phutil_tag('tt', array(), '[mysqld]'),
|
|
||||||
phutil_tag('tt', array(), 'mysqld'),
|
|
||||||
phutil_tag('pre', array(), 'sql_mode=STRICT_ALL_TABLES'),
|
|
||||||
phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY'));
|
|
||||||
|
|
||||||
$this->newIssue('mysql.mode')
|
|
||||||
->setName(pht('MySQL %s Mode Set', 'ONLY_FULL_GROUP_BY'))
|
|
||||||
->setSummary($summary)
|
|
||||||
->setMessage($message)
|
|
||||||
->setDatabaseRef($ref)
|
|
||||||
->addMySQLConfig('sql_mode');
|
|
||||||
}
|
|
||||||
|
|
||||||
$is_innodb_fulltext = false;
|
$is_innodb_fulltext = false;
|
||||||
$is_myisam_fulltext = false;
|
$is_myisam_fulltext = false;
|
||||||
if ($this->shouldUseMySQLSearchEngine()) {
|
if ($this->shouldUseMySQLSearchEngine()) {
|
||||||
|
|||||||
@@ -140,11 +140,20 @@ final class PhabricatorConfigManagementSetWorkflow
|
|||||||
'Wrote configuration key "%s" to database storage.',
|
'Wrote configuration key "%s" to database storage.',
|
||||||
$key);
|
$key);
|
||||||
} else {
|
} else {
|
||||||
$config_source = id(new PhabricatorConfigLocalSource())
|
$config_source = new PhabricatorConfigLocalSource();
|
||||||
->setKeys(array($key => $value));
|
|
||||||
|
|
||||||
$local_path = $config_source->getReadablePath();
|
$local_path = $config_source->getReadablePath();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$config_source->setKeys(array($key => $value));
|
||||||
|
} catch (FilesystemException $ex) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'Local path "%s" is not writable. This file must be writable '.
|
||||||
|
'so that "bin/config" can store configuration.',
|
||||||
|
Filesystem::readablePath($local_path)));
|
||||||
|
}
|
||||||
|
|
||||||
$write_message = pht(
|
$write_message = pht(
|
||||||
'Wrote configuration key "%s" to local storage (in file "%s").',
|
'Wrote configuration key "%s" to local storage (in file "%s").',
|
||||||
$key,
|
$key,
|
||||||
|
|||||||
48
src/applications/diffusion/ref/DiffusionServiceRef.php
Normal file
48
src/applications/diffusion/ref/DiffusionServiceRef.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionServiceRef
|
||||||
|
extends Phobject {
|
||||||
|
|
||||||
|
private $uri;
|
||||||
|
private $protocol;
|
||||||
|
private $isWritable;
|
||||||
|
private $devicePHID;
|
||||||
|
private $deviceName;
|
||||||
|
|
||||||
|
private function __construct() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function newFromDictionary(array $map) {
|
||||||
|
$ref = new self();
|
||||||
|
|
||||||
|
$ref->uri = $map['uri'];
|
||||||
|
$ref->isWritable = $map['writable'];
|
||||||
|
$ref->devicePHID = $map['devicePHID'];
|
||||||
|
$ref->protocol = $map['protocol'];
|
||||||
|
$ref->deviceName = $map['device'];
|
||||||
|
|
||||||
|
return $ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isWritable() {
|
||||||
|
return $this->isWritable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDevicePHID() {
|
||||||
|
return $this->devicePHID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getURI() {
|
||||||
|
return $this->uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProtocol() {
|
||||||
|
return $this->protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDeviceName() {
|
||||||
|
return $this->deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -14,34 +14,26 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function executeRepositoryOperations() {
|
protected function executeRepositoryOperations() {
|
||||||
|
// This is a write, and must have write access.
|
||||||
|
$this->requireWriteAccess();
|
||||||
|
|
||||||
|
$is_proxy = $this->shouldProxy();
|
||||||
|
if ($is_proxy) {
|
||||||
|
return $this->executeRepositoryProxyOperations($for_write = true);
|
||||||
|
}
|
||||||
|
|
||||||
$host_wait_start = microtime(true);
|
$host_wait_start = microtime(true);
|
||||||
|
|
||||||
$repository = $this->getRepository();
|
$repository = $this->getRepository();
|
||||||
$viewer = $this->getSSHUser();
|
$viewer = $this->getSSHUser();
|
||||||
$device = AlmanacKeys::getLiveDevice();
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
|
||||||
// This is a write, and must have write access.
|
|
||||||
$this->requireWriteAccess();
|
|
||||||
|
|
||||||
$cluster_engine = id(new DiffusionRepositoryClusterEngine())
|
$cluster_engine = id(new DiffusionRepositoryClusterEngine())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->setRepository($repository)
|
->setRepository($repository)
|
||||||
->setLog($this);
|
->setLog($this);
|
||||||
|
|
||||||
$is_proxy = $this->shouldProxy();
|
|
||||||
if ($is_proxy) {
|
|
||||||
$command = $this->getProxyCommand(true);
|
|
||||||
$did_write = false;
|
|
||||||
|
|
||||||
if ($device) {
|
|
||||||
$this->writeClusterEngineLogMessage(
|
|
||||||
pht(
|
|
||||||
"# Push received by \"%s\", forwarding to cluster host.\n",
|
|
||||||
$device->getName()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$command = csprintf('git-receive-pack %s', $repository->getLocalPath());
|
$command = csprintf('git-receive-pack %s', $repository->getLocalPath());
|
||||||
$did_write = true;
|
|
||||||
$cluster_engine->synchronizeWorkingCopyBeforeWrite();
|
$cluster_engine->synchronizeWorkingCopyBeforeWrite();
|
||||||
|
|
||||||
if ($device) {
|
if ($device) {
|
||||||
@@ -50,7 +42,6 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||||||
"# Ready to receive on cluster host \"%s\".\n",
|
"# Ready to receive on cluster host \"%s\".\n",
|
||||||
$device->getName()));
|
$device->getName()));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$log = $this->newProtocolLog($is_proxy);
|
$log = $this->newProtocolLog($is_proxy);
|
||||||
if ($log) {
|
if ($log) {
|
||||||
@@ -71,9 +62,7 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||||||
|
|
||||||
// We've committed the write (or rejected it), so we can release the lock
|
// We've committed the write (or rejected it), so we can release the lock
|
||||||
// without waiting for the client to receive the acknowledgement.
|
// without waiting for the client to receive the acknowledgement.
|
||||||
if ($did_write) {
|
|
||||||
$cluster_engine->synchronizeWorkingCopyAfterWrite();
|
$cluster_engine->synchronizeWorkingCopyAfterWrite();
|
||||||
}
|
|
||||||
|
|
||||||
if ($caught) {
|
if ($caught) {
|
||||||
throw $caught;
|
throw $caught;
|
||||||
@@ -85,7 +74,6 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||||||
// When a repository is clustered, we reach this cleanup code on both
|
// When a repository is clustered, we reach this cleanup code on both
|
||||||
// the proxy and the actual final endpoint node. Don't do more cleanup
|
// the proxy and the actual final endpoint node. Don't do more cleanup
|
||||||
// or logging than we need to.
|
// or logging than we need to.
|
||||||
if ($did_write) {
|
|
||||||
$repository->writeStatusMessage(
|
$repository->writeStatusMessage(
|
||||||
PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
|
PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
|
||||||
PhabricatorRepositoryStatusMessage::CODE_OKAY);
|
PhabricatorRepositoryStatusMessage::CODE_OKAY);
|
||||||
@@ -97,7 +85,6 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||||||
$this->getClusterEngineLogProperty('readWait'),
|
$this->getClusterEngineLogProperty('readWait'),
|
||||||
($host_wait_end - $host_wait_start));
|
($host_wait_end - $host_wait_start));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return $err;
|
return $err;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ abstract class DiffusionGitSSHWorkflow
|
|||||||
private $protocolLog;
|
private $protocolLog;
|
||||||
|
|
||||||
private $wireProtocol;
|
private $wireProtocol;
|
||||||
|
private $ioBytesRead = 0;
|
||||||
|
private $ioBytesWritten = 0;
|
||||||
|
private $requestAttempts = 0;
|
||||||
|
private $requestFailures = 0;
|
||||||
|
|
||||||
protected function writeError($message) {
|
protected function writeError($message) {
|
||||||
// Git assumes we'll add our own newlines.
|
// Git assumes we'll add our own newlines.
|
||||||
@@ -98,6 +102,8 @@ abstract class DiffusionGitSSHWorkflow
|
|||||||
PhabricatorSSHPassthruCommand $command,
|
PhabricatorSSHPassthruCommand $command,
|
||||||
$message) {
|
$message) {
|
||||||
|
|
||||||
|
$this->ioBytesWritten += strlen($message);
|
||||||
|
|
||||||
$log = $this->getProtocolLog();
|
$log = $this->getProtocolLog();
|
||||||
if ($log) {
|
if ($log) {
|
||||||
$log->didWriteBytes($message);
|
$log->didWriteBytes($message);
|
||||||
@@ -125,7 +131,131 @@ abstract class DiffusionGitSSHWorkflow
|
|||||||
$message = $protocol->willReadBytes($message);
|
$message = $protocol->willReadBytes($message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note that bytes aren't counted until they're emittted by the protocol
|
||||||
|
// layer. This means the underlying command might emit bytes, but if they
|
||||||
|
// are buffered by the protocol layer they won't count as read bytes yet.
|
||||||
|
|
||||||
|
$this->ioBytesRead += strlen($message);
|
||||||
|
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final protected function getIOBytesRead() {
|
||||||
|
return $this->ioBytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function getIOBytesWritten() {
|
||||||
|
return $this->ioBytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function executeRepositoryProxyOperations($for_write) {
|
||||||
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
|
||||||
|
$refs = $this->getAlmanacServiceRefs($for_write);
|
||||||
|
$err = 1;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
$ref = head($refs);
|
||||||
|
|
||||||
|
$command = $this->getProxyCommandForServiceRef($ref);
|
||||||
|
|
||||||
|
if ($device) {
|
||||||
|
$this->writeClusterEngineLogMessage(
|
||||||
|
pht(
|
||||||
|
"# Request received by \"%s\", forwarding to cluster ".
|
||||||
|
"host \"%s\".\n",
|
||||||
|
$device->getName(),
|
||||||
|
$ref->getDeviceName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
||||||
|
|
||||||
|
$future = id(new ExecFuture('%C', $command))
|
||||||
|
->setEnv($this->getEnvironment());
|
||||||
|
|
||||||
|
$this->didBeginRequest();
|
||||||
|
|
||||||
|
$err = $this->newPassthruCommand()
|
||||||
|
->setIOChannel($this->getIOChannel())
|
||||||
|
->setCommandChannelFromExecFuture($future)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
// TODO: Currently, when proxying, we do not write an event log on the
|
||||||
|
// proxy. Perhaps we should write a "proxy log". This is not very useful
|
||||||
|
// for statistics or auditing, but could be useful for diagnostics.
|
||||||
|
// Marking the proxy logs as proxied (and recording devicePHID on all
|
||||||
|
// logs) would make differentiating between these use cases easier.
|
||||||
|
|
||||||
|
if (!$err) {
|
||||||
|
$this->waitForGitClient();
|
||||||
|
return $err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw away this service: the request failed and we're treating the
|
||||||
|
// failure as persistent, so we don't want to retry another request to
|
||||||
|
// the same host.
|
||||||
|
array_shift($refs);
|
||||||
|
|
||||||
|
$should_retry = $this->shouldRetryRequest($refs);
|
||||||
|
if (!$should_retry) {
|
||||||
|
return $err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we haven't bailed out yet, we'll retry the request with the next
|
||||||
|
// service.
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception(pht('Reached an unreachable place.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function didBeginRequest() {
|
||||||
|
$this->requestAttempts++;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function shouldRetryRequest(array $remaining_refs) {
|
||||||
|
$this->requestFailures++;
|
||||||
|
|
||||||
|
if ($this->requestFailures > $this->requestAttempts) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
"Workflow has recorded more failures than attempts; there is a ".
|
||||||
|
"missing call to \"didBeginRequest()\".\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$remaining_refs) {
|
||||||
|
$this->writeClusterEngineLogMessage(
|
||||||
|
pht(
|
||||||
|
"# All available services failed to serve the request, ".
|
||||||
|
"giving up.\n"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$read_len = $this->getIOBytesRead();
|
||||||
|
if ($read_len) {
|
||||||
|
$this->writeClusterEngineLogMessage(
|
||||||
|
pht(
|
||||||
|
"# Client already read from service (%s bytes), unable to retry.\n",
|
||||||
|
new PhutilNumber($read_len)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$write_len = $this->getIOBytesWritten();
|
||||||
|
if ($write_len) {
|
||||||
|
$this->writeClusterEngineLogMessage(
|
||||||
|
pht(
|
||||||
|
"# Client already wrote to service (%s bytes), unable to retry.\n",
|
||||||
|
new PhutilNumber($write_len)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writeClusterEngineLogMessage(
|
||||||
|
pht(
|
||||||
|
"# Service request failed, retrying (making attempt %s of %s).\n",
|
||||||
|
new PhutilNumber($this->requestAttempts + 1),
|
||||||
|
new PhutilNumber($this->requestAttempts + count($remaining_refs))));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class DiffusionGitUploadPackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
final class DiffusionGitUploadPackSSHWorkflow
|
||||||
|
extends DiffusionGitSSHWorkflow {
|
||||||
|
|
||||||
protected function didConstruct() {
|
protected function didConstruct() {
|
||||||
$this->setName('git-upload-pack');
|
$this->setName('git-upload-pack');
|
||||||
@@ -14,23 +15,17 @@ final class DiffusionGitUploadPackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function executeRepositoryOperations() {
|
protected function executeRepositoryOperations() {
|
||||||
$repository = $this->getRepository();
|
$is_proxy = $this->shouldProxy();
|
||||||
|
if ($is_proxy) {
|
||||||
|
return $this->executeRepositoryProxyOperations($for_write = false);
|
||||||
|
}
|
||||||
|
|
||||||
$viewer = $this->getSSHUser();
|
$viewer = $this->getSSHUser();
|
||||||
|
$repository = $this->getRepository();
|
||||||
$device = AlmanacKeys::getLiveDevice();
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
|
||||||
$skip_sync = $this->shouldSkipReadSynchronization();
|
$skip_sync = $this->shouldSkipReadSynchronization();
|
||||||
$is_proxy = $this->shouldProxy();
|
|
||||||
|
|
||||||
if ($is_proxy) {
|
|
||||||
$command = $this->getProxyCommand(false);
|
|
||||||
|
|
||||||
if ($device) {
|
|
||||||
$this->writeClusterEngineLogMessage(
|
|
||||||
pht(
|
|
||||||
"# Fetch received by \"%s\", forwarding to cluster host.\n",
|
|
||||||
$device->getName()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$command = csprintf('git-upload-pack -- %s', $repository->getLocalPath());
|
$command = csprintf('git-upload-pack -- %s', $repository->getLocalPath());
|
||||||
if (!$skip_sync) {
|
if (!$skip_sync) {
|
||||||
$cluster_engine = id(new DiffusionRepositoryClusterEngine())
|
$cluster_engine = id(new DiffusionRepositoryClusterEngine())
|
||||||
@@ -46,7 +41,7 @@ final class DiffusionGitUploadPackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||||||
$device->getName()));
|
$device->getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
||||||
|
|
||||||
$pull_event = $this->newPullEvent();
|
$pull_event = $this->newPullEvent();
|
||||||
@@ -60,7 +55,6 @@ final class DiffusionGitUploadPackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||||||
$log->didStartSession($command);
|
$log->didStartSession($command);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$is_proxy) {
|
|
||||||
if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) {
|
if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) {
|
||||||
$protocol = new DiffusionGitUploadPackWireProtocol();
|
$protocol = new DiffusionGitUploadPackWireProtocol();
|
||||||
if ($log) {
|
if ($log) {
|
||||||
@@ -68,7 +62,6 @@ final class DiffusionGitUploadPackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||||||
}
|
}
|
||||||
$this->setWireProtocol($protocol);
|
$this->setWireProtocol($protocol);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$err = $this->newPassthruCommand()
|
$err = $this->newPassthruCommand()
|
||||||
->setIOChannel($this->getIOChannel())
|
->setIOChannel($this->getIOChannel())
|
||||||
@@ -89,15 +82,7 @@ final class DiffusionGitUploadPackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||||||
->setResultCode(0);
|
->setResultCode(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Currently, when proxying, we do not write a log on the proxy.
|
|
||||||
// Perhaps we should write a "proxy log". This is not very useful for
|
|
||||||
// statistics or auditing, but could be useful for diagnostics. Marking
|
|
||||||
// the proxy logs as proxied (and recording devicePHID on all logs) would
|
|
||||||
// make differentiating between these use cases easier.
|
|
||||||
|
|
||||||
if (!$is_proxy) {
|
|
||||||
$pull_event->save();
|
$pull_event->save();
|
||||||
}
|
|
||||||
|
|
||||||
if (!$err) {
|
if (!$err) {
|
||||||
$this->waitForGitClient();
|
$this->waitForGitClient();
|
||||||
|
|||||||
@@ -73,13 +73,13 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
|
|||||||
return $this->shouldProxy;
|
return $this->shouldProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getProxyCommand($for_write) {
|
final protected function getAlmanacServiceRefs($for_write) {
|
||||||
$viewer = $this->getSSHUser();
|
$viewer = $this->getSSHUser();
|
||||||
$repository = $this->getRepository();
|
$repository = $this->getRepository();
|
||||||
|
|
||||||
$is_cluster_request = $this->getIsClusterRequest();
|
$is_cluster_request = $this->getIsClusterRequest();
|
||||||
|
|
||||||
$uri = $repository->getAlmanacServiceURI(
|
$refs = $repository->getAlmanacServiceRefs(
|
||||||
$viewer,
|
$viewer,
|
||||||
array(
|
array(
|
||||||
'neverProxy' => $is_cluster_request,
|
'neverProxy' => $is_cluster_request,
|
||||||
@@ -89,14 +89,28 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
|
|||||||
'writable' => $for_write,
|
'writable' => $for_write,
|
||||||
));
|
));
|
||||||
|
|
||||||
if (!$uri) {
|
if (!$refs) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
'Failed to generate an intracluster proxy URI even though this '.
|
'Failed to generate an intracluster proxy URI even though this '.
|
||||||
'request was routed as a proxy request.'));
|
'request was routed as a proxy request.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$uri = new PhutilURI($uri);
|
return $refs;
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function getProxyCommand($for_write) {
|
||||||
|
$refs = $this->getAlmanacServiceRefs($for_write);
|
||||||
|
|
||||||
|
$ref = head($refs);
|
||||||
|
|
||||||
|
return $this->getProxyCommandForServiceRef($ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function getProxyCommandForServiceRef(
|
||||||
|
DiffusionServiceRef $ref) {
|
||||||
|
|
||||||
|
$uri = new PhutilURI($ref->getURI());
|
||||||
|
|
||||||
$username = AlmanacKeys::getClusterSSHUser();
|
$username = AlmanacKeys::getClusterSSHUser();
|
||||||
if ($username === null) {
|
if ($username === null) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PhabricatorAccumulateChartFunction
|
final class PhabricatorAccumulateChartFunction
|
||||||
extends PhabricatorChartFunction {
|
extends PhabricatorHigherOrderChartFunction {
|
||||||
|
|
||||||
const FUNCTIONKEY = 'accumulate';
|
const FUNCTIONKEY = 'accumulate';
|
||||||
|
|
||||||
@@ -13,14 +13,6 @@ final class PhabricatorAccumulateChartFunction
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDomain() {
|
|
||||||
return $this->getArgument('x')->getDomain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
|
||||||
return $this->getArgument('x')->newInputValues($query);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function evaluateFunction(array $xv) {
|
public function evaluateFunction(array $xv) {
|
||||||
// First, we're going to accumulate the underlying function. Then
|
// First, we're going to accumulate the underlying function. Then
|
||||||
// we'll map the inputs through the accumulation.
|
// we'll map the inputs through the accumulation.
|
||||||
|
|||||||
@@ -59,13 +59,6 @@ abstract class PhabricatorChartDataset
|
|||||||
return $dataset;
|
return $dataset;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function toDictionary() {
|
|
||||||
return array(
|
|
||||||
'type' => $this->getDatasetTypeKey(),
|
|
||||||
'functions' => mpull($this->getFunctions(), 'toDictionary'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final public function getChartDisplayData(
|
final public function getChartDisplayData(
|
||||||
PhabricatorChartDataQuery $data_query) {
|
PhabricatorChartDataQuery $data_query) {
|
||||||
return $this->newChartDisplayData($data_query);
|
return $this->newChartDisplayData($data_query);
|
||||||
@@ -75,4 +68,35 @@ abstract class PhabricatorChartDataset
|
|||||||
PhabricatorChartDataQuery $data_query);
|
PhabricatorChartDataQuery $data_query);
|
||||||
|
|
||||||
|
|
||||||
|
final public function getTabularDisplayData(
|
||||||
|
PhabricatorChartDataQuery $data_query) {
|
||||||
|
$results = array();
|
||||||
|
|
||||||
|
$functions = $this->getFunctions();
|
||||||
|
foreach ($functions as $function) {
|
||||||
|
$datapoints = $function->newDatapoints($data_query);
|
||||||
|
|
||||||
|
$refs = $function->getDataRefs(ipull($datapoints, 'x'));
|
||||||
|
|
||||||
|
foreach ($datapoints as $key => $point) {
|
||||||
|
$x = $point['x'];
|
||||||
|
|
||||||
|
if (isset($refs[$x])) {
|
||||||
|
$xrefs = $refs[$x];
|
||||||
|
} else {
|
||||||
|
$xrefs = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$datapoints[$key]['refs'] = $xrefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
$results[] = array(
|
||||||
|
'data' => $datapoints,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id(new PhabricatorChartDisplayData())
|
||||||
|
->setWireData($results);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ abstract class PhabricatorChartFunction
|
|||||||
return $this->functionLabel;
|
return $this->functionLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final public function getKey() {
|
||||||
|
return $this->getFunctionLabel()->getKey();
|
||||||
|
}
|
||||||
|
|
||||||
final public static function newFromDictionary(array $map) {
|
final public static function newFromDictionary(array $map) {
|
||||||
PhutilTypeSpec::checkMap(
|
PhutilTypeSpec::checkMap(
|
||||||
$map,
|
$map,
|
||||||
@@ -86,13 +90,6 @@ abstract class PhabricatorChartFunction
|
|||||||
return $function;
|
return $function;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toDictionary() {
|
|
||||||
return array(
|
|
||||||
'function' => $this->getFunctionKey(),
|
|
||||||
'arguments' => $this->getArgumentParser()->getRawArguments(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSubfunctions() {
|
public function getSubfunctions() {
|
||||||
$result = array();
|
$result = array();
|
||||||
$result[] = $this;
|
$result[] = $this;
|
||||||
@@ -180,6 +177,8 @@ abstract class PhabricatorChartFunction
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract public function evaluateFunction(array $xv);
|
abstract public function evaluateFunction(array $xv);
|
||||||
|
abstract public function getDataRefs(array $xv);
|
||||||
|
abstract public function loadRefs(array $refs);
|
||||||
|
|
||||||
public function getDomain() {
|
public function getDomain() {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -3,11 +3,21 @@
|
|||||||
final class PhabricatorChartFunctionLabel
|
final class PhabricatorChartFunctionLabel
|
||||||
extends Phobject {
|
extends Phobject {
|
||||||
|
|
||||||
|
private $key;
|
||||||
private $name;
|
private $name;
|
||||||
private $color;
|
private $color;
|
||||||
private $icon;
|
private $icon;
|
||||||
private $fillColor;
|
private $fillColor;
|
||||||
|
|
||||||
|
public function setKey($key) {
|
||||||
|
$this->key = $key;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKey() {
|
||||||
|
return $this->key;
|
||||||
|
}
|
||||||
|
|
||||||
public function setName($name) {
|
public function setName($name) {
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
return $this;
|
return $this;
|
||||||
@@ -46,6 +56,7 @@ final class PhabricatorChartFunctionLabel
|
|||||||
|
|
||||||
public function toWireFormat() {
|
public function toWireFormat() {
|
||||||
return array(
|
return array(
|
||||||
|
'key' => $this->getKey(),
|
||||||
'name' => $this->getName(),
|
'name' => $this->getName(),
|
||||||
'color' => $this->getColor(),
|
'color' => $this->getColor(),
|
||||||
'icon' => $this->getIcon(),
|
'icon' => $this->getIcon(),
|
||||||
|
|||||||
@@ -5,24 +5,187 @@ final class PhabricatorChartStackedAreaDataset
|
|||||||
|
|
||||||
const DATASETKEY = 'stacked-area';
|
const DATASETKEY = 'stacked-area';
|
||||||
|
|
||||||
|
private $stacks;
|
||||||
|
|
||||||
|
public function setStacks(array $stacks) {
|
||||||
|
$this->stacks = $stacks;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStacks() {
|
||||||
|
return $this->stacks;
|
||||||
|
}
|
||||||
|
|
||||||
protected function newChartDisplayData(
|
protected function newChartDisplayData(
|
||||||
PhabricatorChartDataQuery $data_query) {
|
PhabricatorChartDataQuery $data_query) {
|
||||||
|
|
||||||
$functions = $this->getFunctions();
|
$functions = $this->getFunctions();
|
||||||
|
$functions = mpull($functions, null, 'getKey');
|
||||||
|
|
||||||
$reversed_functions = array_reverse($functions, true);
|
$stacks = $this->getStacks();
|
||||||
|
|
||||||
$function_points = array();
|
if (!$stacks) {
|
||||||
foreach ($reversed_functions as $function_idx => $function) {
|
$stacks = array(
|
||||||
$function_points[$function_idx] = array();
|
array_reverse(array_keys($functions), true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$datapoints = $function->newDatapoints($data_query);
|
$series = array();
|
||||||
foreach ($datapoints as $point) {
|
$raw_points = array();
|
||||||
|
|
||||||
|
foreach ($stacks as $stack) {
|
||||||
|
$stack_functions = array_select_keys($functions, $stack);
|
||||||
|
|
||||||
|
$function_points = $this->getFunctionDatapoints(
|
||||||
|
$data_query,
|
||||||
|
$stack_functions);
|
||||||
|
|
||||||
|
$stack_points = $function_points;
|
||||||
|
|
||||||
|
$function_points = $this->getGeometry(
|
||||||
|
$data_query,
|
||||||
|
$function_points);
|
||||||
|
|
||||||
|
$baseline = array();
|
||||||
|
foreach ($function_points as $function_idx => $points) {
|
||||||
|
$bounds = array();
|
||||||
|
foreach ($points as $x => $point) {
|
||||||
|
if (!isset($baseline[$x])) {
|
||||||
|
$baseline[$x] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$y0 = $baseline[$x];
|
||||||
|
$baseline[$x] += $point['y'];
|
||||||
|
$y1 = $baseline[$x];
|
||||||
|
|
||||||
|
$bounds[] = array(
|
||||||
|
'x' => $x,
|
||||||
|
'y0' => $y0,
|
||||||
|
'y1' => $y1,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isset($stack_points[$function_idx][$x])) {
|
||||||
|
$stack_points[$function_idx][$x]['y1'] = $y1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$series[$function_idx] = $bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw_points += $stack_points;
|
||||||
|
}
|
||||||
|
|
||||||
|
$series = array_select_keys($series, array_keys($functions));
|
||||||
|
$series = array_values($series);
|
||||||
|
|
||||||
|
$raw_points = array_select_keys($raw_points, array_keys($functions));
|
||||||
|
$raw_points = array_values($raw_points);
|
||||||
|
|
||||||
|
$range_min = null;
|
||||||
|
$range_max = null;
|
||||||
|
|
||||||
|
foreach ($series as $geometry_list) {
|
||||||
|
foreach ($geometry_list as $geometry_item) {
|
||||||
|
$y0 = $geometry_item['y0'];
|
||||||
|
$y1 = $geometry_item['y1'];
|
||||||
|
|
||||||
|
if ($range_min === null) {
|
||||||
|
$range_min = $y0;
|
||||||
|
}
|
||||||
|
$range_min = min($range_min, $y0, $y1);
|
||||||
|
|
||||||
|
if ($range_max === null) {
|
||||||
|
$range_max = $y1;
|
||||||
|
}
|
||||||
|
$range_max = max($range_max, $y0, $y1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're going to group multiple events into a single point if they have
|
||||||
|
// X values that are very close to one another.
|
||||||
|
//
|
||||||
|
// If the Y values are also close to one another (these points are near
|
||||||
|
// one another in a horizontal line), it can be hard to select any
|
||||||
|
// individual point with the mouse.
|
||||||
|
//
|
||||||
|
// Even if the Y values are not close together (the points are on a
|
||||||
|
// fairly steep slope up or down), it's usually better to be able to
|
||||||
|
// mouse over a single point at the top or bottom of the slope and get
|
||||||
|
// a summary of what's going on.
|
||||||
|
|
||||||
|
$domain_max = $data_query->getMaximumValue();
|
||||||
|
$domain_min = $data_query->getMinimumValue();
|
||||||
|
$resolution = ($domain_max - $domain_min) / 100;
|
||||||
|
|
||||||
|
$events = array();
|
||||||
|
foreach ($raw_points as $function_idx => $points) {
|
||||||
|
$event_list = array();
|
||||||
|
|
||||||
|
$event_group = array();
|
||||||
|
$head_event = null;
|
||||||
|
foreach ($points as $point) {
|
||||||
$x = $point['x'];
|
$x = $point['x'];
|
||||||
$function_points[$function_idx][$x] = $point;
|
|
||||||
|
if ($head_event === null) {
|
||||||
|
// We don't have any points yet, so start a new group.
|
||||||
|
$head_event = $x;
|
||||||
|
$event_group[] = $point;
|
||||||
|
} else if (($x - $head_event) <= $resolution) {
|
||||||
|
// This point is close to the first point in this group, so
|
||||||
|
// add it to the existing group.
|
||||||
|
$event_group[] = $point;
|
||||||
|
} else {
|
||||||
|
// This point is not close to the first point in the group,
|
||||||
|
// so create a new group.
|
||||||
|
$event_list[] = $event_group;
|
||||||
|
$head_event = $x;
|
||||||
|
$event_group = array($point);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$raw_points = $function_points;
|
if ($event_group) {
|
||||||
|
$event_list[] = $event_group;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event_spec = array();
|
||||||
|
foreach ($event_list as $key => $event_points) {
|
||||||
|
// NOTE: We're using the last point as the representative point so
|
||||||
|
// that you can learn about a section of a chart by hovering over
|
||||||
|
// the point to right of the section, which is more intuitive than
|
||||||
|
// other points.
|
||||||
|
$event = last($event_points);
|
||||||
|
|
||||||
|
$event = $event + array(
|
||||||
|
'n' => count($event_points),
|
||||||
|
);
|
||||||
|
|
||||||
|
$event_list[$key] = $event;
|
||||||
|
}
|
||||||
|
|
||||||
|
$events[] = $event_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
$wire_labels = array();
|
||||||
|
foreach ($functions as $function_key => $function) {
|
||||||
|
$label = $function->getFunctionLabel();
|
||||||
|
$wire_labels[] = $label->toWireFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = array(
|
||||||
|
'type' => $this->getDatasetTypeKey(),
|
||||||
|
'data' => $series,
|
||||||
|
'events' => $events,
|
||||||
|
'labels' => $wire_labels,
|
||||||
|
);
|
||||||
|
|
||||||
|
return id(new PhabricatorChartDisplayData())
|
||||||
|
->setWireData($result)
|
||||||
|
->setRange(new PhabricatorChartInterval($range_min, $range_max));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getAllXValuesAsMap(
|
||||||
|
PhabricatorChartDataQuery $data_query,
|
||||||
|
array $point_lists) {
|
||||||
|
|
||||||
// We need to define every function we're drawing at every point where
|
// We need to define every function we're drawing at every point where
|
||||||
// any of the functions we're drawing are defined. If we don't, we'll
|
// any of the functions we're drawing are defined. If we don't, we'll
|
||||||
@@ -31,17 +194,54 @@ final class PhabricatorChartStackedAreaDataset
|
|||||||
// stacking the functions on top of one another.
|
// stacking the functions on top of one another.
|
||||||
|
|
||||||
$must_define = array();
|
$must_define = array();
|
||||||
foreach ($function_points as $function_idx => $points) {
|
|
||||||
foreach ($points as $x => $point) {
|
$min = $data_query->getMinimumValue();
|
||||||
|
$max = $data_query->getMaximumValue();
|
||||||
|
$must_define[$max] = $max;
|
||||||
|
$must_define[$min] = $min;
|
||||||
|
|
||||||
|
foreach ($point_lists as $point_list) {
|
||||||
|
foreach ($point_list as $x => $point) {
|
||||||
$must_define[$x] = $x;
|
$must_define[$x] = $x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ksort($must_define);
|
ksort($must_define);
|
||||||
|
|
||||||
foreach ($reversed_functions as $function_idx => $function) {
|
return $must_define;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFunctionDatapoints(
|
||||||
|
PhabricatorChartDataQuery $data_query,
|
||||||
|
array $functions) {
|
||||||
|
|
||||||
|
assert_instances_of($functions, 'PhabricatorChartFunction');
|
||||||
|
|
||||||
|
$points = array();
|
||||||
|
foreach ($functions as $idx => $function) {
|
||||||
|
$points[$idx] = array();
|
||||||
|
|
||||||
|
$datapoints = $function->newDatapoints($data_query);
|
||||||
|
foreach ($datapoints as $point) {
|
||||||
|
$x_value = $point['x'];
|
||||||
|
$points[$idx][$x_value] = $point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $points;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getGeometry(
|
||||||
|
PhabricatorChartDataQuery $data_query,
|
||||||
|
array $point_lists) {
|
||||||
|
|
||||||
|
$must_define = $this->getAllXValuesAsMap($data_query, $point_lists);
|
||||||
|
|
||||||
|
foreach ($point_lists as $idx => $points) {
|
||||||
|
|
||||||
$missing = array();
|
$missing = array();
|
||||||
foreach ($must_define as $x) {
|
foreach ($must_define as $x) {
|
||||||
if (!isset($function_points[$function_idx][$x])) {
|
if (!isset($points[$x])) {
|
||||||
$missing[$x] = true;
|
$missing[$x] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,8 +250,6 @@ final class PhabricatorChartStackedAreaDataset
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$points = $function_points[$function_idx];
|
|
||||||
|
|
||||||
$values = array_keys($points);
|
$values = array_keys($points);
|
||||||
$cursor = -1;
|
$cursor = -1;
|
||||||
$length = count($values);
|
$length = count($values);
|
||||||
@@ -84,88 +282,19 @@ final class PhabricatorChartStackedAreaDataset
|
|||||||
$y = $ymin + (($ymax - $ymin) * $distance);
|
$y = $ymin + (($ymax - $ymin) * $distance);
|
||||||
} else {
|
} else {
|
||||||
$xmin = $values[$cursor];
|
$xmin = $values[$cursor];
|
||||||
$y = $function_points[$function_idx][$xmin]['y'];
|
$y = $points[$xmin]['y'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$function_points[$function_idx][$x] = array(
|
$point_lists[$idx][$x] = array(
|
||||||
'x' => $x,
|
'x' => $x,
|
||||||
'y' => $y,
|
'y' => $y,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ksort($function_points[$function_idx]);
|
ksort($point_lists[$idx]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$range_min = null;
|
return $point_lists;
|
||||||
$range_max = null;
|
|
||||||
|
|
||||||
$series = array();
|
|
||||||
$baseline = array();
|
|
||||||
foreach ($function_points as $function_idx => $points) {
|
|
||||||
$below = idx($function_points, $function_idx - 1);
|
|
||||||
|
|
||||||
$bounds = array();
|
|
||||||
foreach ($points as $x => $point) {
|
|
||||||
if (!isset($baseline[$x])) {
|
|
||||||
$baseline[$x] = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$y0 = $baseline[$x];
|
|
||||||
$baseline[$x] += $point['y'];
|
|
||||||
$y1 = $baseline[$x];
|
|
||||||
|
|
||||||
$bounds[] = array(
|
|
||||||
'x' => $x,
|
|
||||||
'y0' => $y0,
|
|
||||||
'y1' => $y1,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isset($raw_points[$function_idx][$x])) {
|
|
||||||
$raw_points[$function_idx][$x]['y1'] = $y1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($range_min === null) {
|
|
||||||
$range_min = $y0;
|
|
||||||
}
|
|
||||||
$range_min = min($range_min, $y0, $y1);
|
|
||||||
|
|
||||||
if ($range_max === null) {
|
|
||||||
$range_max = $y1;
|
|
||||||
}
|
|
||||||
$range_max = max($range_max, $y0, $y1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$series[] = $bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
$series = array_reverse($series);
|
|
||||||
|
|
||||||
$events = array();
|
|
||||||
foreach ($raw_points as $function_idx => $points) {
|
|
||||||
$event_list = array();
|
|
||||||
foreach ($points as $point) {
|
|
||||||
$event_list[] = $point;
|
|
||||||
}
|
|
||||||
$events[] = $event_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
$wire_labels = array();
|
|
||||||
foreach ($functions as $function_key => $function) {
|
|
||||||
$label = $function->getFunctionLabel();
|
|
||||||
$wire_labels[] = $label->toWireFormat();
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = array(
|
|
||||||
'type' => $this->getDatasetTypeKey(),
|
|
||||||
'data' => $series,
|
|
||||||
'events' => $events,
|
|
||||||
'labels' => $wire_labels,
|
|
||||||
);
|
|
||||||
|
|
||||||
return id(new PhabricatorChartDisplayData())
|
|
||||||
->setWireData($result)
|
|
||||||
->setRange(new PhabricatorChartInterval($range_min, $range_max));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,4 +70,22 @@ final class PhabricatorComposeChartFunction
|
|||||||
return $yv;
|
return $yv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDataRefs(array $xv) {
|
||||||
|
// TODO: This is not entirely correct. The correct implementation would
|
||||||
|
// map "x -> y" at each stage of composition and pull and aggregate all
|
||||||
|
// the datapoint refs. In practice, we currently never compose functions
|
||||||
|
// with a data function somewhere in the middle, so just grabbing the first
|
||||||
|
// result is close enough.
|
||||||
|
|
||||||
|
// In the future, we may: notably, "x -> shift(-1 month) -> ..." to
|
||||||
|
// generate a month-over-month overlay is a sensible operation which will
|
||||||
|
// source data from the middle of a function composition.
|
||||||
|
|
||||||
|
foreach ($this->getFunctionArguments() as $function) {
|
||||||
|
return $function->getDataRefs($xv);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PhabricatorConstantChartFunction
|
final class PhabricatorConstantChartFunction
|
||||||
extends PhabricatorChartFunction {
|
extends PhabricatorPureChartFunction {
|
||||||
|
|
||||||
const FUNCTIONKEY = 'constant';
|
const FUNCTIONKEY = 'constant';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PhabricatorCosChartFunction
|
final class PhabricatorCosChartFunction
|
||||||
extends PhabricatorChartFunction {
|
extends PhabricatorPureChartFunction {
|
||||||
|
|
||||||
const FUNCTIONKEY = 'cos';
|
const FUNCTIONKEY = 'cos';
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ final class PhabricatorFactChartFunction
|
|||||||
|
|
||||||
private $fact;
|
private $fact;
|
||||||
private $map;
|
private $map;
|
||||||
|
private $refs;
|
||||||
|
|
||||||
protected function newArguments() {
|
protected function newArguments() {
|
||||||
$key_argument = $this->newArgument()
|
$key_argument = $this->newArgument()
|
||||||
@@ -51,13 +52,15 @@ final class PhabricatorFactChartFunction
|
|||||||
|
|
||||||
$data = queryfx_all(
|
$data = queryfx_all(
|
||||||
$conn,
|
$conn,
|
||||||
'SELECT value, epoch FROM %T WHERE %LA ORDER BY epoch ASC',
|
'SELECT id, value, epoch FROM %T WHERE %LA ORDER BY epoch ASC',
|
||||||
$table_name,
|
$table_name,
|
||||||
$where);
|
$where);
|
||||||
|
|
||||||
$map = array();
|
$map = array();
|
||||||
|
$refs = array();
|
||||||
if ($data) {
|
if ($data) {
|
||||||
foreach ($data as $row) {
|
foreach ($data as $row) {
|
||||||
|
$ref = (string)$row['id'];
|
||||||
$value = (int)$row['value'];
|
$value = (int)$row['value'];
|
||||||
$epoch = (int)$row['epoch'];
|
$epoch = (int)$row['epoch'];
|
||||||
|
|
||||||
@@ -66,10 +69,17 @@ final class PhabricatorFactChartFunction
|
|||||||
}
|
}
|
||||||
|
|
||||||
$map[$epoch] += $value;
|
$map[$epoch] += $value;
|
||||||
|
|
||||||
|
if (!isset($refs[$epoch])) {
|
||||||
|
$refs[$epoch] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$refs[$epoch][] = $ref;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->map = $map;
|
$this->map = $map;
|
||||||
|
$this->refs = $refs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDomain() {
|
public function getDomain() {
|
||||||
@@ -99,4 +109,60 @@ final class PhabricatorFactChartFunction
|
|||||||
return $yv;
|
return $yv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDataRefs(array $xv) {
|
||||||
|
return array_select_keys($this->refs, $xv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadRefs(array $refs) {
|
||||||
|
$fact = $this->fact;
|
||||||
|
|
||||||
|
$datapoint_table = $fact->newDatapoint();
|
||||||
|
$conn = $datapoint_table->establishConnection('r');
|
||||||
|
|
||||||
|
$dimension_table = new PhabricatorFactObjectDimension();
|
||||||
|
|
||||||
|
$where = array();
|
||||||
|
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'p.id IN (%Ld)',
|
||||||
|
$refs);
|
||||||
|
|
||||||
|
|
||||||
|
$rows = queryfx_all(
|
||||||
|
$conn,
|
||||||
|
'SELECT
|
||||||
|
p.id id,
|
||||||
|
p.value,
|
||||||
|
od.objectPHID objectPHID,
|
||||||
|
dd.objectPHID dimensionPHID
|
||||||
|
FROM %R p
|
||||||
|
LEFT JOIN %R od ON od.id = p.objectID
|
||||||
|
LEFT JOIN %R dd ON dd.id = p.dimensionID
|
||||||
|
WHERE %LA',
|
||||||
|
$datapoint_table,
|
||||||
|
$dimension_table,
|
||||||
|
$dimension_table,
|
||||||
|
$where);
|
||||||
|
$rows = ipull($rows, null, 'id');
|
||||||
|
|
||||||
|
$results = array();
|
||||||
|
|
||||||
|
foreach ($refs as $ref) {
|
||||||
|
if (!isset($rows[$ref])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = $rows[$ref];
|
||||||
|
|
||||||
|
$results[$ref] = array(
|
||||||
|
'objectPHID' => $row['objectPHID'],
|
||||||
|
'dimensionPHID' => $row['dimensionPHID'],
|
||||||
|
'value' => (float)$row['value'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,4 +32,38 @@ abstract class PhabricatorHigherOrderChartFunction
|
|||||||
return array_keys($map);
|
return array_keys($map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDataRefs(array $xv) {
|
||||||
|
$refs = array();
|
||||||
|
|
||||||
|
foreach ($this->getFunctionArguments() as $function) {
|
||||||
|
$function_refs = $function->getDataRefs($xv);
|
||||||
|
|
||||||
|
$function_refs = array_select_keys($function_refs, $xv);
|
||||||
|
if (!$function_refs) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($function_refs as $x => $ref_list) {
|
||||||
|
if (!isset($refs[$x])) {
|
||||||
|
$refs[$x] = array();
|
||||||
|
}
|
||||||
|
foreach ($ref_list as $ref) {
|
||||||
|
$refs[$x][] = $ref;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $refs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadRefs(array $refs) {
|
||||||
|
$results = array();
|
||||||
|
|
||||||
|
foreach ($this->getFunctionArguments() as $function) {
|
||||||
|
$results += $function->loadRefs($refs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,27 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PhabricatorMaxChartFunction
|
final class PhabricatorMaxChartFunction
|
||||||
extends PhabricatorChartFunction {
|
extends PhabricatorPureChartFunction {
|
||||||
|
|
||||||
const FUNCTIONKEY = 'max';
|
const FUNCTIONKEY = 'max';
|
||||||
|
|
||||||
protected function newArguments() {
|
protected function newArguments() {
|
||||||
return array(
|
return array(
|
||||||
$this->newArgument()
|
|
||||||
->setName('x')
|
|
||||||
->setType('function'),
|
|
||||||
$this->newArgument()
|
$this->newArgument()
|
||||||
->setName('max')
|
->setName('max')
|
||||||
->setType('number'),
|
->setType('number'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDomain() {
|
|
||||||
return $this->getArgument('x')->getDomain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
|
||||||
return $this->getArgument('x')->newInputValues($query);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function evaluateFunction(array $xv) {
|
public function evaluateFunction(array $xv) {
|
||||||
$yv = $this->getArgument('x')->evaluateFunction($xv);
|
|
||||||
$max = $this->getArgument('max');
|
$max = $this->getArgument('max');
|
||||||
|
|
||||||
foreach ($yv as $k => $y) {
|
$yv = array();
|
||||||
if ($y > $max) {
|
foreach ($xv as $x) {
|
||||||
$yv[$k] = null;
|
if ($x > $max) {
|
||||||
|
$yv[] = null;
|
||||||
|
} else {
|
||||||
|
$yv[] = $x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,27 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PhabricatorMinChartFunction
|
final class PhabricatorMinChartFunction
|
||||||
extends PhabricatorChartFunction {
|
extends PhabricatorPureChartFunction {
|
||||||
|
|
||||||
const FUNCTIONKEY = 'min';
|
const FUNCTIONKEY = 'min';
|
||||||
|
|
||||||
protected function newArguments() {
|
protected function newArguments() {
|
||||||
return array(
|
return array(
|
||||||
$this->newArgument()
|
|
||||||
->setName('x')
|
|
||||||
->setType('function'),
|
|
||||||
$this->newArgument()
|
$this->newArgument()
|
||||||
->setName('min')
|
->setName('min')
|
||||||
->setType('number'),
|
->setType('number'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDomain() {
|
|
||||||
return $this->getArgument('x')->getDomain();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
|
||||||
return $this->getArgument('x')->newInputValues($query);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function evaluateFunction(array $xv) {
|
public function evaluateFunction(array $xv) {
|
||||||
$yv = $this->getArgument('x')->evaluateFunction($xv);
|
|
||||||
$min = $this->getArgument('min');
|
$min = $this->getArgument('min');
|
||||||
|
|
||||||
foreach ($yv as $k => $y) {
|
$yv = array();
|
||||||
if ($y < $min) {
|
foreach ($xv as $x) {
|
||||||
$yv[$k] = null;
|
if ($x < $min) {
|
||||||
|
$yv[] = null;
|
||||||
|
} else {
|
||||||
|
$yv[] = $x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
src/applications/fact/chart/PhabricatorPureChartFunction.php
Normal file
14
src/applications/fact/chart/PhabricatorPureChartFunction.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class PhabricatorPureChartFunction
|
||||||
|
extends PhabricatorChartFunction {
|
||||||
|
|
||||||
|
public function getDataRefs(array $xv) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadRefs(array $refs) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PhabricatorScaleChartFunction
|
final class PhabricatorScaleChartFunction
|
||||||
extends PhabricatorChartFunction {
|
extends PhabricatorPureChartFunction {
|
||||||
|
|
||||||
const FUNCTIONKEY = 'scale';
|
const FUNCTIONKEY = 'scale';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PhabricatorShiftChartFunction
|
final class PhabricatorShiftChartFunction
|
||||||
extends PhabricatorChartFunction {
|
extends PhabricatorPureChartFunction {
|
||||||
|
|
||||||
const FUNCTIONKEY = 'shift';
|
const FUNCTIONKEY = 'shift';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PhabricatorSinChartFunction
|
final class PhabricatorSinChartFunction
|
||||||
extends PhabricatorChartFunction {
|
extends PhabricatorPureChartFunction {
|
||||||
|
|
||||||
const FUNCTIONKEY = 'sin';
|
const FUNCTIONKEY = 'sin';
|
||||||
|
|
||||||
|
|||||||
@@ -33,10 +33,12 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$chart_view = $engine->newChartView();
|
$chart_view = $engine->newChartView();
|
||||||
return $this->newChartResponse($chart_view);
|
$tabular_view = $engine->newTabularView();
|
||||||
|
|
||||||
|
return $this->newChartResponse($chart_view, $tabular_view);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function newChartResponse($chart_view) {
|
private function newChartResponse($chart_view, $tabular_view) {
|
||||||
$box = id(new PHUIObjectBoxView())
|
$box = id(new PHUIObjectBoxView())
|
||||||
->setHeaderText(pht('Chart'))
|
->setHeaderText(pht('Chart'))
|
||||||
->appendChild($chart_view);
|
->appendChild($chart_view);
|
||||||
@@ -50,56 +52,19 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
|||||||
return $this->newPage()
|
return $this->newPage()
|
||||||
->setTitle($title)
|
->setTitle($title)
|
||||||
->setCrumbs($crumbs)
|
->setCrumbs($crumbs)
|
||||||
->appendChild($box);
|
->appendChild(
|
||||||
|
array(
|
||||||
|
$box,
|
||||||
|
$tabular_view,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function newDemoChart() {
|
private function newDemoChart() {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$argvs = array();
|
$chart = id(new PhabricatorDemoChartEngine())
|
||||||
|
|
||||||
$argvs[] = array('fact', 'tasks.count.create');
|
|
||||||
|
|
||||||
$argvs[] = array('constant', 360);
|
|
||||||
|
|
||||||
$argvs[] = array('fact', 'tasks.open-count.create');
|
|
||||||
|
|
||||||
$argvs[] = array(
|
|
||||||
'sum',
|
|
||||||
array(
|
|
||||||
'accumulate',
|
|
||||||
array('fact', 'tasks.count.create'),
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
'accumulate',
|
|
||||||
array('fact', 'tasks.open-count.create'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
$argvs[] = array(
|
|
||||||
'compose',
|
|
||||||
array('scale', 0.001),
|
|
||||||
array('cos'),
|
|
||||||
array('scale', 100),
|
|
||||||
array('shift', 800),
|
|
||||||
);
|
|
||||||
|
|
||||||
$datasets = array();
|
|
||||||
foreach ($argvs as $argv) {
|
|
||||||
$datasets[] = PhabricatorChartDataset::newFromDictionary(
|
|
||||||
array(
|
|
||||||
'function' => $argv,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$chart = id(new PhabricatorFactChart())
|
|
||||||
->setDatasets($datasets);
|
|
||||||
|
|
||||||
$engine = id(new PhabricatorChartRenderingEngine())
|
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->setChart($chart);
|
->newStoredChart();
|
||||||
|
|
||||||
$chart = $engine->getStoredChart();
|
|
||||||
|
|
||||||
return id(new AphrontRedirectResponse())->setURI($chart->getURI());
|
return id(new AphrontRedirectResponse())->setURI($chart->getURI());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ abstract class PhabricatorChartEngine
|
|||||||
|
|
||||||
abstract protected function newChart(PhabricatorFactChart $chart, array $map);
|
abstract protected function newChart(PhabricatorFactChart $chart, array $map);
|
||||||
|
|
||||||
final public function buildChartPanel() {
|
final public function newStoredChart() {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$parameters = $this->getEngineParameters();
|
$parameters = $this->getEngineParameters();
|
||||||
@@ -76,7 +76,11 @@ abstract class PhabricatorChartEngine
|
|||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->setChart($chart);
|
->setChart($chart);
|
||||||
|
|
||||||
$chart = $rendering_engine->getStoredChart();
|
return $rendering_engine->getStoredChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function buildChartPanel() {
|
||||||
|
$chart = $this->newStoredChart();
|
||||||
|
|
||||||
$panel_type = id(new PhabricatorDashboardChartPanelType())
|
$panel_type = id(new PhabricatorDashboardChartPanelType())
|
||||||
->getPanelTypeKey();
|
->getPanelTypeKey();
|
||||||
@@ -91,7 +95,7 @@ abstract class PhabricatorChartEngine
|
|||||||
final protected function newFunction($name /* , ... */) {
|
final protected function newFunction($name /* , ... */) {
|
||||||
$argv = func_get_args();
|
$argv = func_get_args();
|
||||||
return id(new PhabricatorComposeChartFunction())
|
return id(new PhabricatorComposeChartFunction())
|
||||||
->setArguments(array($argv));
|
->setArguments($argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,146 @@ final class PhabricatorChartRenderingEngine
|
|||||||
return $chart_view;
|
return $chart_view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function newTabularView() {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$tabular_data = $this->newTabularData();
|
||||||
|
|
||||||
|
$ref_keys = array();
|
||||||
|
foreach ($tabular_data['datasets'] as $tabular_dataset) {
|
||||||
|
foreach ($tabular_dataset as $function) {
|
||||||
|
foreach ($function['data'] as $point) {
|
||||||
|
foreach ($point['refs'] as $ref) {
|
||||||
|
$ref_keys[$ref] = $ref;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$chart = $this->getStoredChart();
|
||||||
|
|
||||||
|
$ref_map = array();
|
||||||
|
foreach ($chart->getDatasets() as $dataset) {
|
||||||
|
foreach ($dataset->getFunctions() as $function) {
|
||||||
|
// If we aren't looking for anything else, bail out.
|
||||||
|
if (!$ref_keys) {
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$function_refs = $function->loadRefs($ref_keys);
|
||||||
|
|
||||||
|
$ref_map += $function_refs;
|
||||||
|
|
||||||
|
// Remove the ref keys that we found data for from the list of keys
|
||||||
|
// we are looking for. If any function gives us data for a given ref,
|
||||||
|
// that's satisfactory.
|
||||||
|
foreach ($function_refs as $ref_key => $ref_data) {
|
||||||
|
unset($ref_keys[$ref_key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$phids = array();
|
||||||
|
foreach ($ref_map as $ref => $ref_data) {
|
||||||
|
if (isset($ref_data['objectPHID'])) {
|
||||||
|
$phids[] = $ref_data['objectPHID'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$handles = $viewer->loadHandles($phids);
|
||||||
|
|
||||||
|
$tabular_view = array();
|
||||||
|
foreach ($tabular_data['datasets'] as $tabular_data) {
|
||||||
|
foreach ($tabular_data as $function) {
|
||||||
|
$rows = array();
|
||||||
|
foreach ($function['data'] as $point) {
|
||||||
|
$ref_views = array();
|
||||||
|
|
||||||
|
$xv = date('Y-m-d h:i:s', $point['x']);
|
||||||
|
$yv = $point['y'];
|
||||||
|
|
||||||
|
$point_refs = array();
|
||||||
|
foreach ($point['refs'] as $ref) {
|
||||||
|
if (!isset($ref_map[$ref])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$point_refs[$ref] = $ref_map[$ref];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$point_refs) {
|
||||||
|
$rows[] = array(
|
||||||
|
$xv,
|
||||||
|
$yv,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
foreach ($point_refs as $ref => $ref_data) {
|
||||||
|
$ref_value = $ref_data['value'];
|
||||||
|
$ref_link = $handles[$ref_data['objectPHID']]
|
||||||
|
->renderLink();
|
||||||
|
|
||||||
|
$view_uri = urisprintf(
|
||||||
|
'/fact/object/%s/',
|
||||||
|
$ref_data['objectPHID']);
|
||||||
|
|
||||||
|
$ref_button = id(new PHUIButtonView())
|
||||||
|
->setIcon('fa-table')
|
||||||
|
->setTag('a')
|
||||||
|
->setColor('grey')
|
||||||
|
->setHref($view_uri)
|
||||||
|
->setText(pht('View Data'));
|
||||||
|
|
||||||
|
$rows[] = array(
|
||||||
|
$xv,
|
||||||
|
$yv,
|
||||||
|
$ref_value,
|
||||||
|
$ref_link,
|
||||||
|
$ref_button,
|
||||||
|
);
|
||||||
|
|
||||||
|
$xv = null;
|
||||||
|
$yv = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = id(new AphrontTableView($rows))
|
||||||
|
->setHeaders(
|
||||||
|
array(
|
||||||
|
pht('X'),
|
||||||
|
pht('Y'),
|
||||||
|
pht('Raw'),
|
||||||
|
pht('Refs'),
|
||||||
|
null,
|
||||||
|
))
|
||||||
|
->setColumnClasses(
|
||||||
|
array(
|
||||||
|
'n',
|
||||||
|
'n',
|
||||||
|
'n',
|
||||||
|
'wide',
|
||||||
|
null,
|
||||||
|
));
|
||||||
|
|
||||||
|
$tabular_view[] = id(new PHUIObjectBoxView())
|
||||||
|
->setHeaderText(pht('Function'))
|
||||||
|
->setTable($table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tabular_view;
|
||||||
|
}
|
||||||
|
|
||||||
public function newChartData() {
|
public function newChartData() {
|
||||||
|
return $this->newWireData(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newTabularData() {
|
||||||
|
return $this->newWireData(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function newWireData($is_tabular) {
|
||||||
$chart = $this->getStoredChart();
|
$chart = $this->getStoredChart();
|
||||||
$chart_key = $chart->getChartKey();
|
$chart_key = $chart->getChartKey();
|
||||||
|
|
||||||
@@ -151,7 +290,11 @@ final class PhabricatorChartRenderingEngine
|
|||||||
$wire_datasets = array();
|
$wire_datasets = array();
|
||||||
$ranges = array();
|
$ranges = array();
|
||||||
foreach ($datasets as $dataset) {
|
foreach ($datasets as $dataset) {
|
||||||
|
if ($is_tabular) {
|
||||||
|
$display_data = $dataset->getTabularDisplayData($data_query);
|
||||||
|
} else {
|
||||||
$display_data = $dataset->getChartDisplayData($data_query);
|
$display_data = $dataset->getChartDisplayData($data_query);
|
||||||
|
}
|
||||||
|
|
||||||
$ranges[] = $display_data->getRange();
|
$ranges[] = $display_data->getRange();
|
||||||
$wire_datasets[] = $display_data->getWireData();
|
$wire_datasets[] = $display_data->getWireData();
|
||||||
|
|||||||
44
src/applications/fact/engine/PhabricatorDemoChartEngine.php
Normal file
44
src/applications/fact/engine/PhabricatorDemoChartEngine.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorDemoChartEngine
|
||||||
|
extends PhabricatorChartEngine {
|
||||||
|
|
||||||
|
const CHARTENGINEKEY = 'facts.demo';
|
||||||
|
|
||||||
|
protected function newChart(PhabricatorFactChart $chart, array $map) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$functions = array();
|
||||||
|
|
||||||
|
$function = $this->newFunction(
|
||||||
|
array('scale', 0.0001),
|
||||||
|
array('cos'),
|
||||||
|
array('scale', 128),
|
||||||
|
array('shift', 256));
|
||||||
|
|
||||||
|
$function->getFunctionLabel()
|
||||||
|
->setName(pht('cos(x)'))
|
||||||
|
->setColor('rgba(0, 200, 0, 1)')
|
||||||
|
->setFillColor('rgba(0, 200, 0, 0.15)');
|
||||||
|
|
||||||
|
$functions[] = $function;
|
||||||
|
|
||||||
|
$function = $this->newFunction(
|
||||||
|
array('constant', 345));
|
||||||
|
|
||||||
|
$function->getFunctionLabel()
|
||||||
|
->setName(pht('constant(345)'))
|
||||||
|
->setColor('rgba(0, 0, 200, 1)')
|
||||||
|
->setFillColor('rgba(0, 0, 200, 0.15)');
|
||||||
|
|
||||||
|
$functions[] = $function;
|
||||||
|
|
||||||
|
$datasets = array();
|
||||||
|
|
||||||
|
$datasets[] = id(new PhabricatorChartStackedAreaDataset())
|
||||||
|
->setFunctions($functions);
|
||||||
|
|
||||||
|
$chart->attachDatasets($datasets);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PhabricatorFlagAddFlagHeraldAction extends HeraldAction {
|
final class PhabricatorFlagAddFlagHeraldAction
|
||||||
|
extends PhabricatorFlagHeraldAction {
|
||||||
|
|
||||||
const ACTIONCONST = 'flag';
|
const ACTIONCONST = 'flag';
|
||||||
|
|
||||||
@@ -11,18 +12,6 @@ final class PhabricatorFlagAddFlagHeraldAction extends HeraldAction {
|
|||||||
return pht('Mark with flag');
|
return pht('Mark with flag');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getActionGroupKey() {
|
|
||||||
return HeraldSupportActionGroup::ACTIONGROUPKEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function supportsObject($object) {
|
|
||||||
return ($object instanceof PhabricatorFlaggableInterface);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function supportsRuleType($rule_type) {
|
|
||||||
return ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function applyEffect($object, HeraldEffect $effect) {
|
public function applyEffect($object, HeraldEffect $effect) {
|
||||||
$phid = $this->getAdapter()->getPHID();
|
$phid = $this->getAdapter()->getPHID();
|
||||||
$rule = $effect->getRule();
|
$rule = $effect->getRule();
|
||||||
|
|||||||
18
src/applications/flag/herald/PhabricatorFlagHeraldAction.php
Normal file
18
src/applications/flag/herald/PhabricatorFlagHeraldAction.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class PhabricatorFlagHeraldAction
|
||||||
|
extends HeraldAction {
|
||||||
|
|
||||||
|
public function getActionGroupKey() {
|
||||||
|
return HeraldSupportActionGroup::ACTIONGROUPKEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsObject($object) {
|
||||||
|
return ($object instanceof PhabricatorFlaggableInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsRuleType($rule_type) {
|
||||||
|
return ($rule_type === HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorFlagRemoveFlagHeraldAction
|
||||||
|
extends PhabricatorFlagHeraldAction {
|
||||||
|
|
||||||
|
const ACTIONCONST = 'unflag';
|
||||||
|
|
||||||
|
const DO_UNFLAG = 'do.unflag';
|
||||||
|
const DO_IGNORE_UNFLAG = 'do.ignore-unflag';
|
||||||
|
|
||||||
|
public function getHeraldActionName() {
|
||||||
|
return pht('Remove flag');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyEffect($object, HeraldEffect $effect) {
|
||||||
|
$phid = $this->getAdapter()->getPHID();
|
||||||
|
$rule = $effect->getRule();
|
||||||
|
$author = $rule->getAuthor();
|
||||||
|
|
||||||
|
$flag = PhabricatorFlagQuery::loadUserFlag($author, $phid);
|
||||||
|
if (!$flag) {
|
||||||
|
$this->logEffect(self::DO_IGNORE_UNFLAG, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($flag->getColor() !== $effect->getTarget()) {
|
||||||
|
$this->logEffect(self::DO_IGNORE_UNFLAG, $flag->getColor());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$flag->delete();
|
||||||
|
|
||||||
|
$this->logEffect(self::DO_UNFLAG, $flag->getColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeraldActionValueType() {
|
||||||
|
return id(new HeraldSelectFieldValue())
|
||||||
|
->setKey('flag.color')
|
||||||
|
->setOptions(PhabricatorFlagColor::getColorNameMap())
|
||||||
|
->setDefault(PhabricatorFlagColor::COLOR_BLUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getActionEffectMap() {
|
||||||
|
return array(
|
||||||
|
self::DO_IGNORE_UNFLAG => array(
|
||||||
|
'icon' => 'fa-times',
|
||||||
|
'color' => 'grey',
|
||||||
|
'name' => pht('Did Not Remove Flag'),
|
||||||
|
),
|
||||||
|
self::DO_UNFLAG => array(
|
||||||
|
'icon' => 'fa-flag',
|
||||||
|
'name' => pht('Removed Flag'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderActionDescription($value) {
|
||||||
|
$color = PhabricatorFlagColor::getColorName($value);
|
||||||
|
return pht('Remove %s flag.', $color);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderActionEffectDescription($type, $data) {
|
||||||
|
switch ($type) {
|
||||||
|
case self::DO_IGNORE_UNFLAG:
|
||||||
|
if (!$data) {
|
||||||
|
return pht('Not marked with any flag.');
|
||||||
|
} else {
|
||||||
|
return pht(
|
||||||
|
'Marked with flag of the wrong color ("%s").',
|
||||||
|
PhabricatorFlagColor::getColorName($data));
|
||||||
|
}
|
||||||
|
case self::DO_UNFLAG:
|
||||||
|
return pht(
|
||||||
|
'Removed "%s" flag.',
|
||||||
|
PhabricatorFlagColor::getColorName($data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -136,14 +136,14 @@ final class FundInitiative extends FundDAO
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($capability == PhabricatorPolicyCapability::CAN_VIEW) {
|
if ($capability == PhabricatorPolicyCapability::CAN_VIEW) {
|
||||||
foreach ($viewer->getAuthorities() as $authority) {
|
$can_merchant = PhortuneMerchantQuery::canViewersEditMerchants(
|
||||||
if ($authority instanceof PhortuneMerchant) {
|
array($viewer->getPHID()),
|
||||||
if ($authority->getPHID() == $this->getMerchantPHID()) {
|
array($this->getMerchantPHID()));
|
||||||
|
|
||||||
|
if ($can_merchant) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,4 +96,8 @@ final class HarbormasterRunBuildPlansHeraldAction
|
|||||||
return $record->getTarget();
|
return $record->getTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isActionAvailable() {
|
||||||
|
return id(new PhabricatorHarbormasterApplication())->isInstalled();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -405,4 +405,8 @@ abstract class HeraldAction extends Phobject {
|
|||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isActionAvailable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -373,6 +373,16 @@ abstract class HeraldAdapter extends Phobject {
|
|||||||
return $field->getFieldGroupKey();
|
return $field->getFieldGroupKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isFieldAvailable($field_key) {
|
||||||
|
$field = $this->getFieldImplementation($field_key);
|
||||||
|
|
||||||
|
if (!$field) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $field->isFieldAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( Conditions )--------------------------------------------------------- */
|
/* -( Conditions )--------------------------------------------------------- */
|
||||||
|
|
||||||
@@ -765,6 +775,16 @@ abstract class HeraldAdapter extends Phobject {
|
|||||||
return $action->getActionGroupKey();
|
return $action->getActionGroupKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isActionAvailable($action_key) {
|
||||||
|
$action = $this->getActionImplementation($action_key);
|
||||||
|
|
||||||
|
if (!$action) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $action->isActionAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
public function getActions($rule_type) {
|
public function getActions($rule_type) {
|
||||||
$actions = array();
|
$actions = array();
|
||||||
foreach ($this->getActionsForRuleType($rule_type) as $key => $action) {
|
foreach ($this->getActionsForRuleType($rule_type) as $key => $action) {
|
||||||
|
|||||||
@@ -404,8 +404,8 @@ final class HeraldRuleController extends HeraldController {
|
|||||||
HeraldAdapter $adapter) {
|
HeraldAdapter $adapter) {
|
||||||
|
|
||||||
$all_rules = $this->loadRulesThisRuleMayDependUpon($rule);
|
$all_rules = $this->loadRulesThisRuleMayDependUpon($rule);
|
||||||
$all_rules = mpull($all_rules, 'getName', 'getPHID');
|
$all_rules = msortv($all_rules, 'getEditorSortVector');
|
||||||
asort($all_rules);
|
$all_rules = mpull($all_rules, 'getEditorDisplayName', 'getPHID');
|
||||||
|
|
||||||
$all_fields = $adapter->getFieldNameMap();
|
$all_fields = $adapter->getFieldNameMap();
|
||||||
$all_conditions = $adapter->getConditionNameMap();
|
$all_conditions = $adapter->getConditionNameMap();
|
||||||
@@ -674,15 +674,6 @@ final class HeraldRuleController extends HeraldController {
|
|||||||
->execute();
|
->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark disabled rules as disabled since they are not useful as such;
|
|
||||||
// don't filter though to keep edit cases sane / expected
|
|
||||||
foreach ($all_rules as $current_rule) {
|
|
||||||
if ($current_rule->getIsDisabled()) {
|
|
||||||
$current_rule->makeEphemeral();
|
|
||||||
$current_rule->setName($rule->getName().' '.pht('(Disabled)'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A rule can not depend upon itself.
|
// A rule can not depend upon itself.
|
||||||
unset($all_rules[$rule->getID()]);
|
unset($all_rules[$rule->getID()]);
|
||||||
|
|
||||||
@@ -693,7 +684,10 @@ final class HeraldRuleController extends HeraldController {
|
|||||||
$group_map = array();
|
$group_map = array();
|
||||||
foreach ($field_map as $field_key => $field_name) {
|
foreach ($field_map as $field_key => $field_name) {
|
||||||
$group_key = $adapter->getFieldGroupKey($field_key);
|
$group_key = $adapter->getFieldGroupKey($field_key);
|
||||||
$group_map[$group_key][$field_key] = $field_name;
|
$group_map[$group_key][$field_key] = array(
|
||||||
|
'name' => $field_name,
|
||||||
|
'available' => $adapter->isFieldAvailable($field_key),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getGroups(
|
return $this->getGroups(
|
||||||
@@ -705,7 +699,10 @@ final class HeraldRuleController extends HeraldController {
|
|||||||
$group_map = array();
|
$group_map = array();
|
||||||
foreach ($action_map as $action_key => $action_name) {
|
foreach ($action_map as $action_key => $action_name) {
|
||||||
$group_key = $adapter->getActionGroupKey($action_key);
|
$group_key = $adapter->getActionGroupKey($action_key);
|
||||||
$group_map[$group_key][$action_key] = $action_name;
|
$group_map[$group_key][$action_key] = array(
|
||||||
|
'name' => $action_name,
|
||||||
|
'available' => $adapter->isActionAvailable($action_key),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getGroups(
|
return $this->getGroups(
|
||||||
|
|||||||
@@ -37,6 +37,20 @@ final class HeraldRuleIndexEngineExtension
|
|||||||
|
|
||||||
$phids = array();
|
$phids = array();
|
||||||
|
|
||||||
|
$fields = HeraldField::getAllFields();
|
||||||
|
foreach ($rule->getConditions() as $condition_record) {
|
||||||
|
$field = idx($fields, $condition_record->getFieldName());
|
||||||
|
|
||||||
|
if (!$field) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$affected_phids = $field->getPHIDsAffectedByCondition($condition_record);
|
||||||
|
foreach ($affected_phids as $phid) {
|
||||||
|
$phids[] = $phid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$actions = HeraldAction::getAllActions();
|
$actions = HeraldAction::getAllActions();
|
||||||
foreach ($rule->getActions() as $action_record) {
|
foreach ($rule->getActions() as $action_record) {
|
||||||
$action = idx($actions, $action_record->getAction());
|
$action = idx($actions, $action_record->getAction());
|
||||||
|
|||||||
@@ -176,6 +176,29 @@ abstract class HeraldField extends Phobject {
|
|||||||
return $value_type->renderEditorValue($value);
|
return $value_type->renderEditorValue($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPHIDsAffectedByCondition(HeraldCondition $condition) {
|
||||||
|
try {
|
||||||
|
$standard_type = $this->getHeraldFieldStandardType();
|
||||||
|
} catch (PhutilMethodNotImplementedException $ex) {
|
||||||
|
$standard_type = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($standard_type) {
|
||||||
|
case self::STANDARD_PHID:
|
||||||
|
case self::STANDARD_PHID_NULLABLE:
|
||||||
|
case self::STANDARD_PHID_LIST:
|
||||||
|
$phids = $condition->getValue();
|
||||||
|
|
||||||
|
if (!is_array($phids)) {
|
||||||
|
$phids = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $phids;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
final public function setAdapter(HeraldAdapter $adapter) {
|
final public function setAdapter(HeraldAdapter $adapter) {
|
||||||
$this->adapter = $adapter;
|
$this->adapter = $adapter;
|
||||||
return $this;
|
return $this;
|
||||||
@@ -218,4 +241,8 @@ abstract class HeraldField extends Phobject {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isFieldAvailable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,6 +259,22 @@ final class HeraldRule extends HeraldDAO
|
|||||||
return '/'.$this->getMonogram();
|
return '/'.$this->getMonogram();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEditorSortVector() {
|
||||||
|
return id(new PhutilSortVector())
|
||||||
|
->addInt($this->getIsDisabled() ? 1 : 0)
|
||||||
|
->addString($this->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEditorDisplayName() {
|
||||||
|
$name = pht('%s %s', $this->getMonogram(), $this->getName());
|
||||||
|
|
||||||
|
if ($this->getIsDisabled()) {
|
||||||
|
$name = pht('%s (Disabled)', $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( Repetition Policies )------------------------------------------------ */
|
/* -( Repetition Policies )------------------------------------------------ */
|
||||||
|
|
||||||
|
|||||||
@@ -130,4 +130,9 @@ final class LegalpadRequireSignatureHeraldAction
|
|||||||
'Require document signatures: %s.',
|
'Require document signatures: %s.',
|
||||||
$this->renderHandleList($value));
|
$this->renderHandleList($value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isActionAvailable() {
|
||||||
|
return id(new PhabricatorLegalpadApplication())->isInstalled();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,6 +344,8 @@ dictionary with these keys:
|
|||||||
- `children` //Optional map.// Configure options shown to the user when
|
- `children` //Optional map.// Configure options shown to the user when
|
||||||
they "Create Subtask". See below.
|
they "Create Subtask". See below.
|
||||||
- `fields` //Optional map.// Configure field behaviors. See below.
|
- `fields` //Optional map.// Configure field behaviors. See below.
|
||||||
|
- `mutations` //Optional list.// Configure which subtypes this subtype
|
||||||
|
can easily be converted to by using the "Change Subtype" action. See below.
|
||||||
|
|
||||||
Each subtype must have a unique key, and you must define a subtype with
|
Each subtype must have a unique key, and you must define a subtype with
|
||||||
the key "%s", which is used as a default subtype.
|
the key "%s", which is used as a default subtype.
|
||||||
@@ -421,6 +423,31 @@ Each field supports these options:
|
|||||||
subtypes.
|
subtypes.
|
||||||
- `name` //Optional string.// Custom name of this field for the subtype.
|
- `name` //Optional string.// Custom name of this field for the subtype.
|
||||||
|
|
||||||
|
|
||||||
|
The `mutations` key allows you to control the behavior of the "Change Subtype"
|
||||||
|
action above the comment area. By default, this action allows users to change
|
||||||
|
the task subtype into any other subtype.
|
||||||
|
|
||||||
|
If you'd prefer to make it more difficult to change subtypes or offer only a
|
||||||
|
subset of subtypes, you can specify the list of subtypes that "Change Subtypes"
|
||||||
|
offers. For example, if you have several similar subtypes and want to allow
|
||||||
|
tasks to be converted between them but not easily converted to other types,
|
||||||
|
you can make the "Change Subtypes" control show only these options like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"mutations": ["bug", "issue", "defect"]
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you specify an empty list, the "Change Subtypes" action will be completely
|
||||||
|
hidden.
|
||||||
|
|
||||||
|
This mutation list is advisory and only configures the UI. Tasks may still be
|
||||||
|
converted across subtypes freely by using the Bulk Editor or API.
|
||||||
|
|
||||||
EOTEXT
|
EOTEXT
|
||||||
,
|
,
|
||||||
$subtype_default_key));
|
$subtype_default_key));
|
||||||
|
|||||||
@@ -564,7 +564,8 @@ final class ManiphestTask extends ManiphestDAO
|
|||||||
|
|
||||||
public function newEditEngineSubtypeMap() {
|
public function newEditEngineSubtypeMap() {
|
||||||
$config = PhabricatorEnv::getEnvConfig('maniphest.subtypes');
|
$config = PhabricatorEnv::getEnvConfig('maniphest.subtypes');
|
||||||
return PhabricatorEditEngineSubtype::newSubtypeMap($config);
|
return PhabricatorEditEngineSubtype::newSubtypeMap($config)
|
||||||
|
->setDatasource(new ManiphestTaskSubtypeDatasource());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,17 @@ final class PhabricatorDatasourceURIEngineExtension
|
|||||||
->setProtocol(null)
|
->setProtocol(null)
|
||||||
->setPort(null);
|
->setPort(null);
|
||||||
|
|
||||||
return phutil_string_cast($uri);
|
$uri = phutil_string_cast($uri);
|
||||||
|
|
||||||
|
// See T13412. If the URI was in the form "http://dev.example.com" with
|
||||||
|
// no trailing slash, there may be no path. Redirecting to the empty
|
||||||
|
// string is considered an error by safety checks during redirection,
|
||||||
|
// so treat this like the user entered the URI with a trailing slash.
|
||||||
|
if (!strlen($uri)) {
|
||||||
|
$uri = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -52,28 +52,22 @@ final class PhabricatorApplicationPolicyChangeTransaction
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getTitle() {
|
public function getTitle() {
|
||||||
$old = $this->renderApplicationPolicy($this->getOldValue());
|
|
||||||
$new = $this->renderApplicationPolicy($this->getNewValue());
|
|
||||||
|
|
||||||
return pht(
|
return pht(
|
||||||
'%s changed the "%s" policy from "%s" to "%s".',
|
'%s changed the %s policy from %s to %s.',
|
||||||
$this->renderAuthor(),
|
$this->renderAuthor(),
|
||||||
$this->renderCapability(),
|
$this->renderCapability(),
|
||||||
$old,
|
$this->renderOldPolicy(),
|
||||||
$new);
|
$this->renderNewPolicy());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTitleForFeed() {
|
public function getTitleForFeed() {
|
||||||
$old = $this->renderApplicationPolicy($this->getOldValue());
|
|
||||||
$new = $this->renderApplicationPolicy($this->getNewValue());
|
|
||||||
|
|
||||||
return pht(
|
return pht(
|
||||||
'%s changed the "%s" policy for application %s from "%s" to "%s".',
|
'%s changed the %s policy for application %s from %s to %s.',
|
||||||
$this->renderAuthor(),
|
$this->renderAuthor(),
|
||||||
$this->renderCapability(),
|
$this->renderCapability(),
|
||||||
$this->renderObject(),
|
$this->renderObject(),
|
||||||
$old,
|
$this->renderOldPolicy(),
|
||||||
$new);
|
$this->renderNewPolicy());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateTransactions($object, array $xactions) {
|
public function validateTransactions($object, array $xactions) {
|
||||||
@@ -165,38 +159,11 @@ final class PhabricatorApplicationPolicyChangeTransaction
|
|||||||
return $errors;
|
return $errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function renderApplicationPolicy($name) {
|
|
||||||
$policies = $this->getAllPolicies();
|
|
||||||
if (empty($policies[$name])) {
|
|
||||||
// Not a standard policy, check for a custom policy.
|
|
||||||
$policy = id(new PhabricatorPolicyQuery())
|
|
||||||
->setViewer($this->getViewer())
|
|
||||||
->withPHIDs(array($name))
|
|
||||||
->executeOne();
|
|
||||||
$policies[$name] = $policy;
|
|
||||||
}
|
|
||||||
|
|
||||||
$policy = idx($policies, $name);
|
|
||||||
return $this->renderValue($policy->getFullName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getAllPolicies() {
|
|
||||||
if (!$this->policies) {
|
|
||||||
$viewer = $this->getViewer();
|
|
||||||
$application = $this->getObject();
|
|
||||||
$this->policies = id(new PhabricatorPolicyQuery())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->setObject($application)
|
|
||||||
->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->policies;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function renderCapability() {
|
private function renderCapability() {
|
||||||
$application = $this->getObject();
|
$application = $this->getObject();
|
||||||
$capability = $this->getCapabilityName();
|
$capability = $this->getCapabilityName();
|
||||||
return $application->getCapabilityLabel($capability);
|
$label = $application->getCapabilityLabel($capability);
|
||||||
|
return $this->renderValue($label);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getCapabilityName() {
|
private function getCapabilityName() {
|
||||||
|
|||||||
@@ -190,14 +190,6 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
|||||||
pht('Credential Type'),
|
pht('Credential Type'),
|
||||||
$type->getCredentialTypeName());
|
$type->getCredentialTypeName());
|
||||||
|
|
||||||
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
|
|
||||||
$viewer,
|
|
||||||
$credential);
|
|
||||||
|
|
||||||
$properties->addProperty(
|
|
||||||
pht('Editable By'),
|
|
||||||
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
|
|
||||||
|
|
||||||
if ($type->shouldRequireUsername()) {
|
if ($type->shouldRequireUsername()) {
|
||||||
$properties->addProperty(
|
$properties->addProperty(
|
||||||
pht('Username'),
|
pht('Username'),
|
||||||
|
|||||||
@@ -143,14 +143,6 @@ final class PhameBlogManageController extends PhameBlogController {
|
|||||||
),
|
),
|
||||||
$feed_uri));
|
$feed_uri));
|
||||||
|
|
||||||
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
|
|
||||||
$viewer,
|
|
||||||
$blog);
|
|
||||||
|
|
||||||
$properties->addProperty(
|
|
||||||
pht('Editable By'),
|
|
||||||
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
|
|
||||||
|
|
||||||
$engine = id(new PhabricatorMarkupEngine())
|
$engine = id(new PhabricatorMarkupEngine())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->addObject($blog, PhameBlog::MARKUP_FIELD_DESCRIPTION)
|
->addObject($blog, PhameBlog::MARKUP_FIELD_DESCRIPTION)
|
||||||
|
|||||||
@@ -293,37 +293,6 @@ final class PhrictionDocumentController
|
|||||||
} else {
|
} else {
|
||||||
throw new Exception(pht("Unknown document status '%s'!", $doc_status));
|
throw new Exception(pht("Unknown document status '%s'!", $doc_status));
|
||||||
}
|
}
|
||||||
|
|
||||||
$move_notice = null;
|
|
||||||
if ($current_status == PhrictionChangeType::CHANGE_MOVE_HERE) {
|
|
||||||
$from_doc_id = $content->getChangeRef();
|
|
||||||
|
|
||||||
$slug_uri = null;
|
|
||||||
|
|
||||||
// If the old document exists and is visible, provide a link to it.
|
|
||||||
$from_docs = id(new PhrictionDocumentQuery())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->withIDs(array($from_doc_id))
|
|
||||||
->execute();
|
|
||||||
if ($from_docs) {
|
|
||||||
$from_doc = head($from_docs);
|
|
||||||
$slug_uri = PhrictionDocument::getSlugURI($from_doc->getSlug());
|
|
||||||
}
|
|
||||||
|
|
||||||
$move_notice = id(new PHUIInfoView())
|
|
||||||
->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
|
|
||||||
|
|
||||||
if ($slug_uri) {
|
|
||||||
$move_notice->appendChild(
|
|
||||||
pht(
|
|
||||||
'This document was moved from %s.',
|
|
||||||
phutil_tag('a', array('href' => $slug_uri), $slug_uri)));
|
|
||||||
} else {
|
|
||||||
// Render this for consistency, even though it's a bit silly.
|
|
||||||
$move_notice->appendChild(
|
|
||||||
pht('This document was moved from elsewhere.'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$children = $this->renderDocumentChildren($slug);
|
$children = $this->renderDocumentChildren($slug);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ final class PhrictionDocumentMoveToTransaction
|
|||||||
$new = $this->getNewValue();
|
$new = $this->getNewValue();
|
||||||
|
|
||||||
return pht(
|
return pht(
|
||||||
'%s moved this document from %s',
|
'%s moved this document from %s.',
|
||||||
$this->renderAuthor(),
|
$this->renderAuthor(),
|
||||||
$this->renderHandle($new['phid']));
|
$this->renderHandle($new['phid']));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ final class PhabricatorPolicyExplainController
|
|||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->setIcon($handle->getIcon().' bluegrey')
|
->setIcon($handle->getIcon().' bluegrey')
|
||||||
->setHeader(pht('Object Policy'))
|
->setHeader(pht('Object Policy'))
|
||||||
->appendList(
|
->appendParagraph(
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
phutil_tag('strong', array(), pht('%s:', $capability_name)),
|
phutil_tag('strong', array(), pht('%s:', $capability_name)),
|
||||||
@@ -337,6 +337,13 @@ final class PhabricatorPolicyExplainController
|
|||||||
$policy->getPHID()),
|
$policy->getPHID()),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if ($policy->isCustomPolicy()) {
|
||||||
|
$rules_view = id(new PhabricatorPolicyRulesView())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setPolicy($policy);
|
||||||
|
$object_section->appendRulesView($rules_view);
|
||||||
|
}
|
||||||
|
|
||||||
$strength = $this->getStrengthInformation($object, $policy, $capability);
|
$strength = $this->getStrengthInformation($object, $policy, $capability);
|
||||||
if ($strength) {
|
if ($strength) {
|
||||||
$object_section->appendHint($strength);
|
$object_section->appendHint($strength);
|
||||||
|
|||||||
@@ -602,12 +602,13 @@ final class PhabricatorPolicyFilter extends Phobject {
|
|||||||
PhabricatorPolicyInterface $object,
|
PhabricatorPolicyInterface $object,
|
||||||
$policy,
|
$policy,
|
||||||
$capability) {
|
$capability) {
|
||||||
|
$viewer = $this->viewer;
|
||||||
|
|
||||||
if (!$this->raisePolicyExceptions) {
|
if (!$this->raisePolicyExceptions) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->viewer->isOmnipotent()) {
|
if ($viewer->isOmnipotent()) {
|
||||||
// Never raise policy exceptions for the omnipotent viewer. Although we
|
// Never raise policy exceptions for the omnipotent viewer. Although we
|
||||||
// will never normally issue a policy rejection for the omnipotent
|
// will never normally issue a policy rejection for the omnipotent
|
||||||
// viewer, we can end up here when queries blanket reject objects that
|
// viewer, we can end up here when queries blanket reject objects that
|
||||||
@@ -634,9 +635,60 @@ final class PhabricatorPolicyFilter extends Phobject {
|
|||||||
$capability);
|
$capability);
|
||||||
}
|
}
|
||||||
|
|
||||||
$more = PhabricatorPolicy::getPolicyExplanation($this->viewer, $policy);
|
// See T13411. If you receive a policy exception because you can't view
|
||||||
$more = (array)$more;
|
// an object, we also want to avoid disclosing too many details about the
|
||||||
$more = array_filter($more);
|
// actual policy (for example, the names of projects in the policy).
|
||||||
|
|
||||||
|
// If you failed a "CAN_VIEW" check, or failed some other check and don't
|
||||||
|
// have "CAN_VIEW" on the object, we give you an "opaque" explanation.
|
||||||
|
// Otherwise, we give you a more detailed explanation.
|
||||||
|
|
||||||
|
$view_capability = PhabricatorPolicyCapability::CAN_VIEW;
|
||||||
|
if ($capability === $view_capability) {
|
||||||
|
$show_details = false;
|
||||||
|
} else {
|
||||||
|
$show_details = self::hasCapability(
|
||||||
|
$viewer,
|
||||||
|
$object,
|
||||||
|
$view_capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This is a bit clumsy. We're producing HTML and text versions of
|
||||||
|
// this message, but can't render the full policy rules in text today.
|
||||||
|
// Users almost never get a text-only version of this exception anyway.
|
||||||
|
|
||||||
|
$head = null;
|
||||||
|
$more = null;
|
||||||
|
|
||||||
|
if ($show_details) {
|
||||||
|
$head = PhabricatorPolicy::getPolicyExplanation($viewer, $policy);
|
||||||
|
|
||||||
|
$policy_type = PhabricatorPolicyPHIDTypePolicy::TYPECONST;
|
||||||
|
$is_custom = (phid_get_type($policy) === $policy_type);
|
||||||
|
if ($is_custom) {
|
||||||
|
$policy_map = PhabricatorPolicyQuery::loadPolicies(
|
||||||
|
$viewer,
|
||||||
|
$object);
|
||||||
|
if (isset($policy_map[$capability])) {
|
||||||
|
require_celerity_resource('phui-policy-section-view-css');
|
||||||
|
|
||||||
|
$more = id(new PhabricatorPolicyRulesView())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setPolicy($policy_map[$capability]);
|
||||||
|
|
||||||
|
$more = phutil_tag(
|
||||||
|
'div',
|
||||||
|
array(
|
||||||
|
'class' => 'phui-policy-section-view-rules',
|
||||||
|
),
|
||||||
|
$more);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$head = PhabricatorPolicy::getOpaquePolicyExplanation($viewer, $policy);
|
||||||
|
}
|
||||||
|
|
||||||
|
$head = (array)$head;
|
||||||
|
|
||||||
$exceptions = PhabricatorPolicy::getSpecialRules(
|
$exceptions = PhabricatorPolicy::getSpecialRules(
|
||||||
$object,
|
$object,
|
||||||
@@ -644,7 +696,10 @@ final class PhabricatorPolicyFilter extends Phobject {
|
|||||||
$capability,
|
$capability,
|
||||||
true);
|
true);
|
||||||
|
|
||||||
$details = array_filter(array_merge($more, $exceptions));
|
$text_details = array_filter(array_merge($head, $exceptions));
|
||||||
|
$text_details = implode(' ', $text_details);
|
||||||
|
|
||||||
|
$html_details = array($head, $more, $exceptions);
|
||||||
|
|
||||||
$access_denied = $this->renderAccessDenied($object);
|
$access_denied = $this->renderAccessDenied($object);
|
||||||
|
|
||||||
@@ -653,7 +708,7 @@ final class PhabricatorPolicyFilter extends Phobject {
|
|||||||
$access_denied,
|
$access_denied,
|
||||||
$capability_name,
|
$capability_name,
|
||||||
$rejection,
|
$rejection,
|
||||||
implode(' ', $details));
|
$text_details);
|
||||||
|
|
||||||
$exception = id(new PhabricatorPolicyException($full_message))
|
$exception = id(new PhabricatorPolicyException($full_message))
|
||||||
->setTitle($access_denied)
|
->setTitle($access_denied)
|
||||||
@@ -661,7 +716,7 @@ final class PhabricatorPolicyFilter extends Phobject {
|
|||||||
->setRejection($rejection)
|
->setRejection($rejection)
|
||||||
->setCapability($capability)
|
->setCapability($capability)
|
||||||
->setCapabilityName($capability_name)
|
->setCapabilityName($capability_name)
|
||||||
->setMoreInfo($details);
|
->setMoreInfo($html_details);
|
||||||
|
|
||||||
throw $exception;
|
throw $exception;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,8 +60,10 @@ final class PhabricatorPolicyManagementShowWorkflow
|
|||||||
|
|
||||||
$console->writeOut("__%s__\n\n", pht('CAPABILITIES'));
|
$console->writeOut("__%s__\n\n", pht('CAPABILITIES'));
|
||||||
foreach ($policies as $capability => $policy) {
|
foreach ($policies as $capability => $policy) {
|
||||||
|
$ref = $policy->newRef($viewer);
|
||||||
|
|
||||||
$console->writeOut(" **%s**\n", $capability);
|
$console->writeOut(" **%s**\n", $capability);
|
||||||
$console->writeOut(" %s\n", $policy->renderDescription());
|
$console->writeOut(" %s\n", $ref->getPolicyDisplayName());
|
||||||
$console->writeOut(" %s\n",
|
$console->writeOut(" %s\n",
|
||||||
PhabricatorPolicy::getPolicyExplanation($viewer, $policy->getPHID()));
|
PhabricatorPolicy::getPolicyExplanation($viewer, $policy->getPHID()));
|
||||||
$console->writeOut("\n");
|
$console->writeOut("\n");
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ final class PhabricatorPolicyQuery
|
|||||||
|
|
||||||
public static function renderPolicyDescriptions(
|
public static function renderPolicyDescriptions(
|
||||||
PhabricatorUser $viewer,
|
PhabricatorUser $viewer,
|
||||||
PhabricatorPolicyInterface $object,
|
PhabricatorPolicyInterface $object) {
|
||||||
$icon = false) {
|
|
||||||
|
|
||||||
$policies = self::loadPolicies($viewer, $object);
|
$policies = self::loadPolicies($viewer, $object);
|
||||||
|
|
||||||
foreach ($policies as $capability => $policy) {
|
foreach ($policies as $capability => $policy) {
|
||||||
$policies[$capability] = $policy->renderDescription($icon);
|
$policies[$capability] = $policy->newRef($viewer)
|
||||||
|
->newCapabilityLink($object, $capability);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $policies;
|
return $policies;
|
||||||
|
|||||||
@@ -85,8 +85,10 @@ final class PhabricatorPolicy
|
|||||||
$phid_type = phid_get_type($policy_identifier);
|
$phid_type = phid_get_type($policy_identifier);
|
||||||
switch ($phid_type) {
|
switch ($phid_type) {
|
||||||
case PhabricatorProjectProjectPHIDType::TYPECONST:
|
case PhabricatorProjectProjectPHIDType::TYPECONST:
|
||||||
$policy->setType(PhabricatorPolicyType::TYPE_PROJECT);
|
$policy
|
||||||
$policy->setName($handle->getName());
|
->setType(PhabricatorPolicyType::TYPE_PROJECT)
|
||||||
|
->setName($handle->getName())
|
||||||
|
->setIcon($handle->getIcon());
|
||||||
break;
|
break;
|
||||||
case PhabricatorPeopleUserPHIDType::TYPECONST:
|
case PhabricatorPeopleUserPHIDType::TYPECONST:
|
||||||
$policy->setType(PhabricatorPolicyType::TYPE_USER);
|
$policy->setType(PhabricatorPolicyType::TYPE_USER);
|
||||||
@@ -218,6 +220,25 @@ final class PhabricatorPolicy
|
|||||||
PhabricatorUser $viewer,
|
PhabricatorUser $viewer,
|
||||||
$policy) {
|
$policy) {
|
||||||
|
|
||||||
|
$type = phid_get_type($policy);
|
||||||
|
if ($type === PhabricatorProjectProjectPHIDType::TYPECONST) {
|
||||||
|
$handle = id(new PhabricatorHandleQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($policy))
|
||||||
|
->executeOne();
|
||||||
|
|
||||||
|
return pht(
|
||||||
|
'Members of the project "%s" can take this action.',
|
||||||
|
$handle->getFullName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::getOpaquePolicyExplanation($viewer, $policy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getOpaquePolicyExplanation(
|
||||||
|
PhabricatorUser $viewer,
|
||||||
|
$policy) {
|
||||||
|
|
||||||
$rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy);
|
$rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy);
|
||||||
if ($rule) {
|
if ($rule) {
|
||||||
return $rule->getPolicyExplanation();
|
return $rule->getPolicyExplanation();
|
||||||
@@ -243,7 +264,9 @@ final class PhabricatorPolicy
|
|||||||
$type = phid_get_type($policy);
|
$type = phid_get_type($policy);
|
||||||
if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) {
|
if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) {
|
||||||
return pht(
|
return pht(
|
||||||
'Members of the project "%s" can take this action.',
|
'Members of a particular project can take this action. (You '.
|
||||||
|
'can not see this object, so the name of this project is '.
|
||||||
|
'restricted.)',
|
||||||
$handle->getFullName());
|
$handle->getFullName());
|
||||||
} else if ($type == PhabricatorPeopleUserPHIDType::TYPECONST) {
|
} else if ($type == PhabricatorPeopleUserPHIDType::TYPECONST) {
|
||||||
return pht(
|
return pht(
|
||||||
@@ -274,45 +297,22 @@ final class PhabricatorPolicy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function renderDescription($icon = false) {
|
public function newRef(PhabricatorUser $viewer) {
|
||||||
$img = null;
|
return id(new PhabricatorPolicyRef())
|
||||||
if ($icon) {
|
->setViewer($viewer)
|
||||||
$img = id(new PHUIIconView())
|
->setPolicy($this);
|
||||||
->setIcon($this->getIcon());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->getHref()) {
|
public function isProjectPolicy() {
|
||||||
$desc = javelin_tag(
|
return ($this->getType() === PhabricatorPolicyType::TYPE_PROJECT);
|
||||||
'a',
|
|
||||||
array(
|
|
||||||
'href' => $this->getHref(),
|
|
||||||
'class' => 'policy-link',
|
|
||||||
'sigil' => $this->getWorkflow() ? 'workflow' : null,
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
$img,
|
|
||||||
$this->getName(),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
if ($img) {
|
|
||||||
$desc = array($img, $this->getName());
|
|
||||||
} else {
|
|
||||||
$desc = $this->getName();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($this->getType()) {
|
public function isCustomPolicy() {
|
||||||
case PhabricatorPolicyType::TYPE_PROJECT:
|
return ($this->getType() === PhabricatorPolicyType::TYPE_CUSTOM);
|
||||||
return pht('%s (Project)', $desc);
|
|
||||||
case PhabricatorPolicyType::TYPE_CUSTOM:
|
|
||||||
return $desc;
|
|
||||||
case PhabricatorPolicyType::TYPE_MASKED:
|
|
||||||
return pht(
|
|
||||||
'%s (You do not have permission to view policy details.)',
|
|
||||||
$desc);
|
|
||||||
default:
|
|
||||||
return $desc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isMaskedPolicy() {
|
||||||
|
return ($this->getType() === PhabricatorPolicyType::TYPE_MASKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -93,6 +93,16 @@ final class PHUIPolicySectionView
|
|||||||
return $this->appendChild(phutil_tag('p', array(), $content));
|
return $this->appendChild(phutil_tag('p', array(), $content));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function appendRulesView(PhabricatorPolicyRulesView $rules_view) {
|
||||||
|
return $this->appendChild(
|
||||||
|
phutil_tag(
|
||||||
|
'div',
|
||||||
|
array(
|
||||||
|
'class' => 'phui-policy-section-view-rules',
|
||||||
|
),
|
||||||
|
$rules_view));
|
||||||
|
}
|
||||||
|
|
||||||
protected function getTagAttributes() {
|
protected function getTagAttributes() {
|
||||||
return array(
|
return array(
|
||||||
'class' => 'phui-policy-section-view',
|
'class' => 'phui-policy-section-view',
|
||||||
@@ -100,7 +110,7 @@ final class PHUIPolicySectionView
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function getTagContent() {
|
protected function getTagContent() {
|
||||||
require_celerity_resource('phui-header-view-css');
|
require_celerity_resource('phui-policy-section-view-css');
|
||||||
|
|
||||||
$icon_view = null;
|
$icon_view = null;
|
||||||
$icon = $this->getIcon();
|
$icon = $this->getIcon();
|
||||||
|
|||||||
99
src/applications/policy/view/PhabricatorPolicyRef.php
Normal file
99
src/applications/policy/view/PhabricatorPolicyRef.php
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorPolicyRef
|
||||||
|
extends Phobject {
|
||||||
|
|
||||||
|
private $viewer;
|
||||||
|
private $policy;
|
||||||
|
|
||||||
|
public function setViewer(PhabricatorUser $viewer) {
|
||||||
|
$this->viewer = $viewer;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getViewer() {
|
||||||
|
return $this->viewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPolicy(PhabricatorPolicy $policy) {
|
||||||
|
$this->policy = $policy;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPolicy() {
|
||||||
|
return $this->policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPolicyDisplayName() {
|
||||||
|
$policy = $this->getPolicy();
|
||||||
|
return $policy->getFullName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newTransactionLink(
|
||||||
|
$mode,
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
|
||||||
|
$policy = $this->getPolicy();
|
||||||
|
|
||||||
|
if ($policy->isCustomPolicy()) {
|
||||||
|
$uri = urisprintf(
|
||||||
|
'/transactions/%s/%s/',
|
||||||
|
$mode,
|
||||||
|
$xaction->getPHID());
|
||||||
|
$workflow = true;
|
||||||
|
} else {
|
||||||
|
$uri = $policy->getHref();
|
||||||
|
$workflow = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->newLink($uri, $workflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newCapabilityLink($object, $capability) {
|
||||||
|
$policy = $this->getPolicy();
|
||||||
|
|
||||||
|
$uri = urisprintf(
|
||||||
|
'/policy/explain/%s/%s/',
|
||||||
|
$object->getPHID(),
|
||||||
|
$capability);
|
||||||
|
|
||||||
|
return $this->newLink($uri, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function newLink($uri, $workflow) {
|
||||||
|
$policy = $this->getPolicy();
|
||||||
|
$name = $policy->getName();
|
||||||
|
|
||||||
|
if ($uri !== null) {
|
||||||
|
$name = javelin_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => $uri,
|
||||||
|
'sigil' => ($workflow ? 'workflow' : null),
|
||||||
|
),
|
||||||
|
$name);
|
||||||
|
}
|
||||||
|
|
||||||
|
$hint = $this->getPolicyTypeHint();
|
||||||
|
if ($hint !== null) {
|
||||||
|
$name = pht('%s (%s)', $name, $hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPolicyTypeHint() {
|
||||||
|
$policy = $this->getPolicy();
|
||||||
|
|
||||||
|
if ($policy->isProjectPolicy()) {
|
||||||
|
return pht('Project');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($policy->isMaskedPolicy()) {
|
||||||
|
return pht('You do not have permission to view policy details.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
84
src/applications/policy/view/PhabricatorPolicyRulesView.php
Normal file
84
src/applications/policy/view/PhabricatorPolicyRulesView.php
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorPolicyRulesView
|
||||||
|
extends AphrontView {
|
||||||
|
|
||||||
|
private $policy;
|
||||||
|
|
||||||
|
public function setPolicy(PhabricatorPolicy $policy) {
|
||||||
|
$this->policy = $policy;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPolicy() {
|
||||||
|
return $this->policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$policy = $this->getPolicy();
|
||||||
|
|
||||||
|
require_celerity_resource('policy-transaction-detail-css');
|
||||||
|
|
||||||
|
$rule_objects = array();
|
||||||
|
foreach ($policy->getCustomRuleClasses() as $class) {
|
||||||
|
$rule_objects[$class] = newv($class, array());
|
||||||
|
}
|
||||||
|
|
||||||
|
$policy = clone $policy;
|
||||||
|
$policy->attachRuleObjects($rule_objects);
|
||||||
|
|
||||||
|
$details = array();
|
||||||
|
$details[] = phutil_tag(
|
||||||
|
'p',
|
||||||
|
array(
|
||||||
|
'class' => 'policy-transaction-detail-intro',
|
||||||
|
),
|
||||||
|
pht('These rules are processed in order:'));
|
||||||
|
|
||||||
|
foreach ($policy->getRules() as $index => $rule) {
|
||||||
|
$rule_object = $rule_objects[$rule['rule']];
|
||||||
|
if ($rule['action'] == 'allow') {
|
||||||
|
$icon = 'fa-check-circle green';
|
||||||
|
} else {
|
||||||
|
$icon = 'fa-minus-circle red';
|
||||||
|
}
|
||||||
|
$icon = id(new PHUIIconView())
|
||||||
|
->setIcon($icon)
|
||||||
|
->setText(
|
||||||
|
ucfirst($rule['action']).' '.$rule_object->getRuleDescription());
|
||||||
|
|
||||||
|
$handle_phids = $rule_object->getRequiredHandlePHIDsForSummary(
|
||||||
|
$rule['value']);
|
||||||
|
if ($handle_phids) {
|
||||||
|
$value = $this->getViewer()
|
||||||
|
->renderHandleList($handle_phids)
|
||||||
|
->setAsInline(true);
|
||||||
|
} else {
|
||||||
|
$value = $rule['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$details[] = phutil_tag('div',
|
||||||
|
array(
|
||||||
|
'class' => 'policy-transaction-detail-row',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
$icon,
|
||||||
|
$value,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$details[] = phutil_tag(
|
||||||
|
'p',
|
||||||
|
array(
|
||||||
|
'class' => 'policy-transaction-detail-end',
|
||||||
|
),
|
||||||
|
pht(
|
||||||
|
'If no rules match, %s all other users.',
|
||||||
|
phutil_tag('b',
|
||||||
|
array(),
|
||||||
|
$policy->getDefaultAction())));
|
||||||
|
|
||||||
|
return $details;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorProjectActivityChartEngine
|
||||||
|
extends PhabricatorChartEngine {
|
||||||
|
|
||||||
|
const CHARTENGINEKEY = 'project.activity';
|
||||||
|
|
||||||
|
public function setProjects(array $projects) {
|
||||||
|
assert_instances_of($projects, 'PhabricatorProject');
|
||||||
|
$project_phids = mpull($projects, 'getPHID');
|
||||||
|
return $this->setEngineParameter('projectPHIDs', $project_phids);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newChart(PhabricatorFactChart $chart, array $map) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$map = $map + array(
|
||||||
|
'projectPHIDs' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($map['projectPHIDs']) {
|
||||||
|
$projects = id(new PhabricatorProjectQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs($map['projectPHIDs'])
|
||||||
|
->execute();
|
||||||
|
$project_phids = mpull($projects, 'getPHID');
|
||||||
|
} else {
|
||||||
|
$project_phids = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$project_phid = head($project_phids);
|
||||||
|
|
||||||
|
$functions = array();
|
||||||
|
$stacks = array();
|
||||||
|
|
||||||
|
$function = $this->newFunction(
|
||||||
|
array(
|
||||||
|
'accumulate',
|
||||||
|
array(
|
||||||
|
'compose',
|
||||||
|
array('fact', 'tasks.open-count.assign.project', $project_phid),
|
||||||
|
array('min', 0),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
$function->getFunctionLabel()
|
||||||
|
->setKey('moved-in')
|
||||||
|
->setName(pht('Tasks Moved Into Project'))
|
||||||
|
->setColor('rgba(128, 128, 200, 1)')
|
||||||
|
->setFillColor('rgba(128, 128, 200, 0.15)');
|
||||||
|
|
||||||
|
$functions[] = $function;
|
||||||
|
|
||||||
|
$function = $this->newFunction(
|
||||||
|
array(
|
||||||
|
'accumulate',
|
||||||
|
array(
|
||||||
|
'compose',
|
||||||
|
array('fact', 'tasks.open-count.status.project', $project_phid),
|
||||||
|
array('min', 0),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
$function->getFunctionLabel()
|
||||||
|
->setKey('reopened')
|
||||||
|
->setName(pht('Tasks Reopened'))
|
||||||
|
->setColor('rgba(128, 128, 200, 1)')
|
||||||
|
->setFillColor('rgba(128, 128, 200, 0.15)');
|
||||||
|
|
||||||
|
$functions[] = $function;
|
||||||
|
|
||||||
|
$function = $this->newFunction(
|
||||||
|
array(
|
||||||
|
'accumulate',
|
||||||
|
array('fact', 'tasks.open-count.create.project', $project_phid),
|
||||||
|
));
|
||||||
|
|
||||||
|
$function->getFunctionLabel()
|
||||||
|
->setKey('created')
|
||||||
|
->setName(pht('Tasks Created'))
|
||||||
|
->setColor('rgba(0, 0, 200, 1)')
|
||||||
|
->setFillColor('rgba(0, 0, 200, 0.15)');
|
||||||
|
|
||||||
|
$functions[] = $function;
|
||||||
|
|
||||||
|
$function = $this->newFunction(
|
||||||
|
array(
|
||||||
|
'accumulate',
|
||||||
|
array(
|
||||||
|
'compose',
|
||||||
|
array('fact', 'tasks.open-count.status.project', $project_phid),
|
||||||
|
array('max', 0),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
$function->getFunctionLabel()
|
||||||
|
->setKey('closed')
|
||||||
|
->setName(pht('Tasks Closed'))
|
||||||
|
->setColor('rgba(0, 200, 0, 1)')
|
||||||
|
->setFillColor('rgba(0, 200, 0, 0.15)');
|
||||||
|
|
||||||
|
$functions[] = $function;
|
||||||
|
|
||||||
|
$function = $this->newFunction(
|
||||||
|
array(
|
||||||
|
'accumulate',
|
||||||
|
array(
|
||||||
|
'compose',
|
||||||
|
array('fact', 'tasks.open-count.assign.project', $project_phid),
|
||||||
|
array('max', 0),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
$function->getFunctionLabel()
|
||||||
|
->setKey('moved-out')
|
||||||
|
->setName(pht('Tasks Moved Out of Project'))
|
||||||
|
->setColor('rgba(128, 200, 128, 1)')
|
||||||
|
->setFillColor('rgba(128, 200, 128, 0.15)');
|
||||||
|
|
||||||
|
$functions[] = $function;
|
||||||
|
|
||||||
|
$stacks[] = array('created', 'reopened', 'moved-in');
|
||||||
|
$stacks[] = array('closed', 'moved-out');
|
||||||
|
|
||||||
|
$datasets = array();
|
||||||
|
|
||||||
|
$dataset = id(new PhabricatorChartStackedAreaDataset())
|
||||||
|
->setFunctions($functions)
|
||||||
|
->setStacks($stacks);
|
||||||
|
|
||||||
|
$datasets[] = $dataset;
|
||||||
|
$chart->attachDatasets($datasets);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -30,97 +30,78 @@ final class PhabricatorProjectBurndownChartEngine
|
|||||||
|
|
||||||
$functions = array();
|
$functions = array();
|
||||||
if ($project_phids) {
|
if ($project_phids) {
|
||||||
foreach ($project_phids as $project_phid) {
|
$open_function = $this->newFunction(
|
||||||
$function = $this->newFunction(
|
|
||||||
'min',
|
|
||||||
array(
|
array(
|
||||||
'accumulate',
|
'accumulate',
|
||||||
array('fact', 'tasks.open-count.assign.project', $project_phid),
|
|
||||||
),
|
|
||||||
0);
|
|
||||||
|
|
||||||
$function->getFunctionLabel()
|
|
||||||
->setName(pht('Tasks Moved Into Project'))
|
|
||||||
->setColor('rgba(0, 200, 200, 1)')
|
|
||||||
->setFillColor('rgba(0, 200, 200, 0.15)');
|
|
||||||
|
|
||||||
$functions[] = $function;
|
|
||||||
|
|
||||||
$function = $this->newFunction(
|
|
||||||
'min',
|
|
||||||
array(
|
array(
|
||||||
'accumulate',
|
|
||||||
array('fact', 'tasks.open-count.status.project', $project_phid),
|
|
||||||
),
|
|
||||||
0);
|
|
||||||
|
|
||||||
$function->getFunctionLabel()
|
|
||||||
->setName(pht('Tasks Reopened'))
|
|
||||||
->setColor('rgba(200, 0, 200, 1)')
|
|
||||||
->setFillColor('rgba(200, 0, 200, 0.15)');
|
|
||||||
|
|
||||||
$functions[] = $function;
|
|
||||||
|
|
||||||
$function = $this->newFunction(
|
|
||||||
'sum',
|
'sum',
|
||||||
array(
|
$this->newFactSum(
|
||||||
'accumulate',
|
'tasks.open-count.create.project', $project_phids),
|
||||||
array('fact', 'tasks.open-count.create.project', $project_phid),
|
$this->newFactSum(
|
||||||
|
'tasks.open-count.status.project', $project_phids),
|
||||||
|
$this->newFactSum(
|
||||||
|
'tasks.open-count.assign.project', $project_phids),
|
||||||
),
|
),
|
||||||
array(
|
|
||||||
'max',
|
|
||||||
array(
|
|
||||||
'accumulate',
|
|
||||||
array('fact', 'tasks.open-count.status.project', $project_phid),
|
|
||||||
),
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
'max',
|
|
||||||
array(
|
|
||||||
'accumulate',
|
|
||||||
array('fact', 'tasks.open-count.assign.project', $project_phid),
|
|
||||||
),
|
|
||||||
0,
|
|
||||||
));
|
));
|
||||||
|
|
||||||
$function->getFunctionLabel()
|
$closed_function = $this->newFunction(
|
||||||
->setName(pht('Tasks Created'))
|
array(
|
||||||
|
'accumulate',
|
||||||
|
$this->newFactSum('tasks.open-count.status.project', $project_phids),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
$open_function = $this->newFunction(
|
||||||
|
array(
|
||||||
|
'accumulate',
|
||||||
|
array('fact', 'tasks.open-count.create'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$closed_function = $this->newFunction(
|
||||||
|
array(
|
||||||
|
'accumulate',
|
||||||
|
array('fact', 'tasks.open-count.status'),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$open_function->getFunctionLabel()
|
||||||
|
->setKey('open')
|
||||||
|
->setName(pht('Open Tasks'))
|
||||||
->setColor('rgba(0, 0, 200, 1)')
|
->setColor('rgba(0, 0, 200, 1)')
|
||||||
->setFillColor('rgba(0, 0, 200, 0.15)');
|
->setFillColor('rgba(0, 0, 200, 0.15)');
|
||||||
|
|
||||||
$functions[] = $function;
|
$closed_function->getFunctionLabel()
|
||||||
}
|
->setKey('closed')
|
||||||
} else {
|
->setName(pht('Closed Tasks'))
|
||||||
$function = $this->newFunction(
|
->setColor('rgba(0, 200, 0, 1)')
|
||||||
'accumulate',
|
->setFillColor('rgba(0, 200, 0, 0.15)');
|
||||||
array('fact', 'tasks.open-count.create'));
|
|
||||||
|
|
||||||
$function->getFunctionLabel()
|
|
||||||
->setName(pht('Tasks Created'))
|
|
||||||
->setColor('rgba(0, 200, 200, 1)')
|
|
||||||
->setFillColor('rgba(0, 200, 200, 0.15)');
|
|
||||||
|
|
||||||
$functions[] = $function;
|
|
||||||
|
|
||||||
$function = $this->newFunction(
|
|
||||||
'accumulate',
|
|
||||||
array('fact', 'tasks.open-count.status'));
|
|
||||||
|
|
||||||
$function->getFunctionLabel()
|
|
||||||
->setName(pht('Tasks Closed / Reopened'))
|
|
||||||
->setColor('rgba(200, 0, 200, 1)')
|
|
||||||
->setFillColor('rgba(200, 0, 200, 0.15)');
|
|
||||||
|
|
||||||
$functions[] = $function;
|
|
||||||
}
|
|
||||||
|
|
||||||
$datasets = array();
|
$datasets = array();
|
||||||
|
|
||||||
$datasets[] = id(new PhabricatorChartStackedAreaDataset())
|
$dataset = id(new PhabricatorChartStackedAreaDataset())
|
||||||
->setFunctions($functions);
|
->setFunctions(
|
||||||
|
array(
|
||||||
|
$open_function,
|
||||||
|
$closed_function,
|
||||||
|
))
|
||||||
|
->setStacks(
|
||||||
|
array(
|
||||||
|
array('open'),
|
||||||
|
array('closed'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$datasets[] = $dataset;
|
||||||
$chart->attachDatasets($datasets);
|
$chart->attachDatasets($datasets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function newFactSum($fact_key, array $phids) {
|
||||||
|
$result = array();
|
||||||
|
$result[] = 'sum';
|
||||||
|
|
||||||
|
foreach ($phids as $phid) {
|
||||||
|
$result[] = array('fact', $fact_key, $phid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,10 +44,24 @@ final class PhabricatorProjectReportsController
|
|||||||
->setParentPanelPHIDs(array())
|
->setParentPanelPHIDs(array())
|
||||||
->renderPanel();
|
->renderPanel();
|
||||||
|
|
||||||
|
$activity_panel = id(new PhabricatorProjectActivityChartEngine())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setProjects(array($project))
|
||||||
|
->buildChartPanel();
|
||||||
|
|
||||||
|
$activity_panel->setName(pht('%s: Activity', $project->getName()));
|
||||||
|
|
||||||
|
$activity_view = id(new PhabricatorDashboardPanelRenderingEngine())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setPanel($activity_panel)
|
||||||
|
->setParentPanelPHIDs(array())
|
||||||
|
->renderPanel();
|
||||||
|
|
||||||
$view = id(new PHUITwoColumnView())
|
$view = id(new PHUITwoColumnView())
|
||||||
->setFooter(
|
->setFooter(
|
||||||
array(
|
array(
|
||||||
$chart_view,
|
$chart_view,
|
||||||
|
$activity_view,
|
||||||
));
|
));
|
||||||
|
|
||||||
return $this->newPage()
|
return $this->newPage()
|
||||||
|
|||||||
@@ -904,7 +904,8 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||||||
|
|
||||||
public function newEditEngineSubtypeMap() {
|
public function newEditEngineSubtypeMap() {
|
||||||
$config = PhabricatorEnv::getEnvConfig('projects.subtypes');
|
$config = PhabricatorEnv::getEnvConfig('projects.subtypes');
|
||||||
return PhabricatorEditEngineSubtype::newSubtypeMap($config);
|
return PhabricatorEditEngineSubtype::newSubtypeMap($config)
|
||||||
|
->setDatasource(new PhabricatorProjectSubtypeDatasource());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function newSubtypeObject() {
|
public function newSubtypeObject() {
|
||||||
|
|||||||
@@ -1842,6 +1842,20 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||||||
PhabricatorUser $viewer,
|
PhabricatorUser $viewer,
|
||||||
array $options) {
|
array $options) {
|
||||||
|
|
||||||
|
$refs = $this->getAlmanacServiceRefs($viewer, $options);
|
||||||
|
|
||||||
|
if (!$refs) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ref = head($refs);
|
||||||
|
return $ref->getURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAlmanacServiceRefs(
|
||||||
|
PhabricatorUser $viewer,
|
||||||
|
array $options) {
|
||||||
|
|
||||||
PhutilTypeSpec::checkMap(
|
PhutilTypeSpec::checkMap(
|
||||||
$options,
|
$options,
|
||||||
array(
|
array(
|
||||||
@@ -1856,7 +1870,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||||||
|
|
||||||
$cache_key = $this->getAlmanacServiceCacheKey();
|
$cache_key = $this->getAlmanacServiceCacheKey();
|
||||||
if (!$cache_key) {
|
if (!$cache_key) {
|
||||||
return null;
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
$cache = PhabricatorCaches::getMutableStructureCache();
|
$cache = PhabricatorCaches::getMutableStructureCache();
|
||||||
@@ -1869,7 +1883,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($uris === null) {
|
if ($uris === null) {
|
||||||
return null;
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
$local_device = AlmanacKeys::getDeviceID();
|
$local_device = AlmanacKeys::getDeviceID();
|
||||||
@@ -1893,7 +1907,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||||||
|
|
||||||
if ($local_device && $never_proxy) {
|
if ($local_device && $never_proxy) {
|
||||||
if ($uri['device'] == $local_device) {
|
if ($uri['device'] == $local_device) {
|
||||||
return null;
|
return array();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1954,15 +1968,20 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$refs = array();
|
||||||
|
foreach ($results as $result) {
|
||||||
|
$refs[] = DiffusionServiceRef::newFromDictionary($result);
|
||||||
|
}
|
||||||
|
|
||||||
// If we require a writable device, remove URIs which aren't writable.
|
// If we require a writable device, remove URIs which aren't writable.
|
||||||
if ($writable) {
|
if ($writable) {
|
||||||
foreach ($results as $key => $uri) {
|
foreach ($refs as $key => $ref) {
|
||||||
if (!$uri['writable']) {
|
if (!$ref->isWritable()) {
|
||||||
unset($results[$key]);
|
unset($results[$key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$results) {
|
if (!$refs) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
'This repository ("%s") is not writable with the given '.
|
'This repository ("%s") is not writable with the given '.
|
||||||
@@ -1974,23 +1993,30 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($writable) {
|
if ($writable) {
|
||||||
$results = $this->sortWritableAlmanacServiceURIs($results);
|
$refs = $this->sortWritableAlmanacServiceRefs($refs);
|
||||||
} else {
|
} else {
|
||||||
shuffle($results);
|
$refs = $this->sortReadableAlmanacServiceRefs($refs);
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = head($results);
|
return array_values($refs);
|
||||||
return $result['uri'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sortWritableAlmanacServiceURIs(array $results) {
|
private function sortReadableAlmanacServiceRefs(array $refs) {
|
||||||
|
assert_instances_of($refs, 'DiffusionServiceRef');
|
||||||
|
shuffle($refs);
|
||||||
|
return $refs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sortWritableAlmanacServiceRefs(array $refs) {
|
||||||
|
assert_instances_of($refs, 'DiffusionServiceRef');
|
||||||
|
|
||||||
// See T13109 for discussion of how this method routes requests.
|
// See T13109 for discussion of how this method routes requests.
|
||||||
|
|
||||||
// In the absence of other rules, we'll send traffic to devices randomly.
|
// In the absence of other rules, we'll send traffic to devices randomly.
|
||||||
// We also want to select randomly among nodes which are equally good
|
// We also want to select randomly among nodes which are equally good
|
||||||
// candidates to receive the write, and accomplish that by shuffling the
|
// candidates to receive the write, and accomplish that by shuffling the
|
||||||
// list up front.
|
// list up front.
|
||||||
shuffle($results);
|
shuffle($refs);
|
||||||
|
|
||||||
$order = array();
|
$order = array();
|
||||||
|
|
||||||
@@ -2002,8 +2028,8 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||||||
$this->getPHID());
|
$this->getPHID());
|
||||||
if ($writer) {
|
if ($writer) {
|
||||||
$device_phid = $writer->getWriteProperty('devicePHID');
|
$device_phid = $writer->getWriteProperty('devicePHID');
|
||||||
foreach ($results as $key => $result) {
|
foreach ($refs as $key => $ref) {
|
||||||
if ($result['devicePHID'] === $device_phid) {
|
if ($ref->getDevicePHID() === $device_phid) {
|
||||||
$order[] = $key;
|
$order[] = $key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2025,8 +2051,8 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||||||
}
|
}
|
||||||
$max_devices = array_fuse($max_devices);
|
$max_devices = array_fuse($max_devices);
|
||||||
|
|
||||||
foreach ($results as $key => $result) {
|
foreach ($refs as $key => $ref) {
|
||||||
if (isset($max_devices[$result['devicePHID']])) {
|
if (isset($max_devices[$ref->getDevicePHID()])) {
|
||||||
$order[] = $key;
|
$order[] = $key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2034,9 +2060,9 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||||||
|
|
||||||
// Reorder the results, putting any we've selected as preferred targets for
|
// Reorder the results, putting any we've selected as preferred targets for
|
||||||
// the write at the head of the list.
|
// the write at the head of the list.
|
||||||
$results = array_select_keys($results, $order) + $results;
|
$refs = array_select_keys($refs, $order) + $refs;
|
||||||
|
|
||||||
return $results;
|
return $refs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function supportsSynchronization() {
|
public function supportsSynchronization() {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorSearchSettingsPanel
|
||||||
|
extends PhabricatorEditEngineSettingsPanel {
|
||||||
|
|
||||||
|
const PANELKEY = 'search';
|
||||||
|
|
||||||
|
public function getPanelName() {
|
||||||
|
return pht('Search');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPanelMenuIcon() {
|
||||||
|
return 'fa-search';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPanelGroupKey() {
|
||||||
|
return PhabricatorSettingsApplicationsPanelGroup::PANELGROUPKEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isTemplatePanel() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isUserPanel() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PhabricatorSearchScopeSetting
|
final class PhabricatorSearchScopeSetting
|
||||||
extends PhabricatorInternalSetting {
|
extends PhabricatorSelectSetting {
|
||||||
|
|
||||||
const SETTINGKEY = 'search-scope';
|
const SETTINGKEY = 'search-scope';
|
||||||
|
|
||||||
@@ -9,8 +9,34 @@ final class PhabricatorSearchScopeSetting
|
|||||||
return pht('Search Scope');
|
return pht('Search Scope');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSettingPanelKey() {
|
||||||
|
return PhabricatorSearchSettingsPanel::PANELKEY;
|
||||||
|
}
|
||||||
|
|
||||||
public function getSettingDefaultValue() {
|
public function getSettingDefaultValue() {
|
||||||
return 'all';
|
return 'all';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getControlInstructions() {
|
||||||
|
return pht(
|
||||||
|
'Choose the default behavior of the global search in the main menu.');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSelectOptions() {
|
||||||
|
$scopes = PhabricatorMainMenuSearchView::getGlobalSearchScopeItems(
|
||||||
|
$this->getViewer(),
|
||||||
|
new PhabricatorSettingsApplication(),
|
||||||
|
$only_global = true);
|
||||||
|
|
||||||
|
$scope_map = array();
|
||||||
|
foreach ($scopes as $scope) {
|
||||||
|
if (!isset($scope['value'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$scope_map[$scope['value']] = $scope['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $scope_map;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,14 +80,6 @@ final class PhabricatorSpacesViewController
|
|||||||
? pht('Yes')
|
? pht('Yes')
|
||||||
: pht('No'));
|
: pht('No'));
|
||||||
|
|
||||||
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
|
|
||||||
$viewer,
|
|
||||||
$space);
|
|
||||||
|
|
||||||
$list->addProperty(
|
|
||||||
pht('Editable By'),
|
|
||||||
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
|
|
||||||
|
|
||||||
$description = $space->getDescription();
|
$description = $space->getDescription();
|
||||||
if (strlen($description)) {
|
if (strlen($description)) {
|
||||||
$description = new PHUIRemarkupView($viewer, $description);
|
$description = new PHUIRemarkupView($viewer, $description);
|
||||||
|
|||||||
@@ -100,32 +100,34 @@ final class PhabricatorSystemActionEngine extends Phobject {
|
|||||||
|
|
||||||
$actor_hashes = array();
|
$actor_hashes = array();
|
||||||
foreach ($actors as $actor) {
|
foreach ($actors as $actor) {
|
||||||
$actor_hashes[] = PhabricatorHash::digestForIndex($actor);
|
$digest = PhabricatorHash::digestForIndex($actor);
|
||||||
|
$actor_hashes[$digest] = $actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
$log = new PhabricatorSystemActionLog();
|
$log = new PhabricatorSystemActionLog();
|
||||||
|
|
||||||
$window = self::getWindow();
|
$window = self::getWindow();
|
||||||
|
|
||||||
$conn_r = $log->establishConnection('r');
|
$conn = $log->establishConnection('r');
|
||||||
$scores = queryfx_all(
|
|
||||||
$conn_r,
|
$rows = queryfx_all(
|
||||||
'SELECT actorIdentity, SUM(score) totalScore FROM %T
|
$conn,
|
||||||
|
'SELECT actorHash, SUM(score) totalScore FROM %T
|
||||||
WHERE action = %s AND actorHash IN (%Ls)
|
WHERE action = %s AND actorHash IN (%Ls)
|
||||||
AND epoch >= %d GROUP BY actorHash',
|
AND epoch >= %d GROUP BY actorHash',
|
||||||
$log->getTableName(),
|
$log->getTableName(),
|
||||||
$action->getActionConstant(),
|
$action->getActionConstant(),
|
||||||
$actor_hashes,
|
array_keys($actor_hashes),
|
||||||
(time() - $window));
|
(PhabricatorTime::getNow() - $window));
|
||||||
|
|
||||||
$scores = ipull($scores, 'totalScore', 'actorIdentity');
|
$rows = ipull($rows, 'totalScore', 'actorHash');
|
||||||
|
|
||||||
foreach ($scores as $key => $score) {
|
$scores = array();
|
||||||
$scores[$key] = $score / $window;
|
foreach ($actor_hashes as $digest => $actor) {
|
||||||
|
$score = idx($rows, $digest, 0);
|
||||||
|
$scores[$actor] = ($score / $window);
|
||||||
}
|
}
|
||||||
|
|
||||||
$scores = $scores + array_fill_keys($actors, 0);
|
|
||||||
|
|
||||||
return $scores;
|
return $scores;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ final class PhabricatorApplicationTransactionValueController
|
|||||||
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
||||||
case PhabricatorTransactions::TYPE_JOIN_POLICY:
|
case PhabricatorTransactions::TYPE_JOIN_POLICY:
|
||||||
case PhabricatorRepositoryPushPolicyTransaction::TRANSACTIONTYPE:
|
case PhabricatorRepositoryPushPolicyTransaction::TRANSACTIONTYPE:
|
||||||
|
case PhabricatorApplicationPolicyChangeTransaction::TRANSACTIONTYPE:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
@@ -57,89 +58,16 @@ final class PhabricatorApplicationTransactionValueController
|
|||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
$rule_objects = array();
|
$rules_view = id(new PhabricatorPolicyRulesView())
|
||||||
foreach ($policy->getCustomRuleClasses() as $class) {
|
->setViewer($viewer)
|
||||||
$rule_objects[$class] = newv($class, array());
|
->setPolicy($policy);
|
||||||
}
|
|
||||||
$policy->attachRuleObjects($rule_objects);
|
|
||||||
|
|
||||||
$this->requireResource('policy-transaction-detail-css');
|
|
||||||
$cancel_uri = $this->guessCancelURI($viewer, $xaction);
|
$cancel_uri = $this->guessCancelURI($viewer, $xaction);
|
||||||
|
|
||||||
return $this->newDialog()
|
return $this->newDialog()
|
||||||
->setTitle($policy->getFullName())
|
->setTitle($policy->getFullName())
|
||||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||||
->appendChild($this->renderPolicyDetails($policy, $rule_objects))
|
->appendChild($rules_view)
|
||||||
->addCancelButton($cancel_uri, pht('Close'));
|
->addCancelButton($cancel_uri, pht('Close'));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function extractPHIDs(
|
|
||||||
PhabricatorPolicy $policy,
|
|
||||||
array $rule_objects) {
|
|
||||||
|
|
||||||
$phids = array();
|
|
||||||
foreach ($policy->getRules() as $rule) {
|
|
||||||
$rule_object = $rule_objects[$rule['rule']];
|
|
||||||
$phids[] =
|
|
||||||
$rule_object->getRequiredHandlePHIDsForSummary($rule['value']);
|
|
||||||
}
|
|
||||||
return array_filter(array_mergev($phids));
|
|
||||||
}
|
|
||||||
|
|
||||||
private function renderPolicyDetails(
|
|
||||||
PhabricatorPolicy $policy,
|
|
||||||
array $rule_objects) {
|
|
||||||
$details = array();
|
|
||||||
$details[] = phutil_tag(
|
|
||||||
'p',
|
|
||||||
array(
|
|
||||||
'class' => 'policy-transaction-detail-intro',
|
|
||||||
),
|
|
||||||
pht('These rules are processed in order:'));
|
|
||||||
|
|
||||||
foreach ($policy->getRules() as $index => $rule) {
|
|
||||||
$rule_object = $rule_objects[$rule['rule']];
|
|
||||||
if ($rule['action'] == 'allow') {
|
|
||||||
$icon = 'fa-check-circle green';
|
|
||||||
} else {
|
|
||||||
$icon = 'fa-minus-circle red';
|
|
||||||
}
|
|
||||||
$icon = id(new PHUIIconView())
|
|
||||||
->setIcon($icon)
|
|
||||||
->setText(
|
|
||||||
ucfirst($rule['action']).' '.$rule_object->getRuleDescription());
|
|
||||||
|
|
||||||
$handle_phids = $rule_object->getRequiredHandlePHIDsForSummary(
|
|
||||||
$rule['value']);
|
|
||||||
if ($handle_phids) {
|
|
||||||
$value = $this->getViewer()
|
|
||||||
->renderHandleList($handle_phids)
|
|
||||||
->setAsInline(true);
|
|
||||||
} else {
|
|
||||||
$value = $rule['value'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$details[] = phutil_tag('div',
|
|
||||||
array(
|
|
||||||
'class' => 'policy-transaction-detail-row',
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
$icon,
|
|
||||||
$value,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$details[] = phutil_tag(
|
|
||||||
'p',
|
|
||||||
array(
|
|
||||||
'class' => 'policy-transaction-detail-end',
|
|
||||||
),
|
|
||||||
pht(
|
|
||||||
'If no rules match, %s all other users.',
|
|
||||||
phutil_tag('b',
|
|
||||||
array(),
|
|
||||||
$policy->getDefaultAction())));
|
|
||||||
return $details;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ final class PhabricatorEditEngineSubtype
|
|||||||
private $childSubtypes = array();
|
private $childSubtypes = array();
|
||||||
private $childIdentifiers = array();
|
private $childIdentifiers = array();
|
||||||
private $fieldConfiguration = array();
|
private $fieldConfiguration = array();
|
||||||
|
private $mutations;
|
||||||
|
|
||||||
public function setKey($key) {
|
public function setKey($key) {
|
||||||
$this->key = $key;
|
$this->key = $key;
|
||||||
@@ -78,6 +79,15 @@ final class PhabricatorEditEngineSubtype
|
|||||||
return $this->childIdentifiers;
|
return $this->childIdentifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setMutations($mutations) {
|
||||||
|
$this->mutations = $mutations;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMutations() {
|
||||||
|
return $this->mutations;
|
||||||
|
}
|
||||||
|
|
||||||
public function hasTagView() {
|
public function hasTagView() {
|
||||||
return (bool)strlen($this->getTagText());
|
return (bool)strlen($this->getTagText());
|
||||||
}
|
}
|
||||||
@@ -152,6 +162,7 @@ final class PhabricatorEditEngineSubtype
|
|||||||
'icon' => 'optional string',
|
'icon' => 'optional string',
|
||||||
'children' => 'optional map<string, wild>',
|
'children' => 'optional map<string, wild>',
|
||||||
'fields' => 'optional map<string, wild>',
|
'fields' => 'optional map<string, wild>',
|
||||||
|
'mutations' => 'optional list<string>',
|
||||||
));
|
));
|
||||||
|
|
||||||
$key = $value['key'];
|
$key = $value['key'];
|
||||||
@@ -217,6 +228,28 @@ final class PhabricatorEditEngineSubtype
|
|||||||
'with key "%s". This subtype is required and must be defined.',
|
'with key "%s". This subtype is required and must be defined.',
|
||||||
self::SUBTYPE_DEFAULT));
|
self::SUBTYPE_DEFAULT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($config as $value) {
|
||||||
|
$key = idx($value, 'key');
|
||||||
|
|
||||||
|
$mutations = idx($value, 'mutations');
|
||||||
|
if (!$mutations) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($mutations as $mutation) {
|
||||||
|
if (!isset($map[$mutation])) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Subtype configuration is invalid: subtype with key "%s" '.
|
||||||
|
'specifies that it can mutate into subtype "%s", but that is '.
|
||||||
|
'not a valid subtype.',
|
||||||
|
$key,
|
||||||
|
$mutation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function newSubtypeMap(array $config) {
|
public static function newSubtypeMap(array $config) {
|
||||||
@@ -267,6 +300,8 @@ final class PhabricatorEditEngineSubtype
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$subtype->setMutations(idx($entry, 'mutations'));
|
||||||
|
|
||||||
$map[$key] = $subtype;
|
$map[$key] = $subtype;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ final class PhabricatorEditEngineSubtypeMap
|
|||||||
extends Phobject {
|
extends Phobject {
|
||||||
|
|
||||||
private $subtypes;
|
private $subtypes;
|
||||||
|
private $datasource;
|
||||||
|
|
||||||
public function __construct(array $subtypes) {
|
public function __construct(array $subtypes) {
|
||||||
assert_instances_of($subtypes, 'PhabricatorEditEngineSubtype');
|
assert_instances_of($subtypes, 'PhabricatorEditEngineSubtype');
|
||||||
@@ -39,6 +40,57 @@ final class PhabricatorEditEngineSubtypeMap
|
|||||||
return $this->subtypes[$subtype_key];
|
return $this->subtypes[$subtype_key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setDatasource(PhabricatorTypeaheadDatasource $datasource) {
|
||||||
|
$this->datasource = $datasource;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newDatasource() {
|
||||||
|
if (!$this->datasource) {
|
||||||
|
throw new PhutilInvalidStateException('setDatasource');
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone($this->datasource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMutationMap($source_key) {
|
||||||
|
return mpull($this->getMutations($source_key), 'getName');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMutations($source_key) {
|
||||||
|
$mutations = $this->subtypes;
|
||||||
|
|
||||||
|
$subtype = idx($this->subtypes, $source_key);
|
||||||
|
if ($subtype) {
|
||||||
|
$map = $subtype->getMutations();
|
||||||
|
if ($map !== null) {
|
||||||
|
$map = array_fuse($map);
|
||||||
|
foreach ($mutations as $key => $mutation) {
|
||||||
|
if ($key === $source_key) {
|
||||||
|
// This is the current subtype, so we always want to show it.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($map[$key])) {
|
||||||
|
// This is an allowed mutation, so keep it.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard other subtypes as mutation options.
|
||||||
|
unset($mutations[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the only available mutation is the current subtype, treat this like
|
||||||
|
// no mutations are available.
|
||||||
|
if (array_keys($mutations) === array($source_key)) {
|
||||||
|
$mutations = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mutations;
|
||||||
|
}
|
||||||
|
|
||||||
public function getCreateFormsForSubtype(
|
public function getCreateFormsForSubtype(
|
||||||
PhabricatorEditEngine $edit_engine,
|
PhabricatorEditEngine $edit_engine,
|
||||||
PhabricatorEditEngineSubtypeInterface $object) {
|
PhabricatorEditEngineSubtypeInterface $object) {
|
||||||
|
|||||||
@@ -29,9 +29,17 @@ final class PhabricatorSubtypeEditEngineExtension
|
|||||||
PhabricatorApplicationTransactionInterface $object) {
|
PhabricatorApplicationTransactionInterface $object) {
|
||||||
|
|
||||||
$subtype_type = PhabricatorTransactions::TYPE_SUBTYPE;
|
$subtype_type = PhabricatorTransactions::TYPE_SUBTYPE;
|
||||||
|
$subtype_value = $object->getEditEngineSubtype();
|
||||||
|
|
||||||
$map = $object->newEditEngineSubtypeMap();
|
$map = $object->newEditEngineSubtypeMap();
|
||||||
|
|
||||||
|
if ($object->getID()) {
|
||||||
|
$options = $map->getMutationMap($subtype_value);
|
||||||
|
} else {
|
||||||
|
// NOTE: This is a crude proxy for "are we in the bulk edit workflow".
|
||||||
|
// We want to allow any mutation.
|
||||||
$options = $map->getDisplayMap();
|
$options = $map->getDisplayMap();
|
||||||
|
}
|
||||||
|
|
||||||
$subtype_field = id(new PhabricatorSelectEditField())
|
$subtype_field = id(new PhabricatorSelectEditField())
|
||||||
->setKey(self::EDITKEY)
|
->setKey(self::EDITKEY)
|
||||||
@@ -40,12 +48,12 @@ final class PhabricatorSubtypeEditEngineExtension
|
|||||||
->setTransactionType($subtype_type)
|
->setTransactionType($subtype_type)
|
||||||
->setConduitDescription(pht('Change the object subtype.'))
|
->setConduitDescription(pht('Change the object subtype.'))
|
||||||
->setConduitTypeDescription(pht('New object subtype key.'))
|
->setConduitTypeDescription(pht('New object subtype key.'))
|
||||||
->setValue($object->getEditEngineSubtype())
|
->setValue($subtype_value)
|
||||||
->setOptions($options);
|
->setOptions($options);
|
||||||
|
|
||||||
// If subtypes are configured, enable changing them from the bulk editor
|
// If subtypes are configured, enable changing them from the bulk editor.
|
||||||
// and comment action stack.
|
// Bulk editor
|
||||||
if ($map->getCount() > 1) {
|
if ($options) {
|
||||||
$subtype_field
|
$subtype_field
|
||||||
->setBulkEditLabel(pht('Change subtype to'))
|
->setBulkEditLabel(pht('Change subtype to'))
|
||||||
->setCommentActionLabel(pht('Change Subtype'))
|
->setCommentActionLabel(pht('Change Subtype'))
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorEditEngineSubtypeHeraldField
|
||||||
|
extends HeraldField {
|
||||||
|
|
||||||
|
const FIELDCONST = 'subtype';
|
||||||
|
|
||||||
|
public function getHeraldFieldName() {
|
||||||
|
return pht('Subtype');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFieldGroupKey() {
|
||||||
|
return HeraldSupportFieldGroup::FIELDGROUPKEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsObject($object) {
|
||||||
|
return ($object instanceof PhabricatorEditEngineSubtypeInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeraldFieldValue($object) {
|
||||||
|
return $object->getEditEngineSubtype();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getHeraldFieldStandardType() {
|
||||||
|
return self::STANDARD_PHID;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getDatasource() {
|
||||||
|
$object = $this->getAdapter()->getObject();
|
||||||
|
$map = $object->newEditEngineSubtypeMap();
|
||||||
|
return $map->newDatasource();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getDatasourceValueMap() {
|
||||||
|
$object = $this->getAdapter()->getObject();
|
||||||
|
$map = $object->newEditEngineSubtypeMap();
|
||||||
|
|
||||||
|
$result = array();
|
||||||
|
foreach ($map->getSubtypes() as $subtype) {
|
||||||
|
$result[$subtype->getKey()] = $subtype->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFieldAvailable() {
|
||||||
|
$object = $this->getAdapter()->getObject();
|
||||||
|
$map = $object->newEditEngineSubtypeMap();
|
||||||
|
return ($map->getCount() > 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -445,19 +445,15 @@ abstract class PhabricatorApplicationTransaction
|
|||||||
$policy = PhabricatorPolicy::newFromPolicyAndHandle(
|
$policy = PhabricatorPolicy::newFromPolicyAndHandle(
|
||||||
$phid,
|
$phid,
|
||||||
$this->getHandleIfExists($phid));
|
$this->getHandleIfExists($phid));
|
||||||
|
|
||||||
|
$ref = $policy->newRef($this->getViewer());
|
||||||
|
|
||||||
if ($this->renderingTarget == self::TARGET_HTML) {
|
if ($this->renderingTarget == self::TARGET_HTML) {
|
||||||
switch ($policy->getType()) {
|
$output = $ref->newTransactionLink($state, $this);
|
||||||
case PhabricatorPolicyType::TYPE_CUSTOM:
|
|
||||||
$policy->setHref('/transactions/'.$state.'/'.$this->getPHID().'/');
|
|
||||||
$policy->setWorkflow(true);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
$output = $policy->renderDescription();
|
|
||||||
} else {
|
} else {
|
||||||
$output = hsprintf('%s', $policy->getFullName());
|
$output = $ref->getPolicyDisplayName();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,6 +771,13 @@ abstract class PhabricatorApplicationTransaction
|
|||||||
case PhabricatorTransactions::TYPE_TOKEN:
|
case PhabricatorTransactions::TYPE_TOKEN:
|
||||||
case PhabricatorTransactions::TYPE_MFA:
|
case PhabricatorTransactions::TYPE_MFA:
|
||||||
return true;
|
return true;
|
||||||
|
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
|
||||||
|
// See T8952. When an application (usually Herald) modifies
|
||||||
|
// subscribers, this tends to be very uninteresting.
|
||||||
|
if ($this->isApplicationAuthor()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case PhabricatorTransactions::TYPE_EDGE:
|
case PhabricatorTransactions::TYPE_EDGE:
|
||||||
$edge_type = $this->getMetadataValue('edge:type');
|
$edge_type = $this->getMetadataValue('edge:type');
|
||||||
switch ($edge_type) {
|
switch ($edge_type) {
|
||||||
@@ -1387,12 +1390,6 @@ abstract class PhabricatorApplicationTransaction
|
|||||||
return 25;
|
return 25;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->isApplicationAuthor()) {
|
|
||||||
// When applications (most often: Herald) change subscriptions it
|
|
||||||
// is very uninteresting.
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In other cases, subscriptions are more interesting than comments
|
// In other cases, subscriptions are more interesting than comments
|
||||||
// (which are shown anyway) but less interesting than any other type of
|
// (which are shown anyway) but less interesting than any other type of
|
||||||
// transaction.
|
// transaction.
|
||||||
|
|||||||
@@ -215,17 +215,16 @@ abstract class PhabricatorModularTransactionType
|
|||||||
$phid,
|
$phid,
|
||||||
$handles[$phid]);
|
$handles[$phid]);
|
||||||
|
|
||||||
|
$ref = $policy->newRef($viewer);
|
||||||
|
|
||||||
if ($this->isTextMode()) {
|
if ($this->isTextMode()) {
|
||||||
return $this->renderValue($policy->getFullName());
|
$name = $ref->getPolicyDisplayName();
|
||||||
}
|
} else {
|
||||||
|
|
||||||
$storage = $this->getStorage();
|
$storage = $this->getStorage();
|
||||||
if ($policy->getType() == PhabricatorPolicyType::TYPE_CUSTOM) {
|
$name = $ref->newTransactionLink($mode, $storage);
|
||||||
$policy->setHref('/transactions/'.$mode.'/'.$storage->getPHID().'/');
|
|
||||||
$policy->setWorkflow(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->renderValue($policy->renderDescription());
|
return $this->renderValue($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
final protected function renderHandleList(array $phids) {
|
final protected function renderHandleList(array $phids) {
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ You can use a special preamble script to make arbitrary adjustments to the
|
|||||||
environment and some parts of Phabricator's configuration in order to fix these
|
environment and some parts of Phabricator's configuration in order to fix these
|
||||||
problems and set up the environment which Phabricator expects.
|
problems and set up the environment which Phabricator expects.
|
||||||
|
|
||||||
NOTE: This is an advanced feature. Most installs should not need to configure
|
|
||||||
a preamble script.
|
|
||||||
|
|
||||||
= Creating a Preamble Script =
|
Creating a Preamble Script
|
||||||
|
==========================
|
||||||
|
|
||||||
To create a preamble script, write a file to:
|
To create a preamble script, write a file to:
|
||||||
|
|
||||||
@@ -37,6 +36,7 @@ If present, this script will be executed at the very beginning of each web
|
|||||||
request, allowing you to adjust the environment. For common adjustments and
|
request, allowing you to adjust the environment. For common adjustments and
|
||||||
examples, see the next sections.
|
examples, see the next sections.
|
||||||
|
|
||||||
|
|
||||||
Adjusting Client IPs
|
Adjusting Client IPs
|
||||||
====================
|
====================
|
||||||
|
|
||||||
@@ -44,9 +44,15 @@ If your install is behind a load balancer, Phabricator may incorrectly detect
|
|||||||
all requests as originating from the load balancer, rather than from the
|
all requests as originating from the load balancer, rather than from the
|
||||||
correct client IPs.
|
correct client IPs.
|
||||||
|
|
||||||
If this is the case and some other header (like `X-Forwarded-For`) is known to
|
In common cases where networks are configured like this, the `X-Forwarded-For`
|
||||||
be trustworthy, you can read the header and overwrite the `REMOTE_ADDR` value
|
header will have trustworthy information about the real client IP. You
|
||||||
so Phabricator can figure out the client IP correctly.
|
can use the function `preamble_trust_x_forwarded_for_header()` in your
|
||||||
|
preamble to tell Phabricator that you expect to receive requests from a
|
||||||
|
load balancer or proxy which modifies this header:
|
||||||
|
|
||||||
|
```name="Trust X-Forwarded-For Header", lang=php
|
||||||
|
preamble_trust_x_forwarded_for_header();
|
||||||
|
```
|
||||||
|
|
||||||
You should do this //only// if the `X-Forwarded-For` header is known to be
|
You should do this //only// if the `X-Forwarded-For` header is known to be
|
||||||
trustworthy. In particular, if users can make requests to the web server
|
trustworthy. In particular, if users can make requests to the web server
|
||||||
@@ -54,30 +60,29 @@ directly, they can provide an arbitrary `X-Forwarded-For` header, and thereby
|
|||||||
spoof an arbitrary client IP.
|
spoof an arbitrary client IP.
|
||||||
|
|
||||||
The `X-Forwarded-For` header may also contain a list of addresses if a request
|
The `X-Forwarded-For` header may also contain a list of addresses if a request
|
||||||
has been forwarded through multiple loadbalancers. Using a snippet like this
|
has been forwarded through multiple load balancers. If you know that requests
|
||||||
will usually handle most situations correctly:
|
on your network are routed through `N` trustworthy devices, you can specify
|
||||||
|
that `N` to tell the function how many layers of `X-Forwarded-For` to discard:
|
||||||
|
|
||||||
|
```name="Trust X-Forwarded-For Header, Multiple Layers", lang=php
|
||||||
|
preamble_trust_x_forwarded_for_header(3);
|
||||||
```
|
```
|
||||||
name=Overwrite REMOTE_ADDR with X-Forwarded-For
|
|
||||||
<?php
|
|
||||||
|
|
||||||
// Overwrite REMOTE_ADDR with the value in the "X-Forwarded-For" HTTP header.
|
If you have an unusual network configuration (for example, the number of
|
||||||
|
trustworthy devices depends on the network path) you can also implement your
|
||||||
|
own logic.
|
||||||
|
|
||||||
// Only do this if you're certain the request is coming from a loadbalancer!
|
Note that this is very odd, advanced, and easy to get wrong. If you get it
|
||||||
// If the request came directly from a client, doing this will allow them to
|
wrong, users will most likely be able to spoof any client address.
|
||||||
// them spoof any remote address.
|
|
||||||
|
|
||||||
// The header may contain a list of IPs, like "1.2.3.4, 4.5.6.7", if the
|
```name="Custom X-Forwarded-For Handling", lang=php
|
||||||
// request the load balancer received also had this header.
|
|
||||||
|
|
||||||
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||||
$forwarded_for = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
$raw_header = $_SERVER['X_FORWARDED_FOR'];
|
||||||
if ($forwarded_for) {
|
|
||||||
$forwarded_for = explode(',', $forwarded_for);
|
$real_address = your_custom_parsing_function($raw_header);
|
||||||
$forwarded_for = end($forwarded_for);
|
|
||||||
$forwarded_for = trim($forwarded_for);
|
$_SERVER['REMOTE_ADDR'] = $real_address;
|
||||||
$_SERVER['REMOTE_ADDR'] = $forwarded_for;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -63,16 +63,23 @@ For detailed help on managing and stripping MFA, see the instructions in
|
|||||||
Unlocking Objects
|
Unlocking Objects
|
||||||
=================
|
=================
|
||||||
|
|
||||||
If you aren't sure who owns an object, or no user account has access to an
|
If you aren't sure who owns an object, you can inspect the policies from the
|
||||||
object, you can directly change object policies from the CLI:
|
CLI:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./bin/policy show <object>
|
||||||
|
```
|
||||||
|
|
||||||
|
To identify the object you want to examine, you can specify an object
|
||||||
|
name (like `T123`) or a PHID as the `<object>` parameter.
|
||||||
|
|
||||||
|
If examining the policy isn't helpful, or no user account has access to an
|
||||||
|
object, you can then directly change object policies from the CLI:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ./bin/policy unlock <object> [--view ...] [--edit ...] [--owner ...]
|
$ ./bin/policy unlock <object> [--view ...] [--edit ...] [--owner ...]
|
||||||
```
|
```
|
||||||
|
|
||||||
To identify the object you want to unlock, you can specify an object name (like
|
|
||||||
`T123`) or a PHID as the `<object>` parameter.
|
|
||||||
|
|
||||||
Use the `--view` and `--edit` flags (and, for some objects, the `--owner`
|
Use the `--view` and `--edit` flags (and, for some objects, the `--owner`
|
||||||
flag) to specify new policies for the object.
|
flag) to specify new policies for the object.
|
||||||
|
|
||||||
|
|||||||
@@ -221,6 +221,9 @@ final class PhabricatorDatabaseRef
|
|||||||
return $this->replicaRefs;
|
return $this->replicaRefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDisplayName() {
|
||||||
|
return $this->getRefKey();
|
||||||
|
}
|
||||||
|
|
||||||
public function getRefKey() {
|
public function getRefKey() {
|
||||||
$host = $this->getHost();
|
$host = $this->getHost();
|
||||||
|
|||||||
19
src/infrastructure/env/PhabricatorEnv.php
vendored
19
src/infrastructure/env/PhabricatorEnv.php
vendored
@@ -135,6 +135,11 @@ final class PhabricatorEnv extends Phobject {
|
|||||||
// TODO: Add a "locale.default" config option once we have some reasonable
|
// TODO: Add a "locale.default" config option once we have some reasonable
|
||||||
// defaults which aren't silly nonsense.
|
// defaults which aren't silly nonsense.
|
||||||
self::setLocaleCode('en_US');
|
self::setLocaleCode('en_US');
|
||||||
|
|
||||||
|
// Load the preamble utility library if we haven't already. On web
|
||||||
|
// requests this loaded earlier, but we want to load it for non-web
|
||||||
|
// requests so that unit tests can call these functions.
|
||||||
|
require_once $phabricator_path.'/support/startup/preamble-utils.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function beginScopedLocale($locale_code) {
|
public static function beginScopedLocale($locale_code) {
|
||||||
@@ -249,9 +254,17 @@ final class PhabricatorEnv extends Phobject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$stack->pushSource(
|
// See T13403. If we're starting up in "config optional" mode, suppress
|
||||||
id(new PhabricatorConfigDatabaseSource('default'))
|
// messages about connection retries.
|
||||||
->setName(pht('Database')));
|
if ($config_optional) {
|
||||||
|
$database_source = @new PhabricatorConfigDatabaseSource('default');
|
||||||
|
} else {
|
||||||
|
$database_source = new PhabricatorConfigDatabaseSource('default');
|
||||||
|
}
|
||||||
|
|
||||||
|
$database_source->setName(pht('Database'));
|
||||||
|
|
||||||
|
$stack->pushSource($database_source);
|
||||||
} catch (AphrontSchemaQueryException $exception) {
|
} catch (AphrontSchemaQueryException $exception) {
|
||||||
// If the database is not available, just skip this configuration
|
// If the database is not available, just skip this configuration
|
||||||
// source. This happens during `bin/storage upgrade`, `bin/conf` before
|
// source. This happens during `bin/storage upgrade`, `bin/conf` before
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ abstract class AphrontBaseMySQLDatabaseConnection
|
|||||||
|
|
||||||
private $nextError;
|
private $nextError;
|
||||||
|
|
||||||
|
const CALLERROR_QUERY = 777777;
|
||||||
|
const CALLERROR_CONNECT = 777778;
|
||||||
|
|
||||||
abstract protected function connect();
|
abstract protected function connect();
|
||||||
abstract protected function rawQuery($raw_query);
|
abstract protected function rawQuery($raw_query);
|
||||||
abstract protected function rawQueries(array $raw_queries);
|
abstract protected function rawQueries(array $raw_queries);
|
||||||
@@ -123,7 +126,14 @@ abstract class AphrontBaseMySQLDatabaseConnection
|
|||||||
$code,
|
$code,
|
||||||
$ex->getMessage());
|
$ex->getMessage());
|
||||||
|
|
||||||
|
// See T13403. If we're silenced with the "@" operator, don't log
|
||||||
|
// this connection attempt. This keeps things quiet if we're
|
||||||
|
// running a setup workflow like "bin/config" and expect that the
|
||||||
|
// database credentials will often be incorrect.
|
||||||
|
|
||||||
|
if (error_reporting()) {
|
||||||
phlog($message);
|
phlog($message);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$profiler->endServiceCall($call_id, array());
|
$profiler->endServiceCall($call_id, array());
|
||||||
throw $ex;
|
throw $ex;
|
||||||
|
|||||||
@@ -68,19 +68,47 @@ final class AphrontMySQLiDatabaseConnection
|
|||||||
$host = 'p:'.$host;
|
$host = 'p:'.$host;
|
||||||
}
|
}
|
||||||
|
|
||||||
@$conn->real_connect(
|
$trap = new PhutilErrorTrap();
|
||||||
|
|
||||||
|
$ok = @$conn->real_connect(
|
||||||
$host,
|
$host,
|
||||||
$user,
|
$user,
|
||||||
$pass,
|
$pass,
|
||||||
$database,
|
$database,
|
||||||
$port);
|
$port);
|
||||||
|
|
||||||
|
$call_error = $trap->getErrorsAsString();
|
||||||
|
$trap->destroy();
|
||||||
|
|
||||||
$errno = $conn->connect_errno;
|
$errno = $conn->connect_errno;
|
||||||
if ($errno) {
|
if ($errno) {
|
||||||
$error = $conn->connect_error;
|
$error = $conn->connect_error;
|
||||||
$this->throwConnectionException($errno, $error, $user, $host);
|
$this->throwConnectionException($errno, $error, $user, $host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See T13403. If the parameters to "real_connect()" are wrong, it may
|
||||||
|
// fail without setting an error code. In this case, raise a generic
|
||||||
|
// exception. (One way to reproduce this is to pass a string to the
|
||||||
|
// "port" parameter.)
|
||||||
|
|
||||||
|
if (!$ok) {
|
||||||
|
if (strlen($call_error)) {
|
||||||
|
$message = pht(
|
||||||
|
'mysqli->real_connect() failed: %s',
|
||||||
|
$call_error);
|
||||||
|
} else {
|
||||||
|
$message = pht(
|
||||||
|
'mysqli->real_connect() failed, but did not set an error code '.
|
||||||
|
'or emit a message.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->throwConnectionException(
|
||||||
|
self::CALLERROR_CONNECT,
|
||||||
|
$message,
|
||||||
|
$user,
|
||||||
|
$host);
|
||||||
|
}
|
||||||
|
|
||||||
// See T13238. Attempt to prevent "LOAD DATA LOCAL INFILE", which allows a
|
// See T13238. Attempt to prevent "LOAD DATA LOCAL INFILE", which allows a
|
||||||
// malicious server to ask the client for any file. At time of writing,
|
// malicious server to ask the client for any file. At time of writing,
|
||||||
// this option MUST be set after "real_connect()" on all PHP versions.
|
// this option MUST be set after "real_connect()" on all PHP versions.
|
||||||
@@ -152,7 +180,7 @@ final class AphrontMySQLiDatabaseConnection
|
|||||||
'Call to "mysqli->query()" failed, but did not set an error '.
|
'Call to "mysqli->query()" failed, but did not set an error '.
|
||||||
'code or emit an error message.');
|
'code or emit an error message.');
|
||||||
}
|
}
|
||||||
$this->throwQueryCodeException(777777, $message);
|
$this->throwQueryCodeException(self::CALLERROR_QUERY, $message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ final class PhabricatorStorageManagementAPI extends Phobject {
|
|||||||
return $this->namespace.'_'.$fragment;
|
return $this->namespace.'_'.$fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDisplayName() {
|
||||||
|
return $this->getRef()->getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
public function getDatabaseList(array $patches, $only_living = false) {
|
public function getDatabaseList(array $patches, $only_living = false) {
|
||||||
assert_instances_of($patches, 'PhabricatorStoragePatch');
|
assert_instances_of($patches, 'PhabricatorStoragePatch');
|
||||||
|
|
||||||
|
|||||||
@@ -21,41 +21,59 @@ final class PhabricatorStorageManagementDestroyWorkflow
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function didExecute(PhutilArgumentParser $args) {
|
public function didExecute(PhutilArgumentParser $args) {
|
||||||
$console = PhutilConsole::getConsole();
|
$api = $this->getSingleAPI();
|
||||||
|
|
||||||
|
$host_display = $api->getDisplayName();
|
||||||
|
|
||||||
if (!$this->isDryRun() && !$this->isForce()) {
|
if (!$this->isDryRun() && !$this->isForce()) {
|
||||||
if ($args->getArg('unittest-fixtures')) {
|
if ($args->getArg('unittest-fixtures')) {
|
||||||
$console->writeOut(
|
$warning = pht(
|
||||||
phutil_console_wrap(
|
|
||||||
pht(
|
|
||||||
'Are you completely sure you really want to destroy all unit '.
|
'Are you completely sure you really want to destroy all unit '.
|
||||||
'test fixure data? This operation can not be undone.')));
|
'test fixure data on host "%s"? This operation can not be undone.',
|
||||||
|
$host_display);
|
||||||
|
|
||||||
|
echo tsprintf(
|
||||||
|
'%B',
|
||||||
|
id(new PhutilConsoleBlock())
|
||||||
|
->addParagraph($warning)
|
||||||
|
->drawConsoleString());
|
||||||
|
|
||||||
if (!phutil_console_confirm(pht('Destroy all unit test data?'))) {
|
if (!phutil_console_confirm(pht('Destroy all unit test data?'))) {
|
||||||
$console->writeOut("%s\n", pht('Cancelled.'));
|
$this->logFail(
|
||||||
|
pht('CANCELLED'),
|
||||||
|
pht('User cancelled operation.'));
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$console->writeOut(
|
$warning = pht(
|
||||||
phutil_console_wrap(
|
|
||||||
pht(
|
|
||||||
'Are you completely sure you really want to permanently destroy '.
|
'Are you completely sure you really want to permanently destroy '.
|
||||||
'all storage for Phabricator data? This operation can not be '.
|
'all storage for Phabricator data on host "%s"? This operation '.
|
||||||
'undone and your data will not be recoverable if you proceed.')));
|
'can not be undone and your data will not be recoverable if '.
|
||||||
|
'you proceed.',
|
||||||
|
$host_display);
|
||||||
|
|
||||||
|
echo tsprintf(
|
||||||
|
'%B',
|
||||||
|
id(new PhutilConsoleBlock())
|
||||||
|
->addParagraph($warning)
|
||||||
|
->drawConsoleString());
|
||||||
|
|
||||||
if (!phutil_console_confirm(pht('Permanently destroy all data?'))) {
|
if (!phutil_console_confirm(pht('Permanently destroy all data?'))) {
|
||||||
$console->writeOut("%s\n", pht('Cancelled.'));
|
$this->logFail(
|
||||||
|
pht('CANCELLED'),
|
||||||
|
pht('User cancelled operation.'));
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!phutil_console_confirm(pht('Really destroy all data forever?'))) {
|
if (!phutil_console_confirm(pht('Really destroy all data forever?'))) {
|
||||||
$console->writeOut("%s\n", pht('Cancelled.'));
|
$this->logFail(
|
||||||
|
pht('CANCELLED'),
|
||||||
|
pht('User cancelled operation.'));
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$apis = $this->getMasterAPIs();
|
|
||||||
foreach ($apis as $api) {
|
|
||||||
$patches = $this->getPatches();
|
$patches = $this->getPatches();
|
||||||
|
|
||||||
if ($args->getArg('unittest-fixtures')) {
|
if ($args->getArg('unittest-fixtures')) {
|
||||||
@@ -76,15 +94,24 @@ final class PhabricatorStorageManagementDestroyWorkflow
|
|||||||
$databases[] = $api->getDatabaseName('directory');
|
$databases[] = $api->getDatabaseName('directory');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asort($databases);
|
||||||
|
|
||||||
foreach ($databases as $database) {
|
foreach ($databases as $database) {
|
||||||
if ($this->isDryRun()) {
|
if ($this->isDryRun()) {
|
||||||
$console->writeOut(
|
$this->logInfo(
|
||||||
"%s\n",
|
pht('DRY RUN'),
|
||||||
pht("DRYRUN: Would drop database '%s'.", $database));
|
pht(
|
||||||
|
'Would drop database "%s" on host "%s".',
|
||||||
|
$database,
|
||||||
|
$host_display));
|
||||||
} else {
|
} else {
|
||||||
$console->writeOut(
|
$this->logWarn(
|
||||||
"%s\n",
|
pht('DESTROY'),
|
||||||
pht("Dropping database '%s'...", $database));
|
pht(
|
||||||
|
'Dropping database "%s" on host "%s"...',
|
||||||
|
$database,
|
||||||
|
$host_display));
|
||||||
|
|
||||||
queryfx(
|
queryfx(
|
||||||
$api->getConn(null),
|
$api->getConn(null),
|
||||||
'DROP DATABASE IF EXISTS %T',
|
'DROP DATABASE IF EXISTS %T',
|
||||||
@@ -93,12 +120,11 @@ final class PhabricatorStorageManagementDestroyWorkflow
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->isDryRun()) {
|
if (!$this->isDryRun()) {
|
||||||
$console->writeOut(
|
$this->logOkay(
|
||||||
"%s\n",
|
pht('DONE'),
|
||||||
pht(
|
pht(
|
||||||
'Storage on "%s" was destroyed.',
|
'Storage on "%s" was destroyed.',
|
||||||
$api->getRef()->getRefKey()));
|
$host_display));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorPreambleTestCase
|
||||||
|
extends PhabricatorTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @phutil-external-symbol function preamble_get_x_forwarded_for_address
|
||||||
|
*/
|
||||||
|
public function testXForwardedForLayers() {
|
||||||
|
$tests = array(
|
||||||
|
// This is normal behavior with one load balancer.
|
||||||
|
array(
|
||||||
|
'header' => '1.2.3.4',
|
||||||
|
'layers' => 1,
|
||||||
|
'expect' => '1.2.3.4',
|
||||||
|
),
|
||||||
|
|
||||||
|
// In this case, the LB received a request which already had an
|
||||||
|
// "X-Forwarded-For" header. This might be legitimate (in the case of
|
||||||
|
// a CDN request) or illegitimate (in the case of a client making
|
||||||
|
// things up). We don't want to trust it.
|
||||||
|
array(
|
||||||
|
'header' => '9.9.9.9, 1.2.3.4',
|
||||||
|
'layers' => 1,
|
||||||
|
'expect' => '1.2.3.4',
|
||||||
|
),
|
||||||
|
|
||||||
|
// Multiple layers of load balancers.
|
||||||
|
array(
|
||||||
|
'header' => '9.9.9.9, 1.2.3.4',
|
||||||
|
'layers' => 2,
|
||||||
|
'expect' => '9.9.9.9',
|
||||||
|
),
|
||||||
|
|
||||||
|
// Multiple layers of load balancers, plus a client-supplied value.
|
||||||
|
array(
|
||||||
|
'header' => '8.8.8.8, 9.9.9.9, 1.2.3.4',
|
||||||
|
'layers' => 2,
|
||||||
|
'expect' => '9.9.9.9',
|
||||||
|
),
|
||||||
|
|
||||||
|
// Multiple layers of load balancers, but this request came from
|
||||||
|
// somewhere inside the network.
|
||||||
|
array(
|
||||||
|
'header' => '1.2.3.4',
|
||||||
|
'layers' => 2,
|
||||||
|
'expect' => '1.2.3.4',
|
||||||
|
),
|
||||||
|
|
||||||
|
array(
|
||||||
|
'header' => 'A, B, C, D, E, F, G, H, I',
|
||||||
|
'layers' => 7,
|
||||||
|
'expect' => 'C',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($tests as $test) {
|
||||||
|
$header = $test['header'];
|
||||||
|
$layers = $test['layers'];
|
||||||
|
$expect = $test['expect'];
|
||||||
|
|
||||||
|
$actual = preamble_get_x_forwarded_for_address($header, $layers);
|
||||||
|
|
||||||
|
$this->assertEqual(
|
||||||
|
$expect,
|
||||||
|
$actual,
|
||||||
|
pht(
|
||||||
|
'Address after stripping %d layers from: %s',
|
||||||
|
$layers,
|
||||||
|
$header));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -160,6 +160,20 @@ final class AphrontDialogView
|
|||||||
return $this->appendChild($box);
|
return $this->appendChild($box);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function appendRemarkup($remarkup) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$view = new PHUIRemarkupView($viewer, $remarkup);
|
||||||
|
|
||||||
|
$view_tag = phutil_tag(
|
||||||
|
'div',
|
||||||
|
array(
|
||||||
|
'class' => 'aphront-dialog-view-paragraph',
|
||||||
|
),
|
||||||
|
$view);
|
||||||
|
|
||||||
|
return $this->appendChild($view_tag);
|
||||||
|
}
|
||||||
|
|
||||||
public function appendParagraph($paragraph) {
|
public function appendParagraph($paragraph) {
|
||||||
return $this->appendParagraphTag($paragraph);
|
return $this->appendParagraphTag($paragraph);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,8 +116,10 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
|
|||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildModeSelector($selector_id, $application_id) {
|
public static function getGlobalSearchScopeItems(
|
||||||
$viewer = $this->getViewer();
|
PhabricatorUser $viewer,
|
||||||
|
PhabricatorApplication $application = null,
|
||||||
|
$global_only = false) {
|
||||||
|
|
||||||
$items = array();
|
$items = array();
|
||||||
$items[] = array(
|
$items[] = array(
|
||||||
@@ -132,7 +134,6 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
|
|||||||
|
|
||||||
$application_value = null;
|
$application_value = null;
|
||||||
$application_icon = self::DEFAULT_APPLICATION_ICON;
|
$application_icon = self::DEFAULT_APPLICATION_ICON;
|
||||||
$application = $this->getApplication();
|
|
||||||
if ($application) {
|
if ($application) {
|
||||||
$application_value = get_class($application);
|
$application_value = get_class($application);
|
||||||
if ($application->getApplicationSearchDocumentTypes()) {
|
if ($application->getApplicationSearchDocumentTypes()) {
|
||||||
@@ -154,14 +155,24 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
|
|||||||
$engine = id(new PhabricatorSearchApplicationSearchEngine())
|
$engine = id(new PhabricatorSearchApplicationSearchEngine())
|
||||||
->setViewer($viewer);
|
->setViewer($viewer);
|
||||||
$engine_queries = $engine->loadEnabledNamedQueries();
|
$engine_queries = $engine->loadEnabledNamedQueries();
|
||||||
$query_map = mpull($engine_queries, 'getQueryName', 'getQueryKey');
|
foreach ($engine_queries as $query) {
|
||||||
foreach ($query_map as $query_key => $query_name) {
|
$query_key = $query->getQueryKey();
|
||||||
if ($query_key == 'all') {
|
if ($query_key == 'all') {
|
||||||
// Skip the builtin "All" query since it's redundant with the default
|
// Skip the builtin "All" query since it's redundant with the default
|
||||||
// setting.
|
// setting.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In the global "Settings" panel, we don't want to offer personal
|
||||||
|
// queries the viewer may have saved.
|
||||||
|
if ($global_only) {
|
||||||
|
if (!$query->isGlobal()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$query_name = $query->getQueryName();
|
||||||
|
|
||||||
$items[] = array(
|
$items[] = array(
|
||||||
'icon' => 'fa-certificate',
|
'icon' => 'fa-certificate',
|
||||||
'name' => $query_name,
|
'name' => $query_name,
|
||||||
@@ -185,6 +196,14 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
|
|||||||
'href' => PhabricatorEnv::getDoclink('Search User Guide'),
|
'href' => PhabricatorEnv::getDoclink('Search User Guide'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildModeSelector($selector_id, $application_id) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$items = self::getGlobalSearchScopeItems($viewer, $this->getApplication());
|
||||||
|
|
||||||
$scope_key = PhabricatorSearchScopeSetting::SETTINGKEY;
|
$scope_key = PhabricatorSearchScopeSetting::SETTINGKEY;
|
||||||
$current_value = $viewer->getUserSetting($scope_key);
|
$current_value = $viewer->getUserSetting($scope_key);
|
||||||
|
|
||||||
@@ -196,6 +215,13 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$application = $this->getApplication();
|
||||||
|
|
||||||
|
$application_value = null;
|
||||||
|
if ($application) {
|
||||||
|
$application_value = get_class($application);
|
||||||
|
}
|
||||||
|
|
||||||
$selector = id(new PHUIButtonView())
|
$selector = id(new PHUIButtonView())
|
||||||
->setID($selector_id)
|
->setID($selector_id)
|
||||||
->addClass('phabricator-main-menu-search-dropdown')
|
->addClass('phabricator-main-menu-search-dropdown')
|
||||||
|
|||||||
77
support/startup/preamble-utils.php
Normal file
77
support/startup/preamble-utils.php
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the "X_FORWARDED_FOR" HTTP header to determine the original client
|
||||||
|
* address.
|
||||||
|
*
|
||||||
|
* @param int Number of devices to trust.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function preamble_trust_x_forwarded_for_header($layers = 1) {
|
||||||
|
if (!is_int($layers) || ($layers < 1)) {
|
||||||
|
echo
|
||||||
|
'preamble_trust_x_forwarded_for_header(<layers>): '.
|
||||||
|
'"layers" parameter must an integer larger than 0.'."\n";
|
||||||
|
echo "\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$forwarded_for = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||||
|
if (!strlen($forwarded_for)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$address = preamble_get_x_forwarded_for_address($forwarded_for, $layers);
|
||||||
|
|
||||||
|
$_SERVER['REMOTE_ADDR'] = $address;
|
||||||
|
}
|
||||||
|
|
||||||
|
function preamble_get_x_forwarded_for_address($raw_header, $layers) {
|
||||||
|
// The raw header may be a list of IPs, like "1.2.3.4, 4.5.6.7", if the
|
||||||
|
// request the load balancer received also had this header. In particular,
|
||||||
|
// this happens routinely with requests received through a CDN, but can also
|
||||||
|
// happen illegitimately if the client just makes up an "X-Forwarded-For"
|
||||||
|
// header full of lies.
|
||||||
|
|
||||||
|
// We can only trust the N elements at the end of the list which correspond
|
||||||
|
// to network-adjacent devices we control. Usually, we're behind a single
|
||||||
|
// load balancer and "N" is 1, so we want to take the last element in the
|
||||||
|
// list.
|
||||||
|
|
||||||
|
// In some cases, "N" may be more than 1, if the network is configured so
|
||||||
|
// that that requests are routed through multiple layers of load balancers
|
||||||
|
// and proxies. In this case, we want to take the Nth-to-last element of
|
||||||
|
// the list.
|
||||||
|
|
||||||
|
$addresses = explode(',', $raw_header);
|
||||||
|
|
||||||
|
// If we have more than one trustworthy device on the network path, discard
|
||||||
|
// corresponding elements from the list. For example, if we have 7 devices,
|
||||||
|
// we want to discard the last 6 elements of the list.
|
||||||
|
|
||||||
|
// The final device address does not appear in the list, since devices do
|
||||||
|
// not append their own addresses to "X-Forwarded-For".
|
||||||
|
|
||||||
|
$discard_addresses = ($layers - 1);
|
||||||
|
|
||||||
|
// However, we don't want to throw away all of the addresses. Some requests
|
||||||
|
// may originate from within the network, and may thus not have as many
|
||||||
|
// addresses as we expect. If we have fewer addresses than trustworthy
|
||||||
|
// devices, discard all but one address.
|
||||||
|
|
||||||
|
$max_discard = (count($addresses) - 1);
|
||||||
|
|
||||||
|
$discard_count = min($discard_addresses, $max_discard);
|
||||||
|
if ($discard_count) {
|
||||||
|
$addresses = array_slice($addresses, 0, -$discard_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
$original_address = end($addresses);
|
||||||
|
$original_address = trim($original_address);
|
||||||
|
|
||||||
|
return $original_address;
|
||||||
|
}
|
||||||
@@ -85,6 +85,7 @@ function phabricator_startup() {
|
|||||||
require_once $root.'/support/startup/PhabricatorClientLimit.php';
|
require_once $root.'/support/startup/PhabricatorClientLimit.php';
|
||||||
require_once $root.'/support/startup/PhabricatorClientRateLimit.php';
|
require_once $root.'/support/startup/PhabricatorClientRateLimit.php';
|
||||||
require_once $root.'/support/startup/PhabricatorClientConnectionLimit.php';
|
require_once $root.'/support/startup/PhabricatorClientConnectionLimit.php';
|
||||||
|
require_once $root.'/support/startup/preamble-utils.php';
|
||||||
|
|
||||||
// If the preamble script exists, load it.
|
// If the preamble script exists, load it.
|
||||||
$t_preamble = microtime(true);
|
$t_preamble = microtime(true);
|
||||||
|
|||||||
@@ -36,16 +36,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chart .point {
|
.chart .point {
|
||||||
fill: {$lightblue};
|
fill: #ffffff;
|
||||||
stroke: {$blue};
|
stroke: {$blue};
|
||||||
stroke-width: 1px;
|
stroke-width: 2px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-tooltip {
|
.chart-tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
height: 16px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
background: {$lightbluebackground};
|
background: {$lightbluebackground};
|
||||||
|
|||||||
@@ -354,45 +354,3 @@ body .phui-header-shell.phui-bleed-header
|
|||||||
.phui-header-view .phui-tag-indigo a {
|
.phui-header-view .phui-tag-indigo a {
|
||||||
color: {$sh-indigotext};
|
color: {$sh-indigotext};
|
||||||
}
|
}
|
||||||
|
|
||||||
.phui-policy-section-view {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phui-policy-section-view-header {
|
|
||||||
background: {$bluebackground};
|
|
||||||
border-bottom: 1px solid {$lightblueborder};
|
|
||||||
padding: 4px 8px;
|
|
||||||
color: {$darkbluetext};
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phui-policy-section-view-header-text {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phui-policy-section-view-header .phui-icon-view {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phui-policy-section-view-link {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phui-policy-section-view-link .phui-icon-view {
|
|
||||||
color: {$bluetext};
|
|
||||||
}
|
|
||||||
|
|
||||||
.phui-policy-section-view-hint {
|
|
||||||
color: {$greytext};
|
|
||||||
background: {$lightbluebackground};
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phui-policy-section-view-body {
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phui-policy-section-view-inactive-rule {
|
|
||||||
color: {$greytext};
|
|
||||||
}
|
|
||||||
|
|||||||
62
webroot/rsrc/css/phui/phui-policy-section-view.css
Normal file
62
webroot/rsrc/css/phui/phui-policy-section-view.css
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* @provides phui-policy-section-view-css
|
||||||
|
*/
|
||||||
|
|
||||||
|
.phui-policy-section-view {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phui-policy-section-view-header {
|
||||||
|
background: {$bluebackground};
|
||||||
|
border-bottom: 1px solid {$lightblueborder};
|
||||||
|
padding: 4px 8px;
|
||||||
|
color: {$darkbluetext};
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phui-policy-section-view-header-text {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phui-policy-section-view-header .phui-icon-view {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phui-policy-section-view-link {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phui-policy-section-view-link .phui-icon-view {
|
||||||
|
color: {$bluetext};
|
||||||
|
}
|
||||||
|
|
||||||
|
.phui-policy-section-view-hint {
|
||||||
|
color: {$greytext};
|
||||||
|
background: {$lightbluebackground};
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phui-policy-section-view-body {
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phui-policy-section-view-inactive-rule {
|
||||||
|
color: {$greytext};
|
||||||
|
}
|
||||||
|
|
||||||
|
.phui-policy-section-view-rules {
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 8px;
|
||||||
|
background: {$lightbluebackground};
|
||||||
|
border: 1px solid {$lightblueborder};
|
||||||
|
}
|
||||||
|
|
||||||
|
.phui-policy-section-view .phui-policy-section-view-body ul {
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 0 16px 0 24px;
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phui-policy-section-view .phui-policy-section-view-body p + p {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
@@ -36,6 +36,10 @@
|
|||||||
|
|
||||||
.phui-workcard .phui-oi-link {
|
.phui-workcard .phui-oi-link {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
|
||||||
|
/* See T13413. This works around a Chrome 77 rendering engine freeze. */
|
||||||
|
word-wrap: normal;
|
||||||
|
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color: {$blacktext};
|
color: {$blacktext};
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
|
|||||||
37
webroot/rsrc/js/application/fact/Chart.js
vendored
37
webroot/rsrc/js/application/fact/Chart.js
vendored
@@ -133,18 +133,33 @@ JX.install('Chart', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_newStackedArea: function(g, dataset, x, y, div, curtain) {
|
_newStackedArea: function(g, dataset, x, y, div, curtain) {
|
||||||
|
var ii;
|
||||||
|
|
||||||
var to_date = JX.bind(this, this._newDate);
|
var to_date = JX.bind(this, this._newDate);
|
||||||
|
|
||||||
var area = d3.area()
|
var area = d3.area()
|
||||||
.x(function(d) { return x(to_date(d.x)); })
|
.x(function(d) { return x(to_date(d.x)); })
|
||||||
.y0(function(d) { return y(d.y0); })
|
.y0(function(d) {
|
||||||
|
// When the area is positive, draw it above the X axis. When the area
|
||||||
|
// is negative, draw it below the X axis. We currently avoid having
|
||||||
|
// functions which cross the X axis by clever construction.
|
||||||
|
if (d.y0 >= 0 && d.y1 >= 0) {
|
||||||
|
return y(d.y0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d.y0 <= 0 && d.y1 <= 0) {
|
||||||
|
return y(d.y0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return y(0);
|
||||||
|
})
|
||||||
.y1(function(d) { return y(d.y1); });
|
.y1(function(d) { return y(d.y1); });
|
||||||
|
|
||||||
var line = d3.line()
|
var line = d3.line()
|
||||||
.x(function(d) { return x(to_date(d.x)); })
|
.x(function(d) { return x(to_date(d.x)); })
|
||||||
.y(function(d) { return y(d.y1); });
|
.y(function(d) { return y(d.y1); });
|
||||||
|
|
||||||
for (var ii = 0; ii < dataset.data.length; ii++) {
|
for (ii = 0; ii < dataset.data.length; ii++) {
|
||||||
var label = new JX.ChartFunctionLabel(dataset.labels[ii]);
|
var label = new JX.ChartFunctionLabel(dataset.labels[ii]);
|
||||||
|
|
||||||
var fill_color = label.getFillColor() || label.getColor();
|
var fill_color = label.getFillColor() || label.getColor();
|
||||||
@@ -160,6 +175,11 @@ JX.install('Chart', {
|
|||||||
.style('stroke', stroke_color)
|
.style('stroke', stroke_color)
|
||||||
.attr('d', line(dataset.data[ii]));
|
.attr('d', line(dataset.data[ii]));
|
||||||
|
|
||||||
|
curtain.addFunctionLabel(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we've drawn all the areas and lines, draw the dots.
|
||||||
|
for (ii = 0; ii < dataset.data.length; ii++) {
|
||||||
g.selectAll('dot')
|
g.selectAll('dot')
|
||||||
.data(dataset.events[ii])
|
.data(dataset.events[ii])
|
||||||
.enter()
|
.enter()
|
||||||
@@ -178,8 +198,16 @@ JX.install('Chart', {
|
|||||||
|
|
||||||
var d_d = dd.getDate();
|
var d_d = dd.getDate();
|
||||||
|
|
||||||
|
var y = parseInt(d.y1);
|
||||||
|
|
||||||
|
var label = d.n + ' Points';
|
||||||
|
|
||||||
|
var view =
|
||||||
|
d_y + '-' + d_m + '-' + d_d + ': ' + y + '<br />' +
|
||||||
|
label;
|
||||||
|
|
||||||
div
|
div
|
||||||
.html(d_y + '-' + d_m + '-' + d_d + ': ' + d.y1)
|
.html(view)
|
||||||
.style('opacity', 0.9)
|
.style('opacity', 0.9)
|
||||||
.style('left', (d3.event.pageX - 60) + 'px')
|
.style('left', (d3.event.pageX - 60) + 'px')
|
||||||
.style('top', (d3.event.pageY - 38) + 'px');
|
.style('top', (d3.event.pageY - 38) + 'px');
|
||||||
@@ -187,9 +215,8 @@ JX.install('Chart', {
|
|||||||
.on('mouseout', function() {
|
.on('mouseout', function() {
|
||||||
div.style('opacity', 0);
|
div.style('opacity', 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
curtain.addFunctionLabel(label);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_newDate: function(epoch) {
|
_newDate: function(epoch) {
|
||||||
|
|||||||
@@ -350,8 +350,10 @@ JX.install('HeraldRuleEditor', {
|
|||||||
sigil: 'field-select'
|
sigil: 'field-select'
|
||||||
};
|
};
|
||||||
|
|
||||||
var field_select = this._renderGroupSelect(groups, attrs);
|
var field_select = this._renderGroupSelect(
|
||||||
field_select.value = this._config.conditions[row_id][0];
|
groups,
|
||||||
|
attrs,
|
||||||
|
this._config.conditions[row_id][0]);
|
||||||
|
|
||||||
var field_cell = JX.$N('td', {sigil: 'field-cell'}, field_select);
|
var field_cell = JX.$N('td', {sigil: 'field-cell'}, field_select);
|
||||||
|
|
||||||
@@ -367,18 +369,38 @@ JX.install('HeraldRuleEditor', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderGroupSelect: function(groups, attrs) {
|
_renderGroupSelect: function(groups, attrs, value) {
|
||||||
var optgroups = [];
|
var optgroups = [];
|
||||||
for (var ii = 0; ii < groups.length; ii++) {
|
for (var ii = 0; ii < groups.length; ii++) {
|
||||||
var group = groups[ii];
|
var group = groups[ii];
|
||||||
var options = [];
|
var options = [];
|
||||||
for (var k in group.options) {
|
for (var k in group.options) {
|
||||||
options.push(JX.$N('option', {value: k}, group.options[k]));
|
var option = group.options[k];
|
||||||
|
|
||||||
|
var name = option.name;
|
||||||
|
var available = option.available;
|
||||||
|
|
||||||
|
// See T7961. If the option is not marked as "available", we only
|
||||||
|
// include it in the dropdown if the dropdown already has it as a
|
||||||
|
// value. We want to hide options provided by applications which are
|
||||||
|
// not installed, but do not want to break existing rules.
|
||||||
|
|
||||||
|
if (available || (k === value)) {
|
||||||
|
options.push(JX.$N('option', {value: k}, name));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (options.length) {
|
||||||
optgroups.push(JX.$N('optgroup', {label: group.label}, options));
|
optgroups.push(JX.$N('optgroup', {label: group.label}, options));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return JX.$N('select', attrs, optgroups);
|
var select = JX.$N('select', attrs, optgroups);
|
||||||
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
select.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return select;
|
||||||
},
|
},
|
||||||
|
|
||||||
_newAction : function(data) {
|
_newAction : function(data) {
|
||||||
@@ -402,8 +424,10 @@ JX.install('HeraldRuleEditor', {
|
|||||||
sigil: 'action-select'
|
sigil: 'action-select'
|
||||||
};
|
};
|
||||||
|
|
||||||
var action_select = this._renderGroupSelect(groups, attrs);
|
var action_select = this._renderGroupSelect(
|
||||||
action_select.value = action[0];
|
groups,
|
||||||
|
attrs,
|
||||||
|
action[0]);
|
||||||
|
|
||||||
var action_cell = JX.$N('td', {sigil: 'action-cell'}, action_select);
|
var action_cell = JX.$N('td', {sigil: 'action-cell'}, action_select);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user