1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-15 01:50:55 +01:00

(stable) Promote 2018 Week 5

This commit is contained in:
epriestley 2018-02-02 13:28:58 -08:00
commit 4a338fd408
79 changed files with 2526 additions and 858 deletions

View file

@ -1525,10 +1525,6 @@ phutil_register_library_map(array(
'ManiphestEditProjectsCapability' => 'applications/maniphest/capability/ManiphestEditProjectsCapability.php',
'ManiphestEditStatusCapability' => 'applications/maniphest/capability/ManiphestEditStatusCapability.php',
'ManiphestEmailCommand' => 'applications/maniphest/command/ManiphestEmailCommand.php',
'ManiphestExcelDefaultFormat' => 'applications/maniphest/export/ManiphestExcelDefaultFormat.php',
'ManiphestExcelFormat' => 'applications/maniphest/export/ManiphestExcelFormat.php',
'ManiphestExcelFormatTestCase' => 'applications/maniphest/export/__tests__/ManiphestExcelFormatTestCase.php',
'ManiphestExportController' => 'applications/maniphest/controller/ManiphestExportController.php',
'ManiphestGetTaskTransactionsConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php',
'ManiphestHovercardEngineExtension' => 'applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php',
'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php',
@ -2230,9 +2226,10 @@ phutil_register_library_map(array(
'PhabricatorBulkContentSource' => 'infrastructure/daemon/contentsource/PhabricatorBulkContentSource.php',
'PhabricatorBulkEditGroup' => 'applications/transactions/bulk/PhabricatorBulkEditGroup.php',
'PhabricatorBulkEngine' => 'applications/transactions/bulk/PhabricatorBulkEngine.php',
'PhabricatorBulkManagementExportWorkflow' => 'applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php',
'PhabricatorBulkManagementMakeSilentWorkflow' => 'applications/transactions/bulk/management/PhabricatorBulkManagementMakeSilentWorkflow.php',
'PhabricatorBulkManagementWorkflow' => 'applications/transactions/bulk/management/PhabricatorBulkManagementWorkflow.php',
'PhabricatorCSVExportFormat' => 'infrastructure/export/PhabricatorCSVExportFormat.php',
'PhabricatorCSVExportFormat' => 'infrastructure/export/format/PhabricatorCSVExportFormat.php',
'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php',
'PhabricatorCacheEngine' => 'applications/system/engine/PhabricatorCacheEngine.php',
'PhabricatorCacheEngineExtension' => 'applications/system/engine/PhabricatorCacheEngineExtension.php',
@ -2583,6 +2580,7 @@ phutil_register_library_map(array(
'PhabricatorCustomFieldEditEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldEditEngineExtension.php',
'PhabricatorCustomFieldEditField' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php',
'PhabricatorCustomFieldEditType' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php',
'PhabricatorCustomFieldExportEngineExtension' => 'infrastructure/export/engine/PhabricatorCustomFieldExportEngineExtension.php',
'PhabricatorCustomFieldFulltextEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php',
'PhabricatorCustomFieldHeraldAction' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php',
'PhabricatorCustomFieldHeraldActionGroup' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php',
@ -2840,15 +2838,20 @@ phutil_register_library_map(array(
'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php',
'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php',
'PhabricatorEpochEditField' => 'applications/transactions/editfield/PhabricatorEpochEditField.php',
'PhabricatorEpochExportField' => 'infrastructure/export/PhabricatorEpochExportField.php',
'PhabricatorEpochExportField' => 'infrastructure/export/field/PhabricatorEpochExportField.php',
'PhabricatorEvent' => 'infrastructure/events/PhabricatorEvent.php',
'PhabricatorEventEngine' => 'infrastructure/events/PhabricatorEventEngine.php',
'PhabricatorEventListener' => 'infrastructure/events/PhabricatorEventListener.php',
'PhabricatorEventType' => 'infrastructure/events/constant/PhabricatorEventType.php',
'PhabricatorExampleEventListener' => 'infrastructure/events/PhabricatorExampleEventListener.php',
'PhabricatorExcelExportFormat' => 'infrastructure/export/format/PhabricatorExcelExportFormat.php',
'PhabricatorExecFutureFileUploadSource' => 'applications/files/uploadsource/PhabricatorExecFutureFileUploadSource.php',
'PhabricatorExportField' => 'infrastructure/export/PhabricatorExportField.php',
'PhabricatorExportFormat' => 'infrastructure/export/PhabricatorExportFormat.php',
'PhabricatorExportEngine' => 'infrastructure/export/engine/PhabricatorExportEngine.php',
'PhabricatorExportEngineBulkJobType' => 'infrastructure/export/engine/PhabricatorExportEngineBulkJobType.php',
'PhabricatorExportEngineExtension' => 'infrastructure/export/engine/PhabricatorExportEngineExtension.php',
'PhabricatorExportField' => 'infrastructure/export/field/PhabricatorExportField.php',
'PhabricatorExportFormat' => 'infrastructure/export/format/PhabricatorExportFormat.php',
'PhabricatorExportFormatSetting' => 'infrastructure/export/engine/PhabricatorExportFormatSetting.php',
'PhabricatorExtendedPolicyInterface' => 'applications/policy/interface/PhabricatorExtendedPolicyInterface.php',
'PhabricatorExtendingPhabricatorConfigOptions' => 'applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php',
'PhabricatorExtensionsSetupCheck' => 'applications/config/check/PhabricatorExtensionsSetupCheck.php',
@ -3069,7 +3072,7 @@ phutil_register_library_map(array(
'PhabricatorHomeProfileMenuItem' => 'applications/home/menuitem/PhabricatorHomeProfileMenuItem.php',
'PhabricatorHovercardEngineExtension' => 'applications/search/engineextension/PhabricatorHovercardEngineExtension.php',
'PhabricatorHovercardEngineExtensionModule' => 'applications/search/engineextension/PhabricatorHovercardEngineExtensionModule.php',
'PhabricatorIDExportField' => 'infrastructure/export/PhabricatorIDExportField.php',
'PhabricatorIDExportField' => 'infrastructure/export/field/PhabricatorIDExportField.php',
'PhabricatorIDsSearchEngineExtension' => 'applications/search/engineextension/PhabricatorIDsSearchEngineExtension.php',
'PhabricatorIDsSearchField' => 'applications/search/field/PhabricatorIDsSearchField.php',
'PhabricatorIconDatasource' => 'applications/files/typeahead/PhabricatorIconDatasource.php',
@ -3093,7 +3096,7 @@ phutil_register_library_map(array(
'PhabricatorInlineSummaryView' => 'infrastructure/diff/view/PhabricatorInlineSummaryView.php',
'PhabricatorInstructionsEditField' => 'applications/transactions/editfield/PhabricatorInstructionsEditField.php',
'PhabricatorIntConfigType' => 'applications/config/type/PhabricatorIntConfigType.php',
'PhabricatorIntExportField' => 'infrastructure/export/PhabricatorIntExportField.php',
'PhabricatorIntExportField' => 'infrastructure/export/field/PhabricatorIntExportField.php',
'PhabricatorInternalSetting' => 'applications/settings/setting/PhabricatorInternalSetting.php',
'PhabricatorInternationalizationManagementExtractWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php',
'PhabricatorInternationalizationManagementWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementWorkflow.php',
@ -3103,7 +3106,7 @@ phutil_register_library_map(array(
'PhabricatorIteratorFileUploadSource' => 'applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php',
'PhabricatorJIRAAuthProvider' => 'applications/auth/provider/PhabricatorJIRAAuthProvider.php',
'PhabricatorJSONConfigType' => 'applications/config/type/PhabricatorJSONConfigType.php',
'PhabricatorJSONExportFormat' => 'infrastructure/export/PhabricatorJSONExportFormat.php',
'PhabricatorJSONExportFormat' => 'infrastructure/export/format/PhabricatorJSONExportFormat.php',
'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php',
'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php',
'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php',
@ -3126,9 +3129,11 @@ phutil_register_library_map(array(
'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php',
'PhabricatorLipsumMondrianArtist' => 'applications/lipsum/image/PhabricatorLipsumMondrianArtist.php',
'PhabricatorLiskDAO' => 'infrastructure/storage/lisk/PhabricatorLiskDAO.php',
'PhabricatorLiskExportEngineExtension' => 'infrastructure/export/engine/PhabricatorLiskExportEngineExtension.php',
'PhabricatorLiskFulltextEngineExtension' => 'applications/search/engineextension/PhabricatorLiskFulltextEngineExtension.php',
'PhabricatorLiskSearchEngineExtension' => 'applications/search/engineextension/PhabricatorLiskSearchEngineExtension.php',
'PhabricatorLiskSerializer' => 'infrastructure/storage/lisk/PhabricatorLiskSerializer.php',
'PhabricatorListExportField' => 'infrastructure/export/field/PhabricatorListExportField.php',
'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php',
'PhabricatorLocalTimeTestCase' => 'view/__tests__/PhabricatorLocalTimeTestCase.php',
'PhabricatorLocaleScopeGuard' => 'infrastructure/internationalization/scope/PhabricatorLocaleScopeGuard.php',
@ -3423,10 +3428,11 @@ phutil_register_library_map(array(
'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php',
'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php',
'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php',
'PhabricatorPHIDExportField' => 'infrastructure/export/PhabricatorPHIDExportField.php',
'PhabricatorPHIDExportField' => 'infrastructure/export/field/PhabricatorPHIDExportField.php',
'PhabricatorPHIDInterface' => 'applications/phid/interface/PhabricatorPHIDInterface.php',
'PhabricatorPHIDListEditField' => 'applications/transactions/editfield/PhabricatorPHIDListEditField.php',
'PhabricatorPHIDListEditType' => 'applications/transactions/edittype/PhabricatorPHIDListEditType.php',
'PhabricatorPHIDListExportField' => 'infrastructure/export/field/PhabricatorPHIDListExportField.php',
'PhabricatorPHIDResolver' => 'applications/phid/resolver/PhabricatorPHIDResolver.php',
'PhabricatorPHIDType' => 'applications/phid/type/PhabricatorPHIDType.php',
'PhabricatorPHIDTypeTestCase' => 'applications/phid/type/__tests__/PhabricatorPHIDTypeTestCase.php',
@ -3835,6 +3841,7 @@ phutil_register_library_map(array(
'PhabricatorProjectsCurtainExtension' => 'applications/project/engineextension/PhabricatorProjectsCurtainExtension.php',
'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php',
'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php',
'PhabricatorProjectsExportEngineExtension' => 'infrastructure/export/engine/PhabricatorProjectsExportEngineExtension.php',
'PhabricatorProjectsFulltextEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php',
'PhabricatorProjectsMembersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsMembersSearchEngineAttachment.php',
'PhabricatorProjectsMembershipIndexEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php',
@ -4126,6 +4133,7 @@ phutil_register_library_map(array(
'PhabricatorSpacesController' => 'applications/spaces/controller/PhabricatorSpacesController.php',
'PhabricatorSpacesDAO' => 'applications/spaces/storage/PhabricatorSpacesDAO.php',
'PhabricatorSpacesEditController' => 'applications/spaces/controller/PhabricatorSpacesEditController.php',
'PhabricatorSpacesExportEngineExtension' => 'infrastructure/export/engine/PhabricatorSpacesExportEngineExtension.php',
'PhabricatorSpacesInterface' => 'applications/spaces/interface/PhabricatorSpacesInterface.php',
'PhabricatorSpacesListController' => 'applications/spaces/controller/PhabricatorSpacesListController.php',
'PhabricatorSpacesNamespace' => 'applications/spaces/storage/PhabricatorSpacesNamespace.php',
@ -4189,9 +4197,10 @@ phutil_register_library_map(array(
'PhabricatorStorageSchemaSpec' => 'infrastructure/storage/schema/PhabricatorStorageSchemaSpec.php',
'PhabricatorStorageSetupCheck' => 'applications/config/check/PhabricatorStorageSetupCheck.php',
'PhabricatorStringConfigType' => 'applications/config/type/PhabricatorStringConfigType.php',
'PhabricatorStringExportField' => 'infrastructure/export/PhabricatorStringExportField.php',
'PhabricatorStringExportField' => 'infrastructure/export/field/PhabricatorStringExportField.php',
'PhabricatorStringListConfigType' => 'applications/config/type/PhabricatorStringListConfigType.php',
'PhabricatorStringListEditField' => 'applications/transactions/editfield/PhabricatorStringListEditField.php',
'PhabricatorStringListExportField' => 'infrastructure/export/field/PhabricatorStringListExportField.php',
'PhabricatorStringSetting' => 'applications/settings/setting/PhabricatorStringSetting.php',
'PhabricatorSubmitEditField' => 'applications/transactions/editfield/PhabricatorSubmitEditField.php',
'PhabricatorSubscribableInterface' => 'applications/subscriptions/interface/PhabricatorSubscribableInterface.php',
@ -4206,6 +4215,7 @@ phutil_register_library_map(array(
'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php',
'PhabricatorSubscriptionsEditEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php',
'PhabricatorSubscriptionsEditor' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php',
'PhabricatorSubscriptionsExportEngineExtension' => 'infrastructure/export/engine/PhabricatorSubscriptionsExportEngineExtension.php',
'PhabricatorSubscriptionsFulltextEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsFulltextEngineExtension.php',
'PhabricatorSubscriptionsHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php',
'PhabricatorSubscriptionsListController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsListController.php',
@ -4253,7 +4263,7 @@ phutil_register_library_map(array(
'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php',
'PhabricatorTextConfigType' => 'applications/config/type/PhabricatorTextConfigType.php',
'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php',
'PhabricatorTextExportFormat' => 'infrastructure/export/PhabricatorTextExportFormat.php',
'PhabricatorTextExportFormat' => 'infrastructure/export/format/PhabricatorTextExportFormat.php',
'PhabricatorTextListConfigType' => 'applications/config/type/PhabricatorTextListConfigType.php',
'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php',
'PhabricatorTimeFormatSetting' => 'applications/settings/setting/PhabricatorTimeFormatSetting.php',
@ -4318,6 +4328,7 @@ phutil_register_library_map(array(
'PhabricatorUIExample' => 'applications/uiexample/examples/PhabricatorUIExample.php',
'PhabricatorUIExampleRenderController' => 'applications/uiexample/controller/PhabricatorUIExampleRenderController.php',
'PhabricatorUIExamplesApplication' => 'applications/uiexample/application/PhabricatorUIExamplesApplication.php',
'PhabricatorURIExportField' => 'infrastructure/export/field/PhabricatorURIExportField.php',
'PhabricatorUSEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php',
'PhabricatorUnifiedDiffsSetting' => 'applications/settings/setting/PhabricatorUnifiedDiffsSetting.php',
'PhabricatorUnitTestContentSource' => 'infrastructure/contentsource/PhabricatorUnitTestContentSource.php',
@ -4411,6 +4422,7 @@ phutil_register_library_map(array(
'PhabricatorWorkerManagementWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php',
'PhabricatorWorkerPermanentFailureException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerPermanentFailureException.php',
'PhabricatorWorkerSchemaSpec' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerSchemaSpec.php',
'PhabricatorWorkerSingleBulkJobType' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerSingleBulkJobType.php',
'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php',
'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTaskData.php',
'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/PhabricatorWorkerTaskDetailController.php',
@ -6768,10 +6780,6 @@ phutil_register_library_map(array(
'ManiphestEditProjectsCapability' => 'PhabricatorPolicyCapability',
'ManiphestEditStatusCapability' => 'PhabricatorPolicyCapability',
'ManiphestEmailCommand' => 'MetaMTAEmailTransactionCommand',
'ManiphestExcelDefaultFormat' => 'ManiphestExcelFormat',
'ManiphestExcelFormat' => 'Phobject',
'ManiphestExcelFormatTestCase' => 'PhabricatorTestCase',
'ManiphestExportController' => 'ManiphestController',
'ManiphestGetTaskTransactionsConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod',
@ -7572,6 +7580,7 @@ phutil_register_library_map(array(
'PhabricatorBulkContentSource' => 'PhabricatorContentSource',
'PhabricatorBulkEditGroup' => 'Phobject',
'PhabricatorBulkEngine' => 'Phobject',
'PhabricatorBulkManagementExportWorkflow' => 'PhabricatorBulkManagementWorkflow',
'PhabricatorBulkManagementMakeSilentWorkflow' => 'PhabricatorBulkManagementWorkflow',
'PhabricatorBulkManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorCSVExportFormat' => 'PhabricatorExportFormat',
@ -7991,6 +8000,7 @@ phutil_register_library_map(array(
'PhabricatorCustomFieldEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorCustomFieldEditField' => 'PhabricatorEditField',
'PhabricatorCustomFieldEditType' => 'PhabricatorEditType',
'PhabricatorCustomFieldExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorCustomFieldFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'PhabricatorCustomFieldHeraldAction' => 'HeraldAction',
'PhabricatorCustomFieldHeraldActionGroup' => 'HeraldActionGroup',
@ -8279,9 +8289,14 @@ phutil_register_library_map(array(
'PhabricatorEventListener' => 'PhutilEventListener',
'PhabricatorEventType' => 'PhutilEventType',
'PhabricatorExampleEventListener' => 'PhabricatorEventListener',
'PhabricatorExcelExportFormat' => 'PhabricatorExportFormat',
'PhabricatorExecFutureFileUploadSource' => 'PhabricatorFileUploadSource',
'PhabricatorExportEngine' => 'Phobject',
'PhabricatorExportEngineBulkJobType' => 'PhabricatorWorkerSingleBulkJobType',
'PhabricatorExportEngineExtension' => 'Phobject',
'PhabricatorExportField' => 'Phobject',
'PhabricatorExportFormat' => 'Phobject',
'PhabricatorExportFormatSetting' => 'PhabricatorInternalSetting',
'PhabricatorExtendingPhabricatorConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorExtensionsSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorExternalAccount' => array(
@ -8599,9 +8614,11 @@ phutil_register_library_map(array(
'PhabricatorLipsumManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorLipsumMondrianArtist' => 'PhabricatorLipsumArtist',
'PhabricatorLiskDAO' => 'LiskDAO',
'PhabricatorLiskExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorLiskFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'PhabricatorLiskSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorLiskSerializer' => 'Phobject',
'PhabricatorListExportField' => 'PhabricatorExportField',
'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorLocalTimeTestCase' => 'PhabricatorTestCase',
'PhabricatorLocaleScopeGuard' => 'Phobject',
@ -8939,6 +8956,7 @@ phutil_register_library_map(array(
'PhabricatorPHIDExportField' => 'PhabricatorExportField',
'PhabricatorPHIDListEditField' => 'PhabricatorEditField',
'PhabricatorPHIDListEditType' => 'PhabricatorEditType',
'PhabricatorPHIDListExportField' => 'PhabricatorListExportField',
'PhabricatorPHIDResolver' => 'Phobject',
'PhabricatorPHIDType' => 'Phobject',
'PhabricatorPHIDTypeTestCase' => 'PhutilTestCase',
@ -9439,6 +9457,7 @@ phutil_register_library_map(array(
'PhabricatorProjectsCurtainExtension' => 'PHUICurtainExtension',
'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField',
'PhabricatorProjectsExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorProjectsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'PhabricatorProjectsMembersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorProjectsMembershipIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
@ -9806,6 +9825,7 @@ phutil_register_library_map(array(
'PhabricatorSpacesController' => 'PhabricatorController',
'PhabricatorSpacesDAO' => 'PhabricatorLiskDAO',
'PhabricatorSpacesEditController' => 'PhabricatorSpacesController',
'PhabricatorSpacesExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorSpacesInterface' => 'PhabricatorPHIDInterface',
'PhabricatorSpacesListController' => 'PhabricatorSpacesController',
'PhabricatorSpacesNamespace' => array(
@ -9879,6 +9899,7 @@ phutil_register_library_map(array(
'PhabricatorStringExportField' => 'PhabricatorExportField',
'PhabricatorStringListConfigType' => 'PhabricatorTextListConfigType',
'PhabricatorStringListEditField' => 'PhabricatorEditField',
'PhabricatorStringListExportField' => 'PhabricatorListExportField',
'PhabricatorStringSetting' => 'PhabricatorSetting',
'PhabricatorSubmitEditField' => 'PhabricatorEditField',
'PhabricatorSubscribedToObjectEdgeType' => 'PhabricatorEdgeType',
@ -9892,6 +9913,7 @@ phutil_register_library_map(array(
'PhabricatorSubscriptionsEditController' => 'PhabricatorController',
'PhabricatorSubscriptionsEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorSubscriptionsEditor' => 'PhabricatorEditor',
'PhabricatorSubscriptionsExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorSubscriptionsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'PhabricatorSubscriptionsHeraldAction' => 'HeraldAction',
'PhabricatorSubscriptionsListController' => 'PhabricatorController',
@ -10015,6 +10037,7 @@ phutil_register_library_map(array(
'PhabricatorUIExample' => 'Phobject',
'PhabricatorUIExampleRenderController' => 'PhabricatorController',
'PhabricatorUIExamplesApplication' => 'PhabricatorApplication',
'PhabricatorURIExportField' => 'PhabricatorExportField',
'PhabricatorUSEnglishTranslation' => 'PhutilTranslation',
'PhabricatorUnifiedDiffsSetting' => 'PhabricatorSelectSetting',
'PhabricatorUnitTestContentSource' => 'PhabricatorContentSource',
@ -10138,6 +10161,7 @@ phutil_register_library_map(array(
'PhabricatorWorkerManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorWorkerPermanentFailureException' => 'Exception',
'PhabricatorWorkerSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorWorkerSingleBulkJobType' => 'PhabricatorWorkerBulkJobType',
'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO',
'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO',
'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController',

View file

@ -358,9 +358,17 @@ final class PhabricatorAuditEditor
array $changes,
PhutilMarkupEngine $engine) {
// we are only really trying to find unmentionable phids here...
// don't bother with this outside initial commit (i.e. create)
// transaction
$actor = $this->getActor();
$result = array();
// Some interactions (like "Fixes Txxx" interacting with Maniphest) have
// already been processed, so we're only re-parsing them here to avoid
// generating an extra redundant mention. Other interactions are being
// processed for the first time.
// We're only recognizing magic in the commit message itself, not in
// audit comments.
$is_commit = false;
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
@ -370,8 +378,6 @@ final class PhabricatorAuditEditor
}
}
// "result" is always an array....
$result = array();
if (!$is_commit) {
return $result;
}
@ -403,6 +409,46 @@ final class PhabricatorAuditEditor
->withNames($monograms)
->execute();
$phid_map[] = mpull($objects, 'getPHID', 'getPHID');
$reverts_refs = id(new DifferentialCustomFieldRevertsParser())
->parseCorpus($huge_block);
$reverts = array_mergev(ipull($reverts_refs, 'monograms'));
if ($reverts) {
// Only allow commits to revert other commits in the same repository.
$reverted_commits = id(new DiffusionCommitQuery())
->setViewer($actor)
->withRepository($object->getRepository())
->withIdentifiers($reverts)
->execute();
$reverted_revisions = id(new PhabricatorObjectQuery())
->setViewer($actor)
->withNames($reverts)
->withTypes(
array(
DifferentialRevisionPHIDType::TYPECONST,
))
->execute();
$reverted_phids =
mpull($reverted_commits, 'getPHID', 'getPHID') +
mpull($reverted_revisions, 'getPHID', 'getPHID');
// NOTE: Skip any write attempts if a user cleverly implies a commit
// reverts itself, although this would be exceptionally clever in Git
// or Mercurial.
unset($reverted_phids[$object->getPHID()]);
$reverts_edge = DiffusionCommitRevertsCommitEdgeType::EDGECONST;
$result[] = id(new PhabricatorAuditTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $reverts_edge)
->setNewValue(array('+' => $reverted_phids));
$phid_map[] = $reverted_phids;
}
$phid_map = array_mergev($phid_map);
$this->setUnmentionablePHIDMap($phid_map);

View file

@ -3,9 +3,26 @@
final class ConduitPHIDParameterType
extends ConduitParameterType {
private $isNullable;
public function setIsNullable($is_nullable) {
$this->isNullable = $is_nullable;
return $this;
}
public function getIsNullable() {
return $this->isNullable;
}
protected function getParameterValue(array $request, $key, $strict) {
$value = parent::getParameterValue($request, $key, $strict);
if ($this->getIsNullable()) {
if ($value === null) {
return $value;
}
}
if (!is_string($value)) {
$this->raiseValidationException(
$request,
@ -17,8 +34,12 @@ final class ConduitPHIDParameterType
}
protected function getParameterTypeName() {
if ($this->getIsNullable()) {
return 'phid|null';
} else {
return 'phid';
}
}
protected function getParameterFormatDescriptions() {
return array(
@ -27,9 +48,15 @@ final class ConduitPHIDParameterType
}
protected function getParameterExamples() {
return array(
$examples = array(
'"PHID-WXYZ-1111222233334444"',
);
if ($this->getIsNullable()) {
$examples[] = 'null';
}
return $examples;
}
}

View file

@ -71,18 +71,10 @@ final class PhabricatorDaemonBulkJobViewController
$viewer = $this->getViewer();
$curtain = $this->newCurtainView($job);
if ($job->isConfirming()) {
$continue_uri = $job->getMonitorURI();
} else {
$continue_uri = $job->getDoneURI();
foreach ($job->getCurtainActions($viewer) as $action) {
$curtain->addAction($action);
}
$curtain->addAction(
id(new PhabricatorActionView())
->setHref($continue_uri)
->setIcon('fa-arrow-circle-o-right')
->setName(pht('Continue')));
return $curtain;
}

View file

@ -919,7 +919,44 @@ final class DifferentialTransactionEditor
}
}
$this->setUnmentionablePHIDMap(array_merge($task_phids, $rev_phids));
$revert_refs = id(new DifferentialCustomFieldRevertsParser())
->parseCorpus($content_block);
$revert_monograms = array();
foreach ($revert_refs as $match) {
foreach ($match['monograms'] as $monogram) {
$revert_monograms[] = $monogram;
}
}
if ($revert_monograms) {
$revert_objects = id(new PhabricatorObjectQuery())
->setViewer($this->getActor())
->withNames($revert_monograms)
->withTypes(
array(
DifferentialRevisionPHIDType::TYPECONST,
PhabricatorRepositoryCommitPHIDType::TYPECONST,
))
->execute();
$revert_phids = mpull($revert_objects, 'getPHID', 'getPHID');
// Don't let an object revert itself, although other silly stuff like
// cycles of objects reverting each other is not prevented.
unset($revert_phids[$object->getPHID()]);
$revert_type = DiffusionCommitRevertsCommitEdgeType::EDGECONST;
$edges[$revert_type] = $revert_phids;
} else {
$revert_phids = array();
}
$this->setUnmentionablePHIDMap(
array_merge(
$task_phids,
$rev_phids,
$revert_phids));
$result = array();
foreach ($edges as $type => $specs) {

View file

@ -121,7 +121,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
$this->getEditRoutePattern('edit/') =>
'DiffusionRepositoryEditController',
'pushlog/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DiffusionPushLogListController',
$this->getQueryRoutePattern() => 'DiffusionPushLogListController',
'view/(?P<id>\d+)/' => 'DiffusionPushEventViewController',
),
'pulllog/' => array(

View file

@ -9,4 +9,9 @@ final class DiffusionPullLogListController
->buildResponse();
}
protected function buildApplicationCrumbs() {
return parent::buildApplicationCrumbs()
->addTextCrumb(pht('Pull Logs'), $this->getApplicationURI('pulllog/'));
}
}

View file

@ -9,4 +9,9 @@ final class DiffusionPushLogListController
->buildResponse();
}
protected function buildApplicationCrumbs() {
return parent::buildApplicationCrumbs()
->addTextCrumb(pht('Push Logs'), $this->getApplicationURI('pushlog/'));
}
}

View file

@ -19,7 +19,7 @@ final class DiffusionCommitRevertedByCommitEdgeType
$add_edges) {
return pht(
'%s added %s reverting commit(s): %s.',
'%s added %s reverting change(s): %s.',
$actor,
$add_count,
$add_edges);
@ -31,7 +31,7 @@ final class DiffusionCommitRevertedByCommitEdgeType
$rem_edges) {
return pht(
'%s removed %s reverting commit(s): %s.',
'%s removed %s reverting change(s): %s.',
$actor,
$rem_count,
$rem_edges);
@ -46,7 +46,7 @@ final class DiffusionCommitRevertedByCommitEdgeType
$rem_edges) {
return pht(
'%s edited reverting commit(s), added %s: %s; removed %s: %s.',
'%s edited reverting change(s), added %s: %s; removed %s: %s.',
$actor,
$add_count,
$add_edges,
@ -61,7 +61,7 @@ final class DiffusionCommitRevertedByCommitEdgeType
$add_edges) {
return pht(
'%s added %s reverting commit(s) for %s: %s.',
'%s added %s reverting change(s) for %s: %s.',
$actor,
$add_count,
$object,
@ -75,7 +75,7 @@ final class DiffusionCommitRevertedByCommitEdgeType
$rem_edges) {
return pht(
'%s removed %s reverting commit(s) for %s: %s.',
'%s removed %s reverting change(s) for %s: %s.',
$actor,
$rem_count,
$object,
@ -92,7 +92,7 @@ final class DiffusionCommitRevertedByCommitEdgeType
$rem_edges) {
return pht(
'%s edited reverting commit(s) for %s, added %s: %s; removed %s: %s.',
'%s edited reverting change(s) for %s, added %s: %s; removed %s: %s.',
$actor,
$object,
$add_count,

View file

@ -22,7 +22,7 @@ final class DiffusionCommitRevertsCommitEdgeType extends PhabricatorEdgeType {
$add_edges) {
return pht(
'%s added %s reverted commit(s): %s.',
'%s added %s reverted change(s): %s.',
$actor,
$add_count,
$add_edges);
@ -34,7 +34,7 @@ final class DiffusionCommitRevertsCommitEdgeType extends PhabricatorEdgeType {
$rem_edges) {
return pht(
'%s removed %s reverted commit(s): %s.',
'%s removed %s reverted change(s): %s.',
$actor,
$rem_count,
$rem_edges);
@ -49,7 +49,7 @@ final class DiffusionCommitRevertsCommitEdgeType extends PhabricatorEdgeType {
$rem_edges) {
return pht(
'%s edited reverted commit(s), added %s: %s; removed %s: %s.',
'%s edited reverted change(s), added %s: %s; removed %s: %s.',
$actor,
$add_count,
$add_edges,
@ -64,7 +64,7 @@ final class DiffusionCommitRevertsCommitEdgeType extends PhabricatorEdgeType {
$add_edges) {
return pht(
'%s added %s reverted commit(s) for %s: %s.',
'%s added %s reverted change(s) for %s: %s.',
$actor,
$add_count,
$object,
@ -78,7 +78,7 @@ final class DiffusionCommitRevertsCommitEdgeType extends PhabricatorEdgeType {
$rem_edges) {
return pht(
'%s removed %s reverted commit(s) for %s: %s.',
'%s removed %s reverted change(s) for %s: %s.',
$actor,
$rem_count,
$object,
@ -95,7 +95,7 @@ final class DiffusionCommitRevertsCommitEdgeType extends PhabricatorEdgeType {
$rem_edges) {
return pht(
'%s edited reverted commit(s) for %s, added %s: %s; removed %s: %s.',
'%s edited reverted change(s) for %s, added %s: %s; removed %s: %s.',
$actor,
$object,
$add_count,

View file

@ -135,13 +135,16 @@ final class HeraldCommitAdapter
}
public function loadAffectedPaths() {
$viewer = $this->getViewer();
if ($this->affectedPaths === null) {
$result = PhabricatorOwnerPathQuery::loadAffectedPaths(
$this->getRepository(),
$this->commit,
PhabricatorUser::getOmnipotentUser());
$viewer);
$this->affectedPaths = $result;
}
return $this->affectedPaths;
}
@ -172,6 +175,8 @@ final class HeraldCommitAdapter
}
public function loadDifferentialRevision() {
$viewer = $this->getViewer();
if ($this->affectedRevision === null) {
$this->affectedRevision = false;
@ -189,7 +194,7 @@ final class HeraldCommitAdapter
$revision = id(new DifferentialRevisionQuery())
->withIDs(array($revision_id))
->setViewer(PhabricatorUser::getOmnipotentUser())
->setViewer($viewer)
->needReviewers(true)
->executeOne();
if ($revision) {
@ -197,6 +202,7 @@ final class HeraldCommitAdapter
}
}
}
return $this->affectedRevision;
}
@ -323,7 +329,7 @@ final class HeraldCommitAdapter
}
private function callConduit($method, array $params) {
$viewer = PhabricatorUser::getOmnipotentUser();
$viewer = $this->getViewer();
$drequest = DiffusionRequest::newFromDictionary(
array(

View file

@ -26,6 +26,12 @@ final class DiffusionPullLogSearchEngine
$query->withPullerPHIDs($map['pullerPHIDs']);
}
if ($map['createdStart'] || $map['createdEnd']) {
$query->withEpochBetween(
$map['createdStart'],
$map['createdEnd']);
}
return $query;
}
@ -44,17 +50,19 @@ final class DiffusionPullLogSearchEngine
->setLabel(pht('Pullers'))
->setDescription(
pht('Search for pull logs by specific users.')),
id(new PhabricatorSearchDateField())
->setLabel(pht('Created After'))
->setKey('createdStart'),
id(new PhabricatorSearchDateField())
->setLabel(pht('Created Before'))
->setKey('createdEnd'),
);
}
protected function newExportFields() {
return array(
id(new PhabricatorIDExportField())
->setKey('id')
->setLabel(pht('ID')),
id(new PhabricatorPHIDExportField())
->setKey('phid')
->setLabel(pht('PHID')),
$viewer = $this->requireViewer();
$fields = array(
id(new PhabricatorPHIDExportField())
->setKey('repositoryPHID')
->setLabel(pht('Repository PHID')),
@ -80,9 +88,17 @@ final class DiffusionPullLogSearchEngine
->setKey('date')
->setLabel(pht('Date')),
);
if ($viewer->getIsAdmin()) {
$fields[] = id(new PhabricatorStringExportField())
->setKey('remoteAddress')
->setLabel(pht('Remote Address'));
}
public function newExport(array $events) {
return $fields;
}
protected function newExportData(array $events) {
$viewer = $this->requireViewer();
$phids = array();
@ -111,9 +127,7 @@ final class DiffusionPullLogSearchEngine
$puller_name = null;
}
$export[] = array(
'id' => $event->getID(),
'phid' => $event->getPHID(),
$map = array(
'repositoryPHID' => $repository_phid,
'repository' => $repository_name,
'pullerPHID' => $puller_phid,
@ -123,6 +137,12 @@ final class DiffusionPullLogSearchEngine
'code' => $event->getResultCode(),
'date' => $event->getEpoch(),
);
if ($viewer->getIsAdmin()) {
$map['remoteAddress'] = $event->getRemoteAddress();
}
$export[] = $map;
}
return $export;

View file

@ -22,24 +22,10 @@ final class DiffusionPullLogListView extends AphrontView {
}
$handles = $viewer->loadHandles($handle_phids);
// Figure out which repositories are editable. We only let you see remote
// IPs if you have edit capability on a repository.
$editable_repos = array();
if ($events) {
$editable_repos = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withPHIDs(mpull($events, 'getRepositoryPHID'))
->execute();
$editable_repos = mpull($editable_repos, null, 'getPHID');
}
// Only administrators can view remote addresses.
$remotes_visible = $viewer->getIsAdmin();
$rows = array();
$any_host = false;
foreach ($events as $event) {
if ($event->getRepositoryPHID()) {
$repository = $event->getRepository();
@ -47,13 +33,10 @@ final class DiffusionPullLogListView extends AphrontView {
$repository = null;
}
// Reveal this if it's valid and the user can edit the repository. For
// invalid requests you currently have to go fishing in the database.
$remote_address = '-';
if ($repository) {
if (isset($editable_repos[$event->getRepositoryPHID()])) {
if ($remotes_visible) {
$remote_address = $event->getRemoteAddress();
}
} else {
$remote_address = null;
}
$event_id = $event->getID();
@ -107,6 +90,13 @@ final class DiffusionPullLogListView extends AphrontView {
'',
'n',
'right',
))
->setColumnVisibility(
array(
true,
true,
true,
$remotes_visible,
));
return $table;

View file

@ -25,31 +25,21 @@ final class DiffusionPushLogListView extends AphrontView {
$handles = $viewer->loadHandles($handle_phids);
// Figure out which repositories are editable. We only let you see remote
// IPs if you have edit capability on a repository.
$editable_repos = array();
if ($logs) {
$editable_repos = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withPHIDs(mpull($logs, 'getRepositoryPHID'))
->execute();
$editable_repos = mpull($editable_repos, null, 'getPHID');
}
// Only administrators can view remote addresses.
$remotes_visible = $viewer->getIsAdmin();
$flag_map = PhabricatorRepositoryPushLog::getFlagDisplayNames();
$reject_map = PhabricatorRepositoryPushLog::getRejectCodeDisplayNames();
$rows = array();
$any_host = false;
foreach ($logs as $log) {
$repository = $log->getRepository();
// Reveal this if it's valid and the user can edit the repository.
$remote_address = '-';
if (isset($editable_repos[$log->getRepositoryPHID()])) {
if ($remotes_visible) {
$remote_address = $log->getPushEvent()->getRemoteAddress();
} else {
$remote_address = null;
}
$event_id = $log->getPushEvent()->getID();
@ -72,6 +62,23 @@ final class DiffusionPushLogListView extends AphrontView {
$device = null;
}
$flags = $log->getChangeFlags();
$flag_names = array();
foreach ($flag_map as $flag_key => $flag_name) {
if (($flags & $flag_key) === $flag_key) {
$flag_names[] = $flag_name;
}
}
$flag_names = phutil_implode_html(
phutil_tag('br'),
$flag_names);
$reject_code = $log->getPushEvent()->getRejectCode();
$reject_label = idx(
$reject_map,
$reject_code,
pht('Unknown ("%s")', $reject_code));
$rows[] = array(
phutil_tag(
'a',
@ -98,10 +105,8 @@ final class DiffusionPushLogListView extends AphrontView {
'href' => $repository->getCommitURI($log->getRefNew()),
),
$log->getRefNewShort()),
// TODO: Make these human-readable.
$log->getChangeFlags(),
$log->getPushEvent()->getRejectCode(),
$flag_names,
$reject_label,
$viewer->formatShortDateTime($log->getEpoch()),
);
}
@ -120,7 +125,7 @@ final class DiffusionPushLogListView extends AphrontView {
pht('Old'),
pht('New'),
pht('Flags'),
pht('Code'),
pht('Result'),
pht('Date'),
))
->setColumnClasses(
@ -135,6 +140,8 @@ final class DiffusionPushLogListView extends AphrontView {
'wide',
'n',
'n',
'',
'',
'right',
))
->setColumnVisibility(
@ -142,7 +149,7 @@ final class DiffusionPushLogListView extends AphrontView {
true,
true,
true,
true,
$remotes_visible,
true,
$any_host,
));

View file

@ -272,8 +272,12 @@ final class PhabricatorFile extends PhabricatorFileDAO
$file->setByteSize($length);
// NOTE: Once we receive the first chunk, we'll detect its MIME type and
// update the parent file. This matters for large media files like video.
// update the parent file if a MIME type hasn't been provided. This matters
// for large media files like video.
$mime_type = idx($params, 'mime-type');
if (!strlen($mime_type)) {
$file->setMimeType('application/octet-stream');
}
$chunked_hash = idx($params, 'chunkedHash');

View file

@ -6,6 +6,8 @@ abstract class PhabricatorFileUploadSource
private $name;
private $relativeTTL;
private $viewPolicy;
private $mimeType;
private $authorPHID;
private $rope;
private $data;
@ -51,6 +53,24 @@ abstract class PhabricatorFileUploadSource
return $this->byteLimit;
}
public function setMIMEType($mime_type) {
$this->mimeType = $mime_type;
return $this;
}
public function getMIMEType() {
return $this->mimeType;
}
public function setAuthorPHID($author_phid) {
$this->authorPHID = $author_phid;
return $this;
}
public function getAuthorPHID() {
return $this->authorPHID;
}
public function uploadFile() {
if (!$this->shouldChunkFile()) {
return $this->writeSingleFile();
@ -245,6 +265,16 @@ abstract class PhabricatorFileUploadSource
$parameters['ttl.relative'] = $ttl;
}
$mime_type = $this->getMimeType();
if ($mime_type !== null) {
$parameters['mime-type'] = $mime_type;
}
$author_phid = $this->getAuthorPHID();
if ($author_phid !== null) {
$parameters['authorPHID'] = $author_phid;
}
return $parameters;
}

View file

@ -50,14 +50,13 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication {
return array(
'/T(?P<id>[1-9]\d*)' => 'ManiphestTaskDetailController',
'/maniphest/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'ManiphestTaskListController',
$this->getQueryRoutePattern() => 'ManiphestTaskListController',
'report/(?:(?P<view>\w+)/)?' => 'ManiphestReportController',
$this->getBulkRoutePattern('bulk/') => 'ManiphestBulkEditController',
'task/' => array(
$this->getEditRoutePattern('edit/')
=> 'ManiphestTaskEditController',
),
'export/(?P<key>[^/]+)/' => 'ManiphestExportController',
'subpriority/' => 'ManiphestSubpriorityController',
),
);

View file

@ -1,135 +0,0 @@
<?php
final class ManiphestExportController extends ManiphestController {
/**
* @phutil-external-symbol class PHPExcel
* @phutil-external-symbol class PHPExcel_IOFactory
* @phutil-external-symbol class PHPExcel_Style_NumberFormat
* @phutil-external-symbol class PHPExcel_Cell_DataType
*/
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$key = $request->getURIData('key');
$ok = @include_once 'PHPExcel.php';
if (!$ok) {
$dialog = $this->newDialog();
$inst1 = pht(
'This system does not have PHPExcel installed. This software '.
'component is required to export tasks to Excel. Have your system '.
'administrator install it from:');
$inst2 = pht(
'Your PHP "%s" needs to be updated to include the '.
'PHPExcel Classes directory.',
'include_path');
$dialog->setTitle(pht('Excel Export Not Configured'));
$dialog->appendChild(hsprintf(
'<p>%s</p>'.
'<br />'.
'<p>'.
'<a href="https://github.com/PHPOffice/PHPExcel">'.
'https://github.com/PHPOffice/PHPExcel'.
'</a>'.
'</p>'.
'<br />'.
'<p>%s</p>',
$inst1,
$inst2));
$dialog->addCancelButton('/maniphest/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
// TODO: PHPExcel has a dependency on the PHP zip extension. We should test
// for that here, since it fatals if we don't have the ZipArchive class.
$saved = id(new PhabricatorSavedQueryQuery())
->setViewer($viewer)
->withQueryKeys(array($key))
->executeOne();
if (!$saved) {
$engine = id(new ManiphestTaskSearchEngine())
->setViewer($viewer);
if ($engine->isBuiltinQuery($key)) {
$saved = $engine->buildSavedQueryFromBuiltin($key);
}
if (!$saved) {
return new Aphront404Response();
}
}
$formats = ManiphestExcelFormat::loadAllFormats();
$export_formats = array();
foreach ($formats as $format_class => $format_object) {
$export_formats[$format_class] = $format_object->getName();
}
if (!$request->isDialogFormPost()) {
$dialog = new AphrontDialogView();
$dialog->setUser($viewer);
$dialog->setTitle(pht('Export Tasks to Excel'));
$dialog->appendChild(
phutil_tag(
'p',
array(),
pht('Do you want to export the query results to Excel?')));
$form = id(new PHUIFormLayoutView())
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Format:'))
->setName('excel-format')
->setOptions($export_formats));
$dialog->appendChild($form);
$dialog->addCancelButton('/maniphest/');
$dialog->addSubmitButton(pht('Export to Excel'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$format = idx($formats, $request->getStr('excel-format'));
if ($format === null) {
throw new Exception(pht('Excel format object not found.'));
}
$saved->makeEphemeral();
$saved->setParameter('limit', PHP_INT_MAX);
$engine = id(new ManiphestTaskSearchEngine())
->setViewer($viewer);
$query = $engine->buildQueryFromSavedQuery($saved);
$query->setViewer($viewer);
$tasks = $query->execute();
$all_projects = array_mergev(mpull($tasks, 'getProjectPHIDs'));
$all_assigned = mpull($tasks, 'getOwnerPHID');
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(array_merge($all_projects, $all_assigned))
->execute();
$workbook = new PHPExcel();
$format->buildWorkbook($workbook, $tasks, $handles, $viewer);
$writer = PHPExcel_IOFactory::createWriter($workbook, 'Excel2007');
ob_start();
$writer->save('php://output');
$data = ob_get_clean();
$mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
return id(new AphrontFileResponse())
->setMimeType($mime)
->setDownload($format->getFileName().'.xlsx')
->setContent($data);
}
}

View file

@ -196,6 +196,7 @@ EODOCS
pht('New task owner, or `null` to unassign.'))
->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE)
->setIsCopyable(true)
->setIsNullable(true)
->setSingleValue($object->getOwnerPHID())
->setCommentActionLabel(pht('Assign / Claim'))
->setCommentActionValue($owner_value),

View file

@ -1,140 +0,0 @@
<?php
final class ManiphestExcelDefaultFormat extends ManiphestExcelFormat {
public function getName() {
return pht('Default');
}
public function getFileName() {
return 'maniphest_tasks_'.date('Ymd');
}
/**
* @phutil-external-symbol class PHPExcel
* @phutil-external-symbol class PHPExcel_IOFactory
* @phutil-external-symbol class PHPExcel_Style_NumberFormat
* @phutil-external-symbol class PHPExcel_Cell_DataType
*/
public function buildWorkbook(
PHPExcel $workbook,
array $tasks,
array $handles,
PhabricatorUser $user) {
$sheet = $workbook->setActiveSheetIndex(0);
$sheet->setTitle(pht('Tasks'));
$widths = array(
null,
15,
null,
10,
15,
15,
60,
30,
20,
100,
);
foreach ($widths as $col => $width) {
if ($width !== null) {
$sheet->getColumnDimension($this->col($col))->setWidth($width);
}
}
$status_map = ManiphestTaskStatus::getTaskStatusMap();
$pri_map = ManiphestTaskPriority::getTaskPriorityMap();
$date_format = null;
$rows = array();
$rows[] = array(
pht('ID'),
pht('Owner'),
pht('Status'),
pht('Priority'),
pht('Date Created'),
pht('Date Updated'),
pht('Title'),
pht('Tags'),
pht('URI'),
pht('Description'),
);
$is_date = array(
false,
false,
false,
false,
true,
true,
false,
false,
false,
false,
);
$header_format = array(
'font' => array(
'bold' => true,
),
);
foreach ($tasks as $task) {
$task_owner = null;
if ($task->getOwnerPHID()) {
$task_owner = $handles[$task->getOwnerPHID()]->getName();
}
$projects = array();
foreach ($task->getProjectPHIDs() as $phid) {
$projects[] = $handles[$phid]->getName();
}
$projects = implode(', ', $projects);
$rows[] = array(
'T'.$task->getID(),
$task_owner,
idx($status_map, $task->getStatus(), '?'),
idx($pri_map, $task->getPriority(), '?'),
$this->computeExcelDate($task->getDateCreated()),
$this->computeExcelDate($task->getDateModified()),
$task->getTitle(),
$projects,
PhabricatorEnv::getProductionURI('/T'.$task->getID()),
id(new PhutilUTF8StringTruncator())
->setMaximumBytes(512)
->truncateString($task->getDescription()),
);
}
foreach ($rows as $row => $cols) {
foreach ($cols as $col => $spec) {
$cell_name = $this->col($col).($row + 1);
$cell = $sheet
->setCellValue($cell_name, $spec, $return_cell = true);
if ($row == 0) {
$sheet->getStyle($cell_name)->applyFromArray($header_format);
}
if ($is_date[$col]) {
$code = PHPExcel_Style_NumberFormat::FORMAT_DATE_YYYYMMDD2;
$sheet
->getStyle($cell_name)
->getNumberFormat()
->setFormatCode($code);
} else {
$cell->setDataType(PHPExcel_Cell_DataType::TYPE_STRING);
}
}
}
}
private function col($n) {
return chr(ord('A') + $n);
}
}

View file

@ -1,35 +0,0 @@
<?php
abstract class ManiphestExcelFormat extends Phobject {
final public static function loadAllFormats() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setSortMethod('getOrder')
->execute();
}
abstract public function getName();
abstract public function getFileName();
public function getOrder() {
return 0;
}
protected function computeExcelDate($epoch) {
$seconds_per_day = (60 * 60 * 24);
$offset = ($seconds_per_day * 25569);
return ($epoch + $offset) / $seconds_per_day;
}
/**
* @phutil-external-symbol class PHPExcel
*/
abstract public function buildWorkbook(
PHPExcel $workbook,
array $tasks,
array $handles,
PhabricatorUser $user);
}

View file

@ -1,10 +0,0 @@
<?php
final class ManiphestExcelFormatTestCase extends PhabricatorTestCase {
public function testLoadAllFormats() {
ManiphestExcelFormat::loadAllFormats();
$this->assertTrue(true);
}
}

View file

@ -432,4 +432,111 @@ final class ManiphestTaskSearchEngine
return $view;
}
protected function newExportFields() {
$fields = array(
id(new PhabricatorStringExportField())
->setKey('monogram')
->setLabel(pht('Monogram')),
id(new PhabricatorPHIDExportField())
->setKey('authorPHID')
->setLabel(pht('Author PHID')),
id(new PhabricatorStringExportField())
->setKey('author')
->setLabel(pht('Author')),
id(new PhabricatorPHIDExportField())
->setKey('ownerPHID')
->setLabel(pht('Owner PHID')),
id(new PhabricatorStringExportField())
->setKey('owner')
->setLabel(pht('Owner')),
id(new PhabricatorStringExportField())
->setKey('status')
->setLabel(pht('Status')),
id(new PhabricatorStringExportField())
->setKey('statusName')
->setLabel(pht('Status Name')),
id(new PhabricatorStringExportField())
->setKey('priority')
->setLabel(pht('Priority')),
id(new PhabricatorStringExportField())
->setKey('priorityName')
->setLabel(pht('Priority Name')),
id(new PhabricatorStringExportField())
->setKey('subtype')
->setLabel('Subtype'),
id(new PhabricatorURIExportField())
->setKey('uri')
->setLabel(pht('URI')),
id(new PhabricatorStringExportField())
->setKey('title')
->setLabel(pht('Title')),
id(new PhabricatorStringExportField())
->setKey('description')
->setLabel(pht('Description')),
);
if (ManiphestTaskPoints::getIsEnabled()) {
$fields[] = id(new PhabricatorIntExportField())
->setKey('points')
->setLabel('Points');
}
return $fields;
}
protected function newExportData(array $tasks) {
$viewer = $this->requireViewer();
$phids = array();
foreach ($tasks as $task) {
$phids[] = $task->getAuthorPHID();
$phids[] = $task->getOwnerPHID();
}
$handles = $viewer->loadHandles($phids);
$export = array();
foreach ($tasks as $task) {
$author_phid = $task->getAuthorPHID();
if ($author_phid) {
$author_name = $handles[$author_phid]->getName();
} else {
$author_name = null;
}
$owner_phid = $task->getOwnerPHID();
if ($owner_phid) {
$owner_name = $handles[$owner_phid]->getName();
} else {
$owner_name = null;
}
$status_value = $task->getStatus();
$status_name = ManiphestTaskStatus::getTaskStatusName($status_value);
$priority_value = $task->getPriority();
$priority_name = ManiphestTaskPriority::getTaskPriorityName(
$priority_value);
$export[] = array(
'monogram' => $task->getMonogram(),
'authorPHID' => $author_phid,
'author' => $author_name,
'ownerPHID' => $owner_phid,
'owner' => $owner_name,
'status' => $status_value,
'statusName' => $status_name,
'priority' => $priority_value,
'priorityName' => $priority_name,
'points' => $task->getPoints(),
'subtype' => $task->getSubtype(),
'title' => $task->getTitle(),
'uri' => PhabricatorEnv::getProductionURI($task->getURI()),
'description' => $task->getDescription(),
);
}
return $export;
}
}

View file

@ -175,8 +175,7 @@ final class ManiphestTaskResultListView extends ManiphestView {
}
if (!$user->isLoggedIn()) {
// Don't show the batch editor or excel export for logged-out users.
// Technically we //could// let them export, but ehh.
// Don't show the batch editor for logged-out users.
return null;
}
@ -220,14 +219,6 @@ final class ManiphestTaskResultListView extends ManiphestView {
),
pht("Bulk Edit Selected \xC2\xBB"));
$export = javelin_tag(
'a',
array(
'href' => '/maniphest/export/'.$saved_query->getQueryKey().'/',
'class' => 'button button-grey',
),
pht('Export to Excel'));
$hidden = phutil_tag(
'div',
array(
@ -239,14 +230,12 @@ final class ManiphestTaskResultListView extends ManiphestView {
'<table class="maniphest-batch-editor-layout">'.
'<tr>'.
'<td>%s%s</td>'.
'<td>%s</td>'.
'<td id="batch-select-status-cell">%s</td>'.
'<td class="batch-select-submit-cell">%s%s</td>'.
'</tr>'.
'</table>',
$select_all,
$select_none,
$export,
'',
$submit,
$hidden);

View file

@ -42,8 +42,9 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication {
return array(
'/people/' => array(
$this->getQueryRoutePattern() => 'PhabricatorPeopleListController',
'logs/(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhabricatorPeopleLogsController',
'logs/' => array(
$this->getQueryRoutePattern() => 'PhabricatorPeopleLogsController',
),
'invite/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhabricatorPeopleInviteListController',

View file

@ -9,6 +9,8 @@ final class PhabricatorPeopleLogQuery
private $sessionKeys;
private $actions;
private $remoteAddressPrefix;
private $dateCreatedMin;
private $dateCreatedMax;
public function withActorPHIDs(array $actor_phids) {
$this->actorPHIDs = $actor_phids;
@ -40,70 +42,81 @@ final class PhabricatorPeopleLogQuery
return $this;
}
protected function loadPage() {
$table = new PhabricatorUserLog();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
public function withDateCreatedBetween($min, $max) {
$this->dateCreatedMin = $min;
$this->dateCreatedMax = $max;
return $this;
}
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
public function newResultObject() {
return new PhabricatorUserLog();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->actorPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'actorPHID IN (%Ls)',
$this->actorPHIDs);
}
if ($this->userPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'userPHID IN (%Ls)',
$this->userPHIDs);
}
if ($this->relatedPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
'actorPHID IN (%Ls) OR userPHID IN (%Ls)',
$conn,
'(actorPHID IN (%Ls) OR userPHID IN (%Ls))',
$this->relatedPHIDs,
$this->relatedPHIDs);
}
if ($this->sessionKeys !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'session IN (%Ls)',
$this->sessionKeys);
}
if ($this->actions !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'action IN (%Ls)',
$this->actions);
}
if ($this->remoteAddressPrefix !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'remoteAddr LIKE %>',
$this->remoteAddressPrefix);
}
$where[] = $this->buildPagingClause($conn_r);
if ($this->dateCreatedMin !== null) {
$where[] = qsprintf(
$conn,
'dateCreated >= %d',
$this->dateCreatedMin);
}
return $this->formatWhereClause($where);
if ($this->dateCreatedMax !== null) {
$where[] = qsprintf(
$conn,
'dateCreated <= %d',
$this->dateCreatedMax);
}
return $where;
}
public function getQueryApplicationClass() {

View file

@ -15,34 +15,8 @@ final class PhabricatorPeopleLogSearchEngine
return 500;
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
$saved->setParameter(
'userPHIDs',
$this->readUsersFromRequest($request, 'users'));
$saved->setParameter(
'actorPHIDs',
$this->readUsersFromRequest($request, 'actors'));
$saved->setParameter(
'actions',
$this->readListFromRequest($request, 'actions'));
$saved->setParameter(
'ip',
$request->getStr('ip'));
$saved->setParameter(
'sessions',
$this->readListFromRequest($request, 'sessions'));
return $saved;
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new PhabricatorPeopleLogQuery());
public function newQuery() {
$query = new PhabricatorPeopleLogQuery();
// NOTE: If the viewer isn't an administrator, always restrict the query to
// related records. This echoes the policy logic of these logs. This is
@ -54,82 +28,73 @@ final class PhabricatorPeopleLogSearchEngine
$query->withRelatedPHIDs(array($viewer->getPHID()));
}
$actor_phids = $saved->getParameter('actorPHIDs', array());
if ($actor_phids) {
$query->withActorPHIDs($actor_phids);
return $query;
}
$user_phids = $saved->getParameter('userPHIDs', array());
if ($user_phids) {
$query->withUserPHIDs($user_phids);
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['userPHIDs']) {
$query->withUserPHIDs($map['userPHIDs']);
}
$actions = $saved->getParameter('actions', array());
if ($actions) {
$query->withActions($actions);
if ($map['actorPHIDs']) {
$query->withActorPHIDs($map['actorPHIDs']);
}
$remote_prefix = $saved->getParameter('ip');
if (strlen($remote_prefix)) {
$query->withRemoteAddressprefix($remote_prefix);
if ($map['actions']) {
$query->withActions($map['actions']);
}
$sessions = $saved->getParameter('sessions', array());
if ($sessions) {
$query->withSessionKeys($sessions);
if (strlen($map['ip'])) {
$query->withRemoteAddressPrefix($map['ip']);
}
if ($map['sessions']) {
$query->withSessionKeys($map['sessions']);
}
if ($map['createdStart'] || $map['createdEnd']) {
$query->withDateCreatedBetween(
$map['createdStart'],
$map['createdEnd']);
}
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
$actor_phids = $saved->getParameter('actorPHIDs', array());
$user_phids = $saved->getParameter('userPHIDs', array());
$actions = $saved->getParameter('actions', array());
$remote_prefix = $saved->getParameter('ip');
$sessions = $saved->getParameter('sessions', array());
$actions = array_fuse($actions);
$action_control = id(new AphrontFormCheckboxControl())
->setLabel(pht('Actions'));
$action_types = PhabricatorUserLog::getActionTypeMap();
foreach ($action_types as $type => $label) {
$action_control->addCheckbox(
'actions[]',
$type,
$label,
isset($actions[$label]));
}
$form
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorPeopleDatasource())
->setName('actors')
->setLabel(pht('Actors'))
->setValue($actor_phids))
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorPeopleDatasource())
->setName('users')
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorUsersSearchField())
->setKey('userPHIDs')
->setAliases(array('users', 'user', 'userPHID'))
->setLabel(pht('Users'))
->setValue($user_phids))
->appendChild($action_control)
->appendChild(
id(new AphrontFormTextControl())
->setDescription(pht('Search for activity affecting specific users.')),
id(new PhabricatorUsersSearchField())
->setKey('actorPHIDs')
->setAliases(array('actors', 'actor', 'actorPHID'))
->setLabel(pht('Actors'))
->setDescription(pht('Search for activity by specific users.')),
id(new PhabricatorSearchCheckboxesField())
->setKey('actions')
->setLabel(pht('Actions'))
->setDescription(pht('Search for particular types of activity.'))
->setOptions(PhabricatorUserLog::getActionTypeMap()),
id(new PhabricatorSearchTextField())
->setKey('ip')
->setLabel(pht('Filter IP'))
->setName('ip')
->setValue($remote_prefix))
->appendChild(
id(new AphrontFormTextControl())
->setDescription(pht('Search for actions by remote address.')),
id(new PhabricatorSearchStringListField())
->setKey('sessions')
->setLabel(pht('Sessions'))
->setName('sessions')
->setValue(implode(', ', $sessions)));
->setDescription(pht('Search for activity in particular sessions.')),
id(new PhabricatorSearchDateField())
->setLabel(pht('Created After'))
->setKey('createdStart'),
id(new PhabricatorSearchDateField())
->setLabel(pht('Created Before'))
->setKey('createdEnd'),
);
}
protected function getURI($path) {
@ -156,19 +121,6 @@ final class PhabricatorPeopleLogSearchEngine
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function getRequiredHandlePHIDsForResultList(
array $logs,
PhabricatorSavedQuery $query) {
$phids = array();
foreach ($logs as $log) {
$phids[$log->getActorPHID()] = true;
$phids[$log->getUserPHID()] = true;
}
return array_keys($phids);
}
protected function renderResultList(
array $logs,
PhabricatorSavedQuery $query,
@ -179,16 +131,111 @@ final class PhabricatorPeopleLogSearchEngine
$table = id(new PhabricatorUserLogView())
->setUser($viewer)
->setLogs($logs)
->setHandles($handles);
->setLogs($logs);
if ($viewer->getIsAdmin()) {
$table->setSearchBaseURI($this->getApplicationURI('logs/'));
}
$result = new PhabricatorApplicationSearchResultView();
$result->setTable($table);
return $result;
return id(new PhabricatorApplicationSearchResultView())
->setTable($table);
}
protected function newExportFields() {
$viewer = $this->requireViewer();
$fields = array(
$fields[] = id(new PhabricatorPHIDExportField())
->setKey('actorPHID')
->setLabel(pht('Actor PHID')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('actor')
->setLabel(pht('Actor')),
$fields[] = id(new PhabricatorPHIDExportField())
->setKey('userPHID')
->setLabel(pht('User PHID')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('user')
->setLabel(pht('User')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('action')
->setLabel(pht('Action')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('actionName')
->setLabel(pht('Action Name')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('session')
->setLabel(pht('Session')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('old')
->setLabel(pht('Old Value')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('new')
->setLabel(pht('New Value')),
);
if ($viewer->getIsAdmin()) {
$fields[] = id(new PhabricatorStringExportField())
->setKey('remoteAddress')
->setLabel(pht('Remote Address'));
}
return $fields;
}
protected function newExportData(array $logs) {
$viewer = $this->requireViewer();
$phids = array();
foreach ($logs as $log) {
$phids[] = $log->getUserPHID();
$phids[] = $log->getActorPHID();
}
$handles = $viewer->loadHandles($phids);
$action_map = PhabricatorUserLog::getActionTypeMap();
$export = array();
foreach ($logs as $log) {
$user_phid = $log->getUserPHID();
if ($user_phid) {
$user_name = $handles[$user_phid]->getName();
} else {
$user_name = null;
}
$actor_phid = $log->getActorPHID();
if ($actor_phid) {
$actor_name = $handles[$actor_phid]->getName();
} else {
$actor_name = null;
}
$action = $log->getAction();
$action_name = idx($action_map, $action, pht('Unknown ("%s")', $action));
$map = array(
'actorPHID' => $actor_phid,
'actor' => $actor_name,
'userPHID' => $user_phid,
'user' => $user_name,
'action' => $action,
'actionName' => $action_name,
'session' => substr($log->getSession(), 0, 6),
'old' => $log->getOldValue(),
'new' => $log->getNewValue(),
);
if ($viewer->getIsAdmin()) {
$map['remoteAddress'] = $log->getRemoteAddr();
}
$export[] = $map;
}
return $export;
}
}

View file

@ -322,35 +322,23 @@ final class PhabricatorPeopleSearchEngine
protected function newExportFields() {
return array(
id(new PhabricatorIDExportField())
->setKey('id')
->setLabel(pht('ID')),
id(new PhabricatorPHIDExportField())
->setKey('phid')
->setLabel(pht('PHID')),
id(new PhabricatorStringExportField())
->setKey('username')
->setLabel(pht('Username')),
id(new PhabricatorStringExportField())
->setKey('realName')
->setLabel(pht('Real Name')),
id(new PhabricatorEpochExportField())
->setKey('created')
->setLabel(pht('Date Created')),
);
}
public function newExport(array $users) {
protected function newExportData(array $users) {
$viewer = $this->requireViewer();
$export = array();
foreach ($users as $user) {
$export[] = array(
'id' => $user->getID(),
'phid' => $user->getPHID(),
'username' => $user->getUsername(),
'realName' => $user->getRealName(),
'created' => $user->getDateCreated(),
);
}

View file

@ -3,7 +3,6 @@
final class PhabricatorUserLogView extends AphrontView {
private $logs;
private $handles;
private $searchBaseURI;
public function setSearchBaseURI($search_base_uri) {
@ -17,25 +16,46 @@ final class PhabricatorUserLogView extends AphrontView {
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function render() {
$logs = $this->logs;
$handles = $this->handles;
$viewer = $this->getUser();
$phids = array();
foreach ($logs as $log) {
$phids[] = $log->getActorPHID();
$phids[] = $log->getUserPHID();
}
$handles = $viewer->loadHandles($phids);
$action_map = PhabricatorUserLog::getActionTypeMap();
$base_uri = $this->searchBaseURI;
$viewer_phid = $viewer->getPHID();
$rows = array();
foreach ($logs as $log) {
$ip = $log->getRemoteAddr();
$session = substr($log->getSession(), 0, 6);
$actor_phid = $log->getActorPHID();
$user_phid = $log->getUserPHID();
if ($viewer->getIsAdmin()) {
$can_see_ip = true;
} else if ($viewer_phid == $actor_phid) {
// You can see the address if you took the action.
$can_see_ip = true;
} else if (!$actor_phid && ($viewer_phid == $user_phid)) {
// You can see the address if it wasn't authenticated and applied
// to you (partial login).
$can_see_ip = true;
} else {
// You can't see the address when an administrator disables your
// account, since it's their address.
$can_see_ip = false;
}
if ($can_see_ip) {
$ip = $log->getRemoteAddr();
if ($base_uri) {
$ip = phutil_tag(
'a',
@ -44,18 +64,31 @@ final class PhabricatorUserLogView extends AphrontView {
),
$ip);
}
} else {
$ip = null;
}
$action = $log->getAction();
$action_name = idx($action_map, $action, $action);
if ($actor_phid) {
$actor_name = $handles[$actor_phid]->renderLink();
} else {
$actor_name = null;
}
if ($user_phid) {
$user_name = $handles[$user_phid]->renderLink();
} else {
$user_name = null;
}
$rows[] = array(
phabricator_date($log->getDateCreated(), $viewer),
phabricator_time($log->getDateCreated(), $viewer),
$action_name,
$log->getActorPHID()
? $handles[$log->getActorPHID()]->getName()
: null,
$username = $handles[$log->getUserPHID()]->renderLink(),
$actor_name,
$user_name,
$ip,
$session,
);

View file

@ -7,6 +7,8 @@ final class PhabricatorRepositoryPullEventQuery
private $phids;
private $repositoryPHIDs;
private $pullerPHIDs;
private $epochMin;
private $epochMax;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -28,6 +30,12 @@ final class PhabricatorRepositoryPullEventQuery
return $this;
}
public function withEpochBetween($min, $max) {
$this->epochMin = $min;
$this->epochMax = $max;
return $this;
}
public function newResultObject() {
return new PhabricatorRepositoryPullEvent();
}
@ -103,6 +111,20 @@ final class PhabricatorRepositoryPullEventQuery
$this->pullerPHIDs);
}
if ($this->epochMin !== null) {
$where[] = qsprintf(
$conn,
'epoch >= %d',
$this->epochMin);
}
if ($this->epochMax !== null) {
$where[] = qsprintf(
$conn,
'epoch <= %d',
$this->epochMax);
}
return $where;
}

View file

@ -10,6 +10,8 @@ final class PhabricatorRepositoryPushLogQuery
private $refTypes;
private $newRefs;
private $pushEventPHIDs;
private $epochMin;
private $epochMax;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -46,19 +48,18 @@ final class PhabricatorRepositoryPushLogQuery
return $this;
}
public function withEpochBetween($min, $max) {
$this->epochMin = $min;
$this->epochMax = $max;
return $this;
}
public function newResultObject() {
return new PhabricatorRepositoryPushLog();
}
protected function loadPage() {
$table = new PhabricatorRepositoryPushLog();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
return $this->loadStandardPage($this->newResultObject());
}
protected function willFilterPage(array $logs) {
@ -82,61 +83,73 @@ final class PhabricatorRepositoryPushLogQuery
return $logs;
}
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids) {
if ($this->ids !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
if ($this->phids !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->repositoryPHIDs) {
if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
if ($this->pusherPHIDs) {
if ($this->pusherPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'pusherPHID in (%Ls)',
$this->pusherPHIDs);
}
if ($this->pushEventPHIDs) {
if ($this->pushEventPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'pushEventPHID in (%Ls)',
$this->pushEventPHIDs);
}
if ($this->refTypes) {
if ($this->refTypes !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'refType IN (%Ls)',
$this->refTypes);
}
if ($this->newRefs) {
if ($this->newRefs !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'refNew IN (%Ls)',
$this->newRefs);
}
$where[] = $this->buildPagingClause($conn_r);
if ($this->epochMin !== null) {
$where[] = qsprintf(
$conn,
'epoch >= %d',
$this->epochMin);
}
return $this->formatWhereClause($where);
if ($this->epochMax !== null) {
$where[] = qsprintf(
$conn,
'epoch <= %d',
$this->epochMax);
}
return $where;
}
public function getQueryApplicationClass() {

View file

@ -26,6 +26,12 @@ final class PhabricatorRepositoryPushLogSearchEngine
$query->withPusherPHIDs($map['pusherPHIDs']);
}
if ($map['createdStart'] || $map['createdEnd']) {
$query->withEpochBetween(
$map['createdStart'],
$map['createdEnd']);
}
return $query;
}
@ -44,6 +50,12 @@ final class PhabricatorRepositoryPushLogSearchEngine
->setLabel(pht('Pushers'))
->setDescription(
pht('Search for pull logs by specific users.')),
id(new PhabricatorSearchDateField())
->setLabel(pht('Created After'))
->setKey('createdStart'),
id(new PhabricatorSearchDateField())
->setLabel(pht('Created Before'))
->setKey('createdEnd'),
);
}
@ -82,4 +94,146 @@ final class PhabricatorRepositoryPushLogSearchEngine
->setTable($table);
}
protected function newExportFields() {
$viewer = $this->requireViewer();
$fields = array(
$fields[] = id(new PhabricatorIDExportField())
->setKey('pushID')
->setLabel(pht('Push ID')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('protocol')
->setLabel(pht('Protocol')),
$fields[] = id(new PhabricatorPHIDExportField())
->setKey('repositoryPHID')
->setLabel(pht('Repository PHID')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('repository')
->setLabel(pht('Repository')),
$fields[] = id(new PhabricatorPHIDExportField())
->setKey('pusherPHID')
->setLabel(pht('Pusher PHID')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('pusher')
->setLabel(pht('Pusher')),
$fields[] = id(new PhabricatorPHIDExportField())
->setKey('devicePHID')
->setLabel(pht('Device PHID')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('device')
->setLabel(pht('Device')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('type')
->setLabel(pht('Ref Type')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('name')
->setLabel(pht('Ref Name')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('old')
->setLabel(pht('Ref Old')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('new')
->setLabel(pht('Ref New')),
$fields[] = id(new PhabricatorIntExportField())
->setKey('flags')
->setLabel(pht('Flags')),
$fields[] = id(new PhabricatorStringListExportField())
->setKey('flagNames')
->setLabel(pht('Flag Names')),
$fields[] = id(new PhabricatorIntExportField())
->setKey('result')
->setLabel(pht('Result')),
$fields[] = id(new PhabricatorStringExportField())
->setKey('resultName')
->setLabel(pht('Result Name')),
);
if ($viewer->getIsAdmin()) {
$fields[] = id(new PhabricatorStringExportField())
->setKey('remoteAddress')
->setLabel(pht('Remote Address'));
}
return $fields;
}
protected function newExportData(array $logs) {
$viewer = $this->requireViewer();
$phids = array();
foreach ($logs as $log) {
$phids[] = $log->getPusherPHID();
$phids[] = $log->getDevicePHID();
$phids[] = $log->getPushEvent()->getRepositoryPHID();
}
$handles = $viewer->loadHandles($phids);
$flag_map = PhabricatorRepositoryPushLog::getFlagDisplayNames();
$reject_map = PhabricatorRepositoryPushLog::getRejectCodeDisplayNames();
$export = array();
foreach ($logs as $log) {
$event = $log->getPushEvent();
$repository_phid = $event->getRepositoryPHID();
if ($repository_phid) {
$repository_name = $handles[$repository_phid]->getName();
} else {
$repository_name = null;
}
$pusher_phid = $log->getPusherPHID();
if ($pusher_phid) {
$pusher_name = $handles[$pusher_phid]->getName();
} else {
$pusher_name = null;
}
$device_phid = $log->getDevicePHID();
if ($device_phid) {
$device_name = $handles[$device_phid]->getName();
} else {
$device_name = null;
}
$flags = $log->getChangeFlags();
$flag_names = array();
foreach ($flag_map as $flag_key => $flag_name) {
if (($flags & $flag_key) === $flag_key) {
$flag_names[] = $flag_name;
}
}
$result = $event->getRejectCode();
$result_name = idx($reject_map, $result, pht('Unknown ("%s")', $result));
$map = array(
'pushID' => $event->getID(),
'protocol' => $event->getRemoteProtocol(),
'repositoryPHID' => $repository_phid,
'repository' => $repository_name,
'pusherPHID' => $pusher_phid,
'pusher' => $pusher_name,
'devicePHID' => $device_phid,
'device' => $device_name,
'type' => $log->getRefType(),
'name' => $log->getRefName(),
'old' => $log->getRefOld(),
'new' => $log->getRefNew(),
'flags' => $flags,
'flagNames' => $flag_names,
'result' => $result,
'resultName' => $result_name,
);
if ($viewer->getIsAdmin()) {
$map['remoteAddress'] = $event->getRemoteAddress();
}
$export[] = $map;
}
return $export;
}
}

View file

@ -55,6 +55,28 @@ final class PhabricatorRepositoryPushLog
->setPusherPHID($viewer->getPHID());
}
public static function getFlagDisplayNames() {
return array(
self::CHANGEFLAG_ADD => pht('Create'),
self::CHANGEFLAG_DELETE => pht('Delete'),
self::CHANGEFLAG_APPEND => pht('Append'),
self::CHANGEFLAG_REWRITE => pht('Rewrite'),
self::CHANGEFLAG_DANGEROUS => pht('Dangerous'),
self::CHANGEFLAG_ENORMOUS => pht('Enormous'),
);
}
public static function getRejectCodeDisplayNames() {
return array(
self::REJECT_ACCEPT => pht('Accepted'),
self::REJECT_DANGEROUS => pht('Rejected: Dangerous'),
self::REJECT_HERALD => pht('Rejected: Herald'),
self::REJECT_EXTERNAL => pht('Rejected: External Hook'),
self::REJECT_BROKEN => pht('Rejected: Broken'),
self::REJECT_ENORMOUS => pht('Rejected: Enormous'),
);
}
public static function getHeraldChangeFlagConditionOptions() {
return array(
self::CHANGEFLAG_ADD =>
@ -102,6 +124,9 @@ final class PhabricatorRepositoryPushLog
'key_pusher' => array(
'columns' => array('pusherPHID'),
),
'key_epoch' => array(
'columns' => array('epoch'),
),
),
) + parent::getConfiguration();
}

View file

@ -15,6 +15,7 @@ final class PhabricatorRepositoryCommitHeraldWorker
protected function parseCommit(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
$viewer = PhabricatorUser::getOmnipotentUser();
if ($this->shouldSkipImportStep()) {
// This worker has no followup tasks, so we can just bail out
@ -50,7 +51,7 @@ final class PhabricatorRepositoryCommitHeraldWorker
id(new PhabricatorDiffusionApplication())->getPHID());
$editor = id(new PhabricatorAuditEditor())
->setActor(PhabricatorUser::getOmnipotentUser())
->setActor($viewer)
->setActingAsPHID($acting_as_phid)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
@ -69,29 +70,6 @@ final class PhabricatorRepositoryCommitHeraldWorker
'committerPHID' => $data->getCommitDetail('committerPHID'),
));
$reverts_refs = id(new DifferentialCustomFieldRevertsParser())
->parseCorpus($data->getCommitMessage());
$reverts = array_mergev(ipull($reverts_refs, 'monograms'));
if ($reverts) {
$reverted_commits = id(new DiffusionCommitQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withRepository($repository)
->withIdentifiers($reverts)
->execute();
$reverted_commit_phids = mpull($reverted_commits, 'getPHID', 'getPHID');
// NOTE: Skip any write attempts if a user cleverly implies a commit
// reverts itself.
unset($reverted_commit_phids[$commit->getPHID()]);
$reverts_edge = DiffusionCommitRevertsCommitEdgeType::EDGECONST;
$xactions[] = id(new PhabricatorAuditTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $reverts_edge)
->setNewValue(array('+' => array_fuse($reverted_commit_phids)));
}
try {
$raw_patch = $this->loadRawPatchText($repository, $commit);
} catch (Exception $ex) {

View file

@ -7,6 +7,7 @@ final class PhabricatorApplicationSearchController
private $navigation;
private $queryKey;
private $preface;
private $activeQuery;
public function setPreface($preface) {
$this->preface = $preface;
@ -45,6 +46,14 @@ final class PhabricatorApplicationSearchController
return $this->searchEngine;
}
protected function getActiveQuery() {
if (!$this->activeQuery) {
throw new Exception(pht('There is no active query yet.'));
}
return $this->activeQuery;
}
protected function validateDelegatingController() {
$parent = $this->getDelegatingController();
@ -154,10 +163,12 @@ final class PhabricatorApplicationSearchController
$saved_query = $engine->buildSavedQueryFromRequest($request);
// Save the query to generate a query key, so "Save Custom Query..." and
// other features like Maniphest's "Export..." work correctly.
// other features like "Bulk Edit" and "Export Data" work correctly.
$engine->saveQuery($saved_query);
}
$this->activeQuery = $saved_query;
$nav->selectFilter(
'query/'.$saved_query->getQueryKey(),
'query/advanced');
@ -410,20 +421,72 @@ final class PhabricatorApplicationSearchController
if ($named_query) {
$filename = $named_query->getQueryName();
$sheet_title = $named_query->getQueryName();
} else {
$filename = $engine->getResultTypeDescription();
$sheet_title = $engine->getResultTypeDescription();
}
$filename = phutil_utf8_strtolower($filename);
$filename = PhabricatorFile::normalizeFileName($filename);
$formats = PhabricatorExportFormat::getAllEnabledExportFormats();
$format_options = mpull($formats, 'getExportFormatName');
$all_formats = PhabricatorExportFormat::getAllExportFormats();
$available_options = array();
$unavailable_options = array();
$formats = array();
$unavailable_formats = array();
foreach ($all_formats as $key => $format) {
if ($format->isExportFormatEnabled()) {
$available_options[$key] = $format->getExportFormatName();
$formats[$key] = $format;
} else {
$unavailable_options[$key] = pht(
'%s (Not Available)',
$format->getExportFormatName());
$unavailable_formats[$key] = $format;
}
}
$format_options = $available_options + $unavailable_options;
// Try to default to the format the user used last time. If you just
// exported to Excel, you probably want to export to Excel again.
$format_key = $this->readExportFormatPreference();
if (!isset($formats[$format_key])) {
$format_key = head_key($format_options);
}
// Check if this is a large result set or not. If we're exporting a
// large amount of data, we'll build the actual export file in the daemons.
$threshold = 1000;
$query = $engine->buildQueryFromSavedQuery($saved_query);
$pager = $engine->newPagerForSavedQuery($saved_query);
$pager->setPageSize($threshold + 1);
$objects = $engine->executeQuery($query, $pager);
$object_count = count($objects);
$is_large_export = ($object_count > $threshold);
$errors = array();
$e_format = null;
if ($request->isFormPost()) {
$format_key = $request->getStr('format');
if (isset($unavailable_formats[$format_key])) {
$unavailable = $unavailable_formats[$format_key];
$instructions = $unavailable->getInstallInstructions();
$markup = id(new PHUIRemarkupView($viewer, $instructions))
->setRemarkupOption(
PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS,
false);
return $this->newDialog()
->setTitle(pht('Export Format Not Available'))
->appendChild($markup)
->addCancelButton($cancel_uri, pht('Done'));
}
$format = idx($formats, $format_key);
if (!$format) {
@ -432,54 +495,23 @@ final class PhabricatorApplicationSearchController
}
if (!$errors) {
$query = $engine->buildQueryFromSavedQuery($saved_query);
$this->writeExportFormatPreference($format_key);
// NOTE: We aren't reading the pager from the request. Exports always
// affect the entire result set.
$pager = $engine->newPagerForSavedQuery($saved_query);
$pager->setPageSize(0x7FFFFFFF);
$export_engine = id(new PhabricatorExportEngine())
->setViewer($viewer)
->setSearchEngine($engine)
->setSavedQuery($saved_query)
->setTitle($sheet_title)
->setFilename($filename)
->setExportFormat($format);
$objects = $engine->executeQuery($query, $pager);
if ($is_large_export) {
$job = $export_engine->newBulkJob($request);
$extension = $format->getFileExtension();
$mime_type = $format->getMIMEContentType();
$filename = $filename.'.'.$extension;
$format = clone $format;
$format->setViewer($viewer);
$export_data = $engine->newExport($objects);
if (count($export_data) !== count($objects)) {
throw new Exception(
pht(
'Search engine exported the wrong number of objects, expected '.
'%s but got %s.',
phutil_count($objects),
phutil_count($export_data)));
}
$objects = array_values($objects);
$export_data = array_values($export_data);
$field_list = $engine->newExportFieldList();
$field_list = mpull($field_list, null, 'getKey');
for ($ii = 0; $ii < count($objects); $ii++) {
$format->addObject($objects[$ii], $field_list, $export_data[$ii]);
}
$export_result = $format->newFileData();
$file = PhabricatorFile::newFromFileData(
$export_result,
array(
'name' => $filename,
'authorPHID' => $viewer->getPHID(),
'ttl.relative' => phutil_units('15 minutes in seconds'),
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
'mime-type' => $mime_type,
));
return id(new AphrontRedirectResponse())
->setURI($job->getMonitorURI());
} else {
$file = $export_engine->exportFile();
return $this->newDialog()
->setTitle(pht('Download Results'))
@ -491,6 +523,7 @@ final class PhabricatorApplicationSearchController
->addSubmitButton(pht('Download Data'));
}
}
}
$export_form = id(new AphrontFormView())
->setViewer($viewer)
@ -499,6 +532,7 @@ final class PhabricatorApplicationSearchController
->setName('format')
->setLabel(pht('Format'))
->setError($e_format)
->setValue($format_key)
->setOptions($format_options));
return $this->newDialog()
@ -844,10 +878,8 @@ final class PhabricatorApplicationSearchController
$engine = $this->getSearchEngine();
$engine_class = get_class($engine);
$query_key = $this->getQueryKey();
if (!$query_key) {
$query_key = $engine->getDefaultQueryKey();
}
$query_key = $this->getActiveQuery()->getQueryKey();
$can_use = $engine->canUseInPanelContext();
$is_installed = PhabricatorApplication::isClassInstalledForViewer(
@ -914,4 +946,32 @@ final class PhabricatorApplicationSearchController
return true;
}
private function readExportFormatPreference() {
$viewer = $this->getViewer();
$export_key = PhabricatorPolicyFavoritesSetting::SETTINGKEY;
return $viewer->getUserSetting($export_key);
}
private function writeExportFormatPreference($value) {
$viewer = $this->getViewer();
$request = $this->getRequest();
if (!$viewer->isLoggedIn()) {
return;
}
$export_key = PhabricatorPolicyFavoritesSetting::SETTINGKEY;
$preferences = PhabricatorUserPreferences::loadUserPreferences($viewer);
$editor = id(new PhabricatorUserPreferencesEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$xactions = array();
$xactions[] = $preferences->newTransaction($export_key, $value);
$editor->applyTransactions($preferences, $xactions);
}
}

View file

@ -1455,11 +1455,145 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
}
final public function newExportFieldList() {
return $this->newExportFields();
$object = $this->newResultObject();
$builtin_fields = array(
id(new PhabricatorIDExportField())
->setKey('id')
->setLabel(pht('ID')),
);
if ($object->getConfigOption(LiskDAO::CONFIG_AUX_PHID)) {
$builtin_fields[] = id(new PhabricatorPHIDExportField())
->setKey('phid')
->setLabel(pht('PHID'));
}
$fields = mpull($builtin_fields, null, 'getKey');
$export_fields = $this->newExportFields();
foreach ($export_fields as $export_field) {
$key = $export_field->getKey();
if (isset($fields[$key])) {
throw new Exception(
pht(
'Search engine ("%s") defines an export field with a key ("%s") '.
'that collides with another field. Each field must have a '.
'unique key.',
get_class($this),
$key));
}
$fields[$key] = $export_field;
}
$extensions = $this->newExportExtensions();
foreach ($extensions as $extension) {
$extension_fields = $extension->newExportFields();
foreach ($extension_fields as $extension_field) {
$key = $extension_field->getKey();
if (isset($fields[$key])) {
throw new Exception(
pht(
'Export engine extension ("%s") defines an export field with '.
'a key ("%s") that collides with another field. Each field '.
'must have a unique key.',
get_class($extension_field),
$key));
}
$fields[$key] = $extension_field;
}
}
return $fields;
}
final public function newExport(array $objects) {
$object = $this->newResultObject();
$has_phid = $object->getConfigOption(LiskDAO::CONFIG_AUX_PHID);
$objects = array_values($objects);
$n = count($objects);
$maps = array();
foreach ($objects as $object) {
$map = array(
'id' => $object->getID(),
);
if ($has_phid) {
$map['phid'] = $object->getPHID();
}
$maps[] = $map;
}
$export_data = $this->newExportData($objects);
$export_data = array_values($export_data);
if (count($export_data) !== count($objects)) {
throw new Exception(
pht(
'Search engine ("%s") exported the wrong number of objects, '.
'expected %s but got %s.',
get_class($this),
phutil_count($objects),
phutil_count($export_data)));
}
for ($ii = 0; $ii < $n; $ii++) {
$maps[$ii] += $export_data[$ii];
}
$extensions = $this->newExportExtensions();
foreach ($extensions as $extension) {
$extension_data = $extension->newExportData($objects);
$extension_data = array_values($extension_data);
if (count($export_data) !== count($objects)) {
throw new Exception(
pht(
'Export engine extension ("%s") exported the wrong number of '.
'objects, expected %s but got %s.',
get_class($extension),
phutil_count($objects),
phutil_count($export_data)));
}
for ($ii = 0; $ii < $n; $ii++) {
$maps[$ii] += $extension_data[$ii];
}
}
return $maps;
}
protected function newExportFields() {
return array();
}
protected function newExportData(array $objects) {
throw new PhutilMethodNotImplementedException();
}
private function newExportExtensions() {
$object = $this->newResultObject();
$viewer = $this->requireViewer();
$extensions = PhabricatorExportEngineExtension::getAllExtensions();
$supported = array();
foreach ($extensions as $extension) {
$extension = clone $extension;
$extension->setViewer($viewer);
if ($extension->supportsObject($object)) {
$supported[] = $extension;
}
}
return $supported;
}
}

View file

@ -26,25 +26,9 @@ final class PhabricatorActivitySettingsPanel extends PhabricatorSettingsPanel {
->withRelatedPHIDs(array($user->getPHID()))
->executeWithCursorPager($pager);
$phids = array();
foreach ($logs as $log) {
$phids[] = $log->getUserPHID();
$phids[] = $log->getActorPHID();
}
if ($phids) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs($phids)
->execute();
} else {
$handles = array();
}
$table = id(new PhabricatorUserLogView())
->setUser($viewer)
->setLogs($logs)
->setHandles($handles);
->setLogs($logs);
$panel = $this->newBox(pht('Account Activity Logs'), $table);

View file

@ -0,0 +1,168 @@
<?php
final class PhabricatorBulkManagementExportWorkflow
extends PhabricatorBulkManagementWorkflow {
protected function didConstruct() {
$this
->setName('export')
->setExamples('**export** [options]')
->setSynopsis(
pht('Export data to a flat file (JSON, CSV, Excel, etc).'))
->setArguments(
array(
array(
'name' => 'class',
'param' => 'class',
'help' => pht(
'SearchEngine class to export data from.'),
),
array(
'name' => 'format',
'param' => 'format',
'help' => pht('Export format.'),
),
array(
'name' => 'query',
'param' => 'key',
'help' => pht(
'Export the data selected by this query.'),
),
array(
'name' => 'output',
'param' => 'path',
'help' => pht(
'Write output to a file. If omitted, output will be sent to '.
'stdout.'),
),
array(
'name' => 'overwrite',
'help' => pht(
'If the output file already exists, overwrite it instead of '.
'raising an error.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$class = $args->getArg('class');
if (!strlen($class)) {
throw new PhutilArgumentUsageException(
pht(
'Specify a search engine class to export data from with '.
'"--class".'));
}
if (!is_subclass_of($class, 'PhabricatorApplicationSearchEngine')) {
throw new PhutilArgumentUsageException(
pht(
'SearchEngine class ("%s") is unknown.',
$class));
}
$engine = newv($class, array())
->setViewer($viewer);
if (!$engine->canExport()) {
throw new PhutilArgumentUsageException(
pht(
'SearchEngine class ("%s") does not support data export.',
$class));
}
$query_key = $args->getArg('query');
if (!strlen($query_key)) {
throw new PhutilArgumentUsageException(
pht(
'Specify a query to export with "--query".'));
}
if ($engine->isBuiltinQuery($query_key)) {
$saved_query = $engine->buildSavedQueryFromBuiltin($query_key);
} else if ($query_key) {
$saved_query = id(new PhabricatorSavedQueryQuery())
->setViewer($viewer)
->withQueryKeys(array($query_key))
->executeOne();
} else {
$saved_query = null;
}
if (!$saved_query) {
throw new PhutilArgumentUsageException(
pht(
'Failed to load saved query ("%s").',
$query_key));
}
$format_key = $args->getArg('format');
if (!strlen($format_key)) {
throw new PhutilArgumentUsageException(
pht(
'Specify an export format with "--format".'));
}
$all_formats = PhabricatorExportFormat::getAllExportFormats();
$format = idx($all_formats, $format_key);
if (!$format) {
throw new PhutilArgumentUsageException(
pht(
'Unknown export format ("%s"). Known formats are: %s.',
$format_key,
implode(', ', array_keys($all_formats))));
}
if (!$format->isExportFormatEnabled()) {
throw new PhutilArgumentUsageException(
pht(
'Export format ("%s") is not enabled.',
$format_key));
}
$is_overwrite = $args->getArg('overwrite');
$output_path = $args->getArg('output');
if (!strlen($output_path) && $is_overwrite) {
throw new PhutilArgumentUsageException(
pht(
'Flag "--overwrite" has no effect without "--output".'));
}
if (!$is_overwrite) {
if (Filesystem::pathExists($output_path)) {
throw new PhutilArgumentUsageException(
pht(
'Output path already exists. Use "--overwrite" to overwrite '.
'it.'));
}
}
$export_engine = id(new PhabricatorExportEngine())
->setViewer($viewer)
->setTitle(pht('Export'))
->setFilename(pht('export'))
->setSearchEngine($engine)
->setSavedQuery($saved_query)
->setExportFormat($format);
$file = $export_engine->exportFile();
$iterator = $file->getFileDataIterator();
if (strlen($output_path)) {
foreach ($iterator as $chunk) {
Filesystem::appendFile($output_path, $chunk);
}
} else {
foreach ($iterator as $chunk) {
echo $chunk;
}
}
return 0;
}
}

View file

@ -5,6 +5,7 @@ abstract class PhabricatorPHIDListEditField
private $useEdgeTransactions;
private $isSingleValue;
private $isNullable;
public function setUseEdgeTransactions($use_edge_transactions) {
$this->useEdgeTransactions = $use_edge_transactions;
@ -30,13 +31,23 @@ abstract class PhabricatorPHIDListEditField
return $this->isSingleValue;
}
public function setIsNullable($is_nullable) {
$this->isNullable = $is_nullable;
return $this;
}
public function getIsNullable() {
return $this->isNullable;
}
protected function newHTTPParameterType() {
return new AphrontPHIDListHTTPParameterType();
}
protected function newConduitParameterType() {
if ($this->getIsSingleValue()) {
return new ConduitPHIDParameterType();
return id(new ConduitPHIDParameterType())
->setIsNullable($this->getIsNullable());
} else {
return new ConduitPHIDListParameterType();
}
@ -99,7 +110,8 @@ abstract class PhabricatorPHIDListEditField
}
return id(new PhabricatorDatasourceEditType())
->setIsSingleValue($this->getIsSingleValue());
->setIsSingleValue($this->getIsSingleValue())
->setIsNullable($this->getIsNullable());
}
protected function newBulkEditTypes() {

View file

@ -6,6 +6,7 @@ abstract class PhabricatorPHIDListEditType
private $datasource;
private $isSingleValue;
private $defaultValue;
private $isNullable;
public function setDatasource(PhabricatorTypeaheadDatasource $datasource) {
$this->datasource = $datasource;
@ -30,16 +31,17 @@ abstract class PhabricatorPHIDListEditType
return $this;
}
public function getDefaultValue() {
return $this->defaultValue;
public function setIsNullable($is_nullable) {
$this->isNullable = $is_nullable;
return $this;
}
public function getValueType() {
if ($this->getIsSingleValue()) {
return 'phid';
} else {
return 'list<phid>';
public function getIsNullable() {
return $this->isNullable;
}
public function getDefaultValue() {
return $this->defaultValue;
}
protected function newConduitParameterType() {
@ -49,7 +51,8 @@ abstract class PhabricatorPHIDListEditType
}
if ($this->getIsSingleValue()) {
return new ConduitPHIDParameterType();
return id(new ConduitPHIDParameterType())
->setIsNullable($this->getIsNullable());
} else {
return new ConduitPHIDListParameterType();
}

View file

@ -458,6 +458,12 @@ abstract class PhabricatorApplicationTransaction
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return 'fa-lock';
case PhabricatorTransactions::TYPE_EDGE:
switch ($this->getMetadataValue('edge:type')) {
case DiffusionCommitRevertedByCommitEdgeType::EDGECONST:
return 'fa-undo';
case DiffusionCommitRevertsCommitEdgeType::EDGECONST:
return 'fa-ambulance';
}
return 'fa-link';
case PhabricatorTransactions::TYPE_BUILDABLE:
return 'fa-wrench';
@ -496,6 +502,14 @@ abstract class PhabricatorApplicationTransaction
return 'black';
}
break;
case PhabricatorTransactions::TYPE_EDGE:
switch ($this->getMetadataValue('edge:type')) {
case DiffusionCommitRevertedByCommitEdgeType::EDGECONST:
return 'pink';
case DiffusionCommitRevertsCommitEdgeType::EDGECONST:
return 'sky';
}
break;
case PhabricatorTransactions::TYPE_BUILDABLE:
switch ($this->getNewValue()) {
case HarbormasterBuildable::STATUS_PASSED:

View file

@ -35,6 +35,7 @@ abstract class PhabricatorCustomField extends Phobject {
const ROLE_HERALD = 'herald';
const ROLE_EDITENGINE = 'EditEngine';
const ROLE_HERALDACTION = 'herald.action';
const ROLE_EXPORT = 'export';
/* -( Building Applications with Custom Fields )--------------------------- */
@ -299,6 +300,8 @@ abstract class PhabricatorCustomField extends Phobject {
case self::ROLE_EDITENGINE:
return $this->shouldAppearInEditView() ||
$this->shouldAppearInEditEngine();
case self::ROLE_EXPORT:
return $this->shouldAppearInDataExport();
case self::ROLE_DEFAULT:
return true;
default:
@ -1362,6 +1365,46 @@ abstract class PhabricatorCustomField extends Phobject {
}
/* -( Data Export )-------------------------------------------------------- */
public function shouldAppearInDataExport() {
if ($this->proxy) {
return $this->proxy->shouldAppearInDataExport();
}
try {
$this->newExportFieldType();
return true;
} catch (PhabricatorCustomFieldImplementationIncompleteException $ex) {
return false;
}
}
public function newExportField() {
if ($this->proxy) {
return $this->proxy->newExportField();
}
return $this->newExportFieldType()
->setLabel($this->getFieldName());
}
public function newExportData() {
if ($this->proxy) {
return $this->proxy->newExportData();
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
protected function newExportFieldType() {
if ($this->proxy) {
return $this->proxy->newExportFieldType();
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/* -( Conduit )------------------------------------------------------------ */

View file

@ -496,5 +496,8 @@ abstract class PhabricatorStandardCustomField
return $this->getFieldValue();
}
public function newExportData() {
return $this->getFieldValue();
}
}

View file

@ -124,4 +124,8 @@ final class PhabricatorStandardCustomFieldInt
return new ConduitIntParameterType();
}
protected function newExportFieldType() {
return new PhabricatorIntExportField();
}
}

View file

@ -76,4 +76,8 @@ final class PhabricatorStandardCustomFieldText
return new ConduitStringParameterType();
}
protected function newExportFieldType() {
return new PhabricatorStringExportField();
}
}

View file

@ -25,4 +25,24 @@ abstract class PhabricatorWorkerBulkJobType extends Phobject {
->execute();
}
public function getCurtainActions(
PhabricatorUser $viewer,
PhabricatorWorkerBulkJob $job) {
if ($job->isConfirming()) {
$continue_uri = $job->getMonitorURI();
} else {
$continue_uri = $job->getDoneURI();
}
$continue = id(new PhabricatorActionView())
->setHref($continue_uri)
->setIcon('fa-arrow-circle-o-right')
->setName(pht('Continue'));
return array(
$continue,
);
}
}

View file

@ -77,6 +77,10 @@ abstract class PhabricatorWorkerBulkJobWorker
pht('Job actor does not have permission to edit job.'));
}
// Allow the worker to fill user caches inline; bulk jobs occasionally
// need to access user preferences.
$actor->setAllowInlineCacheGeneration(true);
return $actor;
}

View file

@ -0,0 +1,27 @@
<?php
/**
* An bulk job which can not be parallelized and executes only one task.
*/
abstract class PhabricatorWorkerSingleBulkJobType
extends PhabricatorWorkerBulkJobType {
public function getDescriptionForConfirm(PhabricatorWorkerBulkJob $job) {
return null;
}
public function getJobSize(PhabricatorWorkerBulkJob $job) {
return 1;
}
public function createTasks(PhabricatorWorkerBulkJob $job) {
$tasks = array();
$tasks[] = PhabricatorWorkerBulkTask::initializeNewTask(
$job,
$job->getPHID());
return $tasks;
}
}

View file

@ -180,6 +180,10 @@ final class PhabricatorWorkerBulkJob
return $this->getJobImplementation()->getJobName($this);
}
public function getCurtainActions(PhabricatorUser $viewer) {
return $this->getJobImplementation()->getCurtainActions($viewer, $this);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -1,27 +0,0 @@
<?php
final class PhabricatorEpochExportField
extends PhabricatorExportField {
private $zone;
public function getTextValue($value) {
if (!isset($this->zone)) {
$this->zone = new DateTimeZone('UTC');
}
try {
$date = new DateTime('@'.$value);
} catch (Exception $ex) {
return null;
}
$date->setTimezone($this->zone);
return $date->format('c');
}
public function getNaturalValue($value) {
return (int)$value;
}
}

View file

@ -1,10 +0,0 @@
<?php
final class PhabricatorIntExportField
extends PhabricatorExportField {
public function getNaturalValue($value) {
return (int)$value;
}
}

View file

@ -1,4 +0,0 @@
<?php
final class PhabricatorStringExportField
extends PhabricatorExportField {}

View file

@ -0,0 +1,86 @@
<?php
final class PhabricatorCustomFieldExportEngineExtension
extends PhabricatorExportEngineExtension {
const EXTENSIONKEY = 'custom-field';
private $object;
public function supportsObject($object) {
$this->object = $object;
return ($object instanceof PhabricatorCustomFieldInterface);
}
public function newExportFields() {
$prototype = $this->object;
$fields = $this->newCustomFields($prototype);
$results = array();
foreach ($fields as $field) {
$field_key = $field->getModernFieldKey();
$results[] = $field->newExportField()
->setKey($field_key);
}
return $results;
}
public function newExportData(array $objects) {
$viewer = $this->getViewer();
$field_map = array();
foreach ($objects as $object) {
$object_phid = $object->getPHID();
$fields = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_EXPORT);
$fields
->setViewer($viewer)
->readFieldsFromObject($object);
$field_map[$object_phid] = $fields;
}
$all_fields = array();
foreach ($field_map as $field_list) {
foreach ($field_list->getFields() as $field) {
$all_fields[] = $field;
}
}
id(new PhabricatorCustomFieldStorageQuery())
->addFields($all_fields)
->execute();
$results = array();
foreach ($objects as $object) {
$object_phid = $object->getPHID();
$object_fields = $field_map[$object_phid];
$map = array();
foreach ($object_fields->getFields() as $field) {
$key = $field->getModernFieldKey();
$map[$key] = $field->newExportData();
}
$results[] = $map;
}
return $results;
}
private function newCustomFields($object) {
$fields = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_EXPORT);
$fields->setViewer($this->getViewer());
return $fields->getFields();
}
}

View file

@ -0,0 +1,168 @@
<?php
final class PhabricatorExportEngine
extends Phobject {
private $viewer;
private $searchEngine;
private $savedQuery;
private $exportFormat;
private $filename;
private $title;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setSearchEngine(
PhabricatorApplicationSearchEngine $search_engine) {
$this->searchEngine = $search_engine;
return $this;
}
public function getSearchEngine() {
return $this->searchEngine;
}
public function setSavedQuery(PhabricatorSavedQuery $saved_query) {
$this->savedQuery = $saved_query;
return $this;
}
public function getSavedQuery() {
return $this->savedQuery;
}
public function setExportFormat(
PhabricatorExportFormat $export_format) {
$this->exportFormat = $export_format;
return $this;
}
public function getExportFormat() {
return $this->exportFormat;
}
public function setFilename($filename) {
$this->filename = $filename;
return $this;
}
public function getFilename() {
return $this->filename;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function newBulkJob(AphrontRequest $request) {
$viewer = $this->getViewer();
$engine = $this->getSearchEngine();
$saved_query = $this->getSavedQuery();
$format = $this->getExportFormat();
$params = array(
'engineClass' => get_class($engine),
'queryKey' => $saved_query->getQueryKey(),
'formatKey' => $format->getExportFormatKey(),
'title' => $this->getTitle(),
'filename' => $this->getFilename(),
);
$job = PhabricatorWorkerBulkJob::initializeNewJob(
$viewer,
new PhabricatorExportEngineBulkJobType(),
$params);
// We queue these jobs directly into STATUS_WAITING without requiring
// a confirmation from the user.
$xactions = array();
$xactions[] = id(new PhabricatorWorkerBulkJobTransaction())
->setTransactionType(PhabricatorWorkerBulkJobTransaction::TYPE_STATUS)
->setNewValue(PhabricatorWorkerBulkJob::STATUS_WAITING);
$editor = id(new PhabricatorWorkerBulkJobEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnMissingFields(true)
->applyTransactions($job, $xactions);
return $job;
}
public function exportFile() {
$viewer = $this->getViewer();
$engine = $this->getSearchEngine();
$saved_query = $this->getSavedQuery();
$format = $this->getExportFormat();
$title = $this->getTitle();
$filename = $this->getFilename();
$query = $engine->buildQueryFromSavedQuery($saved_query);
$extension = $format->getFileExtension();
$mime_type = $format->getMIMEContentType();
$filename = $filename.'.'.$extension;
$format = id(clone $format)
->setViewer($viewer)
->setTitle($title);
$field_list = $engine->newExportFieldList();
$field_list = mpull($field_list, null, 'getKey');
$format->addHeaders($field_list);
// Iterate over the query results in large page so we don't have to hold
// too much stuff in memory.
$page_size = 1000;
$page_cursor = null;
do {
$pager = $engine->newPagerForSavedQuery($saved_query);
$pager->setPageSize($page_size);
if ($page_cursor !== null) {
$pager->setAfterID($page_cursor);
}
$objects = $engine->executeQuery($query, $pager);
$objects = array_values($objects);
$page_cursor = $pager->getNextPageID();
$export_data = $engine->newExport($objects);
for ($ii = 0; $ii < count($objects); $ii++) {
$format->addObject($objects[$ii], $field_list, $export_data[$ii]);
}
} while ($pager->getHasMoreResults());
$export_result = $format->newFileData();
// We have all the data in one big string and aren't actually
// streaming it, but pretending that we are allows us to actviate
// the chunk engine and store large files.
$iterator = new ArrayIterator(array($export_result));
$source = id(new PhabricatorIteratorFileUploadSource())
->setName($filename)
->setViewPolicy(PhabricatorPolicies::POLICY_NOONE)
->setMIMEType($mime_type)
->setRelativeTTL(phutil_units('60 minutes in seconds'))
->setAuthorPHID($viewer->getPHID())
->setIterator($iterator);
return $source->uploadFile();
}
}

View file

@ -0,0 +1,118 @@
<?php
final class PhabricatorExportEngineBulkJobType
extends PhabricatorWorkerSingleBulkJobType {
public function getBulkJobTypeKey() {
return 'export';
}
public function getJobName(PhabricatorWorkerBulkJob $job) {
return pht('Data Export');
}
public function getCurtainActions(
PhabricatorUser $viewer,
PhabricatorWorkerBulkJob $job) {
$actions = array();
$file_phid = $job->getParameter('filePHID');
if (!$file_phid) {
$actions[] = id(new PhabricatorActionView())
->setHref('#')
->setIcon('fa-download')
->setDisabled(true)
->setName(pht('Exporting Data...'));
} else {
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phid))
->executeOne();
if (!$file) {
$actions[] = id(new PhabricatorActionView())
->setHref('#')
->setIcon('fa-download')
->setDisabled(true)
->setName(pht('Temporary File Expired'));
} else {
$actions[] = id(new PhabricatorActionView())
->setRenderAsForm(true)
->setHref($file->getDownloadURI())
->setIcon('fa-download')
->setName(pht('Download Data Export'));
}
}
return $actions;
}
public function runTask(
PhabricatorUser $actor,
PhabricatorWorkerBulkJob $job,
PhabricatorWorkerBulkTask $task) {
$engine_class = $job->getParameter('engineClass');
if (!is_subclass_of($engine_class, 'PhabricatorApplicationSearchEngine')) {
throw new Exception(
pht(
'Unknown search engine class "%s".',
$engine_class));
}
$engine = newv($engine_class, array())
->setViewer($actor);
$query_key = $job->getParameter('queryKey');
if ($engine->isBuiltinQuery($query_key)) {
$saved_query = $engine->buildSavedQueryFromBuiltin($query_key);
} else if ($query_key) {
$saved_query = id(new PhabricatorSavedQueryQuery())
->setViewer($actor)
->withQueryKeys(array($query_key))
->executeOne();
} else {
$saved_query = null;
}
if (!$saved_query) {
throw new Exception(
pht(
'Failed to load saved query ("%s").',
$query_key));
}
$format_key = $job->getParameter('formatKey');
$all_formats = PhabricatorExportFormat::getAllExportFormats();
$format = idx($all_formats, $format_key);
if (!$format) {
throw new Exception(
pht(
'Unknown export format ("%s").',
$format_key));
}
if (!$format->isExportFormatEnabled()) {
throw new Exception(
pht(
'Export format ("%s") is not enabled.',
$format_key));
}
$export_engine = id(new PhabricatorExportEngine())
->setViewer($actor)
->setTitle($job->getParameter('title'))
->setFilename($job->getParameter('filename'))
->setSearchEngine($engine)
->setSavedQuery($saved_query)
->setExportFormat($format);
$file = $export_engine->exportFile();
$job
->setParameter('filePHID', $file->getPHID())
->save();
}
}

View file

@ -0,0 +1,31 @@
<?php
abstract class PhabricatorExportEngineExtension extends Phobject {
private $viewer;
final public function getExtensionKey() {
return $this->getPhobjectClassConstant('EXTENSIONKEY');
}
final public function setViewer($viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
abstract public function supportsObject($object);
abstract public function newExportFields();
abstract public function newExportData(array $objects);
final public static function getAllExtensions() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getExtensionKey')
->execute();
}
}

View file

@ -0,0 +1,16 @@
<?php
final class PhabricatorExportFormatSetting
extends PhabricatorInternalSetting {
const SETTINGKEY = 'export.format';
public function getSettingName() {
return pht('Export Format');
}
public function getSettingDefaultValue() {
return null;
}
}

View file

@ -0,0 +1,42 @@
<?php
final class PhabricatorLiskExportEngineExtension
extends PhabricatorExportEngineExtension {
const EXTENSIONKEY = 'lisk';
public function supportsObject($object) {
if (!($object instanceof LiskDAO)) {
return false;
}
if (!$object->getConfigOption(LiskDAO::CONFIG_TIMESTAMPS)) {
return false;
}
return true;
}
public function newExportFields() {
return array(
id(new PhabricatorEpochExportField())
->setKey('dateCreated')
->setLabel(pht('Created')),
id(new PhabricatorEpochExportField())
->setKey('dateModified')
->setLabel(pht('Modified')),
);
}
public function newExportData(array $objects) {
$map = array();
foreach ($objects as $object) {
$map[] = array(
'dateCreated' => $object->getDateCreated(),
'dateModified' => $object->getDateModified(),
);
}
return $map;
}
}

View file

@ -0,0 +1,60 @@
<?php
final class PhabricatorProjectsExportEngineExtension
extends PhabricatorExportEngineExtension {
const EXTENSIONKEY = 'projects';
public function supportsObject($object) {
return ($object instanceof PhabricatorProjectInterface);
}
public function newExportFields() {
return array(
id(new PhabricatorPHIDListExportField())
->setKey('tagPHIDs')
->setLabel(pht('Tag PHIDs')),
id(new PhabricatorStringListExportField())
->setKey('tags')
->setLabel(pht('Tags')),
);
}
public function newExportData(array $objects) {
$viewer = $this->getViewer();
$object_phids = mpull($objects, 'getPHID');
$projects_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs($object_phids)
->withEdgeTypes(
array(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
));
$projects_query->execute();
$handles = $viewer->loadHandles($projects_query->getDestinationPHIDs());
$map = array();
foreach ($objects as $object) {
$object_phid = $object->getPHID();
$project_phids = $projects_query->getDestinationPHIDs(
array($object_phid),
array(PhabricatorProjectObjectHasProjectEdgeType::EDGECONST));
$handle_list = $handles->newSublist($project_phids);
$handle_list = iterator_to_array($handle_list);
$handle_names = mpull($handle_list, 'getName');
$handle_names = array_values($handle_names);
$map[] = array(
'tagPHIDs' => $project_phids,
'tags' => $handle_names,
);
}
return $map;
}
}

View file

@ -0,0 +1,53 @@
<?php
final class PhabricatorSpacesExportEngineExtension
extends PhabricatorExportEngineExtension {
const EXTENSIONKEY = 'spaces';
public function supportsObject($object) {
$viewer = $this->getViewer();
if (!PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) {
return false;
}
return ($object instanceof PhabricatorSpacesInterface);
}
public function newExportFields() {
return array(
id(new PhabricatorPHIDExportField())
->setKey('spacePHID')
->setLabel(pht('Space PHID')),
id(new PhabricatorStringExportField())
->setKey('space')
->setLabel(pht('Space')),
);
}
public function newExportData(array $objects) {
$viewer = $this->getViewer();
$space_phids = array();
foreach ($objects as $object) {
$space_phids[] = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID(
$object);
}
$handles = $viewer->loadHandles($space_phids);
$map = array();
foreach ($objects as $object) {
$space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID(
$object);
$map[] = array(
'spacePHID' => $space_phid,
'space' => $handles[$space_phid]->getName(),
);
}
return $map;
}
}

View file

@ -0,0 +1,60 @@
<?php
final class PhabricatorSubscriptionsExportEngineExtension
extends PhabricatorExportEngineExtension {
const EXTENSIONKEY = 'subscriptions';
public function supportsObject($object) {
return ($object instanceof PhabricatorSubscribableInterface);
}
public function newExportFields() {
return array(
id(new PhabricatorPHIDListExportField())
->setKey('subscriberPHIDs')
->setLabel(pht('Subscriber PHIDs')),
id(new PhabricatorStringListExportField())
->setKey('subscribers')
->setLabel(pht('Subscribers')),
);
}
public function newExportData(array $objects) {
$viewer = $this->getViewer();
$object_phids = mpull($objects, 'getPHID');
$projects_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs($object_phids)
->withEdgeTypes(
array(
PhabricatorObjectHasSubscriberEdgeType::EDGECONST,
));
$projects_query->execute();
$handles = $viewer->loadHandles($projects_query->getDestinationPHIDs());
$map = array();
foreach ($objects as $object) {
$object_phid = $object->getPHID();
$project_phids = $projects_query->getDestinationPHIDs(
array($object_phid),
array(PhabricatorObjectHasSubscriberEdgeType::EDGECONST));
$handle_list = $handles->newSublist($project_phids);
$handle_list = iterator_to_array($handle_list);
$handle_names = mpull($handle_list, 'getName');
$handle_names = array_values($handle_names);
$map[] = array(
'subscriberPHIDs' => $project_phids,
'subscribers' => $handle_names,
);
}
return $map;
}
}

View file

@ -0,0 +1,47 @@
<?php
final class PhabricatorEpochExportField
extends PhabricatorExportField {
private $zone;
public function getTextValue($value) {
if (!isset($this->zone)) {
$this->zone = new DateTimeZone('UTC');
}
try {
$date = new DateTime('@'.$value);
} catch (Exception $ex) {
return null;
}
$date->setTimezone($this->zone);
return $date->format('c');
}
public function getNaturalValue($value) {
return (int)$value;
}
public function getPHPExcelValue($value) {
$epoch = $this->getNaturalValue($value);
$seconds_per_day = phutil_units('1 day in seconds');
$offset = ($seconds_per_day * 25569);
return ($epoch + $offset) / $seconds_per_day;
}
/**
* @phutil-external-symbol class PHPExcel_Style_NumberFormat
*/
public function formatPHPExcelCell($cell, $style) {
$code = PHPExcel_Style_NumberFormat::FORMAT_DATE_YYYYMMDD2;
$style
->getNumberFormat()
->setFormatCode($code);
}
}

View file

@ -25,11 +25,32 @@ abstract class PhabricatorExportField
}
public function getTextValue($value) {
return (string)$this->getNaturalValue($value);
$natural_value = $this->getNaturalValue($value);
if ($natural_value === null) {
return null;
}
return (string)$natural_value;
}
public function getNaturalValue($value) {
return $value;
}
public function getPHPExcelValue($value) {
return $this->getTextValue($value);
}
/**
* @phutil-external-symbol class PHPExcel_Cell_DataType
*/
public function formatPHPExcelCell($cell, $style) {
$cell->setDataType(PHPExcel_Cell_DataType::TYPE_STRING);
}
public function getCharacterWidth() {
return 24;
}
}

View file

@ -7,4 +7,8 @@ final class PhabricatorIDExportField
return (int)$value;
}
public function getCharacterWidth() {
return 12;
}
}

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorIntExportField
extends PhabricatorExportField {
public function getNaturalValue($value) {
if ($value === null) {
return $value;
}
return (int)$value;
}
/**
* @phutil-external-symbol class PHPExcel_Cell_DataType
*/
public function formatPHPExcelCell($cell, $style) {
$cell->setDataType(PHPExcel_Cell_DataType::TYPE_NUMERIC);
}
public function getCharacterWidth() {
return 8;
}
}

View file

@ -0,0 +1,10 @@
<?php
abstract class PhabricatorListExportField
extends PhabricatorExportField {
public function getTextValue($value) {
return implode("\n", $value);
}
}

View file

@ -0,0 +1,10 @@
<?php
final class PhabricatorPHIDExportField
extends PhabricatorExportField {
public function getCharacterWidth() {
return 32;
}
}

View file

@ -0,0 +1,10 @@
<?php
final class PhabricatorPHIDListExportField
extends PhabricatorListExportField {
public function getCharacterWidth() {
return 32;
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorStringExportField
extends PhabricatorExportField {
public function getNaturalValue($value) {
if ($value === null) {
return $value;
}
if (!strlen($value)) {
return null;
}
return (string)$value;
}
}

View file

@ -0,0 +1,4 @@
<?php
final class PhabricatorStringListExportField
extends PhabricatorListExportField {}

View file

@ -1,4 +1,4 @@
<?php
final class PhabricatorPHIDExportField
final class PhabricatorURIExportField
extends PhabricatorExportField {}

View file

@ -23,21 +23,44 @@ final class PhabricatorCSVExportFormat
return 'text/csv';
}
public function addHeaders(array $fields) {
$headers = mpull($fields, 'getLabel');
$this->addRow($headers);
}
public function addObject($object, array $fields, array $map) {
$values = array();
foreach ($fields as $key => $field) {
$value = $map[$key];
$value = $field->getTextValue($value);
$values[] = $value;
}
$this->addRow($values);
}
private function addRow(array $values) {
$row = array();
foreach ($values as $value) {
// Excel is extremely interested in executing arbitrary code it finds in
// untrusted CSV files downloaded from the internet. When a cell looks
// like it might be too tempting for Excel to ignore, mangle the value
// to dissuade remote code execution. See T12800.
if (preg_match('/^\s*[+=@-]/', $value)) {
$value = '(!) '.$value;
}
if (preg_match('/\s|,|\"/', $value)) {
$value = str_replace('"', '""', $value);
$value = '"'.$value.'"';
}
$values[] = $value;
$row[] = $value;
}
$this->rows[] = implode(',', $values);
$this->rows[] = implode(',', $row);
}
public function newFileData() {

View file

@ -0,0 +1,168 @@
<?php
final class PhabricatorExcelExportFormat
extends PhabricatorExportFormat {
const EXPORTKEY = 'excel';
private $workbook;
private $sheet;
private $rowCursor;
public function getExportFormatName() {
return pht('Excel (.xlsx)');
}
public function isExportFormatEnabled() {
// TODO: PHPExcel has a dependency on the PHP zip extension. We should test
// for that here, since it fatals if we don't have the ZipArchive class.
return @include_once 'PHPExcel.php';
}
public function getInstallInstructions() {
return pht(<<<EOHELP
Data can not be exported to Excel because the PHPExcel library is not
installed. This software component is required for Phabricator to create
Excel files.
You can install PHPExcel from GitHub:
> https://github.com/PHPOffice/PHPExcel
Briefly:
- Clone that repository somewhere on the sever
(like `/path/to/example/PHPExcel`).
- Update your PHP `%s` setting (in `php.ini`) to include the PHPExcel
`Classes` directory (like `/path/to/example/PHPExcel/Classes`).
EOHELP
,
'include_path');
}
public function getFileExtension() {
return 'xlsx';
}
public function getMIMEContentType() {
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
}
/**
* @phutil-external-symbol class PHPExcel_Cell_DataType
*/
public function addHeaders(array $fields) {
$sheet = $this->getSheet();
$header_format = array(
'font' => array(
'bold' => true,
),
);
$row = 1;
$col = 0;
foreach ($fields as $field) {
$cell_value = $field->getLabel();
$cell_name = $this->getCellName($col, $row);
$cell = $sheet->setCellValue(
$cell_name,
$cell_value,
$return_cell = true);
$sheet->getStyle($cell_name)->applyFromArray($header_format);
$cell->setDataType(PHPExcel_Cell_DataType::TYPE_STRING);
$width = $field->getCharacterWidth();
if ($width !== null) {
$col_name = $this->getCellName($col);
$sheet->getColumnDimension($col_name)
->setWidth($width);
}
$col++;
}
}
public function addObject($object, array $fields, array $map) {
$sheet = $this->getSheet();
$col = 0;
foreach ($fields as $key => $field) {
$cell_value = $map[$key];
$cell_value = $field->getPHPExcelValue($cell_value);
$cell_name = $this->getCellName($col, $this->rowCursor);
$cell = $sheet->setCellValue(
$cell_name,
$cell_value,
$return_cell = true);
$style = $sheet->getStyle($cell_name);
$field->formatPHPExcelCell($cell, $style);
$col++;
}
$this->rowCursor++;
}
/**
* @phutil-external-symbol class PHPExcel_IOFactory
*/
public function newFileData() {
$workbook = $this->getWorkbook();
$writer = PHPExcel_IOFactory::createWriter($workbook, 'Excel2007');
ob_start();
$writer->save('php://output');
$data = ob_get_clean();
return $data;
}
private function getWorkbook() {
if (!$this->workbook) {
$this->workbook = $this->newWorkbook();
}
return $this->workbook;
}
/**
* @phutil-external-symbol class PHPExcel
*/
private function newWorkbook() {
include_once 'PHPExcel.php';
return new PHPExcel();
}
private function getSheet() {
if (!$this->sheet) {
$workbook = $this->getWorkbook();
$sheet = $workbook->setActiveSheetIndex(0);
$sheet->setTitle($this->getTitle());
$this->sheet = $sheet;
// The row cursor starts on the second row, after the header row.
$this->rowCursor = 2;
}
return $this->sheet;
}
private function getCellName($col, $row = null) {
$col_name = chr(ord('A') + $col);
if ($row === null) {
return $col_name;
}
return $col_name.$row;
}
}

View file

@ -4,6 +4,7 @@ abstract class PhabricatorExportFormat
extends Phobject {
private $viewer;
private $title;
final public function getExportFormatKey() {
return $this->getPhobjectClassConstant('EXPORTKEY');
@ -18,10 +19,23 @@ abstract class PhabricatorExportFormat
return $this->viewer;
}
final public function setTitle($title) {
$this->title = $title;
return $this;
}
final public function getTitle() {
return $this->title;
}
abstract public function getExportFormatName();
abstract public function getMIMEContentType();
abstract public function getFileExtension();
public function addHeaders(array $fields) {
return;
}
abstract public function addObject($object, array $fields, array $map);
abstract public function newFileData();
@ -36,16 +50,4 @@ abstract class PhabricatorExportFormat
->execute();
}
final public static function getAllEnabledExportFormats() {
$formats = self::getAllExportFormats();
foreach ($formats as $key => $format) {
if (!$format->isExportFormatEnabled()) {
unset($formats[$key]);
}
}
return $formats;
}
}

View file

@ -23,17 +23,29 @@ final class PhabricatorTextExportFormat
return 'text/plain';
}
public function addHeaders(array $fields) {
$headers = mpull($fields, 'getLabel');
$this->addRow($headers);
}
public function addObject($object, array $fields, array $map) {
$values = array();
foreach ($fields as $key => $field) {
$value = $map[$key];
$value = $field->getTextValue($value);
$value = addcslashes($value, "\0..\37\\\177..\377");
$values[] = $value;
}
$this->rows[] = implode("\t", $values);
$this->addRow($values);
}
private function addRow(array $values) {
$row = array();
foreach ($values as $value) {
$row[] = addcslashes($value, "\0..\37\\\177..\377");
}
$this->rows[] = implode("\t", $row);
}
public function newFileData() {

View file

@ -676,73 +676,73 @@ final class PhabricatorUSEnglishTranslation
'%s edited commit(s), added %s: %s; removed %s: %s.' =>
'%s edited commits, added %3$s; removed %5$s.',
'%s added %s reverted commit(s): %s.' => array(
'%s added %s reverted change(s): %s.' => array(
array(
'%s added a reverted commit: %3$s.',
'%s added reverted commits: %3$s.',
'%s added a reverted change: %3$s.',
'%s added reverted changes: %3$s.',
),
),
'%s removed %s reverted commit(s): %s.' => array(
'%s removed %s reverted change(s): %s.' => array(
array(
'%s removed a reverted commit: %3$s.',
'%s removed reverted commits: %3$s.',
'%s removed a reverted change: %3$s.',
'%s removed reverted changes: %3$s.',
),
),
'%s edited reverted commit(s), added %s: %s; removed %s: %s.' =>
'%s edited reverted commits, added %3$s; removed %5$s.',
'%s edited reverted change(s), added %s: %s; removed %s: %s.' =>
'%s edited reverted changes, added %3$s; removed %5$s.',
'%s added %s reverted commit(s) for %s: %s.' => array(
'%s added %s reverted change(s) for %s: %s.' => array(
array(
'%s added a reverted commit for %3$s: %4$s.',
'%s added reverted commits for %3$s: %4$s.',
'%s added a reverted change for %3$s: %4$s.',
'%s added reverted changes for %3$s: %4$s.',
),
),
'%s removed %s reverted commit(s) for %s: %s.' => array(
'%s removed %s reverted change(s) for %s: %s.' => array(
array(
'%s removed a reverted commit for %3$s: %4$s.',
'%s removed reverted commits for %3$s: %4$s.',
'%s removed a reverted change for %3$s: %4$s.',
'%s removed reverted changes for %3$s: %4$s.',
),
),
'%s edited reverted commit(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited reverted commits for %2$s, added %4$s; removed %6$s.',
'%s edited reverted change(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited reverted changes for %2$s, added %4$s; removed %6$s.',
'%s added %s reverting commit(s): %s.' => array(
'%s added %s reverting change(s): %s.' => array(
array(
'%s added a reverting commit: %3$s.',
'%s added reverting commits: %3$s.',
'%s added a reverting change: %3$s.',
'%s added reverting changes: %3$s.',
),
),
'%s removed %s reverting commit(s): %s.' => array(
'%s removed %s reverting change(s): %s.' => array(
array(
'%s removed a reverting commit: %3$s.',
'%s removed reverting commits: %3$s.',
'%s removed a reverting change: %3$s.',
'%s removed reverting changes: %3$s.',
),
),
'%s edited reverting commit(s), added %s: %s; removed %s: %s.' =>
'%s edited reverting commits, added %3$s; removed %5$s.',
'%s edited reverting change(s), added %s: %s; removed %s: %s.' =>
'%s edited reverting changes, added %3$s; removed %5$s.',
'%s added %s reverting commit(s) for %s: %s.' => array(
'%s added %s reverting change(s) for %s: %s.' => array(
array(
'%s added a reverting commit for %3$s: %4$s.',
'%s added reverting commits for %3$s: %4$s.',
'%s added a reverting change for %3$s: %4$s.',
'%s added reverting changes for %3$s: %4$s.',
),
),
'%s removed %s reverting commit(s) for %s: %s.' => array(
'%s removed %s reverting change(s) for %s: %s.' => array(
array(
'%s removed a reverting commit for %3$s: %4$s.',
'%s removed reverting commits for %3$s: %4$s.',
'%s removed a reverting change for %3$s: %4$s.',
'%s removed reverting changes for %3$s: %4$s.',
),
),
'%s edited reverting commit(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited reverting commits for %s, added %4$s; removed %6$s.',
'%s edited reverting change(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited reverting changes for %s, added %4$s; removed %6$s.',
'%s changed project member(s), added %d: %s; removed %d: %s.' =>
'%s changed project members, added %3$s; removed %5$s.',

View file

@ -301,18 +301,14 @@ final class PHUITimelineEventView extends AphrontView {
$menu = null;
$items = array();
$has_menu = false;
if (!$this->getIsPreview() && !$this->getHideCommentOptions()) {
foreach ($this->getEventGroup() as $event) {
$items[] = $event->getMenuItems($this->anchor);
if ($event->hasChildren()) {
$has_menu = true;
}
}
$items = array_mergev($items);
}
if ($items || $has_menu) {
if ($items) {
$icon = id(new PHUIIconView())
->setIcon('fa-caret-down');
$aural = javelin_tag(
@ -351,6 +347,8 @@ final class PHUITimelineEventView extends AphrontView {
));
$has_menu = true;
} else {
$has_menu = false;
}
// Render "extra" information (timestamp, etc).

View file

@ -390,10 +390,6 @@
outline: none;
}
.phui-timeline-menu .phui-icon-view {
color: {$lightgreytext};
}
a.phui-timeline-menu .phui-icon-view {
color: {$bluetext};
}