Merge branch 'master' into blender-tweaks

This commit is contained in:
2018-02-09 10:25:27 +01:00
218 changed files with 7098 additions and 1695 deletions

1
bin/conduit Symbolic link
View File

@@ -0,0 +1 @@
../scripts/setup/manage_conduit.php

View File

@@ -9,11 +9,11 @@ return array(
'names' => array(
'conpherence.pkg.css' => 'e68cf1fa',
'conpherence.pkg.js' => '15191c65',
'core.pkg.css' => '144d9932',
'core.pkg.js' => '4c79d74f',
'core.pkg.css' => '07cdfee8',
'core.pkg.js' => '3ac6e174',
'darkconsole.pkg.js' => '1f9a31bc',
'differential.pkg.css' => '45951e9e',
'differential.pkg.js' => '19ee9979',
'differential.pkg.css' => '113e692c',
'differential.pkg.js' => '5d53d5ce',
'diffusion.pkg.css' => 'a2d17c7d',
'diffusion.pkg.js' => '6134c5a1',
'favicon.ico' => '4d48ee79',
@@ -31,7 +31,7 @@ return array(
'rsrc/css/aphront/multi-column.css' => '84cc6640',
'rsrc/css/aphront/notification.css' => '457861ec',
'rsrc/css/aphront/panel-view.css' => '8427b78d',
'rsrc/css/aphront/phabricator-nav-view.css' => 'faf6a6fc',
'rsrc/css/aphront/phabricator-nav-view.css' => '028126f6',
'rsrc/css/aphront/table-view.css' => '8c9bbafe',
'rsrc/css/aphront/tokenizer.css' => '15d5ff71',
'rsrc/css/aphront/tooltip.css' => '173b9431',
@@ -121,7 +121,7 @@ return array(
'rsrc/css/font/font-awesome.css' => 'e838e088',
'rsrc/css/font/font-lato.css' => 'c7ccd872',
'rsrc/css/font/phui-font-icon-base.css' => '870a7360',
'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82',
'rsrc/css/layout/phabricator-filetree-view.css' => 'b912ad97',
'rsrc/css/layout/phabricator-source-code-view.css' => 'aea41829',
'rsrc/css/phui/button/phui-button-bar.css' => 'f1ff5494',
'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68',
@@ -136,7 +136,7 @@ return array(
'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6',
'rsrc/css/phui/object-item/phui-oi-list-view.css' => '6ae18df0',
'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea',
'rsrc/css/phui/phui-action-list.css' => 'f7f61a34',
'rsrc/css/phui/phui-action-list.css' => '0bcd9a45',
'rsrc/css/phui/phui-action-panel.css' => 'b4798122',
'rsrc/css/phui/phui-badge.css' => '22c0cf4f',
'rsrc/css/phui/phui-basic-nav-view.css' => '98c11ab3',
@@ -176,7 +176,7 @@ return array(
'rsrc/css/phui/phui-spacing.css' => '042804d6',
'rsrc/css/phui/phui-status.css' => 'd5263e49',
'rsrc/css/phui/phui-tag-view.css' => 'b4719c50',
'rsrc/css/phui/phui-timeline-view.css' => 'e2ef62b1',
'rsrc/css/phui/phui-timeline-view.css' => '6ddf8126',
'rsrc/css/phui/phui-two-column-view.css' => '44ec4951',
'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5',
'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455',
@@ -397,8 +397,8 @@ return array(
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173',
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375',
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63',
'rsrc/js/application/diff/DiffChangeset.js' => '99abf4cd',
'rsrc/js/application/diff/DiffChangesetList.js' => '3b77efdd',
'rsrc/js/application/diff/DiffChangeset.js' => 'b49b59d6',
'rsrc/js/application/diff/DiffChangesetList.js' => '1f2e5265',
'rsrc/js/application/diff/DiffInline.js' => 'e83d28f3',
'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832',
'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07',
@@ -500,7 +500,7 @@ return array(
'rsrc/js/core/behavior-more.js' => 'a80d0378',
'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0',
'rsrc/js/core/behavior-oncopy.js' => '2926fff2',
'rsrc/js/core/behavior-phabricator-nav.js' => '947753e0',
'rsrc/js/core/behavior-phabricator-nav.js' => '81144dfa',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'acd29eee',
'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207',
'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b',
@@ -534,7 +534,7 @@ return array(
'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03',
'rsrc/js/phuix/PHUIXExample.js' => '68af71ca',
'rsrc/js/phuix/PHUIXFormControl.js' => '1dd0870c',
'rsrc/js/phuix/PHUIXFormControl.js' => '16ad6224',
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
),
'symbols' => array(
@@ -659,7 +659,7 @@ return array(
'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0',
'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0',
'javelin-behavior-phabricator-line-linker' => '1499a8cb',
'javelin-behavior-phabricator-nav' => '947753e0',
'javelin-behavior-phabricator-nav' => '81144dfa',
'javelin-behavior-phabricator-notification-example' => '8ce821c5',
'javelin-behavior-phabricator-object-selector' => '77c1f0b0',
'javelin-behavior-phabricator-oncopy' => '2926fff2',
@@ -768,7 +768,7 @@ return array(
'path-typeahead' => 'f7fc67ec',
'people-picture-menu-item-css' => 'a06f7f34',
'people-profile-css' => '4df76faf',
'phabricator-action-list-view-css' => 'f7f61a34',
'phabricator-action-list-view-css' => '0bcd9a45',
'phabricator-busy' => '59a7976a',
'phabricator-chatlog-css' => 'd295b020',
'phabricator-content-source-view-css' => '4b8b05d4',
@@ -777,8 +777,8 @@ return array(
'phabricator-darklog' => 'c8e1ffe3',
'phabricator-darkmessage' => 'c48cccdd',
'phabricator-dashboard-css' => 'fe5b1869',
'phabricator-diff-changeset' => '99abf4cd',
'phabricator-diff-changeset-list' => '3b77efdd',
'phabricator-diff-changeset' => 'b49b59d6',
'phabricator-diff-changeset-list' => '1f2e5265',
'phabricator-diff-inline' => 'e83d28f3',
'phabricator-drag-and-drop-file-upload' => '58dea2fa',
'phabricator-draggable-list' => 'bea6e7f4',
@@ -786,12 +786,12 @@ return array(
'phabricator-favicon' => '1fe2510c',
'phabricator-feed-css' => 'ecd4ec57',
'phabricator-file-upload' => '680ea2c8',
'phabricator-filetree-view-css' => 'fccf9f82',
'phabricator-filetree-view-css' => 'b912ad97',
'phabricator-flag-css' => 'bba8f811',
'phabricator-keyboard-shortcut' => '1ae869f2',
'phabricator-keyboard-shortcut-manager' => 'c19dd9b9',
'phabricator-main-menu-view' => '7821ca89',
'phabricator-nav-view-css' => 'faf6a6fc',
'phabricator-nav-view-css' => '028126f6',
'phabricator-notification' => '008faf9c',
'phabricator-notification-css' => '457861ec',
'phabricator-notification-menu-css' => '10685bd4',
@@ -876,7 +876,7 @@ return array(
'phui-status-list-view-css' => 'd5263e49',
'phui-tag-view-css' => 'b4719c50',
'phui-theme-css' => '9f261c6b',
'phui-timeline-view-css' => 'e2ef62b1',
'phui-timeline-view-css' => '6ddf8126',
'phui-two-column-view-css' => '44ec4951',
'phui-workboard-color-css' => '783cdff5',
'phui-workboard-view-css' => '3bc85455',
@@ -887,7 +887,7 @@ return array(
'phuix-autocomplete' => 'e0731603',
'phuix-button-view' => '8a91e1ac',
'phuix-dropdown-menu' => '04b2ae03',
'phuix-form-control-view' => '1dd0870c',
'phuix-form-control-view' => '16ad6224',
'phuix-icon-view' => 'bff6884b',
'policy-css' => '957ea14c',
'policy-edit-css' => '815c66f7',
@@ -998,6 +998,10 @@ return array(
'aphront-typeahead-control-css',
'phui-tag-view-css',
),
'16ad6224' => array(
'javelin-install',
'javelin-dom',
),
'17bb8539' => array(
'javelin-behavior',
'javelin-stratcom',
@@ -1029,10 +1033,6 @@ return array(
'javelin-request',
'javelin-uri',
),
'1dd0870c' => array(
'javelin-install',
'javelin-dom',
),
'1e911d0f' => array(
'javelin-stratcom',
'javelin-request',
@@ -1044,6 +1044,10 @@ return array(
'javelin-uri',
'javelin-routable',
),
'1f2e5265' => array(
'javelin-install',
'phuix-button-view',
),
'1f6794f6' => array(
'javelin-behavior',
'javelin-stratcom',
@@ -1143,10 +1147,6 @@ return array(
'javelin-dom',
'javelin-magical-init',
),
'3b77efdd' => array(
'javelin-install',
'phuix-button-view',
),
'3cb0b2fc' => array(
'javelin-behavior',
'javelin-dom',
@@ -1564,6 +1564,16 @@ return array(
'7f243deb' => array(
'javelin-install',
),
'81144dfa' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'javelin-dom',
'javelin-magical-init',
'javelin-vector',
'javelin-request',
'javelin-util',
),
'834a1173' => array(
'javelin-behavior',
'javelin-scrollbar',
@@ -1651,16 +1661,6 @@ return array(
'javelin-workflow',
'javelin-dom',
),
'947753e0' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'javelin-dom',
'javelin-magical-init',
'javelin-vector',
'javelin-request',
'javelin-util',
),
'949c0fe5' => array(
'javelin-install',
),
@@ -1681,17 +1681,6 @@ return array(
'javelin-mask',
'phabricator-drag-and-drop-file-upload',
),
'99abf4cd' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
'javelin-workflow',
'javelin-router',
'javelin-behavior-device',
'javelin-vector',
'phabricator-diff-inline',
),
'9a6dd75c' => array(
'javelin-behavior',
'javelin-stratcom',
@@ -1840,6 +1829,17 @@ return array(
'b3e7d692' => array(
'javelin-install',
),
'b49b59d6' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
'javelin-workflow',
'javelin-router',
'javelin-behavior-device',
'javelin-vector',
'phabricator-diff-inline',
),
'b59e1e96' => array(
'javelin-behavior',
'javelin-stratcom',

View File

@@ -1622,5 +1622,9 @@
"zipper_mouth": "\ud83e\udd10",
"zzz": "\ud83d\udca4",
"100": "\ud83d\udcaf",
"1234": "\ud83d\udd22"
"1234": "\ud83d\udd22",
"party": "\ud83c\udf89",
"celebration": "\ud83c\udf89",
"confetti": "\ud83c\udf89"
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
DROP originalTitle;

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_differential.differential_revision
DROP originalTitle;

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_pholio.pholio_mock
DROP originalName;

View File

@@ -0,0 +1,5 @@
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
ADD closedEpoch INT UNSIGNED;
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
ADD closerPHID VARBINARY(64);

View File

@@ -0,0 +1,65 @@
<?php
$table = new ManiphestTask();
$conn = $table->establishConnection('w');
$viewer = PhabricatorUser::getOmnipotentUser();
foreach (new LiskMigrationIterator($table) as $task) {
if ($task->getClosedEpoch()) {
// Task already has a closed date.
continue;
}
$status = $task->getStatus();
if (!ManiphestTaskStatus::isClosedStatus($status)) {
// Task isn't closed.
continue;
}
// Look through the transactions from newest to oldest until we find one
// where the task was closed. A merge also counts as a close, even though
// it doesn't currently produce a separate transaction.
$type_merge = ManiphestTaskStatusTransaction::TRANSACTIONTYPE;
$type_status = ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE;
$xactions = id(new ManiphestTransactionQuery())
->setViewer($viewer)
->withObjectPHIDs(array($task->getPHID()))
->withTransactionTypes(
array(
$type_merge,
$type_status,
))
->execute();
foreach ($xactions as $xaction) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$type = $xaction->getTransactionType();
// If this is a status change, but is not a close, don't use it.
// (We always use merges, even though it's possible to merge a task which
// was previously closed: we can't tell when this happens very easily.)
if ($type === $type_status) {
if (!ManiphestTaskStatus::isClosedStatus($new)) {
continue;
}
if ($old && ManiphestTaskStatus::isClosedStatus($old)) {
continue;
}
}
queryfx(
$conn,
'UPDATE %T SET closedEpoch = %d, closerPHID = %ns
WHERE id = %d',
$table->getTableName(),
$xaction->getDateCreated(),
$xaction->getAuthorPHID(),
$task->getID());
break;
}
}

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/init/init-script.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('manage Conduit'));
$args->setSynopsis(<<<EOSYNOPSIS
**conduit** __command__ [__options__]
Manage Conduit.
EOSYNOPSIS
);
$args->parseStandardArguments();
$workflows = id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorConduitManagementWorkflow')
->execute();
$workflows[] = new PhutilHelpArgumentWorkflow();
$args->parseWorkflows($workflows);

View File

@@ -487,6 +487,7 @@ phutil_register_library_map(array(
'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php',
'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php',
'DifferentialLocalCommitsView' => 'applications/differential/view/DifferentialLocalCommitsView.php',
'DifferentialMailEngineExtension' => 'applications/differential/engineextension/DifferentialMailEngineExtension.php',
'DifferentialMailView' => 'applications/differential/mail/DifferentialMailView.php',
'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php',
'DifferentialModernHunk' => 'applications/differential/storage/DifferentialModernHunk.php',
@@ -1345,6 +1346,7 @@ phutil_register_library_map(array(
'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php',
'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php',
'HarbormasterWorkingCopyArtifact' => 'applications/harbormaster/artifact/HarbormasterWorkingCopyArtifact.php',
'HeraldActingUserField' => 'applications/herald/field/HeraldActingUserField.php',
'HeraldAction' => 'applications/herald/action/HeraldAction.php',
'HeraldActionGroup' => 'applications/herald/action/HeraldActionGroup.php',
'HeraldActionRecord' => 'applications/herald/storage/HeraldActionRecord.php',
@@ -1525,13 +1527,10 @@ 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',
'ManiphestMailEngineExtension' => 'applications/maniphest/engineextension/ManiphestMailEngineExtension.php',
'ManiphestNameIndex' => 'applications/maniphest/storage/ManiphestNameIndex.php',
'ManiphestPointsConfigType' => 'applications/maniphest/config/ManiphestPointsConfigType.php',
'ManiphestPrioritiesConfigType' => 'applications/maniphest/config/ManiphestPrioritiesConfigType.php',
@@ -1965,6 +1964,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationEditHTTPParameterHelpView' => 'applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php',
'PhabricatorApplicationEditor' => 'applications/meta/editor/PhabricatorApplicationEditor.php',
'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php',
'PhabricatorApplicationObjectMailEngineExtension' => 'applications/transactions/engineextension/PhabricatorApplicationObjectMailEngineExtension.php',
'PhabricatorApplicationPanelController' => 'applications/meta/controller/PhabricatorApplicationPanelController.php',
'PhabricatorApplicationPolicyChangeTransaction' => 'applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php',
'PhabricatorApplicationProfileMenuItem' => 'applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php',
@@ -2226,6 +2226,7 @@ phutil_register_library_map(array(
'PhabricatorBoardResponseEngine' => 'applications/project/engine/PhabricatorBoardResponseEngine.php',
'PhabricatorBoolConfigType' => 'applications/config/type/PhabricatorBoolConfigType.php',
'PhabricatorBoolEditField' => 'applications/transactions/editfield/PhabricatorBoolEditField.php',
'PhabricatorBoolMailStamp' => 'applications/metamta/stamp/PhabricatorBoolMailStamp.php',
'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php',
'PhabricatorBuildbotController' => 'extensions/buildbot/controller/PhabricatorBuildbotController.php',
'PhabricatorBuiltinDraftEngine' => 'applications/transactions/draft/PhabricatorBuiltinDraftEngine.php',
@@ -2234,9 +2235,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',
@@ -2414,6 +2416,7 @@ phutil_register_library_map(array(
'PhabricatorClusterExceptionHandler' => 'infrastructure/cluster/exception/PhabricatorClusterExceptionHandler.php',
'PhabricatorClusterImpossibleWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImpossibleWriteException.php',
'PhabricatorClusterImproperWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImproperWriteException.php',
'PhabricatorClusterMailersConfigType' => 'infrastructure/cluster/config/PhabricatorClusterMailersConfigType.php',
'PhabricatorClusterNoHostForRoleException' => 'infrastructure/cluster/exception/PhabricatorClusterNoHostForRoleException.php',
'PhabricatorClusterSearchConfigType' => 'infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php',
'PhabricatorClusterServiceHealthRecord' => 'infrastructure/cluster/PhabricatorClusterServiceHealthRecord.php',
@@ -2432,6 +2435,7 @@ phutil_register_library_map(array(
'PhabricatorCommonPasswords' => 'applications/auth/constants/PhabricatorCommonPasswords.php',
'PhabricatorConduitAPIController' => 'applications/conduit/controller/PhabricatorConduitAPIController.php',
'PhabricatorConduitApplication' => 'applications/conduit/application/PhabricatorConduitApplication.php',
'PhabricatorConduitCallManagementWorkflow' => 'applications/conduit/management/PhabricatorConduitCallManagementWorkflow.php',
'PhabricatorConduitCertificateToken' => 'applications/conduit/storage/PhabricatorConduitCertificateToken.php',
'PhabricatorConduitConsoleController' => 'applications/conduit/controller/PhabricatorConduitConsoleController.php',
'PhabricatorConduitContentSource' => 'infrastructure/contentsource/PhabricatorConduitContentSource.php',
@@ -2442,6 +2446,7 @@ phutil_register_library_map(array(
'PhabricatorConduitLogController' => 'applications/conduit/controller/PhabricatorConduitLogController.php',
'PhabricatorConduitLogQuery' => 'applications/conduit/query/PhabricatorConduitLogQuery.php',
'PhabricatorConduitLogSearchEngine' => 'applications/conduit/query/PhabricatorConduitLogSearchEngine.php',
'PhabricatorConduitManagementWorkflow' => 'applications/conduit/management/PhabricatorConduitManagementWorkflow.php',
'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php',
'PhabricatorConduitMethodQuery' => 'applications/conduit/query/PhabricatorConduitMethodQuery.php',
'PhabricatorConduitRequestExceptionHandler' => 'aphront/handler/PhabricatorConduitRequestExceptionHandler.php',
@@ -2587,6 +2592,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',
@@ -2751,6 +2757,8 @@ phutil_register_library_map(array(
'PhabricatorDraftEngine' => 'applications/transactions/draft/PhabricatorDraftEngine.php',
'PhabricatorDraftInterface' => 'applications/transactions/draft/PhabricatorDraftInterface.php',
'PhabricatorDrydockApplication' => 'applications/drydock/application/PhabricatorDrydockApplication.php',
'PhabricatorEdgeChangeRecord' => 'infrastructure/edges/util/PhabricatorEdgeChangeRecord.php',
'PhabricatorEdgeChangeRecordTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeChangeRecordTestCase.php',
'PhabricatorEdgeConfig' => 'infrastructure/edges/constants/PhabricatorEdgeConfig.php',
'PhabricatorEdgeConstants' => 'infrastructure/edges/constants/PhabricatorEdgeConstants.php',
'PhabricatorEdgeCycleException' => 'infrastructure/edges/exception/PhabricatorEdgeCycleException.php',
@@ -2814,6 +2822,7 @@ phutil_register_library_map(array(
'PhabricatorEditPage' => 'applications/transactions/editengine/PhabricatorEditPage.php',
'PhabricatorEditType' => 'applications/transactions/edittype/PhabricatorEditType.php',
'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php',
'PhabricatorEditorMailEngineExtension' => 'applications/transactions/engineextension/PhabricatorEditorMailEngineExtension.php',
'PhabricatorEditorMultipleSetting' => 'applications/settings/setting/PhabricatorEditorMultipleSetting.php',
'PhabricatorEditorSetting' => 'applications/settings/setting/PhabricatorEditorSetting.php',
'PhabricatorElasticFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php',
@@ -2830,6 +2839,7 @@ phutil_register_library_map(array(
'PhabricatorEmailPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php',
'PhabricatorEmailRePrefixSetting' => 'applications/settings/setting/PhabricatorEmailRePrefixSetting.php',
'PhabricatorEmailSelfActionsSetting' => 'applications/settings/setting/PhabricatorEmailSelfActionsSetting.php',
'PhabricatorEmailStampsSetting' => 'applications/settings/setting/PhabricatorEmailStampsSetting.php',
'PhabricatorEmailTagsSetting' => 'applications/settings/setting/PhabricatorEmailTagsSetting.php',
'PhabricatorEmailVarySubjectsSetting' => 'applications/settings/setting/PhabricatorEmailVarySubjectsSetting.php',
'PhabricatorEmailVerificationController' => 'applications/auth/controller/PhabricatorEmailVerificationController.php',
@@ -2842,15 +2852,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',
@@ -3020,6 +3035,7 @@ phutil_register_library_map(array(
'PhabricatorGDSetupCheck' => 'applications/config/check/PhabricatorGDSetupCheck.php',
'PhabricatorGarbageCollector' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php',
'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCollectWorkflow.php',
'PhabricatorGarbageCollectorManagementCompactEdgesWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCompactEdgesWorkflow.php',
'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementSetPolicyWorkflow.php',
'PhabricatorGarbageCollectorManagementWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementWorkflow.php',
'PhabricatorGeneralCachePurger' => 'applications/cache/purger/PhabricatorGeneralCachePurger.php',
@@ -3070,7 +3086,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',
@@ -3094,7 +3110,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',
@@ -3104,7 +3120,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',
@@ -3127,9 +3143,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',
@@ -3165,14 +3183,17 @@ phutil_register_library_map(array(
'PhabricatorMacroTransactionQuery' => 'applications/macro/query/PhabricatorMacroTransactionQuery.php',
'PhabricatorMacroTransactionType' => 'applications/macro/xaction/PhabricatorMacroTransactionType.php',
'PhabricatorMacroViewController' => 'applications/macro/controller/PhabricatorMacroViewController.php',
'PhabricatorMailConfigTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMailConfigTestCase.php',
'PhabricatorMailEmailHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailHeraldField.php',
'PhabricatorMailEmailHeraldFieldGroup' => 'applications/metamta/herald/PhabricatorMailEmailHeraldFieldGroup.php',
'PhabricatorMailEmailSubjectHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailSubjectHeraldField.php',
'PhabricatorMailEngineExtension' => 'applications/metamta/engine/PhabricatorMailEngineExtension.php',
'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAdapter.php',
'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php',
'PhabricatorMailImplementationMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php',
'PhabricatorMailImplementationPHPMailerAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php',
'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerLiteAdapter.php',
'PhabricatorMailImplementationPostmarkAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPostmarkAdapter.php',
'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php',
'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php',
'PhabricatorMailManagementListInboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementListInboundWorkflow.php',
@@ -3185,6 +3206,7 @@ phutil_register_library_map(array(
'PhabricatorMailManagementUnverifyWorkflow' => 'applications/metamta/management/PhabricatorMailManagementUnverifyWorkflow.php',
'PhabricatorMailManagementVolumeWorkflow' => 'applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php',
'PhabricatorMailManagementWorkflow' => 'applications/metamta/management/PhabricatorMailManagementWorkflow.php',
'PhabricatorMailMustEncryptHeraldAction' => 'applications/metamta/herald/PhabricatorMailMustEncryptHeraldAction.php',
'PhabricatorMailOutboundMailHeraldAdapter' => 'applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php',
'PhabricatorMailOutboundRoutingHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php',
'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php',
@@ -3195,6 +3217,7 @@ phutil_register_library_map(array(
'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php',
'PhabricatorMailRoutingRule' => 'applications/metamta/constants/PhabricatorMailRoutingRule.php',
'PhabricatorMailSetupCheck' => 'applications/config/check/PhabricatorMailSetupCheck.php',
'PhabricatorMailStamp' => 'applications/metamta/stamp/PhabricatorMailStamp.php',
'PhabricatorMailTarget' => 'applications/metamta/replyhandler/PhabricatorMailTarget.php',
'PhabricatorMailgunConfigOptions' => 'applications/config/option/PhabricatorMailgunConfigOptions.php',
'PhabricatorMainMenuBarExtension' => 'view/page/menu/PhabricatorMainMenuBarExtension.php',
@@ -3254,6 +3277,7 @@ phutil_register_library_map(array(
'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php',
'PhabricatorMetaMTAMemberQuery' => 'applications/metamta/query/PhabricatorMetaMTAMemberQuery.php',
'PhabricatorMetaMTAPermanentFailureException' => 'applications/metamta/exception/PhabricatorMetaMTAPermanentFailureException.php',
'PhabricatorMetaMTAPostmarkReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAPostmarkReceiveController.php',
'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php',
'PhabricatorMetaMTAReceivedMailProcessingException' => 'applications/metamta/exception/PhabricatorMetaMTAReceivedMailProcessingException.php',
'PhabricatorMetaMTAReceivedMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAReceivedMailTestCase.php',
@@ -3271,6 +3295,8 @@ phutil_register_library_map(array(
'PhabricatorMultiFactorSettingsPanel' => 'applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php',
'PhabricatorMultimeterApplication' => 'applications/multimeter/application/PhabricatorMultimeterApplication.php',
'PhabricatorMustVerifyEmailController' => 'applications/auth/controller/PhabricatorMustVerifyEmailController.php',
'PhabricatorMutedByEdgeType' => 'applications/transactions/edges/PhabricatorMutedByEdgeType.php',
'PhabricatorMutedEdgeType' => 'applications/transactions/edges/PhabricatorMutedEdgeType.php',
'PhabricatorMySQLConfigOptions' => 'applications/config/option/PhabricatorMySQLConfigOptions.php',
'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php',
'PhabricatorMySQLSearchHost' => 'infrastructure/cluster/search/PhabricatorMySQLSearchHost.php',
@@ -3424,10 +3450,12 @@ 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',
'PhabricatorPHIDMailStamp' => 'applications/metamta/stamp/PhabricatorPHIDMailStamp.php',
'PhabricatorPHIDResolver' => 'applications/phid/resolver/PhabricatorPHIDResolver.php',
'PhabricatorPHIDType' => 'applications/phid/type/PhabricatorPHIDType.php',
'PhabricatorPHIDTypeTestCase' => 'applications/phid/type/__tests__/PhabricatorPHIDTypeTestCase.php',
@@ -3836,7 +3864,9 @@ 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',
'PhabricatorProjectsMailEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsMailEngineExtension.php',
'PhabricatorProjectsMembersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsMembersSearchEngineAttachment.php',
'PhabricatorProjectsMembershipIndexEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php',
'PhabricatorProjectsPolicyRule' => 'applications/project/policyrule/PhabricatorProjectsPolicyRule.php',
@@ -4128,8 +4158,10 @@ 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',
'PhabricatorSpacesMailEngineExtension' => 'applications/spaces/engineextension/PhabricatorSpacesMailEngineExtension.php',
'PhabricatorSpacesNamespace' => 'applications/spaces/storage/PhabricatorSpacesNamespace.php',
'PhabricatorSpacesNamespaceArchiveTransaction' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceArchiveTransaction.php',
'PhabricatorSpacesNamespaceDatasource' => 'applications/spaces/typeahead/PhabricatorSpacesNamespaceDatasource.php',
@@ -4191,9 +4223,11 @@ 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',
'PhabricatorStringMailStamp' => 'applications/metamta/stamp/PhabricatorStringMailStamp.php',
'PhabricatorStringSetting' => 'applications/settings/setting/PhabricatorStringSetting.php',
'PhabricatorSubmitEditField' => 'applications/transactions/editfield/PhabricatorSubmitEditField.php',
'PhabricatorSubscribableInterface' => 'applications/subscriptions/interface/PhabricatorSubscribableInterface.php',
@@ -4208,9 +4242,12 @@ 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',
'PhabricatorSubscriptionsMailEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsMailEngineExtension.php',
'PhabricatorSubscriptionsMuteController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsMuteController.php',
'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php',
'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php',
'PhabricatorSubscriptionsSearchEngineAttachment' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsSearchEngineAttachment.php',
@@ -4255,7 +4292,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',
@@ -4320,6 +4357,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',
@@ -4413,6 +4451,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',
@@ -5588,6 +5627,7 @@ phutil_register_library_map(array(
'DifferentialLintField' => 'DifferentialHarbormasterField',
'DifferentialLintStatus' => 'Phobject',
'DifferentialLocalCommitsView' => 'AphrontView',
'DifferentialMailEngineExtension' => 'PhabricatorMailEngineExtension',
'DifferentialMailView' => 'Phobject',
'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
'DifferentialModernHunk' => 'DifferentialHunk',
@@ -6560,6 +6600,7 @@ phutil_register_library_map(array(
'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterWorker' => 'PhabricatorWorker',
'HarbormasterWorkingCopyArtifact' => 'HarbormasterDrydockLeaseArtifact',
'HeraldActingUserField' => 'HeraldField',
'HeraldAction' => 'Phobject',
'HeraldActionGroup' => 'HeraldGroup',
'HeraldActionRecord' => 'HeraldDAO',
@@ -6772,13 +6813,10 @@ 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',
'ManiphestMailEngineExtension' => 'PhabricatorMailEngineExtension',
'ManiphestNameIndex' => 'ManiphestDAO',
'ManiphestPointsConfigType' => 'PhabricatorJSONConfigType',
'ManiphestPrioritiesConfigType' => 'PhabricatorJSONConfigType',
@@ -7265,6 +7303,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationEditHTTPParameterHelpView' => 'AphrontView',
'PhabricatorApplicationEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationObjectMailEngineExtension' => 'PhabricatorMailEngineExtension',
'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationPolicyChangeTransaction' => 'PhabricatorApplicationTransactionType',
'PhabricatorApplicationProfileMenuItem' => 'PhabricatorProfileMenuItem',
@@ -7572,6 +7611,7 @@ phutil_register_library_map(array(
'PhabricatorBoardResponseEngine' => 'Phobject',
'PhabricatorBoolConfigType' => 'PhabricatorTextConfigType',
'PhabricatorBoolEditField' => 'PhabricatorEditField',
'PhabricatorBoolMailStamp' => 'PhabricatorMailStamp',
'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation',
'PhabricatorBuildbotController' => 'PhabricatorController',
'PhabricatorBuiltinDraftEngine' => 'PhabricatorDraftEngine',
@@ -7580,6 +7620,7 @@ phutil_register_library_map(array(
'PhabricatorBulkContentSource' => 'PhabricatorContentSource',
'PhabricatorBulkEditGroup' => 'Phobject',
'PhabricatorBulkEngine' => 'Phobject',
'PhabricatorBulkManagementExportWorkflow' => 'PhabricatorBulkManagementWorkflow',
'PhabricatorBulkManagementMakeSilentWorkflow' => 'PhabricatorBulkManagementWorkflow',
'PhabricatorBulkManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorCSVExportFormat' => 'PhabricatorExportFormat',
@@ -7803,6 +7844,7 @@ phutil_register_library_map(array(
'PhabricatorClusterExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorClusterImpossibleWriteException' => 'PhabricatorClusterException',
'PhabricatorClusterImproperWriteException' => 'PhabricatorClusterException',
'PhabricatorClusterMailersConfigType' => 'PhabricatorJSONConfigType',
'PhabricatorClusterNoHostForRoleException' => 'Exception',
'PhabricatorClusterSearchConfigType' => 'PhabricatorJSONConfigType',
'PhabricatorClusterServiceHealthRecord' => 'Phobject',
@@ -7820,6 +7862,7 @@ phutil_register_library_map(array(
'PhabricatorCommonPasswords' => 'Phobject',
'PhabricatorConduitAPIController' => 'PhabricatorConduitController',
'PhabricatorConduitApplication' => 'PhabricatorApplication',
'PhabricatorConduitCallManagementWorkflow' => 'PhabricatorConduitManagementWorkflow',
'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO',
'PhabricatorConduitConsoleController' => 'PhabricatorConduitController',
'PhabricatorConduitContentSource' => 'PhabricatorContentSource',
@@ -7830,6 +7873,7 @@ phutil_register_library_map(array(
'PhabricatorConduitLogController' => 'PhabricatorConduitController',
'PhabricatorConduitLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorConduitLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorConduitManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorConduitMethodCallLog' => array(
'PhabricatorConduitDAO',
'PhabricatorPolicyInterface',
@@ -7999,6 +8043,7 @@ phutil_register_library_map(array(
'PhabricatorCustomFieldEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorCustomFieldEditField' => 'PhabricatorEditField',
'PhabricatorCustomFieldEditType' => 'PhabricatorEditType',
'PhabricatorCustomFieldExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorCustomFieldFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'PhabricatorCustomFieldHeraldAction' => 'HeraldAction',
'PhabricatorCustomFieldHeraldActionGroup' => 'HeraldActionGroup',
@@ -8181,6 +8226,8 @@ phutil_register_library_map(array(
'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
'PhabricatorDraftEngine' => 'Phobject',
'PhabricatorDrydockApplication' => 'PhabricatorApplication',
'PhabricatorEdgeChangeRecord' => 'Phobject',
'PhabricatorEdgeChangeRecordTestCase' => 'PhabricatorTestCase',
'PhabricatorEdgeConfig' => 'PhabricatorEdgeConstants',
'PhabricatorEdgeConstants' => 'Phobject',
'PhabricatorEdgeCycleException' => 'Exception',
@@ -8252,6 +8299,7 @@ phutil_register_library_map(array(
'PhabricatorEditPage' => 'Phobject',
'PhabricatorEditType' => 'Phobject',
'PhabricatorEditor' => 'Phobject',
'PhabricatorEditorMailEngineExtension' => 'PhabricatorMailEngineExtension',
'PhabricatorEditorMultipleSetting' => 'PhabricatorSelectSetting',
'PhabricatorEditorSetting' => 'PhabricatorStringSetting',
'PhabricatorElasticFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine',
@@ -8267,6 +8315,7 @@ phutil_register_library_map(array(
'PhabricatorEmailPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorEmailRePrefixSetting' => 'PhabricatorSelectSetting',
'PhabricatorEmailSelfActionsSetting' => 'PhabricatorSelectSetting',
'PhabricatorEmailStampsSetting' => 'PhabricatorSelectSetting',
'PhabricatorEmailTagsSetting' => 'PhabricatorInternalSetting',
'PhabricatorEmailVarySubjectsSetting' => 'PhabricatorSelectSetting',
'PhabricatorEmailVerificationController' => 'PhabricatorAuthController',
@@ -8285,9 +8334,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(
@@ -8493,6 +8547,7 @@ phutil_register_library_map(array(
'PhabricatorGDSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorGarbageCollector' => 'Phobject',
'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow',
'PhabricatorGarbageCollectorManagementCompactEdgesWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow',
'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow',
'PhabricatorGarbageCollectorManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorGeneralCachePurger' => 'PhabricatorCachePurger',
@@ -8604,9 +8659,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',
@@ -8642,14 +8699,17 @@ phutil_register_library_map(array(
'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorMacroTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorMacroViewController' => 'PhabricatorMacroController',
'PhabricatorMailConfigTestCase' => 'PhabricatorTestCase',
'PhabricatorMailEmailHeraldField' => 'HeraldField',
'PhabricatorMailEmailHeraldFieldGroup' => 'HeraldFieldGroup',
'PhabricatorMailEmailSubjectHeraldField' => 'PhabricatorMailEmailHeraldField',
'PhabricatorMailEngineExtension' => 'Phobject',
'PhabricatorMailImplementationAdapter' => 'Phobject',
'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter',
'PhabricatorMailImplementationMailgunAdapter' => 'PhabricatorMailImplementationAdapter',
'PhabricatorMailImplementationPHPMailerAdapter' => 'PhabricatorMailImplementationAdapter',
'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter',
'PhabricatorMailImplementationPostmarkAdapter' => 'PhabricatorMailImplementationAdapter',
'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter',
'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter',
'PhabricatorMailManagementListInboundWorkflow' => 'PhabricatorMailManagementWorkflow',
@@ -8662,6 +8722,7 @@ phutil_register_library_map(array(
'PhabricatorMailManagementUnverifyWorkflow' => 'PhabricatorMailManagementWorkflow',
'PhabricatorMailManagementVolumeWorkflow' => 'PhabricatorMailManagementWorkflow',
'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorMailMustEncryptHeraldAction' => 'HeraldAction',
'PhabricatorMailOutboundMailHeraldAdapter' => 'HeraldAdapter',
'PhabricatorMailOutboundRoutingHeraldAction' => 'HeraldAction',
'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction',
@@ -8672,6 +8733,7 @@ phutil_register_library_map(array(
'PhabricatorMailReplyHandler' => 'Phobject',
'PhabricatorMailRoutingRule' => 'Phobject',
'PhabricatorMailSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorMailStamp' => 'Phobject',
'PhabricatorMailTarget' => 'Phobject',
'PhabricatorMailgunConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorMainMenuBarExtension' => 'Phobject',
@@ -8725,6 +8787,7 @@ phutil_register_library_map(array(
'PhabricatorMetaMTAMail' => array(
'PhabricatorMetaMTADAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorMetaMTAMailBody' => 'Phobject',
'PhabricatorMetaMTAMailBodyTestCase' => 'PhabricatorTestCase',
@@ -8741,6 +8804,7 @@ phutil_register_library_map(array(
'PhabricatorMetaMTAMailgunReceiveController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAMemberQuery' => 'PhabricatorQuery',
'PhabricatorMetaMTAPermanentFailureException' => 'Exception',
'PhabricatorMetaMTAPostmarkReceiveController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO',
'PhabricatorMetaMTAReceivedMailProcessingException' => 'Exception',
'PhabricatorMetaMTAReceivedMailTestCase' => 'PhabricatorTestCase',
@@ -8758,6 +8822,8 @@ phutil_register_library_map(array(
'PhabricatorMultiFactorSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorMultimeterApplication' => 'PhabricatorApplication',
'PhabricatorMustVerifyEmailController' => 'PhabricatorAuthController',
'PhabricatorMutedByEdgeType' => 'PhabricatorEdgeType',
'PhabricatorMutedEdgeType' => 'PhabricatorEdgeType',
'PhabricatorMySQLConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorMySQLSearchHost' => 'PhabricatorSearchHost',
@@ -8944,6 +9010,8 @@ phutil_register_library_map(array(
'PhabricatorPHIDExportField' => 'PhabricatorExportField',
'PhabricatorPHIDListEditField' => 'PhabricatorEditField',
'PhabricatorPHIDListEditType' => 'PhabricatorEditType',
'PhabricatorPHIDListExportField' => 'PhabricatorListExportField',
'PhabricatorPHIDMailStamp' => 'PhabricatorMailStamp',
'PhabricatorPHIDResolver' => 'Phobject',
'PhabricatorPHIDType' => 'Phobject',
'PhabricatorPHIDTypeTestCase' => 'PhutilTestCase',
@@ -9444,7 +9512,9 @@ phutil_register_library_map(array(
'PhabricatorProjectsCurtainExtension' => 'PHUICurtainExtension',
'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField',
'PhabricatorProjectsExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorProjectsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'PhabricatorProjectsMailEngineExtension' => 'PhabricatorMailEngineExtension',
'PhabricatorProjectsMembersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorProjectsMembershipIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
'PhabricatorProjectsPolicyRule' => 'PhabricatorPolicyRule',
@@ -9812,8 +9882,10 @@ phutil_register_library_map(array(
'PhabricatorSpacesController' => 'PhabricatorController',
'PhabricatorSpacesDAO' => 'PhabricatorLiskDAO',
'PhabricatorSpacesEditController' => 'PhabricatorSpacesController',
'PhabricatorSpacesExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorSpacesInterface' => 'PhabricatorPHIDInterface',
'PhabricatorSpacesListController' => 'PhabricatorSpacesController',
'PhabricatorSpacesMailEngineExtension' => 'PhabricatorMailEngineExtension',
'PhabricatorSpacesNamespace' => array(
'PhabricatorSpacesDAO',
'PhabricatorPolicyInterface',
@@ -9885,6 +9957,8 @@ phutil_register_library_map(array(
'PhabricatorStringExportField' => 'PhabricatorExportField',
'PhabricatorStringListConfigType' => 'PhabricatorTextListConfigType',
'PhabricatorStringListEditField' => 'PhabricatorEditField',
'PhabricatorStringListExportField' => 'PhabricatorListExportField',
'PhabricatorStringMailStamp' => 'PhabricatorMailStamp',
'PhabricatorStringSetting' => 'PhabricatorSetting',
'PhabricatorSubmitEditField' => 'PhabricatorEditField',
'PhabricatorSubscribedToObjectEdgeType' => 'PhabricatorEdgeType',
@@ -9898,9 +9972,12 @@ phutil_register_library_map(array(
'PhabricatorSubscriptionsEditController' => 'PhabricatorController',
'PhabricatorSubscriptionsEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorSubscriptionsEditor' => 'PhabricatorEditor',
'PhabricatorSubscriptionsExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorSubscriptionsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'PhabricatorSubscriptionsHeraldAction' => 'HeraldAction',
'PhabricatorSubscriptionsListController' => 'PhabricatorController',
'PhabricatorSubscriptionsMailEngineExtension' => 'PhabricatorMailEngineExtension',
'PhabricatorSubscriptionsMuteController' => 'PhabricatorController',
'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
'PhabricatorSubscriptionsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
@@ -10021,6 +10098,7 @@ phutil_register_library_map(array(
'PhabricatorUIExample' => 'Phobject',
'PhabricatorUIExampleRenderController' => 'PhabricatorController',
'PhabricatorUIExamplesApplication' => 'PhabricatorApplication',
'PhabricatorURIExportField' => 'PhabricatorExportField',
'PhabricatorUSEnglishTranslation' => 'PhutilTranslation',
'PhabricatorUnifiedDiffsSetting' => 'PhabricatorSelectSetting',
'PhabricatorUnitTestContentSource' => 'PhabricatorContentSource',
@@ -10144,6 +10222,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);
@@ -427,17 +473,14 @@ final class PhabricatorAuditEditor
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$identifier = $object->getCommitIdentifier();
$repository = $object->getRepository();
$monogram = $repository->getMonogram();
$summary = $object->getSummary();
$name = $repository->formatCommitName($identifier);
$subject = "{$name}: {$summary}";
$thread_topic = "Commit {$monogram}{$identifier}";
$template = id(new PhabricatorMetaMTAMail())
->setSubject($subject)
->addHeader('Thread-Topic', $thread_topic);
->setSubject($subject);
$this->attachPatch(
$template,
@@ -453,7 +496,6 @@ final class PhabricatorAuditEditor
$phids[] = $object->getAuthorPHID();
}
$status_resigned = PhabricatorAuditStatusConstants::RESIGNED;
foreach ($object->getAudits() as $audit) {
if (!$audit->isInteresting()) {
// Don't send mail to uninteresting auditors, like packages which
@@ -461,7 +503,7 @@ final class PhabricatorAuditEditor
continue;
}
if ($audit->getAuditStatus() != $status_resigned) {
if (!$audit->isResigned()) {
$phids[] = $audit->getAuditorPHID();
}
}
@@ -471,6 +513,18 @@ final class PhabricatorAuditEditor
return $phids;
}
protected function newMailUnexpandablePHIDs(PhabricatorLiskDAO $object) {
$phids = array();
foreach ($object->getAudits() as $auditor) {
if ($auditor->isResigned()) {
$phids[] = $auditor->getAuditorPHID();
}
}
return $phids;
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {

View File

@@ -255,11 +255,9 @@ final class PhabricatorAuthSSHKeyEditor
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$name = $object->getName();
$phid = $object->getPHID();
$mail = id(new PhabricatorMetaMTAMail())
->setSubject(pht('SSH Key %d: %s', $id, $name))
->addHeader('Thread-Topic', $phid);
->setSubject(pht('SSH Key %d: %s', $id, $name));
// The primary value of this mail is alerting users to account compromises,
// so force delivery. In particular, this mail should still be delivered

View File

@@ -87,12 +87,10 @@ final class PhabricatorBadgesEditor
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$name = $object->getName();
$id = $object->getID();
$topic = pht('Badge %d', $id);
$subject = pht('Badge %d: %s', $id, $name);
return id(new PhabricatorMetaMTAMail())
->setSubject($subject)
->addHeader('Thread-Topic', $topic);
->setSubject($subject);
}
protected function getMailTo(PhabricatorLiskDAO $object) {

View File

@@ -309,13 +309,11 @@ final class PhabricatorCalendarEventEditor
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$name = $object->getName();
$monogram = $object->getMonogram();
return id(new PhabricatorMetaMTAMail())
->setSubject("{$monogram}: {$name}")
->addHeader('Thread-Topic', $monogram);
->setSubject("{$monogram}: {$name}");
}
protected function buildMailBody(

View File

@@ -0,0 +1,66 @@
<?php
final class PhabricatorConduitCallManagementWorkflow
extends PhabricatorConduitManagementWorkflow {
protected function didConstruct() {
$this
->setName('call')
->setSynopsis(pht('Call a Conduit method..'))
->setArguments(
array(
array(
'name' => 'method',
'param' => 'method',
'help' => pht('Method to call.'),
),
array(
'name' => 'input',
'param' => 'input',
'help' => pht(
'File to read parameters from, or "-" to read from '.
'stdin.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$method = $args->getArg('method');
if (!strlen($method)) {
throw new PhutilArgumentUsageException(
pht('Specify a method to call with "--method".'));
}
$input = $args->getArg('input');
if (!strlen($input)) {
throw new PhutilArgumentUsageException(
pht('Specify a file to read parameters from with "--input".'));
}
if ($input === '-') {
fprintf(STDERR, tsprintf("%s\n", pht('Reading input from stdin...')));
$input_json = file_get_contents('php://stdin');
} else {
$input_json = Filesystem::readFile($input);
}
$params = phutil_json_decode($input_json);
$result = id(new ConduitCall($method, $params))
->setUser($viewer)
->execute();
$output = array(
'result' => $result,
);
echo tsprintf(
"%B\n",
id(new PhutilJSON())->encodeFormatted($output));
return 0;
}
}

View File

@@ -0,0 +1,4 @@
<?php
abstract class PhabricatorConduitManagementWorkflow
extends PhabricatorManagementWorkflow {}

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

@@ -7,6 +7,10 @@ final class PhabricatorMailSetupCheck extends PhabricatorSetupCheck {
}
protected function executeChecks() {
if (PhabricatorEnv::getEnvConfig('cluster.mailers')) {
return;
}
$adapter = PhabricatorEnv::getEnvConfig('metamta.mail-adapter');
switch ($adapter) {

View File

@@ -6,7 +6,9 @@ final class PhabricatorConfigManagementSetWorkflow
protected function didConstruct() {
$this
->setName('set')
->setExamples('**set** __key__ __value__')
->setExamples(
"**set** __key__ __value__\n".
"**set** __key__ --stdin < value.json")
->setSynopsis(pht('Set a local configuration value.'))
->setArguments(
array(
@@ -16,6 +18,10 @@ final class PhabricatorConfigManagementSetWorkflow
'Update configuration in the database instead of '.
'in local configuration.'),
),
array(
'name' => 'stdin',
'help' => pht('Read option value from stdin.'),
),
array(
'name' => 'args',
'wildcard' => true,
@@ -31,8 +37,20 @@ final class PhabricatorConfigManagementSetWorkflow
pht('Specify a configuration key and a value to set it to.'));
}
$is_stdin = $args->getArg('stdin');
$key = $argv[0];
if ($is_stdin) {
if (count($argv) > 1) {
throw new PhutilArgumentUsageException(
pht(
'Too many arguments: expected only a key when using "--stdin".'));
}
fprintf(STDERR, tsprintf("%s\n", pht('Reading value from stdin...')));
$value = file_get_contents('php://stdin');
} else {
if (count($argv) == 1) {
throw new PhutilArgumentUsageException(
pht(
@@ -40,14 +58,16 @@ final class PhabricatorConfigManagementSetWorkflow
$key));
}
$value = $argv[1];
if (count($argv) > 2) {
throw new PhutilArgumentUsageException(
pht(
'Too many arguments: expected one key and one value.'));
}
$value = $argv[1];
}
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
if (empty($options[$key])) {
throw new PhutilArgumentUsageException(

View File

@@ -66,7 +66,9 @@ of each approach are:
received a similar message, but can not prevent all stray email arising
from "Reply All".
- Not supported with a private reply-to address.
- Mails are sent in the server default translation.
- Mail messages are sent in the server default translation.
- Mail that must be delivered over secure channels will leak the recipient
list in the "To" and "Cc" headers.
- One mail to each user:
- Policy controls work correctly and are enforced per-user.
- Recipients need to look in the mail body to see To/Cc.
@@ -77,7 +79,7 @@ of each approach are:
- "Reply All" will never send extra mail to other users involved in the
thread.
- Required if private reply-to addresses are configured.
- Mails are sent in the language of user preference.
- Mail messages are sent in the language of user preference.
EODOC
));
@@ -138,24 +140,19 @@ EODOC
,
'metamta.public-replies'));
$adapter_doc_href = PhabricatorEnv::getDoclink(
'Configuring Outbound Email');
$adapter_doc_name = pht('Configuring Outbound Email');
$adapter_description = $this->deformat(pht(<<<EODOC
Adapter class to use to transmit mail to the MTA. The default uses
PHPMailerLite, which will invoke "sendmail". This is appropriate if sendmail
actually works on your host, but if you haven't configured mail it may not be so
great. A number of other mailers are available (e.g., SES, SendGrid, SMTP,
custom mailers) - consult [[ %s | %s ]] for details.
custom mailers). This option is deprecated in favor of 'cluster.mailers'.
EODOC
,
$adapter_doc_href,
$adapter_doc_name));
));
$placeholder_description = $this->deformat(pht(<<<EODOC
When sending a message that has no To recipient (i.e. all recipients are CC'd,
for example when multiplexing mail), set the To field to the following value. If
no value is set, messages with no To will have their CCs upgraded to To.
When sending a message that has no To recipient (i.e. all recipients are CC'd),
set the To field to the following value. If no value is set, messages with no
To will have their CCs upgraded to To.
EODOC
));
@@ -197,7 +194,18 @@ The default is `full`.
EODOC
));
$mailers_description = $this->deformat(pht(<<<EODOC
Define one or more mail transmission services. For help with configuring
mailers, see **[[ %s | %s ]]** in the documentation.
EODOC
,
PhabricatorEnv::getDoclink('Configuring Outbound Email'),
pht('Configuring Outbound Email')));
return array(
$this->newOption('cluster.mailers', 'cluster.mailers', null)
->setHidden(true)
->setDescription($mailers_description),
$this->newOption(
'metamta.default-address',
'string',

View File

@@ -227,11 +227,9 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
'%s sent you a message.',
$this->getActor()->getUserName());
}
$phid = $object->getPHID();
return id(new PhabricatorMetaMTAMail())
->setSubject("Z{$id}: {$title}")
->addHeader('Thread-Topic', "Z{$id}: {$phid}");
->setSubject("Z{$id}: {$title}");
}
protected function getMailTo(PhabricatorLiskDAO $object) {

View File

@@ -45,8 +45,7 @@ final class PhabricatorCountdownEditor
$name = $object->getTitle();
return id(new PhabricatorMetaMTAMail())
->setSubject("{$monogram}: {$name}")
->addHeader('Thread-Topic', $monogram);
->setSubject("{$monogram}: {$name}");
}
protected function buildMailBody(

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

@@ -632,6 +632,8 @@ final class DifferentialTransactionEditor
}
protected function getMailTo(PhabricatorLiskDAO $object) {
$this->requireReviewers($object);
$phids = array();
$phids[] = $object->getAuthorPHID();
foreach ($object->getReviewers() as $reviewer) {
@@ -644,6 +646,20 @@ final class DifferentialTransactionEditor
return $phids;
}
protected function newMailUnexpandablePHIDs(PhabricatorLiskDAO $object) {
$this->requireReviewers($object);
$phids = array();
foreach ($object->getReviewers() as $reviewer) {
if ($reviewer->isResigned()) {
$phids[] = $reviewer->getReviewerPHID();
}
}
return $phids;
}
protected function getMailAction(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -689,15 +705,10 @@ final class DifferentialTransactionEditor
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$title = $object->getTitle();
$original_title = $object->getOriginalTitle();
$subject = "D{$id}: {$title}";
$thread_topic = "D{$id}: {$original_title}";
return id(new PhabricatorMetaMTAMail())
->setSubject($subject)
->addHeader('Thread-Topic', $thread_topic);
->setSubject($subject);
}
protected function getTransactionsForMail(
@@ -919,7 +930,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) {
@@ -1693,4 +1741,25 @@ final class DifferentialTransactionEditor
}
}
private function requireReviewers(DifferentialRevision $revision) {
if ($revision->hasAttachedReviewers()) {
return;
}
$with_reviewers = id(new DifferentialRevisionQuery())
->setViewer($this->getActor())
->needReviewers(true)
->withPHIDs(array($revision->getPHID()))
->executeOne();
if (!$with_reviewers) {
throw new Exception(
pht(
'Failed to reload revision ("%s").',
$revision->getPHID()));
}
$revision->attachReviewers($with_reviewers->getReviewers());
}
}

View File

@@ -0,0 +1,80 @@
<?php
final class DifferentialMailEngineExtension
extends PhabricatorMailEngineExtension {
const EXTENSIONKEY = 'differential';
public function supportsObject($object) {
return ($object instanceof DifferentialRevision);
}
public function newMailStampTemplates($object) {
return array(
id(new PhabricatorPHIDMailStamp())
->setKey('author')
->setLabel(pht('Author')),
id(new PhabricatorPHIDMailStamp())
->setKey('reviewer')
->setLabel(pht('Reviewer')),
id(new PhabricatorPHIDMailStamp())
->setKey('blocking-reviewer')
->setLabel(pht('Reviewer')),
id(new PhabricatorPHIDMailStamp())
->setKey('resigned-reviewer')
->setLabel(pht('Reviewer')),
id(new PhabricatorPHIDMailStamp())
->setKey('revision-repository')
->setLabel(pht('Revision Repository')),
id(new PhabricatorPHIDMailStamp())
->setKey('revision-status')
->setLabel(pht('Revision Status')),
);
}
public function newMailStamps($object, array $xactions) {
$editor = $this->getEditor();
$viewer = $this->getViewer();
$revision = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->needReviewers(true)
->withPHIDs(array($object->getPHID()))
->executeOne();
$reviewers = array();
$blocking = array();
$resigned = array();
foreach ($revision->getReviewers() as $reviewer) {
$reviewer_phid = $reviewer->getReviewerPHID();
if ($reviewer->isResigned()) {
$resigned[] = $reviewer_phid;
} else {
$reviewers[] = $reviewer_phid;
if ($reviewer->isBlocking()) {
$reviewers[] = $blocking;
}
}
}
$this->getMailStamp('author')
->setValue($revision->getAuthorPHID());
$this->getMailStamp('reviewer')
->setValue($reviewers);
$this->getMailStamp('blocking-reviewer')
->setValue($blocking);
$this->getMailStamp('resigned-reviewer')
->setValue($resigned);
$this->getMailStamp('revision-repository')
->setValue($revision->getRepositoryPHID());
$this->getMailStamp('revision-status')
->setValue($revision->getModernRevisionStatus());
}
}

View File

@@ -221,6 +221,51 @@ final class DifferentialChangeset
return $this->assertAttached($this->diff);
}
public function newFileTreeIcon() {
$file_type = $this->getFileType();
$change_type = $this->getChangeType();
$change_icons = array(
DifferentialChangeType::TYPE_DELETE => 'fa-file-o',
);
if (isset($change_icons[$change_type])) {
$icon = $change_icons[$change_type];
} else {
$icon = DifferentialChangeType::getIconForFileType($file_type);
}
$change_colors = array(
DifferentialChangeType::TYPE_ADD => 'green',
DifferentialChangeType::TYPE_DELETE => 'red',
DifferentialChangeType::TYPE_MOVE_AWAY => 'orange',
DifferentialChangeType::TYPE_MOVE_HERE => 'orange',
DifferentialChangeType::TYPE_COPY_HERE => 'orange',
DifferentialChangeType::TYPE_MULTICOPY => 'orange',
);
$color = idx($change_colors, $change_type, 'bluetext');
return id(new PHUIIconView())
->setIcon($icon.' '.$color);
}
public function getFileTreeClass() {
switch ($this->getChangeType()) {
case DifferentialChangeType::TYPE_ADD:
return 'filetree-added';
case DifferentialChangeType::TYPE_DELETE:
return 'filetree-deleted';
case DifferentialChangeType::TYPE_MOVE_AWAY:
case DifferentialChangeType::TYPE_MOVE_HERE:
case DifferentialChangeType::TYPE_COPY_HERE:
case DifferentialChangeType::TYPE_MULTICOPY:
return 'filetree-movecopy';
}
return null;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View File

@@ -69,6 +69,11 @@ final class DifferentialReviewer
return ($this->getReviewerStatus() == $status_resigned);
}
public function isBlocking() {
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
return ($this->getReviewerStatus() == $status_blocking);
}
public function isRejected($diff_phid) {
$status_rejected = DifferentialReviewerStatus::STATUS_REJECTED;

View File

@@ -20,7 +20,6 @@ final class DifferentialRevision extends DifferentialDAO
PhabricatorDraftInterface {
protected $title = '';
protected $originalTitle;
protected $status;
protected $summary = '';
@@ -98,7 +97,6 @@ final class DifferentialRevision extends DifferentialDAO
),
self::CONFIG_COLUMN_SCHEMA => array(
'title' => 'text255',
'originalTitle' => 'text255',
'status' => 'text32',
'summary' => 'text',
'testPlan' => 'text',
@@ -155,14 +153,6 @@ final class DifferentialRevision extends DifferentialDAO
return '/'.$this->getMonogram();
}
public function setTitle($title) {
$this->title = $title;
if (!$this->getID()) {
$this->originalTitle = $title;
}
return $this;
}
public function loadIDsByCommitPHIDs($phids) {
if (!$phids) {
return array();
@@ -593,6 +583,10 @@ final class DifferentialRevision extends DifferentialDAO
return $this;
}
public function hasAttachedReviewers() {
return ($this->reviewerStatus !== self::ATTACHABLE);
}
public function getReviewerPHIDs() {
$reviewers = $this->getReviewers();
return mpull($reviewers, 'getReviewerPHID');
@@ -830,9 +824,15 @@ final class DifferentialRevision extends DifferentialDAO
}
foreach ($reviewers as $reviewer) {
if ($reviewer->getReviewerPHID() == $phid) {
return true;
if ($reviewer->getReviewerPHID() !== $phid) {
continue;
}
if ($reviewer->isResigned()) {
continue;
}
return true;
}
return false;

View File

@@ -87,19 +87,6 @@ final class DifferentialTransaction
}
break;
case PhabricatorTransactions::TYPE_EDGE:
$add = array_diff_key($new, $old);
$rem = array_diff_key($old, $new);
// Hide metadata-only edge transactions. These correspond to users
// accepting or rejecting revisions, but the change is always explicit
// because of the TYPE_ACTION transaction. Rendering these transactions
// just creates clutter.
if (!$add && !$rem) {
return true;
}
break;
case DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE:
// Don't hide the initial "X requested review: ..." transaction from
// mail or feed even when it occurs during creation. We need this

View File

@@ -206,6 +206,7 @@ final class DifferentialChangesetDetailView extends AphrontView {
'displayPath' => hsprintf('%s', $display_parts),
'path' => $display_filename,
'icon' => $display_icon,
'treeNodeID' => 'tree-node-'.$changeset->getAnchorName(),
),
'class' => $class,
'id' => $id,

View File

@@ -83,6 +83,9 @@ final class DifferentialChangesetFileTreeSideNavBuilder extends Phobject {
while (($path = $path->getNextNode())) {
$data = $path->getData();
$classes = array();
$classes[] = 'phabricator-filetree-item';
$name = $path->getName();
$style = 'padding-left: '.(2 + (3 * $path->getDepth())).'px';
@@ -90,13 +93,23 @@ final class DifferentialChangesetFileTreeSideNavBuilder extends Phobject {
if ($data) {
$href = '#'.$data->getAnchorName();
$title = $name;
$icon = id(new PHUIIconView())
->setIcon('fa-file-text-o bluetext');
$icon = $data->newFileTreeIcon();
$classes[] = $data->getFileTreeClass();
$count = phutil_tag(
'span',
array(
'class' => 'filetree-progress-hint',
'id' => 'tree-node-'.$data->getAnchorName(),
));
} else {
$name .= '/';
$title = $path->getFullPath().'/';
$icon = id(new PHUIIconView())
->setIcon('fa-folder-open blue');
$count = null;
}
$name_element = phutil_tag(
@@ -106,15 +119,16 @@ final class DifferentialChangesetFileTreeSideNavBuilder extends Phobject {
),
$name);
$filetree[] = javelin_tag(
$href ? 'a' : 'span',
array(
'href' => $href,
'style' => $style,
'title' => $title,
'class' => 'phabricator-filetree-item',
'class' => implode(' ', $classes),
),
array($icon, $name_element));
array($count, $icon, $name_element));
}
$tree->destroy();

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

@@ -37,7 +37,11 @@ final class DiffusionQueryPathsConduitAPIMethod
$commit = $request->getValue('commit');
$repository = $drequest->getRepository();
// http://comments.gmane.org/gmane.comp.version-control.git/197735
// Recent versions of Git don't work if you pass the empty string, and
// require "." to list everything.
if (!strlen($path)) {
$path = '.';
}
$future = $repository->getLocalCommandFuture(
'ls-tree --name-only -r -z %s -- %s',

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

@@ -23,14 +23,10 @@ final class DiffusionRepositoryURIViewController
return new Aphront404Response();
}
// For display, reload the URI by loading it through the repository. This
// For display, access the URI by loading it through the repository. This
// may adjust builtin URIs for repository configuration, so we may end up
// with a different view of builtin URIs than we'd see if we loaded them
// directly from the database. See T12884.
$repository_with_uris = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->needURIs(true)
->execute();
$repository_uris = $repository->getURIs();
$repository_uris = mpull($repository_uris, null, 'getID');

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

@@ -297,7 +297,11 @@ final class DiffusionCommitHookEngine extends Phobject {
return;
}
$adapter_template->setHookEngine($this);
$viewer = $this->getViewer();
$adapter_template
->setHookEngine($this)
->setActingAsPHID($viewer->getPHID());
$engine = new HeraldEngine();
$rules = null;

View File

@@ -84,7 +84,7 @@ final class DiffusionGitLFSAuthenticateWorkflow
// This works even if normal HTTP repository operations are not available
// on this host, and does not require the user to have a VCS password.
$user = $this->getUser();
$user = $this->getSSHUser();
$authorization = DiffusionGitLFSTemporaryTokenType::newHTTPAuthorization(
$repository,

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

@@ -47,8 +47,7 @@ final class PhabricatorFileEditor
$name = $object->getName();
return id(new PhabricatorMetaMTAMail())
->setSubject("F{$id}: {$name}")
->addHeader('Thread-Topic', "F{$id}");
->setSubject("F{$id}: {$name}");
}
protected function buildMailBody(

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,8 +50,7 @@ final class FundInitiativeEditor
$name = $object->getName();
return id(new PhabricatorMetaMTAMail())
->setSubject("{$monogram}: {$name}")
->addHeader('Thread-Topic', $monogram);
->setSubject("{$monogram}: {$name}");
}
protected function buildMailBody(

View File

@@ -39,6 +39,8 @@ abstract class HeraldAdapter extends Phobject {
private $edgeCache = array();
private $forbiddenActions = array();
private $viewer;
private $mustEncryptReasons = array();
private $actingAsPHID;
public function getEmailPHIDs() {
return array_values($this->emailPHIDs);
@@ -48,6 +50,15 @@ abstract class HeraldAdapter extends Phobject {
return array_values($this->forcedEmailPHIDs);
}
final public function setActingAsPHID($acting_as_phid) {
$this->actingAsPHID = $acting_as_phid;
return $this;
}
final public function getActingAsPHID() {
return $this->actingAsPHID;
}
public function addEmailPHID($phid, $force) {
$this->emailPHIDs[$phid] = $phid;
if ($force) {
@@ -1182,4 +1193,17 @@ abstract class HeraldAdapter extends Phobject {
return $this->forbiddenActions[$action];
}
/* -( Must Encrypt )------------------------------------------------------- */
final public function addMustEncryptReason($reason) {
$this->mustEncryptReasons[] = $reason;
return $this;
}
final public function getMustEncryptReasons() {
return $this->mustEncryptReasons;
}
}

View File

@@ -265,7 +265,15 @@ final class HeraldRuleController extends HeraldController {
$new_name = $request->getStr('name');
$match_all = ($request->getStr('must_match') == 'all');
$repetition_policy_param = $request->getStr('repetition_policy');
$repetition_policy = $request->getStr('repetition_policy');
// If the user selected an invalid policy, or there's only one possible
// value so we didn't render a control, adjust the value to the first
// valid policy value.
$repetition_options = $this->getRepetitionOptionMap($adapter);
if (!isset($repetition_options[$repetition_policy])) {
$repetition_policy = head_key($repetition_options);
}
$e_name = true;
$errors = array();
@@ -348,7 +356,7 @@ final class HeraldRuleController extends HeraldController {
$match_all,
$conditions,
$actions,
$repetition_policy_param);
$repetition_policy);
$xactions = array();
$xactions[] = id(new HeraldRuleTransaction())
@@ -373,7 +381,7 @@ final class HeraldRuleController extends HeraldController {
// mutate current rule, so it would be sent to the client in the right state
$rule->setMustMatchAll((int)$match_all);
$rule->setName($new_name);
$rule->setRepetitionPolicyStringConstant($repetition_policy_param);
$rule->setRepetitionPolicyStringConstant($repetition_policy);
$rule->attachConditions($conditions);
$rule->attachActions($actions);
@@ -594,13 +602,9 @@ final class HeraldRuleController extends HeraldController {
*/
private function renderRepetitionSelector($rule, HeraldAdapter $adapter) {
$repetition_policy = $rule->getRepetitionPolicyStringConstant();
$repetition_options = $adapter->getRepetitionOptions();
$repetition_names = HeraldRule::getRepetitionPolicySelectOptionMap();
$repetition_map = array_select_keys($repetition_names, $repetition_options);
$repetition_map = $this->getRepetitionOptionMap($adapter);
if (count($repetition_map) < 2) {
return head($repetition_names);
return head($repetition_map);
} else {
return AphrontFormSelectControl::renderSelectTag(
$repetition_policy,
@@ -611,6 +615,11 @@ final class HeraldRuleController extends HeraldController {
}
}
private function getRepetitionOptionMap(HeraldAdapter $adapter) {
$repetition_options = $adapter->getRepetitionOptions();
$repetition_names = HeraldRule::getRepetitionPolicySelectOptionMap();
return array_select_keys($repetition_names, $repetition_options);
}
protected function buildTokenizerTemplates() {
$template = new AphrontTokenizerTemplateView();

View File

@@ -41,6 +41,7 @@ final class HeraldTestConsoleController extends HeraldController {
$adapter
->setIsNewObject(false)
->setActingAsPHID($viewer->getPHID())
->setViewer($viewer);
$rules = id(new HeraldRuleQuery())

View File

@@ -0,0 +1,32 @@
<?php
final class HeraldActingUserField
extends HeraldField {
const FIELDCONST = 'herald.acting-user';
public function getHeraldFieldName() {
return pht('Acting user');
}
public function getHeraldFieldValue($object) {
return $this->getAdapter()->getActingAsPHID();
}
protected function getHeraldFieldStandardType() {
return self::STANDARD_PHID;
}
protected function getDatasource() {
return new PhabricatorPeopleDatasource();
}
public function supportsObject($object) {
return true;
}
public function getFieldGroupKey() {
return HeraldEditFieldGroup::FIELDGROUPKEY;
}
}

View File

@@ -124,12 +124,10 @@ final class LegalpadDocumentEditor
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$phid = $object->getPHID();
$title = $object->getDocumentBody()->getTitle();
return id(new PhabricatorMetaMTAMail())
->setSubject("L{$id}: {$title}")
->addHeader('Thread-Topic', "L{$id}: {$phid}");
->setSubject("L{$id}: {$title}");
}
protected function getMailTo(PhabricatorLiskDAO $object) {

View File

@@ -176,7 +176,7 @@ final class LegalpadDocumentSearchEngine
$create_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Create a Document'))
->setHref('/legalpad/create/')
->setHref('/legalpad/edit/')
->setColor(PHUIButtonView::GREEN);
$icon = $this->getApplication()->getIcon();

View File

@@ -35,8 +35,7 @@ final class PhabricatorMacroEditor
$name = 'Image Macro "'.$name.'"';
return id(new PhabricatorMetaMTAMail())
->setSubject($name)
->addHeader('Thread-Topic', $name);
->setSubject($name);
}
protected function getMailTo(PhabricatorLiskDAO $object) {

View File

@@ -51,13 +51,13 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication {
'/T(?P<id>[1-9]\d*)' => 'ManiphestTaskDetailController',
'/maniphest/' => array(
'(?:project/(?P<projectKey>[^/]+)/)?(?:type/(?P<taskTypeKey>[^/]+)/)?(?: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,137 +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)
->setProjectKey($this->projectKey)
->setTaskTypeKey($this->taskTypeKey);
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

@@ -206,8 +206,7 @@ final class ManiphestTransactionEditor
$title = $object->getTitle();
return id(new PhabricatorMetaMTAMail())
->setSubject("T{$id}: {$title}")
->addHeader('Thread-Topic', "T{$id}: ".$object->getOriginalTitle());
->setSubject("T{$id}: {$title}");
}
protected function buildMailBody(
@@ -534,7 +533,6 @@ final class ManiphestTransactionEditor
'status' => '""',
'priority' => 0,
'title' => '""',
'originalTitle' => '""',
'description' => '""',
'dateCreated' => 0,
'dateModified' => 0,

View File

@@ -0,0 +1,58 @@
<?php
final class ManiphestMailEngineExtension
extends PhabricatorMailEngineExtension {
const EXTENSIONKEY = 'maniphest';
public function supportsObject($object) {
return ($object instanceof ManiphestTask);
}
public function newMailStampTemplates($object) {
return array(
id(new PhabricatorPHIDMailStamp())
->setKey('author')
->setLabel(pht('Author')),
id(new PhabricatorPHIDMailStamp())
->setKey('task-owner')
->setLabel(pht('Task Owner')),
id(new PhabricatorBoolMailStamp())
->setKey('task-unassigned')
->setLabel(pht('Task Unassigned')),
id(new PhabricatorStringMailStamp())
->setKey('task-priority')
->setLabel(pht('Task Priority')),
id(new PhabricatorStringMailStamp())
->setKey('task-status')
->setLabel(pht('Task Status')),
id(new PhabricatorStringMailStamp())
->setKey('subtype')
->setLabel(pht('Subtype')),
);
}
public function newMailStamps($object, array $xactions) {
$editor = $this->getEditor();
$viewer = $this->getViewer();
$this->getMailStamp('author')
->setValue($object->getAuthorPHID());
$this->getMailStamp('task-owner')
->setValue($object->getOwnerPHID());
$this->getMailStamp('task-unassigned')
->setValue(!$object->getOwnerPHID());
$this->getMailStamp('task-priority')
->setValue($object->getPriority());
$this->getMailStamp('task-status')
->setValue($object->getStatus());
$this->getMailStamp('subtype')
->setValue($object->getSubtype());
}
}

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

@@ -23,6 +23,9 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $parentTaskIDs;
private $subtaskIDs;
private $subtypes;
private $closedEpochMin;
private $closedEpochMax;
private $closerPHIDs;
private $status = 'status-any';
const STATUS_ANY = 'status-any';
@@ -179,6 +182,17 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $this;
}
public function withClosedEpochBetween($min, $max) {
$this->closedEpochMin = $min;
$this->closedEpochMax = $max;
return $this;
}
public function withCloserPHIDs(array $phids) {
$this->closerPHIDs = $phids;
return $this;
}
public function needSubscriberPHIDs($bool) {
$this->needSubscriberPHIDs = $bool;
return $this;
@@ -411,6 +425,27 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$this->dateModifiedBefore);
}
if ($this->closedEpochMin !== null) {
$where[] = qsprintf(
$conn,
'task.closedEpoch >= %d',
$this->closedEpochMin);
}
if ($this->closedEpochMax !== null) {
$where[] = qsprintf(
$conn,
'task.closedEpoch <= %d',
$this->closedEpochMax);
}
if ($this->closerPHIDs !== null) {
$where[] = qsprintf(
$conn,
'task.closerPHID IN (%Ls)',
$this->closerPHIDs);
}
if ($this->priorities !== null) {
$where[] = qsprintf(
$conn,
@@ -755,6 +790,10 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
'vector' => array('-updated', '-id'),
'name' => pht('Date Updated (Oldest First)'),
),
'closed' => array(
'vector' => array('closed', 'id'),
'name' => pht('Date Closed (Latest First)'),
),
'title' => array(
'vector' => array('title', 'id'),
'name' => pht('Title'),
@@ -773,6 +812,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
'outdated',
'newest',
'oldest',
'closed',
'title',
)) + $orders;
@@ -822,6 +862,12 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
'column' => 'dateModified',
'type' => 'int',
),
'closed' => array(
'table' => 'task',
'column' => 'closedEpoch',
'type' => 'int',
'null' => 'tail',
),
);
}
@@ -840,6 +886,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
'status' => $task->getStatus(),
'title' => $task->getTitle(),
'updated' => $task->getDateModified(),
'closed' => $task->getClosedEpoch(),
);
foreach ($keys as $key) {

View File

@@ -128,6 +128,17 @@ final class ManiphestTaskSearchEngine
id(new PhabricatorSearchDateField())
->setLabel(pht('Updated Before'))
->setKey('modifiedEnd'),
id(new PhabricatorSearchDateField())
->setLabel(pht('Closed After'))
->setKey('closedStart'),
id(new PhabricatorSearchDateField())
->setLabel(pht('Closed Before'))
->setKey('closedEnd'),
id(new PhabricatorUsersSearchField())
->setLabel(pht('Closed By'))
->setKey('closerPHIDs')
->setAliases(array('closer', 'closerPHID', 'closers'))
->setDescription(pht('Search for tasks closed by certain users.')),
id(new PhabricatorSearchTextField())
->setLabel(pht('Page Size'))
->setKey('limit'),
@@ -155,6 +166,9 @@ final class ManiphestTaskSearchEngine
'createdEnd',
'modifiedStart',
'modifiedEnd',
'closedStart',
'closedEnd',
'closerPHIDs',
'limit',
);
}
@@ -210,6 +224,14 @@ final class ManiphestTaskSearchEngine
$query->withDateModifiedBefore($map['modifiedEnd']);
}
if ($map['closedStart'] || $map['closedEnd']) {
$query->withClosedEpochBetween($map['closedStart'], $map['closedEnd']);
}
if ($map['closerPHIDs']) {
$query->withCloserPHIDs($map['closerPHIDs']);
}
if ($map['hasParents'] !== null) {
$query->withOpenParents($map['hasParents']);
}
@@ -485,4 +507,131 @@ 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 PhabricatorEpochExportField())
->setKey('dateClosed')
->setLabel(pht('Date Closed')),
id(new PhabricatorPHIDExportField())
->setKey('closerPHID')
->setLabel(pht('Closer PHID')),
id(new PhabricatorStringExportField())
->setKey('closer')
->setLabel(pht('Closer')),
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();
$phids[] = $task->getCloserPHID();
}
$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;
}
$closer_phid = $task->getCloserPHID();
if ($closer_phid) {
$closer_name = $handles[$closer_phid]->getName();
} else {
$closer_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(),
'dateClosed' => $task->getClosedEpoch(),
'closerPHID' => $closer_phid,
'closer' => $closer_name,
);
}
return $export;
}
}

View File

@@ -31,7 +31,6 @@ final class ManiphestTask extends ManiphestDAO
protected $subpriority = 0;
protected $title = '';
protected $originalTitle = '';
protected $description = '';
protected $originalEmailSource;
protected $mailKey;
@@ -45,6 +44,9 @@ final class ManiphestTask extends ManiphestDAO
protected $points;
protected $subtype;
protected $closedEpoch;
protected $closerPHID;
private $subscriberPHIDs = self::ATTACHABLE;
private $groupByProjectPHID = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
@@ -83,7 +85,6 @@ final class ManiphestTask extends ManiphestDAO
'status' => 'text64',
'priority' => 'uint32',
'title' => 'sort',
'originalTitle' => 'text',
'description' => 'text',
'mailKey' => 'bytes20',
'ownerOrdering' => 'text64?',
@@ -92,6 +93,8 @@ final class ManiphestTask extends ManiphestDAO
'points' => 'double?',
'bridgedObjectPHID' => 'phid?',
'subtype' => 'text64',
'closedEpoch' => 'epoch?',
'closerPHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
@@ -133,6 +136,12 @@ final class ManiphestTask extends ManiphestDAO
'key_subtype' => array(
'columns' => array('subtype'),
),
'key_closed' => array(
'columns' => array('closedEpoch'),
),
'key_closer' => array(
'columns' => array('closerPHID', 'closedEpoch'),
),
),
) + parent::getConfiguration();
}
@@ -176,14 +185,6 @@ final class ManiphestTask extends ManiphestDAO
return $this;
}
public function setTitle($title) {
$this->title = $title;
if (!$this->getID()) {
$this->originalTitle = $title;
}
return $this;
}
public function getMonogram() {
return 'T'.$this->getID();
}
@@ -512,6 +513,16 @@ final class ManiphestTask extends ManiphestDAO
->setKey('subtype')
->setType('string')
->setDescription(pht('Subtype of the task.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('closerPHID')
->setType('phid?')
->setDescription(
pht('User who closed the task, if the task is closed.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('dateClosed')
->setType('int?')
->setDescription(
pht('Epoch timestamp when the task was closed.')),
);
}
@@ -531,6 +542,11 @@ final class ManiphestTask extends ManiphestDAO
'color' => ManiphestTaskPriority::getTaskPriorityColor($priority_value),
);
$closed_epoch = $this->getClosedEpoch();
if ($closed_epoch !== null) {
$closed_epoch = (int)$closed_epoch;
}
return array(
'name' => $this->getTitle(),
'description' => array(
@@ -542,6 +558,8 @@ final class ManiphestTask extends ManiphestDAO
'priority' => $priority_info,
'points' => $this->getPoints(),
'subtype' => $this->getSubtype(),
'closerPHID' => $this->getCloserPHID(),
'dateClosed' => $closed_epoch,
);
}

View File

@@ -68,6 +68,11 @@ final class ManiphestTaskListView extends ManiphestView {
->setHeader($task->getTitle())
->setHref('/T'.$task->getID());
if ($task->getAuthorPHID()) {
$author = $handles[$task->getAuthorPHID()];
$item->addByline(pht('By: %s', $author->renderLink()));
}
if ($task->getOwnerPHID()) {
$owner = $handles[$task->getOwnerPHID()];
$item->addByline(pht('Assigned: %s', $owner->renderLink()));
@@ -87,19 +92,24 @@ final class ManiphestTaskListView extends ManiphestView {
$item->setStatusIcon($icon.' '.$color, $tooltip);
$date = phabricator_datetime($task->getDateModified(), $this->getUser());
if ($task->isClosed()) {
$closed_epoch = $task->getClosedEpoch();
if ($task->getAuthorPHID()) {
$author = idx($handles, $task->getAuthorPHID());
// TODO: This should be guaranteed, see T3817.
if ($author) {
$date = array($date, " by ", $author->renderLink());
}
// We don't expect a task to be closed without a closed epoch, but
// recover if we find one. This can happen with older objects or with
// lipsum test data.
if (!$closed_epoch) {
$closed_epoch = $task->getDateModified();
}
$item->addIcon(
'fa-check-square-o grey',
phabricator_datetime($closed_epoch, $this->getUser()));
} else {
$item->addIcon(
'none',
$date);
phabricator_datetime($task->getDateModified(), $this->getUser()));
}
if ($this->showSubpriorityControls) {
$item->setGrippable(true);

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

@@ -10,7 +10,7 @@ final class ManiphestTaskMergedIntoTransaction
}
public function applyInternalEffects($object, $value) {
$object->setStatus(ManiphestTaskStatus::getDuplicateStatus());
$this->updateStatus($object, ManiphestTaskStatus::getDuplicateStatus());
}
public function getActionName() {

View File

@@ -10,7 +10,7 @@ final class ManiphestTaskStatusTransaction
}
public function applyInternalEffects($object, $value) {
$object->setStatus($value);
$this->updateStatus($object, $value);
}
public function shouldHide() {

View File

@@ -3,4 +3,27 @@
abstract class ManiphestTaskTransactionType
extends PhabricatorModularTransactionType {
protected function updateStatus($object, $new_value) {
$old_value = $object->getStatus();
$object->setStatus($new_value);
// If this status change closes or opens the task, update the closed
// date and actor PHID.
$old_closed = ManiphestTaskStatus::isClosedStatus($old_value);
$new_closed = ManiphestTaskStatus::isClosedStatus($new_value);
$is_close = ($new_closed && !$old_closed);
$is_open = (!$new_closed && $old_closed);
if ($is_close) {
$object
->setClosedEpoch(PhabricatorTime::getNow())
->setCloserPHID($this->getActingAsPHID());
} else if ($is_open) {
$object
->setClosedEpoch(null)
->setCloserPHID(null);
}
}
}

View File

@@ -2,6 +2,22 @@
abstract class PhabricatorMailImplementationAdapter extends Phobject {
private $key;
private $priority;
private $options = array();
final public function getAdapterType() {
return $this->getPhobjectClassConstant('ADAPTERTYPE');
}
final public static function getAllAdapters() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getAdapterType')
->execute();
}
abstract public function setFrom($email, $name = '');
abstract public function addReplyTo($email, $name = '');
abstract public function addTos(array $emails);
@@ -12,6 +28,7 @@ abstract class PhabricatorMailImplementationAdapter extends Phobject {
abstract public function setHTMLBody($html_body);
abstract public function setSubject($subject);
/**
* Some mailers, notably Amazon SES, do not support us setting a specific
* Message-ID header.
@@ -32,4 +49,59 @@ abstract class PhabricatorMailImplementationAdapter extends Phobject {
*/
abstract public function send();
final public function setKey($key) {
$this->key = $key;
return $this;
}
final public function getKey() {
return $this->key;
}
final public function setPriority($priority) {
$this->priority = $priority;
return $this;
}
final public function getPriority() {
return $this->priority;
}
final public function getOption($key) {
if (!array_key_exists($key, $this->options)) {
throw new Exception(
pht(
'Mailer ("%s") is attempting to access unknown option ("%s").',
get_class($this),
$key));
}
return $this->options[$key];
}
final public function setOptions(array $options) {
$this->validateOptions($options);
$this->options = $options;
return $this;
}
abstract protected function validateOptions(array $options);
abstract public function newDefaultOptions();
abstract public function newLegacyOptions();
public function prepareForSend() {
return;
}
protected function renderAddress($email, $name = null) {
if (strlen($name)) {
return (string)id(new PhutilEmailAddress())
->setDisplayName($name)
->setAddress($email);
} else {
return $email;
}
}
}

View File

@@ -3,11 +3,13 @@
final class PhabricatorMailImplementationAmazonSESAdapter
extends PhabricatorMailImplementationPHPMailerLiteAdapter {
const ADAPTERTYPE = 'ses';
private $message;
private $isHTML;
public function __construct() {
parent::__construct();
public function prepareForSend() {
parent::prepareForSend();
$this->mailer->Mailer = 'amazon-ses';
$this->mailer->customMailer = $this;
}
@@ -17,13 +19,39 @@ final class PhabricatorMailImplementationAmazonSESAdapter
return false;
}
protected function validateOptions(array $options) {
PhutilTypeSpec::checkMap(
$options,
array(
'access-key' => 'string',
'secret-key' => 'string',
'endpoint' => 'string',
));
}
public function newDefaultOptions() {
return array(
'access-key' => null,
'secret-key' => null,
'endpoint' => null,
);
}
public function newLegacyOptions() {
return array(
'access-key' => PhabricatorEnv::getEnvConfig('amazon-ses.access-key'),
'secret-key' => PhabricatorEnv::getEnvConfig('amazon-ses.secret-key'),
'endpoint' => PhabricatorEnv::getEnvConfig('amazon-ses.endpoint'),
);
}
/**
* @phutil-external-symbol class SimpleEmailService
*/
public function executeSend($body) {
$key = PhabricatorEnv::getEnvConfig('amazon-ses.access-key');
$secret = PhabricatorEnv::getEnvConfig('amazon-ses.secret-key');
$endpoint = PhabricatorEnv::getEnvConfig('amazon-ses.endpoint');
$key = $this->getOption('access-key');
$secret = $this->getOption('secret-key');
$endpoint = $this->getOption('endpoint');
$root = phutil_get_library_root('phabricator');
$root = dirname($root);

View File

@@ -6,6 +6,8 @@
final class PhabricatorMailImplementationMailgunAdapter
extends PhabricatorMailImplementationAdapter {
const ADAPTERTYPE = 'mailgun';
private $params = array();
private $attachments = array();
@@ -19,7 +21,7 @@ final class PhabricatorMailImplementationMailgunAdapter
if (empty($this->params['reply-to'])) {
$this->params['reply-to'] = array();
}
$this->params['reply-to'][] = "{$name} <{$email}>";
$this->params['reply-to'][] = $this->renderAddress($email, $name);
return $this;
}
@@ -71,9 +73,32 @@ final class PhabricatorMailImplementationMailgunAdapter
return true;
}
protected function validateOptions(array $options) {
PhutilTypeSpec::checkMap(
$options,
array(
'api-key' => 'string',
'domain' => 'string',
));
}
public function newDefaultOptions() {
return array(
'api-key' => null,
'domain' => null,
);
}
public function newLegacyOptions() {
return array(
'api-key' => PhabricatorEnv::getEnvConfig('mailgun.api-key'),
'domain' => PhabricatorEnv::getEnvConfig('mailgun.domain'),
);
}
public function send() {
$key = PhabricatorEnv::getEnvConfig('mailgun.api-key');
$domain = PhabricatorEnv::getEnvConfig('mailgun.domain');
$key = $this->getOption('api-key');
$domain = $this->getOption('domain');
$params = array();
$params['to'] = implode(', ', idx($this->params, 'tos', array()));
@@ -85,11 +110,8 @@ final class PhabricatorMailImplementationMailgunAdapter
}
$from = idx($this->params, 'from');
if (idx($this->params, 'from-name')) {
$params['from'] = "\"{$this->params['from-name']}\" <{$from}>";
} else {
$params['from'] = $from;
}
$from_name = idx($this->params, 'from-name');
$params['from'] = $this->renderAddress($from, $from_name);
if (idx($this->params, 'reply-to')) {
$replyto = $this->params['reply-to'];

View File

@@ -3,40 +3,79 @@
final class PhabricatorMailImplementationPHPMailerAdapter
extends PhabricatorMailImplementationAdapter {
const ADAPTERTYPE = 'smtp';
private $mailer;
protected function validateOptions(array $options) {
PhutilTypeSpec::checkMap(
$options,
array(
'host' => 'string|null',
'port' => 'int',
'user' => 'string|null',
'password' => 'string|null',
'protocol' => 'string|null',
'encoding' => 'string',
'mailer' => 'string',
));
}
public function newDefaultOptions() {
return array(
'host' => null,
'port' => 25,
'user' => null,
'password' => null,
'protocol' => null,
'encoding' => 'base64',
'mailer' => 'smtp',
);
}
public function newLegacyOptions() {
return array(
'host' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-host'),
'port' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-port'),
'user' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-user'),
'password' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-passsword'),
'protocol' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-protocol'),
'encoding' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-encoding'),
'mailer' => PhabricatorEnv::getEnvConfig('phpmailer.mailer'),
);
}
/**
* @phutil-external-symbol class PHPMailer
*/
public function __construct() {
public function prepareForSend() {
$root = phutil_get_library_root('phabricator');
$root = dirname($root);
require_once $root.'/externals/phpmailer/class.phpmailer.php';
$this->mailer = new PHPMailer($use_exceptions = true);
$this->mailer->CharSet = 'utf-8';
$encoding = PhabricatorEnv::getEnvConfig('phpmailer.smtp-encoding');
$encoding = $this->getOption('encoding');
$this->mailer->Encoding = $encoding;
// By default, PHPMailer sends one mail per recipient. We handle
// multiplexing higher in the stack, so tell it to send mail exactly
// like we ask.
// combining or separating To and Cc higher in the stack, so tell it to
// send mail exactly like we ask.
$this->mailer->SingleTo = false;
$mailer = PhabricatorEnv::getEnvConfig('phpmailer.mailer');
$mailer = $this->getOption('mailer');
if ($mailer == 'smtp') {
$this->mailer->IsSMTP();
$this->mailer->Host = PhabricatorEnv::getEnvConfig('phpmailer.smtp-host');
$this->mailer->Port = PhabricatorEnv::getEnvConfig('phpmailer.smtp-port');
$user = PhabricatorEnv::getEnvConfig('phpmailer.smtp-user');
$this->mailer->Host = $this->getOption('host');
$this->mailer->Port = $this->getOption('port');
$user = $this->getOption('user');
if ($user) {
$this->mailer->SMTPAuth = true;
$this->mailer->Username = $user;
$this->mailer->Password =
PhabricatorEnv::getEnvConfig('phpmailer.smtp-password');
$this->mailer->Password = $this->getOption('password');
}
$protocol = PhabricatorEnv::getEnvConfig('phpmailer.smtp-protocol');
$protocol = $this->getOption('protocol');
if ($protocol) {
$protocol = phutil_utf8_strtolower($protocol);
$this->mailer->SMTPSecure = $protocol;

View File

@@ -6,24 +6,46 @@
class PhabricatorMailImplementationPHPMailerLiteAdapter
extends PhabricatorMailImplementationAdapter {
const ADAPTERTYPE = 'sendmail';
protected $mailer;
protected function validateOptions(array $options) {
PhutilTypeSpec::checkMap(
$options,
array(
'encoding' => 'string',
));
}
public function newDefaultOptions() {
return array(
'encoding' => 'base64',
);
}
public function newLegacyOptions() {
return array(
'encoding' => PhabricatorEnv::getEnvConfig('phpmailer.smtp-encoding'),
);
}
/**
* @phutil-external-symbol class PHPMailerLite
*/
public function __construct() {
public function prepareForSend() {
$root = phutil_get_library_root('phabricator');
$root = dirname($root);
require_once $root.'/externals/phpmailer/class.phpmailer-lite.php';
$this->mailer = new PHPMailerLite($use_exceptions = true);
$this->mailer->CharSet = 'utf-8';
$encoding = PhabricatorEnv::getEnvConfig('phpmailer.smtp-encoding');
$encoding = $this->getOption('encoding');
$this->mailer->Encoding = $encoding;
// By default, PHPMailerLite sends one mail per recipient. We handle
// multiplexing higher in the stack, so tell it to send mail exactly
// like we ask.
// combining or separating To and Cc higher in the stack, so tell it to
// send mail exactly like we ask.
$this->mailer->SingleTo = false;
}

View File

@@ -0,0 +1,124 @@
<?php
final class PhabricatorMailImplementationPostmarkAdapter
extends PhabricatorMailImplementationAdapter {
const ADAPTERTYPE = 'postmark';
private $parameters = array();
public function setFrom($email, $name = '') {
$this->parameters['From'] = $this->renderAddress($email, $name);
return $this;
}
public function addReplyTo($email, $name = '') {
$this->parameters['ReplyTo'] = $this->renderAddress($email, $name);
return $this;
}
public function addTos(array $emails) {
foreach ($emails as $email) {
$this->parameters['To'][] = $email;
}
return $this;
}
public function addCCs(array $emails) {
foreach ($emails as $email) {
$this->parameters['Cc'][] = $email;
}
return $this;
}
public function addAttachment($data, $filename, $mimetype) {
$this->parameters['Attachments'][] = array(
'Name' => $filename,
'ContentType' => $mimetype,
'Content' => base64_encode($data),
);
return $this;
}
public function addHeader($header_name, $header_value) {
$this->parameters['Headers'][] = array(
'Name' => $header_name,
'Value' => $header_value,
);
return $this;
}
public function setBody($body) {
$this->parameters['TextBody'] = $body;
return $this;
}
public function setHTMLBody($html_body) {
$this->parameters['HtmlBody'] = $html_body;
return $this;
}
public function setSubject($subject) {
$this->parameters['Subject'] = $subject;
return $this;
}
public function supportsMessageIDHeader() {
return true;
}
protected function validateOptions(array $options) {
PhutilTypeSpec::checkMap(
$options,
array(
'access-token' => 'string',
'inbound-addresses' => 'list<string>',
));
// Make sure this is properly formatted.
PhutilCIDRList::newList($options['inbound-addresses']);
}
public function newDefaultOptions() {
return array(
'access-token' => null,
'inbound-addresses' => array(
// Via Postmark support circa February 2018, see:
//
// https://postmarkapp.com/support/article/800-ips-for-firewalls
//
// "Configuring Outbound Email" should be updated if this changes.
'50.31.156.6/32',
),
);
}
public function newLegacyOptions() {
return array();
}
public function send() {
$access_token = $this->getOption('access-token');
$parameters = $this->parameters;
$flatten = array(
'To',
'Cc',
);
foreach ($flatten as $key) {
if (isset($parameters[$key])) {
$parameters[$key] = implode(', ', $parameters[$key]);
}
}
id(new PhutilPostmarkFuture())
->setAccessToken($access_token)
->setMethod('email', $parameters)
->resolve();
return true;
}
}

View File

@@ -6,8 +6,33 @@
final class PhabricatorMailImplementationSendGridAdapter
extends PhabricatorMailImplementationAdapter {
const ADAPTERTYPE = 'sendgrid';
private $params = array();
protected function validateOptions(array $options) {
PhutilTypeSpec::checkMap(
$options,
array(
'api-user' => 'string',
'api-key' => 'string',
));
}
public function newDefaultOptions() {
return array(
'api-user' => null,
'api-key' => null,
);
}
public function newLegacyOptions() {
return array(
'api-user' => PhabricatorEnv::getEnvConfig('sendgrid.api-user'),
'api-key' => PhabricatorEnv::getEnvConfig('sendgrid.api-key'),
);
}
public function setFrom($email, $name = '') {
$this->params['from'] = $email;
$this->params['from-name'] = $name;
@@ -73,8 +98,8 @@ final class PhabricatorMailImplementationSendGridAdapter
public function send() {
$user = PhabricatorEnv::getEnvConfig('sendgrid.api-user');
$key = PhabricatorEnv::getEnvConfig('sendgrid.api-key');
$user = $this->getOption('api-user');
$key = $this->getOption('api-key');
if (!$user || !$key) {
throw new Exception(

View File

@@ -7,10 +7,26 @@
final class PhabricatorMailImplementationTestAdapter
extends PhabricatorMailImplementationAdapter {
private $guts = array();
private $config;
const ADAPTERTYPE = 'test';
public function __construct(array $config = array()) {
private $guts = array();
private $config = array();
protected function validateOptions(array $options) {
PhutilTypeSpec::checkMap(
$options,
array());
}
public function newDefaultOptions() {
return array();
}
public function newLegacyOptions() {
return array();
}
public function prepareForSend(array $config = array()) {
$this->config = $config;
}

View File

@@ -42,6 +42,7 @@ final class PhabricatorMetaMTAApplication extends PhabricatorApplication {
'detail/(?P<id>[1-9]\d*)/' => 'PhabricatorMetaMTAMailViewController',
'sendgrid/' => 'PhabricatorMetaMTASendGridReceiveController',
'mailgun/' => 'PhabricatorMetaMTAMailgunReceiveController',
'postmark/' => 'PhabricatorMetaMTAPostmarkReceiveController',
),
);
}

View File

@@ -32,6 +32,23 @@ final class PhabricatorMetaMTAMailViewController
$color = PhabricatorMailOutboundStatus::getStatusColor($status);
$header->setStatus($icon, $color, $name);
if ($mail->getMustEncrypt()) {
Javelin::initBehavior('phabricator-tooltips');
$header->addTag(
id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setColor('blue')
->setName(pht('Must Encrypt'))
->setIcon('fa-shield blue')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht(
'Message content can only be transmitted over secure '.
'channels.'),
)));
}
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Mail %d', $mail->getID()))
->setBorder(true);
@@ -58,8 +75,26 @@ final class PhabricatorMetaMTAMailViewController
->setKey('metadata')
->appendChild($this->buildMetadataProperties($mail)));
$header_view = id(new PHUIHeaderView())
->setHeader(pht('Mail'));
$object_phid = $mail->getRelatedPHID();
if ($object_phid) {
$handles = $viewer->loadHandles(array($object_phid));
$handle = $handles[$object_phid];
if ($handle->isComplete() && $handle->getURI()) {
$view_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('View Object'))
->setIcon('fa-chevron-right')
->setHref($handle->getURI());
$header_view->addActionLink($view_button);
}
}
$object_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Mail'))
->setHeader($header_view)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addTabGroup($tab_group);
@@ -134,6 +169,12 @@ final class PhabricatorMetaMTAMailViewController
$properties->addTextContent($body);
$file_phids = $mail->getAttachmentFilePHIDs();
if ($file_phids) {
$properties->addProperty(
pht('Attached Files'),
$viewer->loadHandles($file_phids)->renderList());
}
return $properties;
}
@@ -158,6 +199,15 @@ final class PhabricatorMetaMTAMailViewController
$properties->addProperty($key, $value);
}
$encrypt_phids = $mail->getMustEncryptReasons();
if ($encrypt_phids) {
$properties->addProperty(
pht('Must Encrypt'),
$viewer->loadHandles($encrypt_phids)
->renderList());
}
return $properties;
}

View File

@@ -8,14 +8,28 @@ final class PhabricatorMetaMTAMailgunReceiveController
}
private function verifyMessage() {
$api_key = PhabricatorEnv::getEnvConfig('mailgun.api-key');
$request = $this->getRequest();
$timestamp = $request->getStr('timestamp');
$token = $request->getStr('token');
$sig = $request->getStr('signature');
$hash = hash_hmac('sha256', $timestamp.$token, $api_key);
return phutil_hashes_are_identical($sig, $hash);
// An install may configure multiple Mailgun mailers, and we might receive
// inbound mail from any of them. Test the signature to see if it matches
// any configured Mailgun mailer.
$mailers = PhabricatorMetaMTAMail::newMailersWithTypes(
array(
PhabricatorMailImplementationMailgunAdapter::ADAPTERTYPE,
));
foreach ($mailers as $mailer) {
$api_key = $mailer->getOption('api-key');
$hash = hash_hmac('sha256', $timestamp.$token, $api_key);
if (phutil_hashes_are_identical($sig, $hash)) {
return true;
}
}
return false;
}
public function handleRequest(AphrontRequest $request) {

View File

@@ -0,0 +1,102 @@
<?php
final class PhabricatorMetaMTAPostmarkReceiveController
extends PhabricatorMetaMTAController {
public function shouldRequireLogin() {
return false;
}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
public function handleRequest(AphrontRequest $request) {
// Don't process requests if we don't have a configured Postmark adapter.
$mailers = PhabricatorMetaMTAMail::newMailersWithTypes(
array(
PhabricatorMailImplementationPostmarkAdapter::ADAPTERTYPE,
));
if (!$mailers) {
return new Aphront404Response();
}
$remote_address = $request->getRemoteAddress();
$any_remote_match = false;
foreach ($mailers as $mailer) {
$inbound_addresses = $mailer->getOption('inbound-addresses');
$cidr_list = PhutilCIDRList::newList($inbound_addresses);
if ($cidr_list->containsAddress($remote_address)) {
$any_remote_match = true;
break;
}
}
if (!$any_remote_match) {
return new Aphront400Response();
}
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$raw_input = PhabricatorStartup::getRawInput();
try {
$data = phutil_json_decode($raw_input);
} catch (Exception $ex) {
return new Aphront400Response();
}
$raw_headers = array();
$header_items = idx($data, 'Headers', array());
foreach ($header_items as $header_item) {
$name = idx($header_item, 'Name');
$value = idx($header_item, 'Value');
$raw_headers[$name] = $value;
}
$headers = array(
'to' => idx($data, 'To'),
'from' => idx($data, 'From'),
'cc' => idx($data, 'Cc'),
'subject' => idx($data, 'Subject'),
) + $raw_headers;
$received = id(new PhabricatorMetaMTAReceivedMail())
->setHeaders($headers)
->setBodies(
array(
'text' => idx($data, 'TextBody'),
'html' => idx($data, 'HtmlBody'),
));
$file_phids = array();
$attachments = idx($data, 'Attachments', array());
foreach ($attachments as $attachment) {
$file_data = idx($attachment, 'Content');
$file_data = base64_decode($file_data);
try {
$file = PhabricatorFile::newFromFileData(
$file_data,
array(
'name' => idx($attachment, 'Name'),
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
));
$file_phids[] = $file->getPHID();
} catch (Exception $ex) {
phlog($ex);
}
}
$received->setAttachments($file_phids);
try {
$received->save();
$received->processReceivedMail();
} catch (Exception $ex) {
phlog($ex);
}
return id(new AphrontWebpageResponse())
->setContent(pht("Got it! Thanks, Postmark!\n"));
}
}

View File

@@ -8,6 +8,16 @@ final class PhabricatorMetaMTASendGridReceiveController
}
public function handleRequest(AphrontRequest $request) {
// SendGrid doesn't sign payloads so we can't be sure that SendGrid
// actually sent this request, but require a configured SendGrid mailer
// before we activate this endpoint.
$mailers = PhabricatorMetaMTAMail::newMailersWithTypes(
array(
PhabricatorMailImplementationSendGridAdapter::ADAPTERTYPE,
));
if (!$mailers) {
return new Aphront404Response();
}
// No CSRF for SendGrid.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();

View File

@@ -0,0 +1,47 @@
<?php
abstract class PhabricatorMailEngineExtension
extends Phobject {
private $viewer;
private $editor;
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;
}
final public function setEditor(
PhabricatorApplicationTransactionEditor $editor) {
$this->editor = $editor;
return $this;
}
final public function getEditor() {
return $this->editor;
}
abstract public function supportsObject($object);
abstract public function newMailStampTemplates($object);
abstract public function newMailStamps($object, array $xactions);
final public static function getAllExtensions() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getExtensionKey')
->execute();
}
final protected function getMailStamp($key) {
return $this->getEditor()->getMailStamp($key);
}
}

View File

@@ -18,8 +18,9 @@ final class MetaMTAMailSentGarbageCollector
'dateCreated < %d LIMIT 100',
$this->getGarbageEpoch());
$engine = new PhabricatorDestructionEngine();
foreach ($mails as $mail) {
$mail->delete();
$engine->destroyObject($mail);
}
return (count($mails) == 100);

View File

@@ -0,0 +1,62 @@
<?php
final class PhabricatorMailMustEncryptHeraldAction
extends HeraldAction {
const DO_MUST_ENCRYPT = 'do.must-encrypt';
const ACTIONCONST = 'email.must-encrypt';
public function getHeraldActionName() {
return pht('Require secure email');
}
public function renderActionDescription($value) {
return pht(
'Require mail content be transmitted only over secure channels.');
}
public function supportsObject($object) {
return PhabricatorMetaMTAEmailHeraldAction::isMailGeneratingObject($object);
}
public function getActionGroupKey() {
return HeraldUtilityActionGroup::ACTIONGROUPKEY;
}
public function supportsRuleType($rule_type) {
return ($rule_type != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);
}
public function getHeraldActionStandardType() {
return self::STANDARD_NONE;
}
public function applyEffect($object, HeraldEffect $effect) {
$rule_phid = $effect->getRule()->getPHID();
$adapter = $this->getAdapter();
$adapter->addMustEncryptReason($rule_phid);
$this->logEffect(self::DO_MUST_ENCRYPT, array($rule_phid));
}
protected function getActionEffectMap() {
return array(
self::DO_MUST_ENCRYPT => array(
'icon' => 'fa-shield',
'color' => 'blue',
'name' => pht('Must Encrypt'),
),
);
}
protected function renderActionEffectDescription($type, $data) {
switch ($type) {
case self::DO_MUST_ENCRYPT:
return pht(
'Made it a requirement that mail content be transmitted only '.
'over secure channels.');
}
}
}

View File

@@ -13,6 +13,10 @@ abstract class PhabricatorMetaMTAEmailHeraldAction
}
public function supportsObject($object) {
return self::isMailGeneratingObject($object);
}
public static function isMailGeneratingObject($object) {
// NOTE: This implementation lacks generality, but there's no great way to
// figure out if something generates email right now.

View File

@@ -37,6 +37,7 @@ final class PhabricatorMailManagementListOutboundWorkflow
$table = id(new PhutilConsoleTable())
->setShowHeader(false)
->addColumn('id', array('title' => pht('ID')))
->addColumn('encrypt', array('title' => pht('#')))
->addColumn('status', array('title' => pht('Status')))
->addColumn('subject', array('title' => pht('Subject')));
@@ -45,6 +46,7 @@ final class PhabricatorMailManagementListOutboundWorkflow
$table->addRow(array(
'id' => $mail->getID(),
'encrypt' => ($mail->getMustEncrypt() ? '#' : ' '),
'status' => PhabricatorMailOutboundStatus::getStatusName($status),
'subject' => $mail->getSubject(),
));

View File

@@ -47,6 +47,11 @@ final class PhabricatorMailManagementSendTestWorkflow
'help' => pht('Attach a file.'),
'repeat' => true,
),
array(
'name' => 'mailer',
'param' => 'key',
'help' => pht('Send with a specific configured mailer.'),
),
array(
'name' => 'html',
'help' => pht('Send as HTML mail.'),
@@ -161,6 +166,21 @@ final class PhabricatorMailManagementSendTestWorkflow
$mail->setFrom($from->getPHID());
}
$mailer_key = $args->getArg('mailer');
if ($mailer_key !== null) {
$mailers = PhabricatorMetaMTAMail::newMailers();
$mailers = mpull($mailers, null, 'getKey');
if (!isset($mailers[$mailer_key])) {
throw new PhutilArgumentUsageException(
pht(
'Mailer key ("%s") is not configured. Available keys are: %s.',
$mailer_key,
implode(', ', array_keys($mailers))));
}
$mail->setTryMailers(array($mailer_key));
}
foreach ($attach as $attachment) {
$data = Filesystem::readFile($attachment);
$name = basename($attachment);

View File

@@ -79,7 +79,7 @@ final class PhabricatorMailManagementShowOutboundWorkflow
$info = array();
$info[] = pht('PROPERTIES');
$info[] = $this->newSectionHeader(pht('PROPERTIES'));
$info[] = pht('ID: %d', $message->getID());
$info[] = pht('Status: %s', $message->getStatus());
$info[] = pht('Related PHID: %s', $message->getRelatedPHID());
@@ -87,15 +87,17 @@ final class PhabricatorMailManagementShowOutboundWorkflow
$ignore = array(
'body' => true,
'body.sent' => true,
'html-body' => true,
'headers' => true,
'attachments' => true,
'headers.sent' => true,
'headers.unfiltered' => true,
'authors.sent' => true,
);
$info[] = null;
$info[] = pht('PARAMETERS');
$info[] = $this->newSectionHeader(pht('PARAMETERS'));
$parameters = $message->getParameters();
foreach ($parameters as $key => $value) {
if (isset($ignore[$key])) {
@@ -110,22 +112,40 @@ final class PhabricatorMailManagementShowOutboundWorkflow
}
$info[] = null;
$info[] = pht('HEADERS');
$info[] = $this->newSectionHeader(pht('HEADERS'));
$headers = $message->getDeliveredHeaders();
if (!$headers) {
$unfiltered = $message->getUnfilteredHeaders();
if (!$unfiltered) {
$headers = $message->generateHeaders();
$unfiltered = $headers;
}
$header_map = array();
foreach ($headers as $header) {
list($name, $value) = $header;
$info[] = "{$name}: {$value}";
$header_map[$name.':'.$value] = true;
}
foreach ($unfiltered as $header) {
list($name, $value) = $header;
$was_sent = isset($header_map[$name.':'.$value]);
if ($was_sent) {
$marker = ' ';
} else {
$marker = '#';
}
$info[] = "{$marker} {$name}: {$value}";
}
$attachments = idx($parameters, 'attachments');
if ($attachments) {
$info[] = null;
$info[] = pht('ATTACHMENTS');
$info[] = $this->newSectionHeader(pht('ATTACHMENTS'));
foreach ($attachments as $attachment) {
$info[] = idx($attachment, 'filename', pht('Unnamed File'));
}
@@ -136,7 +156,9 @@ final class PhabricatorMailManagementShowOutboundWorkflow
$actors = $message->getDeliveredActors();
if ($actors) {
$info[] = null;
$info[] = pht('RECIPIENTS');
$info[] = $this->newSectionHeader(pht('RECIPIENTS'));
foreach ($actors as $actor_phid => $actor_info) {
$actor = idx($all_actors, $actor_phid);
if ($actor) {
@@ -162,15 +184,22 @@ final class PhabricatorMailManagementShowOutboundWorkflow
}
$info[] = null;
$info[] = pht('TEXT BODY');
$info[] = $this->newSectionHeader(pht('TEXT BODY'));
if (strlen($message->getBody())) {
$info[] = $message->getBody();
$info[] = tsprintf('%B', $message->getBody());
} else {
$info[] = pht('(This message has no text body.)');
}
$delivered_body = $message->getDeliveredBody();
if ($delivered_body !== null) {
$info[] = null;
$info[] = pht('HTML BODY');
$info[] = $this->newSectionHeader(pht('BODY AS DELIVERED'), true);
$info[] = tsprintf('%B', $delivered_body);
}
$info[] = null;
$info[] = $this->newSectionHeader(pht('HTML BODY'));
if (strlen($message->getHTMLBody())) {
$info[] = $message->getHTMLBody();
$info[] = null;
@@ -186,4 +215,12 @@ final class PhabricatorMailManagementShowOutboundWorkflow
}
}
private function newSectionHeader($label, $emphasize = false) {
if ($emphasize) {
return tsprintf('**<bg:yellow> %s </bg>**', $label);
} else {
return tsprintf('**<bg:blue> %s </bg>**', $label);
}
}
}

View File

@@ -21,6 +21,7 @@ final class PhabricatorMetaMTAActor extends Phobject {
const REASON_ROUTE_AS_NOTIFICATION = 'route-as-notification';
const REASON_ROUTE_AS_MAIL = 'route-as-mail';
const REASON_UNVERIFIED = 'unverified';
const REASON_MUTED = 'muted';
private $phid;
private $emailAddress;
@@ -116,6 +117,7 @@ final class PhabricatorMetaMTAActor extends Phobject {
self::REASON_ROUTE_AS_NOTIFICATION => pht('Route as Notification'),
self::REASON_ROUTE_AS_MAIL => pht('Route as Mail'),
self::REASON_UNVERIFIED => pht('Address Not Verified'),
self::REASON_MUTED => pht('Muted'),
);
return idx($names, $reason, pht('Unknown ("%s")', $reason));
@@ -172,6 +174,8 @@ final class PhabricatorMetaMTAActor extends Phobject {
'in Herald.'),
self::REASON_UNVERIFIED => pht(
'This recipient does not have a verified primary email address.'),
self::REASON_MUTED => pht(
'This recipient has muted notifications for this object.'),
);
return idx($descriptions, $reason, pht('Unknown Reason ("%s")', $reason));

View File

@@ -6,6 +6,7 @@ abstract class PhabricatorMailReplyHandler extends Phobject {
private $applicationEmail;
private $actor;
private $excludePHIDs = array();
private $unexpandablePHIDs = array();
final public function setMailReceiver($mail_receiver) {
$this->validateMailReceiver($mail_receiver);
@@ -45,6 +46,15 @@ abstract class PhabricatorMailReplyHandler extends Phobject {
return $this->excludePHIDs;
}
public function setUnexpandablePHIDs(array $phids) {
$this->unexpandablePHIDs = $phids;
return $this;
}
public function getUnexpandablePHIDs() {
return $this->unexpandablePHIDs;
}
abstract public function validateMailReceiver($mail_receiver);
abstract public function getPrivateReplyHandlerEmailAddress(
PhabricatorUser $user);
@@ -297,6 +307,16 @@ abstract class PhabricatorMailReplyHandler extends Phobject {
$to_result = array();
$cc_result = array();
// "Unexpandable" users have disengaged from an object (for example,
// by resigning from a revision).
// If such a user is still a direct recipient (for example, they're still
// on the Subscribers list) they're fair game, but group targets (like
// projects) will no longer include them when expanded.
$unexpandable = $this->getUnexpandablePHIDs();
$unexpandable = array_fuse($unexpandable);
$all_phids = array_merge($to, $cc);
if ($all_phids) {
$map = id(new PhabricatorMetaMTAMemberQuery())
@@ -305,11 +325,21 @@ abstract class PhabricatorMailReplyHandler extends Phobject {
->execute();
foreach ($to as $phid) {
foreach ($map[$phid] as $expanded) {
if ($expanded !== $phid) {
if (isset($unexpandable[$expanded])) {
continue;
}
}
$to_result[$expanded] = $expanded;
}
}
foreach ($cc as $phid) {
foreach ($map[$phid] as $expanded) {
if ($expanded !== $phid) {
if (isset($unexpandable[$expanded])) {
continue;
}
}
$cc_result[$expanded] = $expanded;
}
}

View File

@@ -58,23 +58,55 @@ final class PhabricatorMailTarget extends Phobject {
public function willSendMail(PhabricatorMetaMTAMail $mail) {
$viewer = $this->getViewer();
$show_stamps = $mail->shouldRenderMailStampsInBody($viewer);
$body = $mail->getBody();
$html_body = $mail->getHTMLBody();
$has_html = (strlen($html_body) > 0);
if ($show_stamps) {
$stamps = $mail->getMailStamps();
if ($stamps) {
$body .= "\n";
$body .= pht('STAMPS');
$body .= "\n";
$body .= implode(' ', $stamps);
$body .= "\n";
if ($has_html) {
$html = array();
$html[] = phutil_tag('strong', array(), pht('STAMPS'));
$html[] = phutil_tag('br');
$html[] = phutil_tag(
'span',
array(
'style' => 'font-size: smaller; color: #92969D',
),
phutil_implode_html(' ', $stamps));
$html[] = phutil_tag('br');
$html[] = phutil_tag('br');
$html = phutil_tag('div', array(), $html);
$html_body .= hsprintf('%s', $html);
}
}
}
$mail->addPHIDHeaders('X-Phabricator-To', $this->rawToPHIDs);
$mail->addPHIDHeaders('X-Phabricator-Cc', $this->rawCCPHIDs);
$to_handles = $viewer->loadHandles($this->rawToPHIDs);
$cc_handles = $viewer->loadHandles($this->rawCCPHIDs);
$body = $mail->getBody();
$body .= "\n";
$body .= $this->getRecipientsSummary($to_handles, $cc_handles);
$mail->setBody($body);
$html_body = $mail->getHTMLBody();
if (strlen($html_body)) {
if ($has_html) {
$html_body .= hsprintf(
'%s',
$this->getRecipientsSummaryHTML($to_handles, $cc_handles));
}
$mail->setBody($body);
$mail->setHTMLBody($html_body);
$reply_to = $this->getReplyTo();

View File

@@ -0,0 +1,16 @@
<?php
final class PhabricatorBoolMailStamp
extends PhabricatorMailStamp {
const STAMPTYPE = 'bool';
public function renderStamps($value) {
if (!$value) {
return null;
}
return $this->renderStamp($this->getKey());
}
}

View File

@@ -0,0 +1,88 @@
<?php
abstract class PhabricatorMailStamp
extends Phobject {
private $key;
private $value;
private $label;
private $viewer;
final public function getStampType() {
return $this->getPhobjectClassConstant('STAMPTYPE');
}
final public function setKey($key) {
$this->key = $key;
return $this;
}
final public function getKey() {
return $this->key;
}
final protected function setRawValue($value) {
$this->value = $value;
return $this;
}
final protected function getRawValue() {
return $this->value;
}
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
final public function setLabel($label) {
$this->label = $label;
return $this;
}
final public function getLabel() {
return $this->label;
}
public function setValue($value) {
return $this->setRawValue($value);
}
final public function toDictionary() {
return array(
'type' => $this->getStampType(),
'key' => $this->getKey(),
'value' => $this->getValueForDictionary(),
);
}
final public static function getAllStamps() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getStampType')
->execute();
}
protected function getValueForDictionary() {
return $this->getRawValue();
}
public function setValueFromDictionary($value) {
return $this->setRawValue($value);
}
public function getValueForRendering() {
return $this->getRawValue();
}
abstract public function renderStamps($value);
final protected function renderStamp($key, $value = null) {
return $key.'('.$value.')';
}
}

View File

@@ -0,0 +1,36 @@
<?php
final class PhabricatorPHIDMailStamp
extends PhabricatorMailStamp {
const STAMPTYPE = 'phid';
public function renderStamps($value) {
if ($value === null) {
return null;
}
$value = (array)$value;
if (!$value) {
return null;
}
$viewer = $this->getViewer();
$handles = $viewer->loadHandles($value);
$results = array();
foreach ($value as $phid) {
$handle = $handles[$phid];
$mail_name = $handle->getMailStampName();
if ($mail_name === null) {
$mail_name = $handle->getPHID();
}
$results[] = $this->renderStamp($this->getKey(), $mail_name);
}
return $results;
}
}

View File

@@ -0,0 +1,26 @@
<?php
final class PhabricatorStringMailStamp
extends PhabricatorMailStamp {
const STAMPTYPE = 'string';
public function renderStamps($value) {
if ($value === null || $value === '') {
return null;
}
$value = (array)$value;
if (!$value) {
return null;
}
$results = array();
foreach ($value as $v) {
$results[] = $this->renderStamp($this->getKey(), $v);
}
return $results;
}
}

View File

@@ -1,9 +1,12 @@
<?php
final class PhabricatorMetaMTAAttachment extends Phobject {
protected $data;
protected $filename;
protected $mimetype;
private $data;
private $filename;
private $mimetype;
private $file;
private $filePHID;
public function __construct($data, $filename, $mimetype) {
$this->setData($data);
@@ -39,18 +42,49 @@ final class PhabricatorMetaMTAAttachment extends Phobject {
}
public function toDictionary() {
if (!$this->file) {
$iterator = new ArrayIterator(array($this->getData()));
$source = id(new PhabricatorIteratorFileUploadSource())
->setName($this->getFilename())
->setViewPolicy(PhabricatorPolicies::POLICY_NOONE)
->setMIMEType($this->getMimeType())
->setIterator($iterator);
$this->file = $source->uploadFile();
}
return array(
'filename' => $this->getFilename(),
'mimetype' => $this->getMimeType(),
'data' => $this->getData(),
'filePHID' => $this->file->getPHID(),
);
}
public static function newFromDictionary(array $dict) {
return new PhabricatorMetaMTAAttachment(
$file = null;
$file_phid = idx($dict, 'filePHID');
if ($file_phid) {
$file = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($file_phid))
->executeOne();
if ($file) {
$dict['data'] = $file->loadFileData();
}
}
$attachment = new self(
idx($dict, 'data'),
idx($dict, 'filename'),
idx($dict, 'mimetype'));
if ($file) {
$attachment->file = $file;
}
return $attachment;
}
}

View File

@@ -5,7 +5,9 @@
*/
final class PhabricatorMetaMTAMail
extends PhabricatorMetaMTADAO
implements PhabricatorPolicyInterface {
implements
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
const RETRY_DELAY = 5;
@@ -21,7 +23,10 @@ final class PhabricatorMetaMTAMail
public function __construct() {
$this->status = PhabricatorMailOutboundStatus::STATUS_QUEUE;
$this->parameters = array('sensitive' => true);
$this->parameters = array(
'sensitive' => true,
'mustEncrypt' => false,
);
parent::__construct();
}
@@ -155,6 +160,15 @@ final class PhabricatorMetaMTAMail
return $this->getParam('exclude', array());
}
public function setMutedPHIDs(array $muted) {
$this->setParam('muted', $muted);
return $this;
}
private function getMutedPHIDs() {
return $this->getParam('muted', array());
}
public function setForceHeraldMailRecipientPHIDs(array $force) {
$this->setParam('herald-force-recipients', $force);
return $this;
@@ -192,6 +206,35 @@ final class PhabricatorMetaMTAMail
return $result;
}
public function getAttachmentFilePHIDs() {
$file_phids = array();
$dictionaries = $this->getParam('attachments');
if ($dictionaries) {
foreach ($dictionaries as $dictionary) {
$file_phid = idx($dictionary, 'filePHID');
if ($file_phid) {
$file_phids[] = $file_phid;
}
}
}
return $file_phids;
}
public function loadAttachedFiles(PhabricatorUser $viewer) {
$file_phids = $this->getAttachmentFilePHIDs();
if (!$file_phids) {
return array();
}
return id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs($file_phids)
->execute();
}
public function setAttachments(array $attachments) {
assert_instances_of($attachments, 'PhabricatorMetaMTAAttachment');
$this->setParam('attachments', mpull($attachments, 'toDictionary'));
@@ -247,6 +290,48 @@ final class PhabricatorMetaMTAMail
return $this->getParam('sensitive', true);
}
public function setMustEncrypt($bool) {
$this->setParam('mustEncrypt', $bool);
return $this;
}
public function getMustEncrypt() {
return $this->getParam('mustEncrypt', false);
}
public function setMustEncryptReasons(array $reasons) {
$this->setParam('mustEncryptReasons', $reasons);
return $this;
}
public function getMustEncryptReasons() {
return $this->getParam('mustEncryptReasons', array());
}
public function setMailStamps(array $stamps) {
return $this->setParam('stamps', $stamps);
}
public function getMailStamps() {
return $this->getParam('stamps', array());
}
public function setMailStampMetadata($metadata) {
return $this->setParam('stampMetadata', $metadata);
}
public function getMailStampMetadata() {
return $this->getParam('stampMetadata', array());
}
public function getMailerKey() {
return $this->getParam('mailer.key');
}
public function setTryMailers(array $mailers) {
return $this->setParam('mailers.try', $mailers);
}
public function setHTMLBody($html) {
$this->setParam('html-body', $html);
return $this;
@@ -385,36 +470,196 @@ final class PhabricatorMetaMTAMail
return $result;
}
public function buildDefaultMailer() {
return PhabricatorEnv::newObjectFromConfig('metamta.mail-adapter');
}
/**
* Attempt to deliver an email immediately, in this process.
*
* @param bool Try to deliver this email even if it has already been
* delivered or is in backoff after a failed delivery attempt.
* @param PhabricatorMailImplementationAdapter Use a specific mail adapter,
* instead of the default.
*
* @return void
*/
public function sendNow(
$force_send = false,
PhabricatorMailImplementationAdapter $mailer = null) {
if ($mailer === null) {
$mailer = $this->buildDefaultMailer();
}
if (!$force_send) {
public function sendNow() {
if ($this->getStatus() != PhabricatorMailOutboundStatus::STATUS_QUEUE) {
throw new Exception(pht('Trying to send an already-sent mail!'));
}
$mailers = self::newMailers();
$try_mailers = $this->getParam('mailers.try');
if ($try_mailers) {
$mailers = mpull($mailers, null, 'getKey');
$mailers = array_select_keys($mailers, $try_mailers);
}
return $this->sendWithMailers($mailers);
}
public static function newMailersWithTypes(array $types) {
$mailers = self::newMailers();
$types = array_fuse($types);
foreach ($mailers as $key => $mailer) {
$mailer_type = $mailer->getAdapterType();
if (!isset($types[$mailer_type])) {
unset($mailers[$key]);
}
}
return array_values($mailers);
}
public static function newMailers() {
$mailers = array();
$config = PhabricatorEnv::getEnvConfig('cluster.mailers');
if ($config === null) {
$mailer = PhabricatorEnv::newObjectFromConfig('metamta.mail-adapter');
$defaults = $mailer->newDefaultOptions();
$options = $mailer->newLegacyOptions();
$options = $options + $defaults;
$mailer
->setKey('default')
->setPriority(-1)
->setOptions($options);
$mailers[] = $mailer;
} else {
$adapters = PhabricatorMailImplementationAdapter::getAllAdapters();
$next_priority = -1;
foreach ($config as $spec) {
$type = $spec['type'];
if (!isset($adapters[$type])) {
throw new Exception(
pht(
'Unknown mailer ("%s")!',
$type));
}
$key = $spec['key'];
$mailer = id(clone $adapters[$type])
->setKey($key);
$priority = idx($spec, 'priority');
if (!$priority) {
$priority = $next_priority;
$next_priority--;
}
$mailer->setPriority($priority);
$defaults = $mailer->newDefaultOptions();
$options = idx($spec, 'options', array()) + $defaults;
$mailer->setOptions($options);
$mailers[] = $mailer;
}
}
$sorted = array();
$groups = mgroup($mailers, 'getPriority');
krsort($groups);
foreach ($groups as $group) {
// Reorder services within the same priority group randomly.
shuffle($group);
foreach ($group as $mailer) {
$sorted[] = $mailer;
}
}
foreach ($sorted as $mailer) {
$mailer->prepareForSend();
}
return $sorted;
}
public function sendWithMailers(array $mailers) {
if (!$mailers) {
return $this
->setStatus(PhabricatorMailOutboundStatus::STATUS_VOID)
->setMessage(pht('No mailers are configured.'))
->save();
}
$exceptions = array();
foreach ($mailers as $template_mailer) {
$mailer = null;
try {
$mailer = $this->buildMailer($template_mailer);
} catch (Exception $ex) {
$exceptions[] = $ex;
continue;
}
if (!$mailer) {
// If we don't get a mailer back, that means the mail doesn't
// actually need to be sent (for example, because recipients have
// declined to receive the mail). Void it and return.
return $this
->setStatus(PhabricatorMailOutboundStatus::STATUS_VOID)
->save();
}
try {
$ok = $mailer->send();
if (!$ok) {
// TODO: At some point, we should clean this up and make all mailers
// throw.
throw new Exception(
pht(
'Mail adapter encountered an unexpected, unspecified '.
'failure.'));
}
} catch (PhabricatorMetaMTAPermanentFailureException $ex) {
// If any mailer raises a permanent failure, stop trying to send the
// mail with other mailers.
$this
->setStatus(PhabricatorMailOutboundStatus::STATUS_FAIL)
->setMessage($ex->getMessage())
->save();
throw $ex;
} catch (Exception $ex) {
$exceptions[] = $ex;
continue;
}
// Keep track of which mailer actually ended up accepting the message.
$mailer_key = $mailer->getKey();
if ($mailer_key !== null) {
$this->setParam('mailer.key', $mailer_key);
}
return $this
->setStatus(PhabricatorMailOutboundStatus::STATUS_SENT)
->save();
}
// If we make it here, no mailer could send the mail but no mailer failed
// permanently either. We update the error message for the mail, but leave
// it in the current status (usually, STATUS_QUEUE) and try again later.
$messages = array();
foreach ($exceptions as $ex) {
$messages[] = $ex->getMessage();
}
$messages = implode("\n\n", $messages);
$this
->setMessage($messages)
->save();
if (count($exceptions) === 1) {
throw head($exceptions);
}
throw new PhutilAggregateException(
pht('Encountered multiple exceptions while transmitting mail.'),
$exceptions);
}
private function buildMailer(PhabricatorMailImplementationAdapter $mailer) {
$headers = $this->generateHeaders();
$params = $this->parameters;
@@ -431,6 +676,7 @@ final class PhabricatorMetaMTAMail
unset($params['is-first-message']);
$is_threaded = (bool)idx($params, 'thread-id');
$must_encrypt = $this->getMustEncrypt();
$reply_to_name = idx($params, 'reply-to-name', '');
unset($params['reply-to-name']);
@@ -438,8 +684,8 @@ final class PhabricatorMetaMTAMail
$add_cc = array();
$add_to = array();
// If multiplexing is enabled, some recipients will be in "Cc"
// rather than "To". We'll move them to "To" later (or supply a
// If we're sending one mail to everyone, some recipients will be in
// "Cc" rather than "To". We'll move them to "To" later (or supply a
// dummy "To") but need to look for the recipient in either the
// "To" or "Cc" fields here.
$target_phid = head(idx($params, 'to', array()));
@@ -456,6 +702,12 @@ final class PhabricatorMetaMTAMail
$mailer->setFrom($from_email, $from_name);
break;
case 'from':
// If the mail content must be encrypted, disguise the sender.
if ($must_encrypt) {
$mailer->setFrom($default_from, pht('Phabricator'));
break;
}
$from = $value;
$actor_email = null;
$actor_name = null;
@@ -502,6 +754,17 @@ final class PhabricatorMetaMTAMail
mpull($cc_actors, 'getEmailAddress'));
break;
case 'attachments':
$attached_viewer = PhabricatorUser::getOmnipotentUser();
$files = $this->loadAttachedFiles($attached_viewer);
foreach ($files as $file) {
$file->attachToObject($this->getPHID());
}
// If the mail content must be encrypted, don't add attachments.
if ($must_encrypt) {
break;
}
$value = $this->getAttachments();
foreach ($value as $attachment) {
$mailer->addAttachment(
@@ -521,6 +784,11 @@ final class PhabricatorMetaMTAMail
$subject[] = trim(idx($params, 'subject-prefix'));
// If mail content must be encrypted, we replace the subject with
// a generic one.
if ($must_encrypt) {
$subject[] = pht('Object Updated');
} else {
$vary_prefix = idx($params, 'vary-subject-prefix');
if ($vary_prefix != '') {
if ($this->shouldVarySubject($preferences)) {
@@ -529,6 +797,7 @@ final class PhabricatorMetaMTAMail
}
$subject[] = $value;
}
$mailer->setSubject(implode(' ', array_filter($subject)));
break;
@@ -567,7 +836,27 @@ final class PhabricatorMetaMTAMail
}
}
$body = idx($params, 'body', '');
$stamps = $this->getMailStamps();
if ($stamps) {
$headers[] = array('X-Phabricator-Stamps', implode(' ', $stamps));
}
$raw_body = idx($params, 'body', '');
$body = $raw_body;
if ($must_encrypt) {
$parts = array();
$parts[] = pht(
'The content for this message can only be transmitted over a '.
'secure channel. To view the message content, follow this '.
'link:');
$parts[] = PhabricatorEnv::getProductionURI($this->getURI());
$body = implode("\n\n", $parts);
} else {
$body = $raw_body;
}
$max = PhabricatorEnv::getEnvConfig('metamta.email-body-limit');
if (strlen($body) > $max) {
$body = id(new PhutilUTF8StringTruncator())
@@ -578,18 +867,32 @@ final class PhabricatorMetaMTAMail
}
$mailer->setBody($body);
$html_emails = $this->shouldSendHTML($preferences);
if ($html_emails && isset($params['html-body'])) {
// If we sent a different message body than we were asked to, record
// what we actually sent to make debugging and diagnostics easier.
if ($body !== $raw_body) {
$this->setParam('body.sent', $body);
}
if ($must_encrypt) {
$send_html = false;
} else {
$send_html = $this->shouldSendHTML($preferences);
}
if ($send_html && isset($params['html-body'])) {
$mailer->setHTMLBody($params['html-body']);
}
// Pass the headers to the mailer, then save the state so we can show
// them in the web UI.
foreach ($headers as $header) {
// them in the web UI. If the mail must be encrypted, we remove headers
// which are not on a strict whitelist to avoid disclosing information.
$filtered_headers = $this->filterHeaders($headers, $must_encrypt);
foreach ($filtered_headers as $header) {
list($header_key, $header_value) = $header;
$mailer->addHeader($header_key, $header_value);
}
$this->setParam('headers.sent', $headers);
$this->setParam('headers.unfiltered', $headers);
$this->setParam('headers.sent', $filtered_headers);
// Save the final deliverability outcomes and reasoning so we can
// explain why things happened the way they did.
@@ -606,35 +909,35 @@ final class PhabricatorMetaMTAMail
$this->setParam('routingmap.sent', $this->getRoutingRuleMap());
if (!$add_to && !$add_cc) {
$this->setStatus(PhabricatorMailOutboundStatus::STATUS_VOID);
$this->setMessage(
pht(
'Message has no valid recipients: all To/Cc are disabled, '.
'invalid, or configured not to receive this mail.'));
return $this->save();
return null;
}
if ($this->getIsErrorEmail()) {
$all_recipients = array_merge($add_to, $add_cc);
if ($this->shouldRateLimitMail($all_recipients)) {
$this->setStatus(PhabricatorMailOutboundStatus::STATUS_VOID);
$this->setMessage(
pht(
'This is an error email, but one or more recipients have '.
'exceeded the error email rate limit. Declining to deliver '.
'message.'));
return $this->save();
return null;
}
}
if (PhabricatorEnv::getEnvConfig('phabricator.silent')) {
$this->setStatus(PhabricatorMailOutboundStatus::STATUS_VOID);
$this->setMessage(
pht(
'Phabricator is running in silent mode. See `%s` '.
'in the configuration to change this setting.',
'phabricator.silent'));
return $this->save();
return null;
}
// Some mailers require a valid "To:" in order to deliver mail. If we
@@ -658,42 +961,8 @@ final class PhabricatorMetaMTAMail
if ($add_cc) {
$mailer->addCCs($add_cc);
}
} catch (Exception $ex) {
$this
->setStatus(PhabricatorMailOutboundStatus::STATUS_FAIL)
->setMessage($ex->getMessage())
->save();
throw $ex;
}
try {
$ok = $mailer->send();
if (!$ok) {
// TODO: At some point, we should clean this up and make all mailers
// throw.
throw new Exception(
pht('Mail adapter encountered an unexpected, unspecified failure.'));
}
$this->setStatus(PhabricatorMailOutboundStatus::STATUS_SENT);
$this->save();
return $this;
} catch (PhabricatorMetaMTAPermanentFailureException $ex) {
$this
->setStatus(PhabricatorMailOutboundStatus::STATUS_FAIL)
->setMessage($ex->getMessage())
->save();
throw $ex;
} catch (Exception $ex) {
$this
->setMessage($ex->getMessage()."\n".$ex->getTraceAsString())
->save();
throw $ex;
}
return $mailer;
}
private function generateThreadIndex($seed, $is_first_mail) {
@@ -727,7 +996,7 @@ final class PhabricatorMetaMTAMail
return base64_encode($base);
}
public static function shouldMultiplexAllMail() {
public static function shouldMailEachRecipient() {
return PhabricatorEnv::getEnvConfig('metamta.one-mail-per-recipient');
}
@@ -853,6 +1122,18 @@ final class PhabricatorMetaMTAMail
}
}
// Exclude muted recipients. We're doing this after saving deliverability
// so that Herald "Send me an email" actions can still punch through a
// mute.
foreach ($this->getMutedPHIDs() as $muted_phid) {
$muted_actor = idx($actors, $muted_phid);
if (!$muted_actor) {
continue;
}
$muted_actor->setUndeliverable(PhabricatorMetaMTAActor::REASON_MUTED);
}
// For the rest of the rules, order matters. We're going to run all the
// possible rules in order from weakest to strongest, and let the strongest
// matching rule win. The weaker rules leave annotations behind which help
@@ -979,20 +1260,6 @@ final class PhabricatorMetaMTAMail
}
}
public function delete() {
$this->openTransaction();
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T WHERE src = %s AND type = %d',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
$this->getPHID(),
PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST);
$ret = parent::delete();
$this->saveTransaction();
return $ret;
}
public function generateHeaders() {
$headers = array();
@@ -1002,8 +1269,6 @@ final class PhabricatorMetaMTAMail
// Some clients respect this to suppress OOF and other auto-responses.
$headers[] = array('X-Auto-Response-Suppress', 'All');
// If the message has mailtags, filter out any recipients who don't want
// to receive this type of mail.
$mailtags = $this->getParam('mailtags');
if ($mailtags) {
$tag_header = array();
@@ -1028,6 +1293,15 @@ final class PhabricatorMetaMTAMail
$headers[] = array('Precedence', 'bulk');
}
if ($this->getMustEncrypt()) {
$headers[] = array('X-Phabricator-Must-Encrypt', 'Yes');
}
$related_phid = $this->getRelatedPHID();
if ($related_phid) {
$headers[] = array('Thread-Topic', $related_phid);
}
return $headers;
}
@@ -1035,6 +1309,19 @@ final class PhabricatorMetaMTAMail
return $this->getParam('headers.sent');
}
public function getUnfilteredHeaders() {
$unfiltered = $this->getParam('headers.unfiltered');
if ($unfiltered === null) {
// Older versions of Phabricator did not filter headers, and thus did
// not record unfiltered headers. If we don't have unfiltered header
// data just return the delivered headers for compatibility.
return $this->getDeliveredHeaders();
}
return $unfiltered;
}
public function getDeliveredActors() {
return $this->getParam('actors.sent');
}
@@ -1047,6 +1334,55 @@ final class PhabricatorMetaMTAMail
return $this->getParam('routingmap.sent');
}
public function getDeliveredBody() {
return $this->getParam('body.sent');
}
private function filterHeaders(array $headers, $must_encrypt) {
if (!$must_encrypt) {
return $headers;
}
$whitelist = array(
'In-Reply-To',
'Message-ID',
'Precedence',
'References',
'Thread-Index',
'Thread-Topic',
'X-Mail-Transport-Agent',
'X-Auto-Response-Suppress',
'X-Phabricator-Sent-This-Message',
'X-Phabricator-Must-Encrypt',
);
// NOTE: The major header we want to drop is "X-Phabricator-Mail-Tags".
// This header contains a significant amount of meaningful information
// about the object.
$whitelist_map = array();
foreach ($whitelist as $term) {
$whitelist_map[phutil_utf8_strtolower($term)] = true;
}
foreach ($headers as $key => $header) {
list($name, $value) = $header;
$name = phutil_utf8_strtolower($name);
if (!isset($whitelist_map[$name])) {
unset($headers[$key]);
}
}
return $headers;
}
public function getURI() {
return '/mail/detail/'.$this->getID().'/';
}
/* -( Routing )------------------------------------------------------------ */
@@ -1121,7 +1457,7 @@ final class PhabricatorMetaMTAMail
private function loadPreferences($target_phid) {
$viewer = PhabricatorUser::getOmnipotentUser();
if (self::shouldMultiplexAllMail()) {
if (self::shouldMailEachRecipient()) {
$preferences = id(new PhabricatorUserPreferencesQuery())
->setViewer($viewer)
->withUserPHIDs(array($target_phid))
@@ -1156,6 +1492,14 @@ final class PhabricatorMetaMTAMail
return ($value == PhabricatorEmailFormatSetting::VALUE_HTML_EMAIL);
}
public function shouldRenderMailStampsInBody($viewer) {
$preferences = $this->loadPreferences($viewer->getPHID());
$value = $preferences->getSettingValue(
PhabricatorEmailStampsSetting::SETTINGKEY);
return ($value == PhabricatorEmailStampsSetting::VALUE_BODY_STAMPS);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@@ -1181,4 +1525,18 @@ final class PhabricatorMetaMTAMail
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$files = $this->loadAttachedFiles($engine->getViewer());
foreach ($files as $file) {
$engine->destroyObject($file);
}
$this->delete();
}
}

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