mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-28 09:42:41 +01:00
(stable) Promote 2016 Week 25
This commit is contained in:
commit
59bc6adc0e
124 changed files with 4008 additions and 945 deletions
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
return array(
|
||||
'names' => array(
|
||||
'core.pkg.css' => '6913fe66',
|
||||
'core.pkg.css' => 'c7fc5aec',
|
||||
'core.pkg.js' => '10275c16',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => 'b3eea3f5',
|
||||
|
@ -81,7 +81,7 @@ return array(
|
|||
'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b',
|
||||
'rsrc/css/application/paste/paste.css' => '1898e534',
|
||||
'rsrc/css/application/people/people-profile.css' => '2473d929',
|
||||
'rsrc/css/application/phame/phame.css' => '7448a969',
|
||||
'rsrc/css/application/phame/phame.css' => 'bf6a743f',
|
||||
'rsrc/css/application/pholio/pholio-edit.css' => '07676f51',
|
||||
'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49',
|
||||
'rsrc/css/application/pholio/pholio.css' => 'ca89d380',
|
||||
|
@ -128,7 +128,7 @@ return array(
|
|||
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
|
||||
'rsrc/css/phui/phui-crumbs-view.css' => '6b813619',
|
||||
'rsrc/css/phui/phui-curtain-view.css' => '7148ae25',
|
||||
'rsrc/css/phui/phui-document-pro.css' => '8419560b',
|
||||
'rsrc/css/phui/phui-document-pro.css' => 'a3730b94',
|
||||
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
|
||||
'rsrc/css/phui/phui-document.css' => '715aedfb',
|
||||
'rsrc/css/phui/phui-feed-story.css' => 'aa49845d',
|
||||
|
@ -155,7 +155,7 @@ return array(
|
|||
'rsrc/css/phui/phui-spacing.css' => '042804d6',
|
||||
'rsrc/css/phui/phui-status.css' => 'd5263e49',
|
||||
'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2',
|
||||
'rsrc/css/phui/phui-timeline-view.css' => '6e342216',
|
||||
'rsrc/css/phui/phui-timeline-view.css' => '8ea41b25',
|
||||
'rsrc/css/phui/phui-two-column-view.css' => '9fb86c85',
|
||||
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7',
|
||||
'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647',
|
||||
|
@ -808,7 +808,7 @@ return array(
|
|||
'phabricator-uiexample-reactor-sendclass' => '1def2711',
|
||||
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
|
||||
'phabricator-zindex-css' => '5b6fcf3f',
|
||||
'phame-css' => '7448a969',
|
||||
'phame-css' => 'bf6a743f',
|
||||
'pholio-css' => 'ca89d380',
|
||||
'pholio-edit-css' => '07676f51',
|
||||
'pholio-inline-comments-css' => '8e545e49',
|
||||
|
@ -831,7 +831,7 @@ return array(
|
|||
'phui-curtain-view-css' => '7148ae25',
|
||||
'phui-document-summary-view-css' => '9ca48bdf',
|
||||
'phui-document-view-css' => '715aedfb',
|
||||
'phui-document-view-pro-css' => '8419560b',
|
||||
'phui-document-view-pro-css' => 'a3730b94',
|
||||
'phui-feed-story-css' => 'aa49845d',
|
||||
'phui-font-icon-base-css' => '6449bce8',
|
||||
'phui-fontkit-css' => '9cda225e',
|
||||
|
@ -860,7 +860,7 @@ return array(
|
|||
'phui-status-list-view-css' => 'd5263e49',
|
||||
'phui-tag-view-css' => '6bbd83e2',
|
||||
'phui-theme-css' => '027ba77e',
|
||||
'phui-timeline-view-css' => '6e342216',
|
||||
'phui-timeline-view-css' => '8ea41b25',
|
||||
'phui-two-column-view-css' => '9fb86c85',
|
||||
'phui-workboard-color-css' => 'ac6fe6a7',
|
||||
'phui-workboard-view-css' => 'e6d89647',
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_phame.phame_blog
|
||||
ADD headerImagePHID VARBINARY(64);
|
6
resources/sql/autopatches/20160616.repo.01.oldref.sql
Normal file
6
resources/sql/autopatches/20160616.repo.01.oldref.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
CREATE TABLE {$NAMESPACE}_repository.repository_oldref (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
repositoryPHID VARBINARY(64) NOT NULL,
|
||||
commitIdentifier VARCHAR(40) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
KEY `key_repository` (repositoryPHID)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildartifact
|
||||
ADD isReleased BOOL NOT NULL;
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_phame.phame_blog
|
||||
ADD subtitle VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT};
|
|
@ -1,47 +1,5 @@
|
|||
<?php
|
||||
|
||||
$table = new PhabricatorPaste();
|
||||
$x_table = new PhabricatorPasteTransaction();
|
||||
|
||||
$conn_w = $table->establishConnection('w');
|
||||
$conn_w->openTransaction();
|
||||
|
||||
echo pht('Adding transactions for existing paste objects...')."\n";
|
||||
|
||||
$rows = new LiskRawMigrationIterator($conn_w, 'pastebin_paste');
|
||||
foreach ($rows as $row) {
|
||||
|
||||
$id = $row['id'];
|
||||
echo pht('Adding transactions for paste id %d...', $id)."\n";
|
||||
|
||||
$xaction_phid = PhabricatorPHID::generateNewPHID(
|
||||
PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST);
|
||||
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'INSERT INTO %T (phid, authorPHID, objectPHID, viewPolicy, editPolicy,
|
||||
transactionType, oldValue, newValue,
|
||||
contentSource, metadata, dateCreated, dateModified,
|
||||
commentVersion)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %ns, %ns, %s, %s, %d, %d, %d)',
|
||||
$x_table->getTableName(),
|
||||
$xaction_phid,
|
||||
$row['authorPHID'],
|
||||
$row['phid'],
|
||||
'public',
|
||||
$row['authorPHID'],
|
||||
PhabricatorPasteTransaction::TYPE_CONTENT,
|
||||
'null',
|
||||
$row['filePHID'],
|
||||
PhabricatorContentSource::newForSource(
|
||||
PhabricatorOldWorldContentSource::SOURCECONST)->serialize(),
|
||||
'[]',
|
||||
$row['dateCreated'],
|
||||
$row['dateCreated'],
|
||||
0);
|
||||
|
||||
}
|
||||
|
||||
$conn_w->saveTransaction();
|
||||
|
||||
echo pht('Done.')."\n";
|
||||
// Long ago, this migration populated initial "create" transactions for old
|
||||
// pastes from before transactions came into existence. It was removed after
|
||||
// about three years.
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
// This is a wrapper script for Git, Mercurial, and Subversion. It primarily
|
||||
// serves to inject "-o StrictHostKeyChecking=no" into the SSH arguments.
|
||||
|
||||
// In some cases, Subversion sends us SIGTERM. If we don't catch the signal and
|
||||
// react to it, we won't run object destructors by default and thus won't clean
|
||||
// up temporary files. Declare ticks so we can install a signal handler.
|
||||
declare(ticks=1);
|
||||
|
||||
$root = dirname(dirname(dirname(__FILE__)));
|
||||
require_once $root.'/scripts/__init_script__.php';
|
||||
|
||||
|
@ -21,6 +26,16 @@ $args->parsePartial(
|
|||
));
|
||||
$unconsumed_argv = $args->getUnconsumedArgumentVector();
|
||||
|
||||
if (function_exists('pcntl_signal')) {
|
||||
pcntl_signal(SIGTERM, 'ssh_connect_signal');
|
||||
}
|
||||
|
||||
function ssh_connect_signal($signo) {
|
||||
// This is just letting destructors fire. In particular, we want to clean
|
||||
// up any temporary files we wrote. See T10547.
|
||||
exit(128 + $signo);
|
||||
}
|
||||
|
||||
$pattern = array();
|
||||
$arguments = array();
|
||||
|
||||
|
|
|
@ -489,6 +489,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialReleephRequestFieldSpecification' => 'applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php',
|
||||
'DifferentialRemarkupRule' => 'applications/differential/remarkup/DifferentialRemarkupRule.php',
|
||||
'DifferentialReplyHandler' => 'applications/differential/mail/DifferentialReplyHandler.php',
|
||||
'DifferentialRepositoryDatasource' => 'applications/differential/typeahead/DifferentialRepositoryDatasource.php',
|
||||
'DifferentialRepositoryField' => 'applications/differential/customfield/DifferentialRepositoryField.php',
|
||||
'DifferentialRepositoryLookup' => 'applications/differential/query/DifferentialRepositoryLookup.php',
|
||||
'DifferentialRequiredSignaturesField' => 'applications/differential/customfield/DifferentialRequiredSignaturesField.php',
|
||||
|
@ -821,6 +822,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionSymbolQuery' => 'applications/diffusion/query/DiffusionSymbolQuery.php',
|
||||
'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php',
|
||||
'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php',
|
||||
'DiffusionTaggedRepositoriesFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionTaggedRepositoriesFunctionDatasource.php',
|
||||
'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php',
|
||||
'DiffusionURIEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionURIEditConduitAPIMethod.php',
|
||||
'DiffusionURIEditEngine' => 'applications/diffusion/editor/DiffusionURIEditEngine.php',
|
||||
|
@ -2158,6 +2160,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorController' => 'applications/base/controller/PhabricatorController.php',
|
||||
'PhabricatorCookies' => 'applications/auth/constants/PhabricatorCookies.php',
|
||||
'PhabricatorCoreConfigOptions' => 'applications/config/option/PhabricatorCoreConfigOptions.php',
|
||||
'PhabricatorCoreCreateTransaction' => 'applications/transactions/xaction/PhabricatorCoreCreateTransaction.php',
|
||||
'PhabricatorCoreTransactionType' => 'applications/transactions/xaction/PhabricatorCoreTransactionType.php',
|
||||
'PhabricatorCoreVoidTransaction' => 'applications/transactions/xaction/PhabricatorCoreVoidTransaction.php',
|
||||
'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php',
|
||||
'PhabricatorCountdownApplication' => 'applications/countdown/application/PhabricatorCountdownApplication.php',
|
||||
'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php',
|
||||
|
@ -2463,6 +2468,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php',
|
||||
'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php',
|
||||
'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php',
|
||||
'PhabricatorFileAES256StorageFormat' => 'applications/files/format/PhabricatorFileAES256StorageFormat.php',
|
||||
'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php',
|
||||
'PhabricatorFileChunk' => 'applications/files/storage/PhabricatorFileChunk.php',
|
||||
'PhabricatorFileChunkIterator' => 'applications/files/engine/PhabricatorFileChunkIterator.php',
|
||||
|
@ -2485,12 +2491,16 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php',
|
||||
'PhabricatorFileListController' => 'applications/files/controller/PhabricatorFileListController.php',
|
||||
'PhabricatorFileQuery' => 'applications/files/query/PhabricatorFileQuery.php',
|
||||
'PhabricatorFileROT13StorageFormat' => 'applications/files/format/PhabricatorFileROT13StorageFormat.php',
|
||||
'PhabricatorFileRawStorageFormat' => 'applications/files/format/PhabricatorFileRawStorageFormat.php',
|
||||
'PhabricatorFileSchemaSpec' => 'applications/files/storage/PhabricatorFileSchemaSpec.php',
|
||||
'PhabricatorFileSearchEngine' => 'applications/files/query/PhabricatorFileSearchEngine.php',
|
||||
'PhabricatorFileStorageBlob' => 'applications/files/storage/PhabricatorFileStorageBlob.php',
|
||||
'PhabricatorFileStorageConfigurationException' => 'applications/files/exception/PhabricatorFileStorageConfigurationException.php',
|
||||
'PhabricatorFileStorageEngine' => 'applications/files/engine/PhabricatorFileStorageEngine.php',
|
||||
'PhabricatorFileStorageEngineTestCase' => 'applications/files/engine/__tests__/PhabricatorFileStorageEngineTestCase.php',
|
||||
'PhabricatorFileStorageFormat' => 'applications/files/format/PhabricatorFileStorageFormat.php',
|
||||
'PhabricatorFileStorageFormatTestCase' => 'applications/files/format/__tests__/PhabricatorFileStorageFormatTestCase.php',
|
||||
'PhabricatorFileTemporaryGarbageCollector' => 'applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php',
|
||||
'PhabricatorFileTestCase' => 'applications/files/storage/__tests__/PhabricatorFileTestCase.php',
|
||||
'PhabricatorFileTestDataGenerator' => 'applications/files/lipsum/PhabricatorFileTestDataGenerator.php',
|
||||
|
@ -2514,7 +2524,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFilesConfigOptions' => 'applications/files/config/PhabricatorFilesConfigOptions.php',
|
||||
'PhabricatorFilesManagementCatWorkflow' => 'applications/files/management/PhabricatorFilesManagementCatWorkflow.php',
|
||||
'PhabricatorFilesManagementCompactWorkflow' => 'applications/files/management/PhabricatorFilesManagementCompactWorkflow.php',
|
||||
'PhabricatorFilesManagementCycleWorkflow' => 'applications/files/management/PhabricatorFilesManagementCycleWorkflow.php',
|
||||
'PhabricatorFilesManagementEncodeWorkflow' => 'applications/files/management/PhabricatorFilesManagementEncodeWorkflow.php',
|
||||
'PhabricatorFilesManagementEnginesWorkflow' => 'applications/files/management/PhabricatorFilesManagementEnginesWorkflow.php',
|
||||
'PhabricatorFilesManagementGenerateKeyWorkflow' => 'applications/files/management/PhabricatorFilesManagementGenerateKeyWorkflow.php',
|
||||
'PhabricatorFilesManagementMigrateWorkflow' => 'applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php',
|
||||
'PhabricatorFilesManagementPurgeWorkflow' => 'applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php',
|
||||
'PhabricatorFilesManagementRebuildWorkflow' => 'applications/files/management/PhabricatorFilesManagementRebuildWorkflow.php',
|
||||
|
@ -2617,6 +2630,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php',
|
||||
'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php',
|
||||
'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php',
|
||||
'PhabricatorKeyring' => 'applications/files/keyring/PhabricatorKeyring.php',
|
||||
'PhabricatorKeyringConfigOptionType' => 'applications/files/keyring/PhabricatorKeyringConfigOptionType.php',
|
||||
'PhabricatorLDAPAuthProvider' => 'applications/auth/provider/PhabricatorLDAPAuthProvider.php',
|
||||
'PhabricatorLegalpadApplication' => 'applications/legalpad/application/PhabricatorLegalpadApplication.php',
|
||||
'PhabricatorLegalpadConfigOptions' => 'applications/legalpad/config/PhabricatorLegalpadConfigOptions.php',
|
||||
|
@ -2755,6 +2770,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMetaMTASendGridReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTASendGridReceiveController.php',
|
||||
'PhabricatorMetaMTAWorker' => 'applications/metamta/PhabricatorMetaMTAWorker.php',
|
||||
'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php',
|
||||
'PhabricatorModularTransaction' => 'applications/transactions/storage/PhabricatorModularTransaction.php',
|
||||
'PhabricatorModularTransactionType' => 'applications/transactions/storage/PhabricatorModularTransactionType.php',
|
||||
'PhabricatorMonospacedFontSetting' => 'applications/settings/setting/PhabricatorMonospacedFontSetting.php',
|
||||
'PhabricatorMonospacedTextareasSetting' => 'applications/settings/setting/PhabricatorMonospacedTextareasSetting.php',
|
||||
'PhabricatorMotivatorProfilePanel' => 'applications/search/profilepanel/PhabricatorMotivatorProfilePanel.php',
|
||||
|
@ -2914,12 +2931,14 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPasteArchiveController' => 'applications/paste/controller/PhabricatorPasteArchiveController.php',
|
||||
'PhabricatorPasteConfigOptions' => 'applications/paste/config/PhabricatorPasteConfigOptions.php',
|
||||
'PhabricatorPasteContentSearchEngineAttachment' => 'applications/paste/engineextension/PhabricatorPasteContentSearchEngineAttachment.php',
|
||||
'PhabricatorPasteContentTransaction' => 'applications/paste/xaction/PhabricatorPasteContentTransaction.php',
|
||||
'PhabricatorPasteController' => 'applications/paste/controller/PhabricatorPasteController.php',
|
||||
'PhabricatorPasteDAO' => 'applications/paste/storage/PhabricatorPasteDAO.php',
|
||||
'PhabricatorPasteEditController' => 'applications/paste/controller/PhabricatorPasteEditController.php',
|
||||
'PhabricatorPasteEditEngine' => 'applications/paste/editor/PhabricatorPasteEditEngine.php',
|
||||
'PhabricatorPasteEditor' => 'applications/paste/editor/PhabricatorPasteEditor.php',
|
||||
'PhabricatorPasteFilenameContextFreeGrammar' => 'applications/paste/lipsum/PhabricatorPasteFilenameContextFreeGrammar.php',
|
||||
'PhabricatorPasteLanguageTransaction' => 'applications/paste/xaction/PhabricatorPasteLanguageTransaction.php',
|
||||
'PhabricatorPasteListController' => 'applications/paste/controller/PhabricatorPasteListController.php',
|
||||
'PhabricatorPastePastePHIDType' => 'applications/paste/phid/PhabricatorPastePastePHIDType.php',
|
||||
'PhabricatorPasteQuery' => 'applications/paste/query/PhabricatorPasteQuery.php',
|
||||
|
@ -2928,10 +2947,13 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPasteSchemaSpec' => 'applications/paste/storage/PhabricatorPasteSchemaSpec.php',
|
||||
'PhabricatorPasteSearchEngine' => 'applications/paste/query/PhabricatorPasteSearchEngine.php',
|
||||
'PhabricatorPasteSnippet' => 'applications/paste/snippet/PhabricatorPasteSnippet.php',
|
||||
'PhabricatorPasteStatusTransaction' => 'applications/paste/xaction/PhabricatorPasteStatusTransaction.php',
|
||||
'PhabricatorPasteTestDataGenerator' => 'applications/paste/lipsum/PhabricatorPasteTestDataGenerator.php',
|
||||
'PhabricatorPasteTitleTransaction' => 'applications/paste/xaction/PhabricatorPasteTitleTransaction.php',
|
||||
'PhabricatorPasteTransaction' => 'applications/paste/storage/PhabricatorPasteTransaction.php',
|
||||
'PhabricatorPasteTransactionComment' => 'applications/paste/storage/PhabricatorPasteTransactionComment.php',
|
||||
'PhabricatorPasteTransactionQuery' => 'applications/paste/query/PhabricatorPasteTransactionQuery.php',
|
||||
'PhabricatorPasteTransactionType' => 'applications/paste/xaction/PhabricatorPasteTransactionType.php',
|
||||
'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php',
|
||||
'PhabricatorPathSetupCheck' => 'applications/config/check/PhabricatorPathSetupCheck.php',
|
||||
'PhabricatorPeopleAnyOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleAnyOwnerDatasource.php',
|
||||
|
@ -3241,6 +3263,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php',
|
||||
'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php',
|
||||
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php',
|
||||
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkReachableWorkflow.php',
|
||||
'PhabricatorRepositoryManagementMirrorWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php',
|
||||
'PhabricatorRepositoryManagementMovePathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php',
|
||||
'PhabricatorRepositoryManagementParentsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php',
|
||||
|
@ -3254,6 +3277,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryMercurialCommitMessageParserWorker.php',
|
||||
'PhabricatorRepositoryMirror' => 'applications/repository/storage/PhabricatorRepositoryMirror.php',
|
||||
'PhabricatorRepositoryMirrorEngine' => 'applications/repository/engine/PhabricatorRepositoryMirrorEngine.php',
|
||||
'PhabricatorRepositoryOldRef' => 'applications/repository/storage/PhabricatorRepositoryOldRef.php',
|
||||
'PhabricatorRepositoryParsedChange' => 'applications/repository/data/PhabricatorRepositoryParsedChange.php',
|
||||
'PhabricatorRepositoryPullEngine' => 'applications/repository/engine/PhabricatorRepositoryPullEngine.php',
|
||||
'PhabricatorRepositoryPullEvent' => 'applications/repository/storage/PhabricatorRepositoryPullEvent.php',
|
||||
|
@ -3589,6 +3613,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorTokensCurtainExtension' => 'applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php',
|
||||
'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php',
|
||||
'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php',
|
||||
'PhabricatorTransactionChange' => 'applications/transactions/data/PhabricatorTransactionChange.php',
|
||||
'PhabricatorTransactionRemarkupChange' => 'applications/transactions/data/PhabricatorTransactionRemarkupChange.php',
|
||||
'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php',
|
||||
'PhabricatorTransactionsApplication' => 'applications/transactions/application/PhabricatorTransactionsApplication.php',
|
||||
'PhabricatorTransactionsDestructionEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsDestructionEngineExtension.php',
|
||||
|
@ -3750,6 +3776,8 @@ phutil_register_library_map(array(
|
|||
'PhameBlogEditEngine' => 'applications/phame/editor/PhameBlogEditEngine.php',
|
||||
'PhameBlogEditor' => 'applications/phame/editor/PhameBlogEditor.php',
|
||||
'PhameBlogFeedController' => 'applications/phame/controller/blog/PhameBlogFeedController.php',
|
||||
'PhameBlogFulltextEngine' => 'applications/phame/search/PhameBlogFulltextEngine.php',
|
||||
'PhameBlogHeaderPictureController' => 'applications/phame/controller/blog/PhameBlogHeaderPictureController.php',
|
||||
'PhameBlogListController' => 'applications/phame/controller/blog/PhameBlogListController.php',
|
||||
'PhameBlogListView' => 'applications/phame/view/PhameBlogListView.php',
|
||||
'PhameBlogManageController' => 'applications/phame/controller/blog/PhameBlogManageController.php',
|
||||
|
@ -3771,12 +3799,14 @@ phutil_register_library_map(array(
|
|||
'PhameLiveController' => 'applications/phame/controller/PhameLiveController.php',
|
||||
'PhameNextPostView' => 'applications/phame/view/PhameNextPostView.php',
|
||||
'PhamePost' => 'applications/phame/storage/PhamePost.php',
|
||||
'PhamePostArchiveController' => 'applications/phame/controller/post/PhamePostArchiveController.php',
|
||||
'PhamePostCommentController' => 'applications/phame/controller/post/PhamePostCommentController.php',
|
||||
'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php',
|
||||
'PhamePostEditConduitAPIMethod' => 'applications/phame/conduit/PhamePostEditConduitAPIMethod.php',
|
||||
'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php',
|
||||
'PhamePostEditEngine' => 'applications/phame/editor/PhamePostEditEngine.php',
|
||||
'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php',
|
||||
'PhamePostFulltextEngine' => 'applications/phame/search/PhamePostFulltextEngine.php',
|
||||
'PhamePostHistoryController' => 'applications/phame/controller/post/PhamePostHistoryController.php',
|
||||
'PhamePostListController' => 'applications/phame/controller/post/PhamePostListController.php',
|
||||
'PhamePostListView' => 'applications/phame/view/PhamePostListView.php',
|
||||
|
@ -4787,6 +4817,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialReleephRequestFieldSpecification' => 'Phobject',
|
||||
'DifferentialRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||
'DifferentialReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
|
||||
'DifferentialRepositoryDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'DifferentialRepositoryField' => 'DifferentialCoreCustomField',
|
||||
'DifferentialRepositoryLookup' => 'Phobject',
|
||||
'DifferentialRequiredSignaturesField' => 'DifferentialCoreCustomField',
|
||||
|
@ -5137,6 +5168,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionSymbolQuery' => 'PhabricatorOffsetPagedQuery',
|
||||
'DiffusionTagListController' => 'DiffusionController',
|
||||
'DiffusionTagListView' => 'DiffusionView',
|
||||
'DiffusionTaggedRepositoriesFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||
'DiffusionURIEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
|
||||
'DiffusionURIEditEngine' => 'PhabricatorEditEngine',
|
||||
|
@ -6722,6 +6754,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorController' => 'AphrontController',
|
||||
'PhabricatorCookies' => 'Phobject',
|
||||
'PhabricatorCoreConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorCoreCreateTransaction' => 'PhabricatorCoreTransactionType',
|
||||
'PhabricatorCoreTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'PhabricatorCoreVoidTransaction' => 'PhabricatorModularTransactionType',
|
||||
'PhabricatorCountdown' => array(
|
||||
'PhabricatorCountdownDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
|
@ -7076,6 +7111,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
),
|
||||
'PhabricatorFileAES256StorageFormat' => 'PhabricatorFileStorageFormat',
|
||||
'PhabricatorFileBundleLoader' => 'Phobject',
|
||||
'PhabricatorFileChunk' => array(
|
||||
'PhabricatorFileDAO',
|
||||
|
@ -7112,12 +7148,16 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileLinkView' => 'AphrontView',
|
||||
'PhabricatorFileListController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorFileROT13StorageFormat' => 'PhabricatorFileStorageFormat',
|
||||
'PhabricatorFileRawStorageFormat' => 'PhabricatorFileStorageFormat',
|
||||
'PhabricatorFileSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||
'PhabricatorFileSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO',
|
||||
'PhabricatorFileStorageConfigurationException' => 'Exception',
|
||||
'PhabricatorFileStorageEngine' => 'Phobject',
|
||||
'PhabricatorFileStorageEngineTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorFileStorageFormat' => 'Phobject',
|
||||
'PhabricatorFileStorageFormatTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorFileTemporaryGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'PhabricatorFileTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorFileTestDataGenerator' => 'PhabricatorTestDataGenerator',
|
||||
|
@ -7141,7 +7181,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFilesConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorFilesManagementCatWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementCompactWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementCycleWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementEncodeWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementEnginesWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementGenerateKeyWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementMigrateWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementPurgeWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
'PhabricatorFilesManagementRebuildWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
|
@ -7251,6 +7294,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorJumpNavHandler' => 'Phobject',
|
||||
'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache',
|
||||
'PhabricatorKeyring' => 'Phobject',
|
||||
'PhabricatorKeyringConfigOptionType' => 'PhabricatorConfigJSONOptionType',
|
||||
'PhabricatorLDAPAuthProvider' => 'PhabricatorAuthProvider',
|
||||
'PhabricatorLegalpadApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorLegalpadConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
|
@ -7399,6 +7444,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMetaMTASendGridReceiveController' => 'PhabricatorMetaMTAController',
|
||||
'PhabricatorMetaMTAWorker' => 'PhabricatorWorker',
|
||||
'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock',
|
||||
'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorModularTransactionType' => 'Phobject',
|
||||
'PhabricatorMonospacedFontSetting' => 'PhabricatorStringSetting',
|
||||
'PhabricatorMonospacedTextareasSetting' => 'PhabricatorSelectSetting',
|
||||
'PhabricatorMotivatorProfilePanel' => 'PhabricatorProfilePanel',
|
||||
|
@ -7594,12 +7641,14 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPasteArchiveController' => 'PhabricatorPasteController',
|
||||
'PhabricatorPasteConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorPasteContentSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
|
||||
'PhabricatorPasteContentTransaction' => 'PhabricatorPasteTransactionType',
|
||||
'PhabricatorPasteController' => 'PhabricatorController',
|
||||
'PhabricatorPasteDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorPasteEditController' => 'PhabricatorPasteController',
|
||||
'PhabricatorPasteEditEngine' => 'PhabricatorEditEngine',
|
||||
'PhabricatorPasteEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhabricatorPasteFilenameContextFreeGrammar' => 'PhutilContextFreeGrammar',
|
||||
'PhabricatorPasteLanguageTransaction' => 'PhabricatorPasteTransactionType',
|
||||
'PhabricatorPasteListController' => 'PhabricatorPasteController',
|
||||
'PhabricatorPastePastePHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorPasteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
|
@ -7608,10 +7657,13 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPasteSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||
'PhabricatorPasteSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorPasteSnippet' => 'Phobject',
|
||||
'PhabricatorPasteStatusTransaction' => 'PhabricatorPasteTransactionType',
|
||||
'PhabricatorPasteTestDataGenerator' => 'PhabricatorTestDataGenerator',
|
||||
'PhabricatorPasteTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorPasteTitleTransaction' => 'PhabricatorPasteTransactionType',
|
||||
'PhabricatorPasteTransaction' => 'PhabricatorModularTransaction',
|
||||
'PhabricatorPasteTransactionComment' => 'PhabricatorApplicationTransactionComment',
|
||||
'PhabricatorPasteTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorPasteTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'PhabricatorPasteViewController' => 'PhabricatorPasteController',
|
||||
'PhabricatorPathSetupCheck' => 'PhabricatorSetupCheck',
|
||||
'PhabricatorPeopleAnyOwnerDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
|
@ -8003,6 +8055,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementMirrorWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementMovePathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementParentsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
|
@ -8016,6 +8069,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
|
||||
'PhabricatorRepositoryMirror' => 'PhabricatorRepositoryDAO',
|
||||
'PhabricatorRepositoryMirrorEngine' => 'PhabricatorRepositoryEngine',
|
||||
'PhabricatorRepositoryOldRef' => array(
|
||||
'PhabricatorRepositoryDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'PhabricatorRepositoryParsedChange' => 'Phobject',
|
||||
'PhabricatorRepositoryPullEngine' => 'PhabricatorRepositoryEngine',
|
||||
'PhabricatorRepositoryPullEvent' => array(
|
||||
|
@ -8392,6 +8449,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorTokensCurtainExtension' => 'PHUICurtainExtension',
|
||||
'PhabricatorTokensSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
'PhabricatorTooltipUIExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorTransactionChange' => 'Phobject',
|
||||
'PhabricatorTransactionRemarkupChange' => 'PhabricatorTransactionChange',
|
||||
'PhabricatorTransactions' => 'Phobject',
|
||||
'PhabricatorTransactionsApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorTransactionsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
|
||||
|
@ -8585,6 +8644,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDestructibleInterface',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
'PhabricatorConduitResultInterface',
|
||||
'PhabricatorFulltextInterface',
|
||||
),
|
||||
'PhameBlog404Controller' => 'PhameLiveController',
|
||||
'PhameBlogArchiveController' => 'PhameBlogController',
|
||||
|
@ -8595,6 +8655,8 @@ phutil_register_library_map(array(
|
|||
'PhameBlogEditEngine' => 'PhabricatorEditEngine',
|
||||
'PhameBlogEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhameBlogFeedController' => 'PhameBlogController',
|
||||
'PhameBlogFulltextEngine' => 'PhabricatorFulltextEngine',
|
||||
'PhameBlogHeaderPictureController' => 'PhameBlogController',
|
||||
'PhameBlogListController' => 'PhameBlogController',
|
||||
'PhameBlogListView' => 'AphrontTagView',
|
||||
'PhameBlogManageController' => 'PhameBlogController',
|
||||
|
@ -8626,13 +8688,16 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDestructibleInterface',
|
||||
'PhabricatorTokenReceiverInterface',
|
||||
'PhabricatorConduitResultInterface',
|
||||
'PhabricatorFulltextInterface',
|
||||
),
|
||||
'PhamePostArchiveController' => 'PhamePostController',
|
||||
'PhamePostCommentController' => 'PhamePostController',
|
||||
'PhamePostController' => 'PhameController',
|
||||
'PhamePostEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
|
||||
'PhamePostEditController' => 'PhamePostController',
|
||||
'PhamePostEditEngine' => 'PhabricatorEditEngine',
|
||||
'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhamePostFulltextEngine' => 'PhabricatorFulltextEngine',
|
||||
'PhamePostHistoryController' => 'PhamePostController',
|
||||
'PhamePostListController' => 'PhamePostController',
|
||||
'PhamePostListView' => 'AphrontTagView',
|
||||
|
|
|
@ -542,7 +542,7 @@ final class PhabricatorAuditEditor
|
|||
protected function expandCustomRemarkupBlockTransactions(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions,
|
||||
$blocks,
|
||||
array $changes,
|
||||
PhutilMarkupEngine $engine) {
|
||||
|
||||
// we are only really trying to find unmentionable phids here...
|
||||
|
@ -563,7 +563,7 @@ final class PhabricatorAuditEditor
|
|||
return $result;
|
||||
}
|
||||
|
||||
$flat_blocks = array_mergev($blocks);
|
||||
$flat_blocks = mpull($changes, 'getNewValue');
|
||||
$huge_block = implode("\n\n", $flat_blocks);
|
||||
$phid_map = array();
|
||||
$phid_map[] = $this->getUnmentionablePHIDMap();
|
||||
|
|
|
@ -30,9 +30,11 @@ final class PhabricatorAuthAuthProviderPHIDType extends PhabricatorPHIDType {
|
|||
array $objects) {
|
||||
|
||||
foreach ($handles as $phid => $handle) {
|
||||
$provider = $objects[$phid];
|
||||
$provider = $objects[$phid]->getProvider();
|
||||
|
||||
$handle->setName($provider->getProviderName());
|
||||
if ($provider) {
|
||||
$handle->setName($provider->getProviderName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -190,6 +190,10 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
|
|||
'The Differential revision list view age UI elements have been removed '.
|
||||
'to simplify the interface.');
|
||||
|
||||
$global_settings_reason = pht(
|
||||
'The "Re: Prefix" and "Vary Subjects" settings are now configured '.
|
||||
'in global settings.');
|
||||
|
||||
$ancient_config += array(
|
||||
'phid.external-loaders' =>
|
||||
pht(
|
||||
|
@ -321,6 +325,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
|
|||
|
||||
'differential.days-fresh' => $stale_reason,
|
||||
'differential.days-stale' => $stale_reason,
|
||||
|
||||
'metamta.re-prefix' => $global_settings_reason,
|
||||
'metamta.vary-subjects' => $global_settings_reason,
|
||||
);
|
||||
|
||||
return $ancient_config;
|
||||
|
|
|
@ -276,22 +276,6 @@ EODOC
|
|||
))
|
||||
->setSummary(pht('Show email preferences link in email.'))
|
||||
->setDescription($email_preferences_description),
|
||||
$this->newOption('metamta.re-prefix', 'bool', false)
|
||||
->setBoolOptions(
|
||||
array(
|
||||
pht('Force "Re:" Subject Prefix'),
|
||||
pht('No "Re:" Subject Prefix'),
|
||||
))
|
||||
->setSummary(pht('Control "Re:" subject prefix, for Mail.app.'))
|
||||
->setDescription($re_prefix_description),
|
||||
$this->newOption('metamta.vary-subjects', 'bool', true)
|
||||
->setBoolOptions(
|
||||
array(
|
||||
pht('Allow Varied Subjects'),
|
||||
pht('Always Use the Same Thread Subject'),
|
||||
))
|
||||
->setSummary(pht('Control subject variance, for some mail clients.'))
|
||||
->setDescription($vary_subjects_description),
|
||||
$this->newOption('metamta.insecure-auth-with-reply-to', 'bool', false)
|
||||
->setBoolOptions(
|
||||
array(
|
||||
|
|
|
@ -43,6 +43,14 @@ final class PhabricatorSecurityConfigOptions
|
|||
'255.255.255.255/32',
|
||||
);
|
||||
|
||||
$keyring_type = 'custom:PhabricatorKeyringConfigOptionType';
|
||||
$keyring_description = $this->deformat(pht(<<<EOTEXT
|
||||
The keyring stores master encryption keys. For help with configuring a keyring
|
||||
and encryption, see **[[ %s | Configuring Encryption ]]**.
|
||||
EOTEXT
|
||||
,
|
||||
PhabricatorEnv::getDoclink('Configuring Encryption')));
|
||||
|
||||
return array(
|
||||
$this->newOption('security.alternate-file-domain', 'string', null)
|
||||
->setLocked(true)
|
||||
|
@ -276,6 +284,10 @@ final class PhabricatorSecurityConfigOptions
|
|||
'unsecured content over plain HTTP. It is very difficult to '.
|
||||
'undo this change once users\' browsers have accepted the '.
|
||||
'setting.')),
|
||||
$this->newOption('keyring', $keyring_type, array())
|
||||
->setHidden(true)
|
||||
->setSummary(pht('Configure master encryption keys.'))
|
||||
->setDescription($keyring_description),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1303,10 +1303,10 @@ final class DifferentialTransactionEditor
|
|||
protected function expandCustomRemarkupBlockTransactions(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions,
|
||||
$blocks,
|
||||
array $changes,
|
||||
PhutilMarkupEngine $engine) {
|
||||
|
||||
$flat_blocks = array_mergev($blocks);
|
||||
$flat_blocks = mpull($changes, 'getNewValue');
|
||||
$huge_block = implode("\n\n", $flat_blocks);
|
||||
|
||||
$task_map = array();
|
||||
|
|
|
@ -75,7 +75,7 @@ final class DifferentialRevisionSearchEngine
|
|||
->setLabel(pht('Repositories'))
|
||||
->setKey('repositoryPHIDs')
|
||||
->setAliases(array('repository', 'repositories', 'repositoryPHID'))
|
||||
->setDatasource(new DiffusionRepositoryDatasource())
|
||||
->setDatasource(new DifferentialRepositoryDatasource())
|
||||
->setDescription(
|
||||
pht('Find revisions from specific repositories.')),
|
||||
id(new PhabricatorSearchSelectField())
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialRepositoryDatasource
|
||||
extends PhabricatorTypeaheadCompositeDatasource {
|
||||
|
||||
public function getBrowseTitle() {
|
||||
return pht('Browse Repositories');
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type a repository name or function...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorDifferentialApplication';
|
||||
}
|
||||
|
||||
public function getComponentDatasources() {
|
||||
return array(
|
||||
new DiffusionTaggedRepositoriesFunctionDatasource(),
|
||||
new DiffusionRepositoryDatasource(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -129,6 +129,12 @@ final class DiffusionCommitController extends DiffusionController {
|
|||
),
|
||||
$message));
|
||||
|
||||
if ($commit->isUnreachable()) {
|
||||
$this->commitErrors[] = pht(
|
||||
'This commit has been deleted in the repository: it is no longer '.
|
||||
'reachable from any branch, tag, or ref.');
|
||||
}
|
||||
|
||||
if ($this->getCommitErrors()) {
|
||||
$error_panel = id(new PHUIInfoView())
|
||||
->appendChild($this->getCommitErrors())
|
||||
|
|
|
@ -113,9 +113,7 @@ final class DiffusionPathChange extends Phobject {
|
|||
if (!$this->getCommitData()) {
|
||||
return null;
|
||||
}
|
||||
$message = $this->getCommitData()->getCommitMessage();
|
||||
$first = idx(explode("\n", $message), 0);
|
||||
return substr($first, 0, 80);
|
||||
return $this->getCommitData()->getSummary();
|
||||
}
|
||||
|
||||
public static function convertToArcanistChanges(array $changes) {
|
||||
|
|
|
@ -7,6 +7,7 @@ final class DiffusionRepositoryRef extends Phobject {
|
|||
|
||||
private $shortName;
|
||||
private $commitIdentifier;
|
||||
private $refType;
|
||||
private $rawFields = array();
|
||||
|
||||
public function setRawFields(array $raw_fields) {
|
||||
|
@ -36,6 +37,25 @@ final class DiffusionRepositoryRef extends Phobject {
|
|||
return $this->shortName;
|
||||
}
|
||||
|
||||
public function setRefType($ref_type) {
|
||||
$this->refType = $ref_type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRefType() {
|
||||
return $this->refType;
|
||||
}
|
||||
|
||||
public function isBranch() {
|
||||
$type_branch = PhabricatorRepositoryRefCursor::TYPE_BRANCH;
|
||||
return ($this->getRefType() === $type_branch);
|
||||
}
|
||||
|
||||
public function isTag() {
|
||||
$type_tag = PhabricatorRepositoryRefCursor::TYPE_TAG;
|
||||
return ($this->getRefType() === $type_tag);
|
||||
}
|
||||
|
||||
|
||||
/* -( Serialization )------------------------------------------------------ */
|
||||
|
||||
|
@ -44,6 +64,7 @@ final class DiffusionRepositoryRef extends Phobject {
|
|||
return array(
|
||||
'shortName' => $this->shortName,
|
||||
'commitIdentifier' => $this->commitIdentifier,
|
||||
'refType' => $this->refType,
|
||||
'rawFields' => $this->rawFields,
|
||||
);
|
||||
}
|
||||
|
@ -52,6 +73,7 @@ final class DiffusionRepositoryRef extends Phobject {
|
|||
return id(new DiffusionRepositoryRef())
|
||||
->setShortName($dict['shortName'])
|
||||
->setCommitIdentifier($dict['commitIdentifier'])
|
||||
->setRefType($dict['refType'])
|
||||
->setRawFields($dict['rawFields']);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,96 +14,121 @@ final class DiffusionLowLevelGitRefQuery extends DiffusionLowLevelQuery {
|
|||
}
|
||||
|
||||
protected function executeQuery() {
|
||||
$type_branch = PhabricatorRepositoryRefCursor::TYPE_BRANCH;
|
||||
$type_tag = PhabricatorRepositoryRefCursor::TYPE_TAG;
|
||||
$type_ref = PhabricatorRepositoryRefCursor::TYPE_REF;
|
||||
|
||||
$ref_types = $this->refTypes;
|
||||
if ($ref_types) {
|
||||
$type_branch = PhabricatorRepositoryRefCursor::TYPE_BRANCH;
|
||||
$type_tag = PhabricatorRepositoryRefCursor::TYPE_TAG;
|
||||
|
||||
$ref_types = array_fuse($ref_types);
|
||||
|
||||
$with_branches = isset($ref_types[$type_branch]);
|
||||
$with_tags = isset($ref_types[$type_tag]);
|
||||
} else {
|
||||
$with_branches = true;
|
||||
$with_tags = true;
|
||||
if (!$ref_types) {
|
||||
$ref_types = array($type_branch, $type_tag, $type_ref);
|
||||
}
|
||||
|
||||
$ref_types = array_fuse($ref_types);
|
||||
|
||||
$with_branches = isset($ref_types[$type_branch]);
|
||||
$with_tags = isset($ref_types[$type_tag]);
|
||||
$with_refs = isset($refs_types[$type_ref]);
|
||||
|
||||
$repository = $this->getRepository();
|
||||
|
||||
$prefixes = array();
|
||||
|
||||
if ($with_branches) {
|
||||
if ($repository->isWorkingCopyBare()) {
|
||||
$prefix = 'refs/heads/';
|
||||
} else {
|
||||
$remote = DiffusionGitBranch::DEFAULT_GIT_REMOTE;
|
||||
$prefix = 'refs/remotes/'.$remote.'/';
|
||||
if ($repository->isWorkingCopyBare()) {
|
||||
$branch_prefix = 'refs/heads/';
|
||||
} else {
|
||||
$remote = DiffusionGitBranch::DEFAULT_GIT_REMOTE;
|
||||
$branch_prefix = 'refs/remotes/'.$remote.'/';
|
||||
}
|
||||
|
||||
$tag_prefix = 'refs/tags/';
|
||||
|
||||
|
||||
if ($with_refs || count($ref_types) > 1) {
|
||||
// If we're loading refs or more than one type of ref, just query
|
||||
// everything.
|
||||
$prefix = 'refs/';
|
||||
} else {
|
||||
if ($with_branches) {
|
||||
$prefix = $branch_prefix;
|
||||
}
|
||||
if ($with_tags) {
|
||||
$prefix = $tag_prefix;
|
||||
}
|
||||
$prefixes[] = $prefix;
|
||||
}
|
||||
|
||||
if ($with_tags) {
|
||||
$prefixes[] = 'refs/tags/';
|
||||
$branch_len = strlen($branch_prefix);
|
||||
$tag_len = strlen($tag_prefix);
|
||||
|
||||
list($stdout) = $repository->execxLocalCommand(
|
||||
'for-each-ref --sort=%s --format=%s -- %s',
|
||||
'-creatordate',
|
||||
$this->getFormatString(),
|
||||
$prefix);
|
||||
|
||||
$stdout = rtrim($stdout);
|
||||
if (!strlen($stdout)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$order = '-creatordate';
|
||||
$remote_prefix = 'refs/remotes/';
|
||||
$remote_len = strlen($remote_prefix);
|
||||
|
||||
$futures = array();
|
||||
foreach ($prefixes as $prefix) {
|
||||
$futures[$prefix] = $repository->getLocalCommandFuture(
|
||||
'for-each-ref --sort=%s --format=%s %s',
|
||||
$order,
|
||||
$this->getFormatString(),
|
||||
$prefix);
|
||||
}
|
||||
|
||||
// Resolve all the futures first. We want to iterate over them in prefix
|
||||
// order, not resolution order.
|
||||
foreach (new FutureIterator($futures) as $prefix => $future) {
|
||||
$future->resolvex();
|
||||
}
|
||||
// NOTE: Although git supports --count, we can't apply any offset or
|
||||
// limit logic until the very end because we may encounter a HEAD which
|
||||
// we want to discard.
|
||||
|
||||
$lines = explode("\n", $stdout);
|
||||
$results = array();
|
||||
foreach ($futures as $prefix => $future) {
|
||||
list($stdout) = $future->resolvex();
|
||||
foreach ($lines as $line) {
|
||||
$fields = $this->extractFields($line);
|
||||
|
||||
$stdout = rtrim($stdout);
|
||||
if (!strlen($stdout)) {
|
||||
$refname = $fields['refname'];
|
||||
if (!strncmp($refname, $branch_prefix, $branch_len)) {
|
||||
$short = substr($refname, $branch_len);
|
||||
$type = $type_branch;
|
||||
} else if (!strncmp($refname, $tag_prefix, $tag_len)) {
|
||||
$short = substr($refname, $tag_len);
|
||||
$type = $type_tag;
|
||||
} else if (!strncmp($refname, $remote_prefix, $remote_len)) {
|
||||
// If we've found a remote ref that we didn't recognize as naming a
|
||||
// branch, just ignore it. This can happen if we're observing a remote,
|
||||
// and that remote has its own remotes. We don't care about their
|
||||
// state and they may be out of date, so ignore them.
|
||||
continue;
|
||||
} else {
|
||||
$short = $refname;
|
||||
$type = $type_ref;
|
||||
}
|
||||
|
||||
// If this isn't a type of ref we care about, skip it.
|
||||
if (empty($ref_types[$type])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: Although git supports --count, we can't apply any offset or
|
||||
// limit logic until the very end because we may encounter a HEAD which
|
||||
// we want to discard.
|
||||
|
||||
$lines = explode("\n", $stdout);
|
||||
foreach ($lines as $line) {
|
||||
$fields = $this->extractFields($line);
|
||||
|
||||
$creator = $fields['creator'];
|
||||
$matches = null;
|
||||
if (preg_match('/^(.*) ([0-9]+) ([0-9+-]+)$/', $creator, $matches)) {
|
||||
$fields['author'] = $matches[1];
|
||||
$fields['epoch'] = (int)$matches[2];
|
||||
} else {
|
||||
$fields['author'] = null;
|
||||
$fields['epoch'] = null;
|
||||
}
|
||||
|
||||
$commit = nonempty($fields['*objectname'], $fields['objectname']);
|
||||
|
||||
$short = substr($fields['refname'], strlen($prefix));
|
||||
if ($short == 'HEAD') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ref = id(new DiffusionRepositoryRef())
|
||||
->setShortName($short)
|
||||
->setCommitIdentifier($commit)
|
||||
->setRawFields($fields);
|
||||
|
||||
$results[] = $ref;
|
||||
// If this is the local HEAD, skip it.
|
||||
if ($short == 'HEAD') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$creator = $fields['creator'];
|
||||
$matches = null;
|
||||
if (preg_match('/^(.*) ([0-9]+) ([0-9+-]+)$/', $creator, $matches)) {
|
||||
$fields['author'] = $matches[1];
|
||||
$fields['epoch'] = (int)$matches[2];
|
||||
} else {
|
||||
$fields['author'] = null;
|
||||
$fields['epoch'] = null;
|
||||
}
|
||||
|
||||
$commit = nonempty($fields['*objectname'], $fields['objectname']);
|
||||
|
||||
$ref = id(new DiffusionRepositoryRef())
|
||||
->setRefType($type)
|
||||
->setShortName($short)
|
||||
->setCommitIdentifier($commit)
|
||||
->setRawFields($fields);
|
||||
|
||||
$results[] = $ref;
|
||||
}
|
||||
|
||||
return $results;
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionTaggedRepositoriesFunctionDatasource
|
||||
extends PhabricatorTypeaheadCompositeDatasource {
|
||||
|
||||
public function getBrowseTitle() {
|
||||
return pht('Browse Repositories');
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type tagged(<project>)...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return 'PhabricatorProjectApplication';
|
||||
}
|
||||
|
||||
public function getComponentDatasources() {
|
||||
return array(
|
||||
new PhabricatorProjectDatasource(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getDatasourceFunctions() {
|
||||
return array(
|
||||
'tagged' => array(
|
||||
'name' => pht('Repositories: ...'),
|
||||
'arguments' => pht('project'),
|
||||
'summary' => pht('Find results for repositories of a project.'),
|
||||
'description' => pht(
|
||||
'This function allows you to find results for any of the `.
|
||||
`repositories of a project:'.
|
||||
"\n\n".
|
||||
'> tagged(engineering)'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected function didLoadResults(array $results) {
|
||||
foreach ($results as $result) {
|
||||
$result
|
||||
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
|
||||
->setColor(null)
|
||||
->setPHID('tagged('.$result->getPHID().')')
|
||||
->setDisplayName(pht('Tagged: %s', $result->getDisplayName()))
|
||||
->setName('tagged '.$result->getName());
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected function evaluateFunction($function, array $argv_list) {
|
||||
$phids = array();
|
||||
foreach ($argv_list as $argv) {
|
||||
$phids[] = head($argv);
|
||||
}
|
||||
|
||||
$repositories = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withEdgeLogicPHIDs(
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
||||
PhabricatorQueryConstraint::OPERATOR_OR,
|
||||
$phids)
|
||||
->execute();
|
||||
|
||||
$results = array();
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
$results[] = $repository->getPHID();
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function renderFunctionTokens($function, array $argv_list) {
|
||||
$phids = array();
|
||||
foreach ($argv_list as $argv) {
|
||||
$phids[] = head($argv);
|
||||
}
|
||||
|
||||
$tokens = $this->renderTokens($phids);
|
||||
foreach ($tokens as $token) {
|
||||
// Remove any project color on this token.
|
||||
$token->setColor(null);
|
||||
|
||||
if ($token->isInvalid()) {
|
||||
$token
|
||||
->setValue(pht('Repositories: Invalid Project'));
|
||||
} else {
|
||||
$token
|
||||
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
|
||||
->setKey('tagged('.$token->getKey().')')
|
||||
->setValue(pht('Tagged: %s', $token->getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
}
|
|
@ -215,6 +215,7 @@ final class PhabricatorFeedStoryPublisher extends Phobject {
|
|||
$all_prefs = id(new PhabricatorUserPreferencesQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withUserPHIDs($phids)
|
||||
->needSyntheticPreferences(true)
|
||||
->execute();
|
||||
$all_prefs = mpull($all_prefs, null, 'getUserPHID');
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ final class PhabricatorFilesConfigOptions
|
|||
'video/mp4' => 'video/mp4',
|
||||
'video/ogg' => 'video/ogg',
|
||||
'video/webm' => 'video/webm',
|
||||
'video/quicktime' => 'video/quicktime',
|
||||
);
|
||||
|
||||
$image_default = array(
|
||||
|
@ -71,6 +72,7 @@ final class PhabricatorFilesConfigOptions
|
|||
// to set the mood for your task without distracting viewers.)
|
||||
'video/mp4' => true,
|
||||
'video/ogg' => true,
|
||||
'video/quicktime' => true,
|
||||
'application/ogg' => true,
|
||||
);
|
||||
|
||||
|
@ -78,6 +80,7 @@ final class PhabricatorFilesConfigOptions
|
|||
'video/mp4' => true,
|
||||
'video/ogg' => true,
|
||||
'video/webm' => true,
|
||||
'video/quicktime' => true,
|
||||
'application/ogg' => true,
|
||||
);
|
||||
|
||||
|
|
|
@ -256,8 +256,10 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
|
|||
$types[] = pht('Profile');
|
||||
}
|
||||
|
||||
$types = implode(', ', $types);
|
||||
$finfo->addProperty(pht('Attributes'), $types);
|
||||
if ($types) {
|
||||
$types = implode(', ', $types);
|
||||
$finfo->addProperty(pht('Attributes'), $types);
|
||||
}
|
||||
|
||||
$storage_properties = new PHUIPropertyListView();
|
||||
$box->addPropertyList($storage_properties, pht('Storage'));
|
||||
|
@ -266,9 +268,14 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
|
|||
pht('Engine'),
|
||||
$file->getStorageEngine());
|
||||
|
||||
$storage_properties->addProperty(
|
||||
pht('Format'),
|
||||
$file->getStorageFormat());
|
||||
$format_key = $file->getStorageFormat();
|
||||
$format = PhabricatorFileStorageFormat::getFormat($format_key);
|
||||
if ($format) {
|
||||
$format_name = $format->getStorageFormatName();
|
||||
} else {
|
||||
$format_name = pht('Unknown ("%s")', $format_key);
|
||||
}
|
||||
$storage_properties->addProperty(pht('Format'), $format_name);
|
||||
|
||||
$storage_properties->addProperty(
|
||||
pht('Handle'),
|
||||
|
|
|
@ -174,7 +174,7 @@ final class PhabricatorChunkedFileStorageEngine
|
|||
return (4 * 1024 * 1024);
|
||||
}
|
||||
|
||||
public function getFileDataIterator(PhabricatorFile $file, $begin, $end) {
|
||||
public function getRawFileDataIterator(PhabricatorFile $file, $begin, $end) {
|
||||
$chunks = id(new PhabricatorFileChunkQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withChunkHandles(array($file->getStorageHandle()))
|
||||
|
|
|
@ -325,10 +325,10 @@ abstract class PhabricatorFileStorageEngine extends Phobject {
|
|||
return $engine->getChunkSize();
|
||||
}
|
||||
|
||||
public function getFileDataIterator(PhabricatorFile $file, $begin, $end) {
|
||||
public function getRawFileDataIterator(PhabricatorFile $file, $begin, $end) {
|
||||
// The default implementation is trivial and just loads the entire file
|
||||
// upfront.
|
||||
$data = $file->loadFileData();
|
||||
$data = $this->readFile($file->getStorageHandle());
|
||||
|
||||
if ($begin !== null && $end !== null) {
|
||||
$data = substr($data, $begin, ($end - $begin));
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* At-rest encryption format using AES256 CBC.
|
||||
*/
|
||||
final class PhabricatorFileAES256StorageFormat
|
||||
extends PhabricatorFileStorageFormat {
|
||||
|
||||
const FORMATKEY = 'aes-256-cbc';
|
||||
|
||||
private $keyName;
|
||||
|
||||
public function getStorageFormatName() {
|
||||
return pht('Encrypted (AES-256-CBC)');
|
||||
}
|
||||
|
||||
public function canGenerateNewKeyMaterial() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function generateNewKeyMaterial() {
|
||||
$envelope = self::newAES256Key();
|
||||
$material = $envelope->openEnvelope();
|
||||
return base64_encode($material);
|
||||
}
|
||||
|
||||
public function canCycleMasterKey() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function cycleStorageProperties() {
|
||||
$file = $this->getFile();
|
||||
list($key, $iv) = $this->extractKeyAndIV($file);
|
||||
return $this->formatStorageProperties($key, $iv);
|
||||
}
|
||||
|
||||
public function newReadIterator($raw_iterator) {
|
||||
$file = $this->getFile();
|
||||
$data = $file->loadDataFromIterator($raw_iterator);
|
||||
|
||||
list($key, $iv) = $this->extractKeyAndIV($file);
|
||||
|
||||
$data = $this->decryptData($data, $key, $iv);
|
||||
|
||||
return array($data);
|
||||
}
|
||||
|
||||
public function newWriteIterator($raw_iterator) {
|
||||
$file = $this->getFile();
|
||||
$data = $file->loadDataFromIterator($raw_iterator);
|
||||
|
||||
list($key, $iv) = $this->extractKeyAndIV($file);
|
||||
|
||||
$data = $this->encryptData($data, $key, $iv);
|
||||
|
||||
return array($data);
|
||||
}
|
||||
|
||||
public function newStorageProperties() {
|
||||
// Generate a unique key and IV for this block of data.
|
||||
$key_envelope = self::newAES256Key();
|
||||
$iv_envelope = self::newAES256IV();
|
||||
|
||||
return $this->formatStorageProperties($key_envelope, $iv_envelope);
|
||||
}
|
||||
|
||||
private function formatStorageProperties(
|
||||
PhutilOpaqueEnvelope $key_envelope,
|
||||
PhutilOpaqueEnvelope $iv_envelope) {
|
||||
|
||||
// Encode the raw binary data with base64 so we can wrap it in JSON.
|
||||
$data = array(
|
||||
'iv.base64' => base64_encode($iv_envelope->openEnvelope()),
|
||||
'key.base64' => base64_encode($key_envelope->openEnvelope()),
|
||||
);
|
||||
|
||||
// Encode the base64 data with JSON.
|
||||
$data_clear = phutil_json_encode($data);
|
||||
|
||||
// Encrypt the block key with the master key, using a unique IV.
|
||||
$data_iv = self::newAES256IV();
|
||||
$key_name = $this->getMasterKeyName();
|
||||
$master_key = $this->getMasterKeyMaterial($key_name);
|
||||
$data_cipher = $this->encryptData($data_clear, $master_key, $data_iv);
|
||||
|
||||
return array(
|
||||
'key.name' => $key_name,
|
||||
'iv.base64' => base64_encode($data_iv->openEnvelope()),
|
||||
'payload.base64' => base64_encode($data_cipher),
|
||||
);
|
||||
}
|
||||
|
||||
private function extractKeyAndIV(PhabricatorFile $file) {
|
||||
$outer_iv = $file->getStorageProperty('iv.base64');
|
||||
$outer_iv = base64_decode($outer_iv);
|
||||
$outer_iv = new PhutilOpaqueEnvelope($outer_iv);
|
||||
|
||||
$outer_payload = $file->getStorageProperty('payload.base64');
|
||||
$outer_payload = base64_decode($outer_payload);
|
||||
|
||||
$outer_key_name = $file->getStorageProperty('key.name');
|
||||
$outer_key = $this->getMasterKeyMaterial($outer_key_name);
|
||||
|
||||
$payload = $this->decryptData($outer_payload, $outer_key, $outer_iv);
|
||||
$payload = phutil_json_decode($payload);
|
||||
|
||||
$inner_iv = $payload['iv.base64'];
|
||||
$inner_iv = base64_decode($inner_iv);
|
||||
$inner_iv = new PhutilOpaqueEnvelope($inner_iv);
|
||||
|
||||
$inner_key = $payload['key.base64'];
|
||||
$inner_key = base64_decode($inner_key);
|
||||
$inner_key = new PhutilOpaqueEnvelope($inner_key);
|
||||
|
||||
return array($inner_key, $inner_iv);
|
||||
}
|
||||
|
||||
private function encryptData(
|
||||
$data,
|
||||
PhutilOpaqueEnvelope $key,
|
||||
PhutilOpaqueEnvelope $iv) {
|
||||
|
||||
$method = 'aes-256-cbc';
|
||||
$key = $key->openEnvelope();
|
||||
$iv = $iv->openEnvelope();
|
||||
|
||||
$result = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
|
||||
if ($result === false) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Failed to openssl_encrypt() data: %s',
|
||||
openssl_error_string()));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function decryptData(
|
||||
$data,
|
||||
PhutilOpaqueEnvelope $key,
|
||||
PhutilOpaqueEnvelope $iv) {
|
||||
|
||||
$method = 'aes-256-cbc';
|
||||
$key = $key->openEnvelope();
|
||||
$iv = $iv->openEnvelope();
|
||||
|
||||
$result = openssl_decrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
|
||||
if ($result === false) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Failed to openssl_decrypt() data: %s',
|
||||
openssl_error_string()));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function newAES256Key() {
|
||||
// Unsurprisingly, AES256 uses a 256 bit key.
|
||||
$key = Filesystem::readRandomBytes(phutil_units('256 bits in bytes'));
|
||||
return new PhutilOpaqueEnvelope($key);
|
||||
}
|
||||
|
||||
public static function newAES256IV() {
|
||||
// AES256 uses a 256 bit key, but the initialization vector length is
|
||||
// only 128 bits.
|
||||
$iv = Filesystem::readRandomBytes(phutil_units('128 bits in bytes'));
|
||||
return new PhutilOpaqueEnvelope($iv);
|
||||
}
|
||||
|
||||
public function selectMasterKey($key_name) {
|
||||
// Require that the key exist on the key ring.
|
||||
$this->getMasterKeyMaterial($key_name);
|
||||
|
||||
$this->keyName = $key_name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function getMasterKeyName() {
|
||||
if ($this->keyName !== null) {
|
||||
return $this->keyName;
|
||||
}
|
||||
|
||||
$default = PhabricatorKeyring::getDefaultKeyName(self::FORMATKEY);
|
||||
if ($default !== null) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
pht(
|
||||
'No AES256 key is specified in the keyring as a default encryption '.
|
||||
'key, and no encryption key has been explicitly selected.'));
|
||||
}
|
||||
|
||||
private function getMasterKeyMaterial($key_name) {
|
||||
return PhabricatorKeyring::getKey($key_name, self::FORMATKEY);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Trivial example of a file storage format for at-rest encryption.
|
||||
*
|
||||
* This format applies ROT13 encoding to file data as it is stored and
|
||||
* reverses it on the way out. This encoding is trivially reversible. This
|
||||
* format is for testing, developing, and understanding encoding formats and
|
||||
* is not intended for production use.
|
||||
*/
|
||||
final class PhabricatorFileROT13StorageFormat
|
||||
extends PhabricatorFileStorageFormat {
|
||||
|
||||
const FORMATKEY = 'rot13';
|
||||
|
||||
public function getStorageFormatName() {
|
||||
return pht('Encoded (ROT13)');
|
||||
}
|
||||
|
||||
public function newReadIterator($raw_iterator) {
|
||||
$file = $this->getFile();
|
||||
$iterations = $file->getStorageProperty('iterations', 1);
|
||||
|
||||
$value = $file->loadDataFromIterator($raw_iterator);
|
||||
for ($ii = 0; $ii < $iterations; $ii++) {
|
||||
$value = str_rot13($value);
|
||||
}
|
||||
|
||||
return array($value);
|
||||
}
|
||||
|
||||
public function newWriteIterator($raw_iterator) {
|
||||
return $this->newReadIterator($raw_iterator);
|
||||
}
|
||||
|
||||
public function newStorageProperties() {
|
||||
// For extreme security, repeatedly encode the data using a random (odd)
|
||||
// number of iterations.
|
||||
return array(
|
||||
'iterations' => (mt_rand(1, 3) * 2) - 1,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFileRawStorageFormat
|
||||
extends PhabricatorFileStorageFormat {
|
||||
|
||||
const FORMATKEY = 'raw';
|
||||
|
||||
public function getStorageFormatName() {
|
||||
return pht('Raw Data');
|
||||
}
|
||||
|
||||
public function newReadIterator($raw_iterator) {
|
||||
return $raw_iterator;
|
||||
}
|
||||
|
||||
public function newWriteIterator($raw_iterator) {
|
||||
return $raw_iterator;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorFileStorageFormat
|
||||
extends Phobject {
|
||||
|
||||
private $file;
|
||||
|
||||
final public function setFile(PhabricatorFile $file) {
|
||||
$this->file = $file;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getFile() {
|
||||
if (!$this->file) {
|
||||
throw new PhutilInvalidStateException('setFile');
|
||||
}
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
abstract public function getStorageFormatName();
|
||||
|
||||
abstract public function newReadIterator($raw_iterator);
|
||||
abstract public function newWriteIterator($raw_iterator);
|
||||
|
||||
public function newStorageProperties() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function canGenerateNewKeyMaterial() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function generateNewKeyMaterial() {
|
||||
throw new PhutilMethodNotImplementedException();
|
||||
}
|
||||
|
||||
public function canCycleMasterKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function cycleStorageProperties() {
|
||||
throw new PhutilMethodNotImplementedException();
|
||||
}
|
||||
|
||||
public function selectMasterKey($key_name) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'This storage format ("%s") does not support key selection.',
|
||||
$this->getStorageFormatName()));
|
||||
}
|
||||
|
||||
final public function getStorageFormatKey() {
|
||||
return $this->getPhobjectClassConstant('FORMATKEY');
|
||||
}
|
||||
|
||||
final public static function getAllFormats() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->setUniqueMethod('getStorageFormatKey')
|
||||
->execute();
|
||||
}
|
||||
|
||||
final public static function getFormat($key) {
|
||||
$formats = self::getAllFormats();
|
||||
return idx($formats, $key);
|
||||
}
|
||||
|
||||
final public static function requireFormat($key) {
|
||||
$format = self::getFormat($key);
|
||||
|
||||
if (!$format) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'No file storage format with key "%s" exists.',
|
||||
$key));
|
||||
}
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFileStorageFormatTestCase extends PhabricatorTestCase {
|
||||
|
||||
protected function getPhabricatorTestCaseConfiguration() {
|
||||
return array(
|
||||
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
|
||||
);
|
||||
}
|
||||
|
||||
public function testRot13Storage() {
|
||||
$engine = new PhabricatorTestStorageEngine();
|
||||
$rot13_format = PhabricatorFileROT13StorageFormat::FORMATKEY;
|
||||
|
||||
$data = 'The cow jumped over the full moon.';
|
||||
$expect = 'Gur pbj whzcrq bire gur shyy zbba.';
|
||||
|
||||
$params = array(
|
||||
'name' => 'test.dat',
|
||||
'storageEngines' => array(
|
||||
$engine,
|
||||
),
|
||||
'format' => $rot13_format,
|
||||
);
|
||||
|
||||
$file = PhabricatorFile::newFromFileData($data, $params);
|
||||
|
||||
// We should have a file stored as rot13, which reads back the input
|
||||
// data correctly.
|
||||
$this->assertEqual($rot13_format, $file->getStorageFormat());
|
||||
$this->assertEqual($data, $file->loadFileData());
|
||||
|
||||
// The actual raw data in the storage engine should be encoded.
|
||||
$raw_data = $engine->readFile($file->getStorageHandle());
|
||||
$this->assertEqual($expect, $raw_data);
|
||||
}
|
||||
|
||||
public function testAES256Storage() {
|
||||
$engine = new PhabricatorTestStorageEngine();
|
||||
|
||||
$key_name = 'test.abcd';
|
||||
$key_text = 'abcdefghijklmnopABCDEFGHIJKLMNOP';
|
||||
|
||||
PhabricatorKeyring::addKey(
|
||||
array(
|
||||
'name' => $key_name,
|
||||
'type' => 'aes-256-cbc',
|
||||
'material.base64' => base64_encode($key_text),
|
||||
));
|
||||
|
||||
$format = id(new PhabricatorFileAES256StorageFormat())
|
||||
->selectMasterKey($key_name);
|
||||
|
||||
$data = 'The cow jumped over the full moon.';
|
||||
|
||||
$params = array(
|
||||
'name' => 'test.dat',
|
||||
'storageEngines' => array(
|
||||
$engine,
|
||||
),
|
||||
'format' => $format,
|
||||
);
|
||||
|
||||
$file = PhabricatorFile::newFromFileData($data, $params);
|
||||
|
||||
// We should have a file stored as AES256.
|
||||
$format_key = $format->getStorageFormatKey();
|
||||
$this->assertEqual($format_key, $file->getStorageFormat());
|
||||
$this->assertEqual($data, $file->loadFileData());
|
||||
|
||||
// The actual raw data in the storage engine should be encrypted. We
|
||||
// can't really test this, but we can make sure it's not the same as the
|
||||
// input data.
|
||||
$raw_data = $engine->readFile($file->getStorageHandle());
|
||||
$this->assertTrue($data !== $raw_data);
|
||||
}
|
||||
}
|
52
src/applications/files/keyring/PhabricatorKeyring.php
Normal file
52
src/applications/files/keyring/PhabricatorKeyring.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorKeyring extends Phobject {
|
||||
|
||||
private static $hasReadConfiguration;
|
||||
private static $keyRing = array();
|
||||
|
||||
public static function addKey($spec) {
|
||||
self::$keyRing[$spec['name']] = $spec;
|
||||
}
|
||||
|
||||
public static function getKey($name, $type) {
|
||||
self::readConfiguration();
|
||||
|
||||
if (empty(self::$keyRing[$name])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'No key "%s" exists in keyring.',
|
||||
$name));
|
||||
}
|
||||
|
||||
$spec = self::$keyRing[$name];
|
||||
|
||||
$material = base64_decode($spec['material.base64'], true);
|
||||
return new PhutilOpaqueEnvelope($material);
|
||||
}
|
||||
|
||||
public static function getDefaultKeyName($type) {
|
||||
self::readConfiguration();
|
||||
|
||||
foreach (self::$keyRing as $name => $key) {
|
||||
if (!empty($key['default'])) {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function readConfiguration() {
|
||||
if (self::$hasReadConfiguration) {
|
||||
return true;
|
||||
}
|
||||
|
||||
self::$hasReadConfiguration = true;
|
||||
|
||||
foreach (PhabricatorEnv::getEnvConfig('keyring') as $spec) {
|
||||
self::addKey($spec);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorKeyringConfigOptionType
|
||||
extends PhabricatorConfigJSONOptionType {
|
||||
|
||||
public function validateOption(PhabricatorConfigOption $option, $value) {
|
||||
if (!is_array($value)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Keyring configuration is not valid: value must be a '.
|
||||
'list of encryption keys.'));
|
||||
}
|
||||
|
||||
foreach ($value as $index => $spec) {
|
||||
if (!is_array($spec)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Keyring configuration is not valid: each entry in the list must '.
|
||||
'be a dictionary describing an encryption key, but the value '.
|
||||
'with index "%s" is not a dictionary.',
|
||||
$index));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$map = array();
|
||||
$defaults = array();
|
||||
foreach ($value as $index => $spec) {
|
||||
try {
|
||||
PhutilTypeSpec::checkMap(
|
||||
$spec,
|
||||
array(
|
||||
'name' => 'string',
|
||||
'type' => 'string',
|
||||
'material.base64' => 'string',
|
||||
'default' => 'optional bool',
|
||||
));
|
||||
} catch (Exception $ex) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Keyring configuration has an invalid key specification (at '.
|
||||
'index "%s"): %s.',
|
||||
$index,
|
||||
$ex->getMessage()));
|
||||
}
|
||||
|
||||
$name = $spec['name'];
|
||||
if (isset($map[$name])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Keyring configuration is invalid: it describes multiple keys '.
|
||||
'with the same name ("%s"). Each key must have a unique name.',
|
||||
$name));
|
||||
}
|
||||
$map[$name] = true;
|
||||
|
||||
if (idx($spec, 'default')) {
|
||||
$defaults[] = $name;
|
||||
}
|
||||
|
||||
$type = $spec['type'];
|
||||
switch ($type) {
|
||||
case 'aes-256-cbc':
|
||||
if (!function_exists('openssl_encrypt')) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Keyring is configured with a "%s" key, but the PHP OpenSSL '.
|
||||
'extension is not installed. Install the OpenSSL extension '.
|
||||
'to enable encryption.',
|
||||
$type));
|
||||
}
|
||||
|
||||
$material = $spec['material.base64'];
|
||||
$material = base64_decode($material, true);
|
||||
if ($material === false) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Keyring specifies an invalid key ("%s"): key material '.
|
||||
'should be base64 encoded.',
|
||||
$name));
|
||||
}
|
||||
|
||||
if (strlen($material) != 32) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Keyring specifies an invalid key ("%s"): key material '.
|
||||
'should be 32 bytes (256 bits) but has length %s.',
|
||||
$name,
|
||||
new PhutilNumber(strlen($material))));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Keyring configuration is invalid: it describes a key with '.
|
||||
'type "%s", but this type is unknown.',
|
||||
$type));
|
||||
}
|
||||
}
|
||||
|
||||
if (count($defaults) > 1) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Keyring configuration is invalid: it describes multiple default '.
|
||||
'encryption keys. No more than one key may be the default key. '.
|
||||
'Keys currently configured as defaults: %s.',
|
||||
implode(', ', $defaults)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFilesManagementCycleWorkflow
|
||||
extends PhabricatorFilesManagementWorkflow {
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->setName('cycle')
|
||||
->setSynopsis(
|
||||
pht('Cycle master key for encrypted files.'))
|
||||
->setArguments(
|
||||
array(
|
||||
array(
|
||||
'name' => 'key',
|
||||
'param' => 'keyname',
|
||||
'help' => pht('Select a specific storage key to cycle to.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'all',
|
||||
'help' => pht('Change encoding for all files.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'names',
|
||||
'wildcard' => true,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$iterator = $this->buildIterator($args);
|
||||
if (!$iterator) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Either specify a list of files to cycle, or use --all to cycle '.
|
||||
'all files.'));
|
||||
}
|
||||
|
||||
$format_map = PhabricatorFileStorageFormat::getAllFormats();
|
||||
$engines = PhabricatorFileStorageEngine::loadAllEngines();
|
||||
|
||||
$key_name = $args->getArg('key');
|
||||
|
||||
$failed = array();
|
||||
foreach ($iterator as $file) {
|
||||
$monogram = $file->getMonogram();
|
||||
|
||||
$engine_key = $file->getStorageEngine();
|
||||
$engine = idx($engines, $engine_key);
|
||||
|
||||
if (!$engine) {
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'%s: Uses unknown storage engine "%s".',
|
||||
$monogram,
|
||||
$engine_key));
|
||||
$failed[] = $file;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($engine->isChunkEngine()) {
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'%s: Stored as chunks, declining to cycle directly.',
|
||||
$monogram));
|
||||
continue;
|
||||
}
|
||||
|
||||
$format_key = $file->getStorageFormat();
|
||||
if (empty($format_map[$format_key])) {
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'%s: Uses unknown storage format "%s".',
|
||||
$monogram,
|
||||
$format_key));
|
||||
$failed[] = $file;
|
||||
continue;
|
||||
}
|
||||
|
||||
$format = clone $format_map[$format_key];
|
||||
$format->setFile($file);
|
||||
|
||||
if (!$format->canCycleMasterKey()) {
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'%s: Storage format ("%s") does not support key cycling.',
|
||||
$monogram,
|
||||
$format->getStorageFormatName()));
|
||||
continue;
|
||||
}
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'%s: Cycling master key.',
|
||||
$monogram));
|
||||
|
||||
try {
|
||||
if ($key_name) {
|
||||
$format->selectMasterKey($key_name);
|
||||
}
|
||||
|
||||
$file->cycleMasterStorageKey($format);
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht('Done.'));
|
||||
} catch (Exception $ex) {
|
||||
echo tsprintf(
|
||||
"%B\n",
|
||||
pht('Failed! %s', (string)$ex));
|
||||
$failed[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
if ($failed) {
|
||||
$monograms = mpull($failed, 'getMonogram');
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht('Failures: %s.', implode(', ', $monograms)));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFilesManagementEncodeWorkflow
|
||||
extends PhabricatorFilesManagementWorkflow {
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->setName('encode')
|
||||
->setSynopsis(
|
||||
pht('Change the storage encoding of files.'))
|
||||
->setArguments(
|
||||
array(
|
||||
array(
|
||||
'name' => 'as',
|
||||
'param' => 'format',
|
||||
'help' => pht('Select the storage format to use.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'key',
|
||||
'param' => 'keyname',
|
||||
'help' => pht('Select a specific storage key.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'all',
|
||||
'help' => pht('Change encoding for all files.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'force',
|
||||
'help' => pht(
|
||||
'Re-encode files which are already stored in the target '.
|
||||
'encoding.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'names',
|
||||
'wildcard' => true,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$iterator = $this->buildIterator($args);
|
||||
if (!$iterator) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Either specify a list of files to encode, or use --all to '.
|
||||
'encode all files.'));
|
||||
}
|
||||
|
||||
$force = (bool)$args->getArg('force');
|
||||
|
||||
$format_list = PhabricatorFileStorageFormat::getAllFormats();
|
||||
$format_list = array_keys($format_list);
|
||||
$format_list = implode(', ', $format_list);
|
||||
|
||||
$format_key = $args->getArg('as');
|
||||
if (!strlen($format_key)) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Use --as <format> to select a target encoding format. Available '.
|
||||
'formats are: %s.',
|
||||
$format_list));
|
||||
}
|
||||
|
||||
$format = PhabricatorFileStorageFormat::getFormat($format_key);
|
||||
if (!$format) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Storage format "%s" is not valid. Available formats are: %s.',
|
||||
$format_key,
|
||||
$format_list));
|
||||
}
|
||||
|
||||
$key_name = $args->getArg('key');
|
||||
if (strlen($key_name)) {
|
||||
$format->selectMasterKey($key_name);
|
||||
}
|
||||
|
||||
$engines = PhabricatorFileStorageEngine::loadAllEngines();
|
||||
|
||||
$failed = array();
|
||||
foreach ($iterator as $file) {
|
||||
$monogram = $file->getMonogram();
|
||||
|
||||
$engine_key = $file->getStorageEngine();
|
||||
$engine = idx($engines, $engine_key);
|
||||
|
||||
if (!$engine) {
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'%s: Uses unknown storage engine "%s".',
|
||||
$monogram,
|
||||
$engine_key));
|
||||
$failed[] = $file;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($engine->isChunkEngine()) {
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'%s: Stored as chunks, no data to encode directly.',
|
||||
$monogram));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($file->getStorageFormat() == $format_key) && !$force) {
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'%s: Already encoded in target format.',
|
||||
$monogram));
|
||||
continue;
|
||||
}
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'%s: Changing encoding from "%s" to "%s".',
|
||||
$monogram,
|
||||
$file->getStorageFormat(),
|
||||
$format_key));
|
||||
|
||||
try {
|
||||
$file->migrateToStorageFormat($format);
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht('Done.'));
|
||||
} catch (Exception $ex) {
|
||||
echo tsprintf(
|
||||
"%B\n",
|
||||
pht('Failed! %s', (string)$ex));
|
||||
$failed[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
if ($failed) {
|
||||
$monograms = mpull($failed, 'getMonogram');
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht('Failures: %s.', implode(', ', $monograms)));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFilesManagementGenerateKeyWorkflow
|
||||
extends PhabricatorFilesManagementWorkflow {
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->setName('generate-key')
|
||||
->setSynopsis(
|
||||
pht('Generate an encryption key.'))
|
||||
->setArguments(
|
||||
array(
|
||||
array(
|
||||
'name' => 'type',
|
||||
'param' => 'keytype',
|
||||
'help' => pht('Select the type of key to generate.'),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$type = $args->getArg('type');
|
||||
if (!strlen($type)) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Specify the type of key to generate with --type.'));
|
||||
}
|
||||
|
||||
$format = PhabricatorFileStorageFormat::getFormat($type);
|
||||
if (!$format) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'No key type "%s" exists.',
|
||||
$type));
|
||||
}
|
||||
|
||||
if (!$format->canGenerateNewKeyMaterial()) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Storage format "%s" can not generate keys.',
|
||||
$format->getStorageFormatName()));
|
||||
}
|
||||
|
||||
$material = $format->generateNewKeyMaterial();
|
||||
|
||||
$structure = array(
|
||||
'name' => 'generated-key-'.Filesystem::readRandomCharacters(12),
|
||||
'type' => $type,
|
||||
'material.base64' => $material,
|
||||
);
|
||||
|
||||
$json = id(new PhutilJSON())->encodeFormatted($structure);
|
||||
|
||||
echo tsprintf(
|
||||
"%s: %s\n\n%B\n",
|
||||
pht('Key Material'),
|
||||
$format->getStorageFormatName(),
|
||||
$json);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -26,14 +26,13 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
PhabricatorPolicyInterface,
|
||||
PhabricatorDestructibleInterface {
|
||||
|
||||
const STORAGE_FORMAT_RAW = 'raw';
|
||||
|
||||
const METADATA_IMAGE_WIDTH = 'width';
|
||||
const METADATA_IMAGE_HEIGHT = 'height';
|
||||
const METADATA_CAN_CDN = 'canCDN';
|
||||
const METADATA_BUILTIN = 'builtin';
|
||||
const METADATA_PARTIAL = 'partial';
|
||||
const METADATA_PROFILE = 'profile';
|
||||
const METADATA_STORAGE = 'storage';
|
||||
|
||||
protected $name;
|
||||
protected $mimeType;
|
||||
|
@ -233,10 +232,10 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
$hash);
|
||||
|
||||
if ($file) {
|
||||
// copy storageEngine, storageHandle, storageFormat
|
||||
$copy_of_storage_engine = $file->getStorageEngine();
|
||||
$copy_of_storage_handle = $file->getStorageHandle();
|
||||
$copy_of_storage_format = $file->getStorageFormat();
|
||||
$copy_of_storage_properties = $file->getStorageProperties();
|
||||
$copy_of_byte_size = $file->getByteSize();
|
||||
$copy_of_mime_type = $file->getMimeType();
|
||||
|
||||
|
@ -248,6 +247,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
$new_file->setStorageEngine($copy_of_storage_engine);
|
||||
$new_file->setStorageHandle($copy_of_storage_handle);
|
||||
$new_file->setStorageFormat($copy_of_storage_format);
|
||||
$new_file->setStorageProperties($copy_of_storage_properties);
|
||||
$new_file->setMimeType($copy_of_mime_type);
|
||||
$new_file->copyDimensions($file);
|
||||
|
||||
|
@ -290,7 +290,11 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
|
||||
$file->setStorageEngine($engine->getEngineIdentifier());
|
||||
$file->setStorageHandle(PhabricatorFileChunk::newChunkHandle());
|
||||
$file->setStorageFormat(self::STORAGE_FORMAT_RAW);
|
||||
|
||||
// Chunked files are always stored raw because they do not actually store
|
||||
// data. The chunks do, and can be individually formatted.
|
||||
$file->setStorageFormat(PhabricatorFileRawStorageFormat::FORMATKEY);
|
||||
|
||||
$file->setIsPartial(1);
|
||||
|
||||
$file->readPropertiesFromParameters($params);
|
||||
|
@ -322,6 +326,29 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
|
||||
$file = self::initializeNewFile();
|
||||
|
||||
$aes_type = PhabricatorFileAES256StorageFormat::FORMATKEY;
|
||||
$has_aes = PhabricatorKeyring::getDefaultKeyName($aes_type);
|
||||
if ($has_aes !== null) {
|
||||
$default_key = PhabricatorFileAES256StorageFormat::FORMATKEY;
|
||||
} else {
|
||||
$default_key = PhabricatorFileRawStorageFormat::FORMATKEY;
|
||||
}
|
||||
$key = idx($params, 'format', $default_key);
|
||||
|
||||
// Callers can pass in an object explicitly instead of a key. This is
|
||||
// primarily useful for unit tests.
|
||||
if ($key instanceof PhabricatorFileStorageFormat) {
|
||||
$format = clone $key;
|
||||
} else {
|
||||
$format = clone PhabricatorFileStorageFormat::requireFormat($key);
|
||||
}
|
||||
|
||||
$format->setFile($file);
|
||||
|
||||
$properties = $format->newStorageProperties();
|
||||
$file->setStorageFormat($format->getStorageFormatKey());
|
||||
$file->setStorageProperties($properties);
|
||||
|
||||
$data_handle = null;
|
||||
$engine_identifier = null;
|
||||
$exceptions = array();
|
||||
|
@ -361,10 +388,6 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
$file->setStorageEngine($engine_identifier);
|
||||
$file->setStorageHandle($data_handle);
|
||||
|
||||
// TODO: This is probably YAGNI, but allows for us to do encryption or
|
||||
// compression later if we want.
|
||||
$file->setStorageFormat(self::STORAGE_FORMAT_RAW);
|
||||
|
||||
$file->readPropertiesFromParameters($params);
|
||||
|
||||
if (!$file->getMimeType()) {
|
||||
|
@ -427,6 +450,53 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function migrateToStorageFormat(PhabricatorFileStorageFormat $format) {
|
||||
if (!$this->getID() || !$this->getStorageHandle()) {
|
||||
throw new Exception(
|
||||
pht("You can not migrate a file which hasn't yet been saved."));
|
||||
}
|
||||
|
||||
$data = $this->loadFileData();
|
||||
$params = array(
|
||||
'name' => $this->getName(),
|
||||
);
|
||||
|
||||
$engine = $this->instantiateStorageEngine();
|
||||
$old_handle = $this->getStorageHandle();
|
||||
|
||||
$properties = $format->newStorageProperties();
|
||||
$this->setStorageFormat($format->getStorageFormatKey());
|
||||
$this->setStorageProperties($properties);
|
||||
|
||||
list($identifier, $new_handle) = $this->writeToEngine(
|
||||
$engine,
|
||||
$data,
|
||||
$params);
|
||||
|
||||
$this->setStorageHandle($new_handle);
|
||||
$this->save();
|
||||
|
||||
$this->deleteFileDataIfUnused(
|
||||
$engine,
|
||||
$identifier,
|
||||
$old_handle);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function cycleMasterStorageKey(PhabricatorFileStorageFormat $format) {
|
||||
if (!$this->getID() || !$this->getStorageHandle()) {
|
||||
throw new Exception(
|
||||
pht("You can not cycle keys for a file which hasn't yet been saved."));
|
||||
}
|
||||
|
||||
$properties = $format->cycleStorageProperties();
|
||||
$this->setStorageProperties($properties);
|
||||
$this->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function writeToEngine(
|
||||
PhabricatorFileStorageEngine $engine,
|
||||
$data,
|
||||
|
@ -434,7 +504,15 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
|
||||
$engine_class = get_class($engine);
|
||||
|
||||
$data_handle = $engine->writeFile($data, $params);
|
||||
$key = $this->getStorageFormat();
|
||||
$format = id(clone PhabricatorFileStorageFormat::requireFormat($key))
|
||||
->setFile($this);
|
||||
|
||||
$data_iterator = array($data);
|
||||
$formatted_iterator = $format->newWriteIterator($data_iterator);
|
||||
$formatted_data = $this->loadDataFromIterator($formatted_iterator);
|
||||
|
||||
$data_handle = $engine->writeFile($formatted_data, $params);
|
||||
|
||||
if (!$data_handle || strlen($data_handle) > 255) {
|
||||
// This indicates an improperly implemented storage engine.
|
||||
|
@ -663,19 +741,8 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
}
|
||||
|
||||
public function loadFileData() {
|
||||
|
||||
$engine = $this->instantiateStorageEngine();
|
||||
$data = $engine->readFile($this->getStorageHandle());
|
||||
|
||||
switch ($this->getStorageFormat()) {
|
||||
case self::STORAGE_FORMAT_RAW:
|
||||
$data = $data;
|
||||
break;
|
||||
default:
|
||||
throw new Exception(pht('Unknown storage format.'));
|
||||
}
|
||||
|
||||
return $data;
|
||||
$iterator = $this->getFileDataIterator();
|
||||
return $this->loadDataFromIterator($iterator);
|
||||
}
|
||||
|
||||
|
||||
|
@ -688,7 +755,14 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
*/
|
||||
public function getFileDataIterator($begin = null, $end = null) {
|
||||
$engine = $this->instantiateStorageEngine();
|
||||
return $engine->getFileDataIterator($this, $begin, $end);
|
||||
$raw_iterator = $engine->getRawFileDataIterator($this, $begin, $end);
|
||||
|
||||
$key = $this->getStorageFormat();
|
||||
|
||||
$format = id(clone PhabricatorFileStorageFormat::requireFormat($key))
|
||||
->setFile($this);
|
||||
|
||||
return $format->newReadIterator($raw_iterator);
|
||||
}
|
||||
|
||||
|
||||
|
@ -917,6 +991,30 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
return Filesystem::readRandomCharacters(20);
|
||||
}
|
||||
|
||||
public function setStorageProperties(array $properties) {
|
||||
$this->metadata[self::METADATA_STORAGE] = $properties;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStorageProperties() {
|
||||
return idx($this->metadata, self::METADATA_STORAGE, array());
|
||||
}
|
||||
|
||||
public function getStorageProperty($key, $default = null) {
|
||||
$properties = $this->getStorageProperties();
|
||||
return idx($properties, $key, $default);
|
||||
}
|
||||
|
||||
public function loadDataFromIterator($iterator) {
|
||||
$result = '';
|
||||
|
||||
foreach ($iterator as $chunk) {
|
||||
$result .= $chunk;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function updateDimensions($save = true) {
|
||||
if (!$this->isViewableImage()) {
|
||||
throw new Exception(pht('This file is not a viewable image.'));
|
||||
|
|
|
@ -9,6 +9,7 @@ final class HarbormasterBuildEngine extends Phobject {
|
|||
private $build;
|
||||
private $viewer;
|
||||
private $newBuildTargets = array();
|
||||
private $artifactReleaseQueue = array();
|
||||
private $forceBuildableUpdate;
|
||||
|
||||
public function setForceBuildableUpdate($force_buildable_update) {
|
||||
|
@ -94,6 +95,8 @@ final class HarbormasterBuildEngine extends Phobject {
|
|||
$this->updateBuildable($build->getBuildable());
|
||||
}
|
||||
|
||||
$this->releaseQueuedArtifacts();
|
||||
|
||||
// If we are no longer building for any reason, release all artifacts.
|
||||
if (!$build->isBuilding()) {
|
||||
$this->releaseAllArtifacts($build);
|
||||
|
@ -149,20 +152,21 @@ final class HarbormasterBuildEngine extends Phobject {
|
|||
}
|
||||
|
||||
private function updateBuildSteps(HarbormasterBuild $build) {
|
||||
$targets = id(new HarbormasterBuildTargetQuery())
|
||||
$all_targets = id(new HarbormasterBuildTargetQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withBuildPHIDs(array($build->getPHID()))
|
||||
->withBuildGenerations(array($build->getBuildGeneration()))
|
||||
->execute();
|
||||
|
||||
$this->updateWaitingTargets($targets);
|
||||
$this->updateWaitingTargets($all_targets);
|
||||
|
||||
$targets = mgroup($targets, 'getBuildStepPHID');
|
||||
$targets = mgroup($all_targets, 'getBuildStepPHID');
|
||||
|
||||
$steps = id(new HarbormasterBuildStepQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withBuildPlanPHIDs(array($build->getBuildPlan()->getPHID()))
|
||||
->execute();
|
||||
$steps = mpull($steps, null, 'getPHID');
|
||||
|
||||
// Identify steps which are in various states.
|
||||
|
||||
|
@ -252,6 +256,12 @@ final class HarbormasterBuildEngine extends Phobject {
|
|||
return;
|
||||
}
|
||||
|
||||
// Release any artifacts which are not inputs to any remaining build
|
||||
// step. We're done with these, so something else is free to use them.
|
||||
$ongoing_phids = array_keys($queued + $waiting + $underway);
|
||||
$ongoing_steps = array_select_keys($steps, $ongoing_phids);
|
||||
$this->releaseUnusedArtifacts($all_targets, $ongoing_steps);
|
||||
|
||||
// Identify all the steps which are ready to run (because all their
|
||||
// dependencies are complete).
|
||||
|
||||
|
@ -294,6 +304,59 @@ final class HarbormasterBuildEngine extends Phobject {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Release any artifacts which aren't used by any running or waiting steps.
|
||||
*
|
||||
* This releases artifacts as soon as they're no longer used. This can be
|
||||
* particularly relevant when a build uses multiple hosts since it returns
|
||||
* hosts to the pool more quickly.
|
||||
*
|
||||
* @param list<HarbormasterBuildTarget> Targets in the build.
|
||||
* @param list<HarbormasterBuildStep> List of running and waiting steps.
|
||||
* @return void
|
||||
*/
|
||||
private function releaseUnusedArtifacts(array $targets, array $steps) {
|
||||
assert_instances_of($targets, 'HarbormasterBuildTarget');
|
||||
assert_instances_of($steps, 'HarbormasterBuildStep');
|
||||
|
||||
if (!$targets || !$steps) {
|
||||
return;
|
||||
}
|
||||
|
||||
$target_phids = mpull($targets, 'getPHID');
|
||||
|
||||
$artifacts = id(new HarbormasterBuildArtifactQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withBuildTargetPHIDs($target_phids)
|
||||
->withIsReleased(false)
|
||||
->execute();
|
||||
if (!$artifacts) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect all the artifacts that remaining build steps accept as inputs.
|
||||
$must_keep = array();
|
||||
foreach ($steps as $step) {
|
||||
$inputs = $step->getStepImplementation()->getArtifactInputs();
|
||||
foreach ($inputs as $input) {
|
||||
$artifact_key = $input['key'];
|
||||
$must_keep[$artifact_key] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Queue unreleased artifacts which no remaining step uses for immediate
|
||||
// release.
|
||||
foreach ($artifacts as $artifact) {
|
||||
$key = $artifact->getArtifactKey();
|
||||
if (isset($must_keep[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->artifactReleaseQueue[] = $artifact;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process messages which were sent to these targets, kicking applicable
|
||||
* targets out of "Waiting" and into either "Passed" or "Failed".
|
||||
|
@ -488,12 +551,18 @@ final class HarbormasterBuildEngine extends Phobject {
|
|||
$artifacts = id(new HarbormasterBuildArtifactQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withBuildTargetPHIDs($target_phids)
|
||||
->withIsReleased(false)
|
||||
->execute();
|
||||
|
||||
foreach ($artifacts as $artifact) {
|
||||
$artifact->releaseArtifact();
|
||||
}
|
||||
}
|
||||
|
||||
private function releaseQueuedArtifacts() {
|
||||
foreach ($this->artifactReleaseQueue as $key => $artifact) {
|
||||
$artifact->releaseArtifact();
|
||||
unset($this->artifactReleaseQueue[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ final class HarbormasterBuildArtifactQuery
|
|||
private $artifactIndexes;
|
||||
private $keyBuildPHID;
|
||||
private $keyBuildGeneration;
|
||||
private $isReleased;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
|
@ -30,6 +31,11 @@ final class HarbormasterBuildArtifactQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withIsReleased($released) {
|
||||
$this->isReleased = $released;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new HarbormasterBuildArtifact();
|
||||
}
|
||||
|
@ -94,6 +100,13 @@ final class HarbormasterBuildArtifactQuery
|
|||
$this->artifactIndexes);
|
||||
}
|
||||
|
||||
if ($this->isReleased !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'isReleased = %d',
|
||||
(int)$this->isReleased);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
|
|
@ -103,11 +103,6 @@ abstract class HarbormasterBuildStepImplementation extends Phobject {
|
|||
/**
|
||||
* Return the name of artifacts produced by this command.
|
||||
*
|
||||
* Something like:
|
||||
*
|
||||
* return array(
|
||||
* 'some_name_input_by_user' => 'host');
|
||||
*
|
||||
* Future steps will calculate all available artifact mappings
|
||||
* before them and filter on the type.
|
||||
*
|
||||
|
|
|
@ -8,6 +8,7 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO
|
|||
protected $artifactIndex;
|
||||
protected $artifactKey;
|
||||
protected $artifactData = array();
|
||||
protected $isReleased = 0;
|
||||
|
||||
private $buildTarget = self::ATTACHABLE;
|
||||
private $artifactImplementation;
|
||||
|
@ -29,6 +30,7 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO
|
|||
'artifactType' => 'text32',
|
||||
'artifactIndex' => 'bytes12',
|
||||
'artifactKey' => 'text255',
|
||||
'isReleased' => 'bool',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_artifact' => array(
|
||||
|
@ -83,13 +85,18 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO
|
|||
}
|
||||
|
||||
public function releaseArtifact() {
|
||||
$impl = $this->getArtifactImplementation();
|
||||
if ($this->getIsReleased()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$impl = $this->getArtifactImplementation();
|
||||
if ($impl) {
|
||||
$impl->releaseArtifact(PhabricatorUser::getOmnipotentUser());
|
||||
}
|
||||
|
||||
return null;
|
||||
return $this
|
||||
->setIsReleased(1)
|
||||
->save();
|
||||
}
|
||||
|
||||
public function getArtifactImplementation() {
|
||||
|
|
|
@ -54,16 +54,18 @@ final class ManiphestTransaction
|
|||
return parent::shouldGenerateOldValue();
|
||||
}
|
||||
|
||||
public function getRemarkupBlocks() {
|
||||
$blocks = parent::getRemarkupBlocks();
|
||||
protected function newRemarkupChanges() {
|
||||
$changes = array();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_DESCRIPTION:
|
||||
$blocks[] = $this->getNewValue();
|
||||
$changes[] = $this->newRemarkupChange()
|
||||
->setOldValue($this->getOldValue())
|
||||
->setNewValue($this->getNewValue());
|
||||
break;
|
||||
}
|
||||
|
||||
return $blocks;
|
||||
return $changes;
|
||||
}
|
||||
|
||||
public function getRequiredHandlePHIDs() {
|
||||
|
|
|
@ -877,6 +877,7 @@ final class PhabricatorMetaMTAMail
|
|||
$all_prefs = id(new PhabricatorUserPreferencesQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withUserPHIDs($actor_phids)
|
||||
->needSyntheticPreferences(true)
|
||||
->execute();
|
||||
$all_prefs = mpull($all_prefs, null, 'getUserPHID');
|
||||
|
||||
|
@ -1105,53 +1106,33 @@ final class PhabricatorMetaMTAMail
|
|||
|
||||
|
||||
private function loadPreferences($target_phid) {
|
||||
if (!self::shouldMultiplexAllMail()) {
|
||||
$target_phid = null;
|
||||
}
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
|
||||
if ($target_phid) {
|
||||
if (self::shouldMultiplexAllMail()) {
|
||||
$preferences = id(new PhabricatorUserPreferencesQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->setViewer($viewer)
|
||||
->withUserPHIDs(array($target_phid))
|
||||
->needSyntheticPreferences(true)
|
||||
->executeOne();
|
||||
} else {
|
||||
$preferences = null;
|
||||
if ($preferences) {
|
||||
return $preferences;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Here, we would load global preferences once they exist.
|
||||
|
||||
if (!$preferences) {
|
||||
// If we haven't found suitable preferences yet, return an empty object
|
||||
// which implicitly has all the default values.
|
||||
$preferences = id(new PhabricatorUserPreferences())
|
||||
->attachUser(new PhabricatorUser());
|
||||
}
|
||||
|
||||
return $preferences;
|
||||
return PhabricatorUserPreferences::loadGlobalPreferences($viewer);
|
||||
}
|
||||
|
||||
private function shouldAddRePrefix(PhabricatorUserPreferences $preferences) {
|
||||
$default_value = PhabricatorEnv::getEnvConfig('metamta.re-prefix');
|
||||
|
||||
$value = $preferences->getPreference(
|
||||
$value = $preferences->getSettingValue(
|
||||
PhabricatorEmailRePrefixSetting::SETTINGKEY);
|
||||
if ($value === null) {
|
||||
return $default_value;
|
||||
}
|
||||
|
||||
return ($value == PhabricatorEmailRePrefixSetting::VALUE_RE_PREFIX);
|
||||
}
|
||||
|
||||
private function shouldVarySubject(PhabricatorUserPreferences $preferences) {
|
||||
$default_value = PhabricatorEnv::getEnvConfig('metamta.vary-subjects');
|
||||
|
||||
$value = $preferences->getPreference(
|
||||
$value = $preferences->getSettingValue(
|
||||
PhabricatorEmailVarySubjectsSetting::SETTINGKEY);
|
||||
|
||||
if ($value === null) {
|
||||
return $default_value;
|
||||
}
|
||||
|
||||
return ($value == PhabricatorEmailVarySubjectsSetting::VALUE_VARY_SUBJECTS);
|
||||
}
|
||||
|
||||
|
|
|
@ -47,15 +47,15 @@ final class PasteCreateConduitAPIMethod extends PasteConduitAPIMethod {
|
|||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PhabricatorPasteTransaction())
|
||||
->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT)
|
||||
->setTransactionType(PhabricatorPasteContentTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue($content);
|
||||
|
||||
$xactions[] = id(new PhabricatorPasteTransaction())
|
||||
->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)
|
||||
->setTransactionType(PhabricatorPasteTitleTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue($title);
|
||||
|
||||
$xactions[] = id(new PhabricatorPasteTransaction())
|
||||
->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE)
|
||||
->setTransactionType(PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue($language);
|
||||
|
||||
$editor = id(new PhabricatorPasteEditor())
|
||||
|
|
|
@ -20,7 +20,7 @@ final class PhabricatorPasteArchiveController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$view_uri = '/P'.$paste->getID();
|
||||
$view_uri = $paste->getURI();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
if ($paste->isArchived()) {
|
||||
|
@ -32,7 +32,7 @@ final class PhabricatorPasteArchiveController
|
|||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PhabricatorPasteTransaction())
|
||||
->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS)
|
||||
->setTransactionType(PhabricatorPasteStatusTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue($new_status);
|
||||
|
||||
id(new PhabricatorPasteEditor())
|
||||
|
|
|
@ -71,7 +71,7 @@ final class PhabricatorPasteEditEngine
|
|||
id(new PhabricatorTextEditField())
|
||||
->setKey('title')
|
||||
->setLabel(pht('Title'))
|
||||
->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)
|
||||
->setTransactionType(PhabricatorPasteTitleTransaction::TRANSACTIONTYPE)
|
||||
->setDescription(pht('The title of the paste.'))
|
||||
->setConduitDescription(pht('Retitle the paste.'))
|
||||
->setConduitTypeDescription(pht('New paste title.'))
|
||||
|
@ -79,7 +79,8 @@ final class PhabricatorPasteEditEngine
|
|||
id(new PhabricatorSelectEditField())
|
||||
->setKey('language')
|
||||
->setLabel(pht('Language'))
|
||||
->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE)
|
||||
->setTransactionType(
|
||||
PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE)
|
||||
->setAliases(array('lang'))
|
||||
->setIsCopyable(true)
|
||||
->setOptions($langs)
|
||||
|
@ -94,7 +95,8 @@ final class PhabricatorPasteEditEngine
|
|||
id(new PhabricatorTextAreaEditField())
|
||||
->setKey('text')
|
||||
->setLabel(pht('Text'))
|
||||
->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT)
|
||||
->setTransactionType(
|
||||
PhabricatorPasteContentTransaction::TRANSACTIONTYPE)
|
||||
->setMonospaced(true)
|
||||
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
|
||||
->setDescription(pht('The main body text of the paste.'))
|
||||
|
@ -104,7 +106,8 @@ final class PhabricatorPasteEditEngine
|
|||
id(new PhabricatorSelectEditField())
|
||||
->setKey('status')
|
||||
->setLabel(pht('Status'))
|
||||
->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS)
|
||||
->setTransactionType(
|
||||
PhabricatorPasteStatusTransaction::TRANSACTIONTYPE)
|
||||
->setIsConduitOnly(true)
|
||||
->setOptions(PhabricatorPaste::getStatusNameMap())
|
||||
->setDescription(pht('Active or archived status.'))
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
final class PhabricatorPasteEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
private $fileName;
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorPasteApplication';
|
||||
}
|
||||
|
@ -13,29 +11,17 @@ final class PhabricatorPasteEditor
|
|||
return pht('Pastes');
|
||||
}
|
||||
|
||||
public static function initializeFileForPaste(
|
||||
PhabricatorUser $actor,
|
||||
$name,
|
||||
$data) {
|
||||
public function getCreateObjectTitle($author, $object) {
|
||||
return pht('%s created this paste.', $author);
|
||||
}
|
||||
|
||||
return PhabricatorFile::newFromFileData(
|
||||
$data,
|
||||
array(
|
||||
'name' => $name,
|
||||
'mime-type' => 'text/plain; charset=utf-8',
|
||||
'authorPHID' => $actor->getPHID(),
|
||||
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
|
||||
'editPolicy' => PhabricatorPolicies::POLICY_NOONE,
|
||||
));
|
||||
public function getCreateObjectTitleForFeed($author, $object) {
|
||||
return pht('%s created %s.', $author, $object);
|
||||
}
|
||||
|
||||
public function getTransactionTypes() {
|
||||
$types = parent::getTransactionTypes();
|
||||
|
||||
$types[] = PhabricatorPasteTransaction::TYPE_CONTENT;
|
||||
$types[] = PhabricatorPasteTransaction::TYPE_TITLE;
|
||||
$types[] = PhabricatorPasteTransaction::TYPE_LANGUAGE;
|
||||
$types[] = PhabricatorPasteTransaction::TYPE_STATUS;
|
||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||
$types[] = PhabricatorTransactions::TYPE_COMMENT;
|
||||
|
@ -43,163 +29,14 @@ final class PhabricatorPasteEditor
|
|||
return $types;
|
||||
}
|
||||
|
||||
protected function shouldApplyInitialEffects(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function applyInitialEffects(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
|
||||
// Find the most user-friendly filename we can by examining the title of
|
||||
// the paste and the pending transactions. We'll use this if we create a
|
||||
// new file to store raw content later.
|
||||
|
||||
$name = $object->getTitle();
|
||||
if (!strlen($name)) {
|
||||
$name = 'paste.raw';
|
||||
}
|
||||
|
||||
$type_title = PhabricatorPasteTransaction::TYPE_TITLE;
|
||||
foreach ($xactions as $xaction) {
|
||||
if ($xaction->getTransactionType() == $type_title) {
|
||||
$name = $xaction->getNewValue();
|
||||
}
|
||||
}
|
||||
|
||||
$this->fileName = $name;
|
||||
}
|
||||
|
||||
protected function validateTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
$type,
|
||||
array $xactions) {
|
||||
|
||||
$errors = parent::validateTransaction($object, $type, $xactions);
|
||||
switch ($type) {
|
||||
case PhabricatorPasteTransaction::TYPE_CONTENT:
|
||||
if (!$object->getFilePHID() && !$xactions) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Required'),
|
||||
pht('You must provide content to create a paste.'),
|
||||
null);
|
||||
|
||||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
protected function getCustomTransactionOldValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorPasteTransaction::TYPE_CONTENT:
|
||||
return $object->getFilePHID();
|
||||
case PhabricatorPasteTransaction::TYPE_TITLE:
|
||||
return $object->getTitle();
|
||||
case PhabricatorPasteTransaction::TYPE_LANGUAGE:
|
||||
return $object->getLanguage();
|
||||
case PhabricatorPasteTransaction::TYPE_STATUS:
|
||||
return $object->getStatus();
|
||||
}
|
||||
}
|
||||
|
||||
protected function getCustomTransactionNewValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorPasteTransaction::TYPE_TITLE:
|
||||
case PhabricatorPasteTransaction::TYPE_LANGUAGE:
|
||||
case PhabricatorPasteTransaction::TYPE_STATUS:
|
||||
return $xaction->getNewValue();
|
||||
case PhabricatorPasteTransaction::TYPE_CONTENT:
|
||||
// If this transaction does not really change the paste content, return
|
||||
// the current file PHID so this transaction no-ops.
|
||||
$new_content = $xaction->getNewValue();
|
||||
$old_content = $object->getRawContent();
|
||||
$file_phid = $object->getFilePHID();
|
||||
if (($new_content === $old_content) && $file_phid) {
|
||||
return $file_phid;
|
||||
}
|
||||
|
||||
$file = self::initializeFileForPaste(
|
||||
$this->getActor(),
|
||||
$this->fileName,
|
||||
$xaction->getNewValue());
|
||||
|
||||
return $file->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyCustomInternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorPasteTransaction::TYPE_CONTENT:
|
||||
$object->setFilePHID($xaction->getNewValue());
|
||||
return;
|
||||
case PhabricatorPasteTransaction::TYPE_TITLE:
|
||||
$object->setTitle($xaction->getNewValue());
|
||||
return;
|
||||
case PhabricatorPasteTransaction::TYPE_LANGUAGE:
|
||||
$object->setLanguage($xaction->getNewValue());
|
||||
return;
|
||||
case PhabricatorPasteTransaction::TYPE_STATUS:
|
||||
$object->setStatus($xaction->getNewValue());
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function applyCustomExternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorPasteTransaction::TYPE_CONTENT:
|
||||
case PhabricatorPasteTransaction::TYPE_TITLE:
|
||||
case PhabricatorPasteTransaction::TYPE_LANGUAGE:
|
||||
case PhabricatorPasteTransaction::TYPE_STATUS:
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomExternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function extractFilePHIDsFromCustomTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorPasteTransaction::TYPE_CONTENT:
|
||||
return array($xaction->getNewValue());
|
||||
}
|
||||
|
||||
return parent::extractFilePHIDsFromCustomTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function shouldSendMail(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
foreach ($xactions as $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorPasteTransaction::TYPE_CONTENT:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->getIsNewObject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -258,8 +95,4 @@ final class PhabricatorPasteEditor
|
|||
return true;
|
||||
}
|
||||
|
||||
protected function supportsSearch() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,15 +17,15 @@ final class PhabricatorPasteTestDataGenerator
|
|||
$xactions = array();
|
||||
|
||||
$xactions[] = $this->newTransaction(
|
||||
PhabricatorPasteTransaction::TYPE_TITLE,
|
||||
PhabricatorPasteTitleTransaction::TRANSACTIONTYPE,
|
||||
$name);
|
||||
|
||||
$xactions[] = $this->newTransaction(
|
||||
PhabricatorPasteTransaction::TYPE_LANGUAGE,
|
||||
PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE,
|
||||
$language);
|
||||
|
||||
$xactions[] = $this->newTransaction(
|
||||
PhabricatorPasteTransaction::TYPE_CONTENT,
|
||||
PhabricatorPasteContentTransaction::TRANSACTIONTYPE,
|
||||
$content);
|
||||
|
||||
$editor = id(new PhabricatorPasteEditor())
|
||||
|
|
|
@ -24,17 +24,13 @@ final class PasteCreateMailReceiver extends PhabricatorMailReceiver {
|
|||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PhabricatorPasteTransaction())
|
||||
->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT)
|
||||
->setTransactionType(PhabricatorPasteContentTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue($mail->getCleanTextBody());
|
||||
|
||||
$xactions[] = id(new PhabricatorPasteTransaction())
|
||||
->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)
|
||||
->setTransactionType(PhabricatorPasteTitleTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue($title);
|
||||
|
||||
$xactions[] = id(new PhabricatorPasteTransaction())
|
||||
->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE)
|
||||
->setNewValue(''); // auto-detect
|
||||
|
||||
$paste = PhabricatorPaste::initializeNewPaste($sender);
|
||||
|
||||
$content_source = $mail->newContentSource();
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorPasteTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
|
||||
const TYPE_CONTENT = 'paste.create';
|
||||
const TYPE_TITLE = 'paste.title';
|
||||
const TYPE_LANGUAGE = 'paste.language';
|
||||
const TYPE_STATUS = 'paste.status';
|
||||
extends PhabricatorModularTransaction {
|
||||
|
||||
const MAILTAG_CONTENT = 'paste-content';
|
||||
const MAILTAG_OTHER = 'paste-other';
|
||||
|
@ -24,226 +19,16 @@ final class PhabricatorPasteTransaction
|
|||
return new PhabricatorPasteTransactionComment();
|
||||
}
|
||||
|
||||
public function getRequiredHandlePHIDs() {
|
||||
$phids = parent::getRequiredHandlePHIDs();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_CONTENT:
|
||||
$phids[] = $this->getObjectPHID();
|
||||
break;
|
||||
}
|
||||
|
||||
return $phids;
|
||||
}
|
||||
|
||||
public function shouldHide() {
|
||||
$old = $this->getOldValue();
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_TITLE:
|
||||
case self::TYPE_LANGUAGE:
|
||||
if ($old === null) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return parent::shouldHide();
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_CONTENT:
|
||||
return 'fa-plus';
|
||||
break;
|
||||
case self::TYPE_TITLE:
|
||||
case self::TYPE_LANGUAGE:
|
||||
return 'fa-pencil';
|
||||
break;
|
||||
case self::TYPE_STATUS:
|
||||
$new = $this->getNewValue();
|
||||
switch ($new) {
|
||||
case PhabricatorPaste::STATUS_ACTIVE:
|
||||
return 'fa-check';
|
||||
case PhabricatorPaste::STATUS_ARCHIVED:
|
||||
return 'fa-ban';
|
||||
}
|
||||
break;
|
||||
}
|
||||
return parent::getIcon();
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
$author_phid = $this->getAuthorPHID();
|
||||
$object_phid = $this->getObjectPHID();
|
||||
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
$type = $this->getTransactionType();
|
||||
switch ($type) {
|
||||
case PhabricatorTransactions::TYPE_CREATE:
|
||||
return pht(
|
||||
'%s created this paste.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
case self::TYPE_CONTENT:
|
||||
if ($old === null) {
|
||||
return pht(
|
||||
'%s created this paste.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
} else {
|
||||
return pht(
|
||||
'%s edited the content of this paste.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
}
|
||||
break;
|
||||
case self::TYPE_TITLE:
|
||||
return pht(
|
||||
'%s updated the paste\'s title to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$new);
|
||||
break;
|
||||
case self::TYPE_LANGUAGE:
|
||||
return pht(
|
||||
"%s updated the paste's language.",
|
||||
$this->renderHandleLink($author_phid));
|
||||
break;
|
||||
case self::TYPE_STATUS:
|
||||
switch ($new) {
|
||||
case PhabricatorPaste::STATUS_ACTIVE:
|
||||
return pht(
|
||||
'%s activated this paste.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
case PhabricatorPaste::STATUS_ARCHIVED:
|
||||
return pht(
|
||||
'%s archived this paste.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
$author_phid = $this->getAuthorPHID();
|
||||
$object_phid = $this->getObjectPHID();
|
||||
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
$type = $this->getTransactionType();
|
||||
switch ($type) {
|
||||
case self::TYPE_CONTENT:
|
||||
if ($old === null) {
|
||||
return pht(
|
||||
'%s created %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
} else {
|
||||
return pht(
|
||||
'%s edited %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
}
|
||||
break;
|
||||
case self::TYPE_TITLE:
|
||||
return pht(
|
||||
'%s updated the title for %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
break;
|
||||
case self::TYPE_LANGUAGE:
|
||||
return pht(
|
||||
'%s updated the language for %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
break;
|
||||
case self::TYPE_STATUS:
|
||||
switch ($new) {
|
||||
case PhabricatorPaste::STATUS_ACTIVE:
|
||||
return pht(
|
||||
'%s activated %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
case PhabricatorPaste::STATUS_ARCHIVED:
|
||||
return pht(
|
||||
'%s archived %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getTitleForFeed();
|
||||
}
|
||||
|
||||
public function getColor() {
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_CONTENT:
|
||||
return PhabricatorTransactions::COLOR_GREEN;
|
||||
case self::TYPE_STATUS:
|
||||
switch ($new) {
|
||||
case PhabricatorPaste::STATUS_ACTIVE:
|
||||
return 'green';
|
||||
case PhabricatorPaste::STATUS_ARCHIVED:
|
||||
return 'indigo';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getColor();
|
||||
}
|
||||
|
||||
|
||||
public function hasChangeDetails() {
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_CONTENT:
|
||||
return ($this->getOldValue() !== null);
|
||||
}
|
||||
|
||||
return parent::hasChangeDetails();
|
||||
}
|
||||
|
||||
public function renderChangeDetails(PhabricatorUser $viewer) {
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_CONTENT:
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
$files = id(new PhabricatorFileQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array_filter(array($old, $new)))
|
||||
->execute();
|
||||
$files = mpull($files, null, 'getPHID');
|
||||
|
||||
$old_text = '';
|
||||
if (idx($files, $old)) {
|
||||
$old_text = $files[$old]->loadFileData();
|
||||
}
|
||||
|
||||
$new_text = '';
|
||||
if (idx($files, $new)) {
|
||||
$new_text = $files[$new]->loadFileData();
|
||||
}
|
||||
|
||||
return $this->renderTextCorpusChangeDetails(
|
||||
$viewer,
|
||||
$old_text,
|
||||
$new_text);
|
||||
}
|
||||
|
||||
return parent::renderChangeDetails($viewer);
|
||||
public function getBaseTransactionClass() {
|
||||
return 'PhabricatorPasteTransactionType';
|
||||
}
|
||||
|
||||
public function getMailTags() {
|
||||
$tags = array();
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_TITLE:
|
||||
case self::TYPE_CONTENT:
|
||||
case self::TYPE_LANGUAGE:
|
||||
case PhabricatorPasteTitleTransaction::TRANSACTIONTYPE:
|
||||
case PhabricatorPasteContentTransaction::TRANSACTIONTYPE:
|
||||
case PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE:
|
||||
$tags[] = self::MAILTAG_CONTENT;
|
||||
break;
|
||||
case PhabricatorTransactions::TYPE_COMMENT:
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorPasteContentTransaction
|
||||
extends PhabricatorPasteTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'paste.create';
|
||||
|
||||
private $fileName;
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getFilePHID();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setFilePHID($value);
|
||||
}
|
||||
|
||||
public function extractFilePHIDs($object, $value) {
|
||||
return array($value);
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
if ($object->getFilePHID() || $xactions) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$error = $this->newError(
|
||||
pht('Required'),
|
||||
pht('You must provide content to create a paste.'));
|
||||
$error->setIsMissingFieldError(true);
|
||||
|
||||
return array($error);
|
||||
}
|
||||
|
||||
public function willApplyTransactions($object, array $xactions) {
|
||||
// Find the most user-friendly filename we can by examining the title of
|
||||
// the paste and the pending transactions. We'll use this if we create a
|
||||
// new file to store raw content later.
|
||||
|
||||
$name = $object->getTitle();
|
||||
if (!strlen($name)) {
|
||||
$name = 'paste.raw';
|
||||
}
|
||||
|
||||
$type_title = PhabricatorPasteTitleTransaction::TRANSACTIONTYPE;
|
||||
foreach ($xactions as $xaction) {
|
||||
if ($xaction->getTransactionType() == $type_title) {
|
||||
$name = $xaction->getNewValue();
|
||||
}
|
||||
}
|
||||
|
||||
$this->fileName = $name;
|
||||
}
|
||||
|
||||
public function generateNewValue($object, $value) {
|
||||
// If this transaction does not really change the paste content, return
|
||||
// the current file PHID so this transaction no-ops.
|
||||
$old_content = $object->getRawContent();
|
||||
if ($value === $old_content) {
|
||||
$file_phid = $object->getFilePHID();
|
||||
if ($file_phid) {
|
||||
return $file_phid;
|
||||
}
|
||||
}
|
||||
|
||||
$editor = $this->getEditor();
|
||||
$actor = $editor->getActor();
|
||||
|
||||
$file = $this->newFileForPaste($actor, $this->fileName, $value);
|
||||
|
||||
return $file->getPHID();
|
||||
}
|
||||
|
||||
private function newFileForPaste(PhabricatorUser $actor, $name, $data) {
|
||||
return PhabricatorFile::newFromFileData(
|
||||
$data,
|
||||
array(
|
||||
'name' => $name,
|
||||
'mime-type' => 'text/plain; charset=utf-8',
|
||||
'authorPHID' => $actor->getPHID(),
|
||||
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
|
||||
'editPolicy' => PhabricatorPolicies::POLICY_NOONE,
|
||||
));
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'fa-plus';
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s edited the content of this paste.',
|
||||
$this->renderAuthor());
|
||||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
return pht(
|
||||
'%s edited %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject());
|
||||
}
|
||||
|
||||
public function hasChangeDetailView() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function newChangeDetailView() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$old = $this->getOldValue();
|
||||
$new = $this->getNewValue();
|
||||
|
||||
$files = id(new PhabricatorFileQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($old, $new))
|
||||
->execute();
|
||||
$files = mpull($files, null, 'getPHID');
|
||||
|
||||
$old_text = '';
|
||||
if (idx($files, $old)) {
|
||||
$old_text = $files[$old]->loadFileData();
|
||||
}
|
||||
|
||||
$new_text = '';
|
||||
if (idx($files, $new)) {
|
||||
$new_text = $files[$new]->loadFileData();
|
||||
}
|
||||
|
||||
return id(new PhabricatorApplicationTransactionTextDiffDetailView())
|
||||
->setViewer($viewer)
|
||||
->setOldText($old_text)
|
||||
->setNewText($new_text);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorPasteLanguageTransaction
|
||||
extends PhabricatorPasteTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'paste.language';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getLanguage();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setLanguage($value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
"%s updated the paste's language.",
|
||||
$this->renderAuthor());
|
||||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
return pht(
|
||||
'%s updated the language for %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorPasteStatusTransaction
|
||||
extends PhabricatorPasteTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'paste.status';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getStatus();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setStatus($value);
|
||||
}
|
||||
|
||||
private function isActivate() {
|
||||
return ($this->getNewValue() == PhabricatorPaste::STATUS_ARCHIVED);
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
if ($this->isActivate()) {
|
||||
return 'fa-check';
|
||||
} else {
|
||||
return 'fa-ban';
|
||||
}
|
||||
}
|
||||
|
||||
public function getColor() {
|
||||
if ($this->isActivate()) {
|
||||
return 'green';
|
||||
} else {
|
||||
return 'indigo';
|
||||
}
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
if ($this->isActivate()) {
|
||||
return pht(
|
||||
'%s activated this paste.',
|
||||
$this->renderAuthor());
|
||||
} else {
|
||||
return pht(
|
||||
'%s archived this paste.',
|
||||
$this->renderAuthor());
|
||||
}
|
||||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
if ($this->isActivate()) {
|
||||
return pht(
|
||||
'%s activated %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject());
|
||||
} else {
|
||||
return pht(
|
||||
'%s archived %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorPasteTitleTransaction
|
||||
extends PhabricatorPasteTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'paste.title';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getTitle();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setTitle($value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s updated the paste\'s title from "%s" to "%s".',
|
||||
$this->renderAuthor(),
|
||||
$this->getOldValue(),
|
||||
$this->getNewValue());
|
||||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
return pht(
|
||||
'%s updated the title for %s from "%s" to "%s".',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject(),
|
||||
$this->getOldValue(),
|
||||
$this->getNewValue());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorPasteTransactionType
|
||||
extends PhabricatorModularTransactionType {}
|
|
@ -29,21 +29,28 @@ final class PhabricatorUserPreferencesCacheType
|
|||
|
||||
$preferences = id(new PhabricatorUserPreferencesQuery())
|
||||
->setViewer($viewer)
|
||||
->withUserPHIDs($user_phids)
|
||||
->withUsers($users)
|
||||
->needSyntheticPreferences(true)
|
||||
->execute();
|
||||
$preferences = mpull($preferences, null, 'getUserPHID');
|
||||
|
||||
$all_settings = PhabricatorSetting::getAllSettings();
|
||||
|
||||
$settings = array();
|
||||
foreach ($preferences as $preference) {
|
||||
$user_phid = $preference->getUserPHID();
|
||||
foreach ($users as $user_phid => $user) {
|
||||
$preference = idx($preferences, $user_phid);
|
||||
|
||||
if (!$preference) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($all_settings as $key => $setting) {
|
||||
$value = $preference->getSettingValue($key);
|
||||
|
||||
// As an optimization, we omit the value from the cache if it is
|
||||
// exactly the same as the hardcoded default.
|
||||
$default_value = id(clone $setting)
|
||||
->setViewer($users[$user_phid])
|
||||
->setViewer($user)
|
||||
->getSettingDefaultValue();
|
||||
if ($value === $default_value) {
|
||||
continue;
|
||||
|
|
|
@ -55,6 +55,7 @@ final class PhabricatorPhameApplication extends PhabricatorApplication {
|
|||
'preview/' => 'PhabricatorMarkupPreviewController',
|
||||
'framed/(?P<id>\d+)/' => 'PhamePostFramedController',
|
||||
'move/(?P<id>\d+)/' => 'PhamePostMoveController',
|
||||
'archive/(?P<id>\d+)/' => 'PhamePostArchiveController',
|
||||
'comment/(?P<id>[1-9]\d*)/' => 'PhamePostCommentController',
|
||||
),
|
||||
'blog/' => array(
|
||||
|
@ -66,6 +67,7 @@ final class PhabricatorPhameApplication extends PhabricatorApplication {
|
|||
'manage/(?P<id>[^/]+)/' => 'PhameBlogManageController',
|
||||
'feed/(?P<id>[^/]+)/' => 'PhameBlogFeedController',
|
||||
'picture/(?P<id>[1-9]\d*)/' => 'PhameBlogProfilePictureController',
|
||||
'header/(?P<id>[1-9]\d*)/' => 'PhameBlogHeaderPictureController',
|
||||
),
|
||||
) + $this->getResourceSubroutes(),
|
||||
);
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
|
||||
final class PhameConstants extends Phobject {
|
||||
|
||||
const VISIBILITY_DRAFT = 0;
|
||||
const VISIBILITY_DRAFT = 0;
|
||||
const VISIBILITY_PUBLISHED = 1;
|
||||
const VISIBILITY_ARCHIVED = 2;
|
||||
|
||||
public static function getPhamePostStatusMap() {
|
||||
return array(
|
||||
self::VISIBILITY_PUBLISHED => pht('Published'),
|
||||
self::VISIBILITY_DRAFT => pht('Draft'),
|
||||
self::VISIBILITY_ARCHIVED => pht('Archived'),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -16,6 +18,7 @@ final class PhameConstants extends Phobject {
|
|||
$map = array(
|
||||
self::VISIBILITY_PUBLISHED => pht('Published'),
|
||||
self::VISIBILITY_DRAFT => pht('Draft'),
|
||||
self::VISIBILITY_ARCHIVED => pht('Archived'),
|
||||
);
|
||||
return idx($map, $status, pht('Unknown'));
|
||||
}
|
||||
|
|
|
@ -35,7 +35,8 @@ final class PhameHomeController extends PhamePostController {
|
|||
$posts = id(new PhamePostQuery())
|
||||
->setViewer($viewer)
|
||||
->withBlogPHIDs($blog_phids)
|
||||
->withVisibility(PhameConstants::VISIBILITY_PUBLISHED)
|
||||
->withVisibility(array(PhameConstants::VISIBILITY_PUBLISHED))
|
||||
->setOrder('datePublished')
|
||||
->executeWithCursorPager($pager);
|
||||
|
||||
if ($posts) {
|
||||
|
@ -97,7 +98,7 @@ final class PhameHomeController extends PhamePostController {
|
|||
->setViewer($viewer)
|
||||
->withBloggerPHIDs(array($viewer->getPHID()))
|
||||
->withBlogPHIDs(mpull($blogs, 'getPHID'))
|
||||
->withVisibility(PhameConstants::VISIBILITY_DRAFT)
|
||||
->withVisibility(array(PhameConstants::VISIBILITY_DRAFT))
|
||||
->setLimit(5)
|
||||
->execute();
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ abstract class PhameLiveController extends PhameController {
|
|||
$blog_query = id(new PhameBlogQuery())
|
||||
->setViewer($viewer)
|
||||
->needProfileImage(true)
|
||||
->needHeaderImage(true)
|
||||
->withIDs(array($blog_id));
|
||||
|
||||
// If this is a live view, only show active blogs.
|
||||
|
@ -97,7 +98,8 @@ abstract class PhameLiveController extends PhameController {
|
|||
|
||||
// Only show published posts on external domains.
|
||||
if ($is_external) {
|
||||
$post_query->withVisibility(PhameConstants::VISIBILITY_PUBLISHED);
|
||||
$post_query->withVisibility(
|
||||
array(PhameConstants::VISIBILITY_PUBLISHED));
|
||||
}
|
||||
|
||||
$post = $post_query->executeOne();
|
||||
|
@ -210,6 +212,7 @@ abstract class PhameLiveController extends PhameController {
|
|||
|
||||
if ($this->getIsLive()) {
|
||||
$page
|
||||
->addClass('phame-live-view')
|
||||
->setShowChrome(false)
|
||||
->setShowFooter(false);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ final class PhameBlogFeedController extends PhameBlogController {
|
|||
$posts = id(new PhamePostQuery())
|
||||
->setViewer($viewer)
|
||||
->withBlogPHIDs(array($blog->getPHID()))
|
||||
->withVisibility(PhameConstants::VISIBILITY_PUBLISHED)
|
||||
->withVisibility(array(PhameConstants::VISIBILITY_PUBLISHED))
|
||||
->execute();
|
||||
|
||||
$blog_uri = PhabricatorEnv::getProductionURI(
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
final class PhameBlogHeaderPictureController
|
||||
extends PhameBlogController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$blog = id(new PhameBlogQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->needHeaderImage(true)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$blog) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$blog_uri = '/phame/blog/manage/'.$id;
|
||||
|
||||
$supported_formats = PhabricatorFile::getTransformableImageFormats();
|
||||
$e_file = true;
|
||||
$errors = array();
|
||||
$delete_header = ($request->getInt('delete') == 1);
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
if ($request->getFileExists('header')) {
|
||||
$file = PhabricatorFile::newFromPHPUpload(
|
||||
$_FILES['header'],
|
||||
array(
|
||||
'authorPHID' => $viewer->getPHID(),
|
||||
'canCDN' => true,
|
||||
));
|
||||
} else if (!$delete_header) {
|
||||
$e_file = pht('Required');
|
||||
$errors[] = pht(
|
||||
'You must choose a file when uploading a new blog header.');
|
||||
}
|
||||
|
||||
if (!$errors && !$delete_header) {
|
||||
if (!$file->isTransformableImage()) {
|
||||
$e_file = pht('Not Supported');
|
||||
$errors[] = pht(
|
||||
'This server only supports these image formats: %s.',
|
||||
implode(', ', $supported_formats));
|
||||
}
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
if ($delete_header) {
|
||||
$blog->setHeaderImagePHID(null);
|
||||
} else {
|
||||
$blog->setHeaderImagePHID($file->getPHID());
|
||||
$file->attachToObject($blog->getPHID());
|
||||
}
|
||||
$blog->save();
|
||||
return id(new AphrontRedirectResponse())->setURI($blog_uri);
|
||||
}
|
||||
}
|
||||
|
||||
$title = pht('Edit Blog Header');
|
||||
|
||||
$upload_form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->setEncType('multipart/form-data')
|
||||
->appendChild(
|
||||
id(new AphrontFormFileControl())
|
||||
->setName('header')
|
||||
->setLabel(pht('Upload Header'))
|
||||
->setError($e_file)
|
||||
->setCaption(
|
||||
pht('Supported formats: %s', implode(', ', $supported_formats))))
|
||||
->appendChild(
|
||||
id(new AphrontFormCheckboxControl())
|
||||
->setName('delete')
|
||||
->setLabel(pht('Delete Header'))
|
||||
->addCheckbox(
|
||||
'delete',
|
||||
1,
|
||||
null,
|
||||
null))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->addCancelButton($blog_uri)
|
||||
->setValue(pht('Upload Header')));
|
||||
|
||||
$upload_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Upload New Header'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setForm($upload_form);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(
|
||||
pht('Blogs'),
|
||||
$this->getApplicationURI('blog/'));
|
||||
$crumbs->addTextCrumb(
|
||||
$blog->getName(),
|
||||
$this->getApplicationURI('blog/view/'.$id));
|
||||
$crumbs->addTextCrumb(pht('Blog Header'));
|
||||
$crumbs->setBorder(true);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Edit Blog Header'))
|
||||
->setHeaderIcon('fa-camera');
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setFooter(array(
|
||||
$upload_box,
|
||||
));
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild(
|
||||
array(
|
||||
$view,
|
||||
));
|
||||
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ final class PhameBlogManageController extends PhameBlogController {
|
|||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->needProfileImage(true)
|
||||
->needHeaderImage(true)
|
||||
->executeOne();
|
||||
if (!$blog) {
|
||||
return new Aphront404Response();
|
||||
|
@ -31,22 +32,33 @@ final class PhameBlogManageController extends PhameBlogController {
|
|||
|
||||
$picture = $blog->getProfileImageURI();
|
||||
|
||||
$view = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setText(pht('View Live'))
|
||||
->setIcon('fa-external-link')
|
||||
->setHref($blog->getLiveURI());
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($blog->getName())
|
||||
->setUser($viewer)
|
||||
->setPolicyObject($blog)
|
||||
->setImage($picture)
|
||||
->setStatus($header_icon, $header_color, $header_name);
|
||||
->setStatus($header_icon, $header_color, $header_name)
|
||||
->addActionLink($view);
|
||||
|
||||
$actions = $this->renderActions($blog, $viewer);
|
||||
$properties = $this->renderProperties($blog, $viewer, $actions);
|
||||
$curtain = $this->buildCurtain($blog);
|
||||
$properties = $this->buildPropertyView($blog);
|
||||
$file = $this->buildFileView($blog);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(
|
||||
pht('Blogs'),
|
||||
$this->getApplicationURI('blog/'));
|
||||
$crumbs->addTextCrumb(
|
||||
$blog->getName());
|
||||
$blog->getName(),
|
||||
$this->getApplicationURI('blog/view/'.$id));
|
||||
$crumbs->addTextCrumb(pht('Manage Blog'));
|
||||
$crumbs->setBorder(true);
|
||||
|
||||
$object_box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
|
@ -57,28 +69,34 @@ final class PhameBlogManageController extends PhameBlogController {
|
|||
new PhameBlogTransactionQuery());
|
||||
$timeline->setShouldTerminate(true);
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setCurtain($curtain)
|
||||
->addPropertySection(pht('Details'), $properties)
|
||||
->addPropertySection(pht('Header'), $file)
|
||||
->setMainColumn(
|
||||
array(
|
||||
$timeline,
|
||||
));
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($blog->getName())
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild(
|
||||
array(
|
||||
$object_box,
|
||||
$timeline,
|
||||
$view,
|
||||
));
|
||||
}
|
||||
|
||||
private function renderProperties(
|
||||
PhameBlog $blog,
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorActionListView $actions) {
|
||||
private function buildPropertyView(PhameBlog $blog) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
require_celerity_resource('aphront-tooltip-css');
|
||||
Javelin::initBehavior('phabricator-tooltips');
|
||||
|
||||
$properties = id(new PHUIPropertyListView())
|
||||
->setUser($viewer)
|
||||
->setObject($blog)
|
||||
->setActionList($actions);
|
||||
->setObject($blog);
|
||||
|
||||
$domain = $blog->getDomain();
|
||||
if (!$domain) {
|
||||
|
@ -129,7 +147,11 @@ final class PhameBlogManageController extends PhameBlogController {
|
|||
return $properties;
|
||||
}
|
||||
|
||||
private function renderActions(PhameBlog $blog, PhabricatorUser $viewer) {
|
||||
private function buildCurtain(PhameBlog $blog) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$curtain = $this->newCurtainView($viewer);
|
||||
|
||||
$actions = id(new PhabricatorActionListView())
|
||||
->setObject($blog)
|
||||
->setUser($viewer);
|
||||
|
@ -139,7 +161,7 @@ final class PhameBlogManageController extends PhameBlogController {
|
|||
$blog,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$actions->addAction(
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-pencil')
|
||||
->setHref($this->getApplicationURI('blog/edit/'.$blog->getID().'/'))
|
||||
|
@ -147,7 +169,15 @@ final class PhameBlogManageController extends PhameBlogController {
|
|||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(!$can_edit));
|
||||
|
||||
$actions->addAction(
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-camera')
|
||||
->setHref($this->getApplicationURI('blog/header/'.$blog->getID().'/'))
|
||||
->setName(pht('Edit Blog Header'))
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(!$can_edit));
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-picture-o')
|
||||
->setHref($this->getApplicationURI('blog/picture/'.$blog->getID().'/'))
|
||||
|
@ -156,7 +186,7 @@ final class PhameBlogManageController extends PhameBlogController {
|
|||
->setWorkflow(!$can_edit));
|
||||
|
||||
if ($blog->isArchived()) {
|
||||
$actions->addAction(
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Activate Blog'))
|
||||
->setIcon('fa-check')
|
||||
|
@ -165,7 +195,7 @@ final class PhameBlogManageController extends PhameBlogController {
|
|||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true));
|
||||
} else {
|
||||
$actions->addAction(
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Archive Blog'))
|
||||
->setIcon('fa-ban')
|
||||
|
@ -175,7 +205,27 @@ final class PhameBlogManageController extends PhameBlogController {
|
|||
->setWorkflow(true));
|
||||
}
|
||||
|
||||
return $actions;
|
||||
return $curtain;
|
||||
}
|
||||
|
||||
private function buildFileView(
|
||||
PhameBlog $blog) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$view = id(new PHUIPropertyListView())
|
||||
->setUser($viewer);
|
||||
|
||||
if ($blog->getHeaderImagePHID()) {
|
||||
$view->addImageContent(
|
||||
phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $blog->getHeaderImageURI(),
|
||||
'class' => 'phabricator-image-macro-hero',
|
||||
)));
|
||||
return $view;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
final class PhameBlogProfilePictureController
|
||||
extends PhameBlogController {
|
||||
|
||||
public function shouldRequireAdmin() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
@ -175,6 +171,7 @@ final class PhameBlogProfilePictureController
|
|||
$form_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText($title)
|
||||
->setFormErrors($errors)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setForm($form);
|
||||
|
||||
$upload_form = id(new AphrontFormView())
|
||||
|
@ -194,6 +191,7 @@ final class PhameBlogProfilePictureController
|
|||
|
||||
$upload_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Upload New Picture'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setForm($upload_form);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
|
@ -204,14 +202,25 @@ final class PhameBlogProfilePictureController
|
|||
$blog->getName(),
|
||||
$this->getApplicationURI('blog/view/'.$id));
|
||||
$crumbs->addTextCrumb(pht('Blog Picture'));
|
||||
$crumbs->setBorder(true);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Edit Blog Picture'))
|
||||
->setHeaderIcon('fa-camera');
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setFooter(array(
|
||||
$form_box,
|
||||
$upload_box,
|
||||
));
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild(
|
||||
array(
|
||||
$form_box,
|
||||
$upload_box,
|
||||
$view,
|
||||
));
|
||||
|
||||
}
|
||||
|
|
|
@ -19,16 +19,23 @@ final class PhameBlogViewController extends PhameLiveController {
|
|||
|
||||
$post_query = id(new PhamePostQuery())
|
||||
->setViewer($viewer)
|
||||
->withBlogPHIDs(array($blog->getPHID()));
|
||||
->withBlogPHIDs(array($blog->getPHID()))
|
||||
->setOrder('datePublished')
|
||||
->withVisibility(array(
|
||||
PhameConstants::VISIBILITY_PUBLISHED,
|
||||
PhameConstants::VISIBILITY_DRAFT,
|
||||
));
|
||||
|
||||
if ($is_live) {
|
||||
$post_query->withVisibility(PhameConstants::VISIBILITY_PUBLISHED);
|
||||
$post_query->withVisibility(array(PhameConstants::VISIBILITY_PUBLISHED));
|
||||
}
|
||||
|
||||
$posts = $post_query->executeWithCursorPager($pager);
|
||||
|
||||
$hero = $this->buildPhameHeader($blog);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($blog->getName())
|
||||
->addClass('phame-header-bar')
|
||||
->setUser($viewer);
|
||||
|
||||
if (!$is_external) {
|
||||
|
@ -104,6 +111,7 @@ final class PhameBlogViewController extends PhameLiveController {
|
|||
->setCrumbs($crumbs)
|
||||
->appendChild(
|
||||
array(
|
||||
$hero,
|
||||
$page,
|
||||
$about,
|
||||
));
|
||||
|
@ -147,4 +155,33 @@ final class PhameBlogViewController extends PhameLiveController {
|
|||
return $actions;
|
||||
}
|
||||
|
||||
private function buildPhameHeader(
|
||||
PhameBlog $blog) {
|
||||
|
||||
$image = null;
|
||||
if ($blog->getHeaderImagePHID()) {
|
||||
$image = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phame-header-hero',
|
||||
),
|
||||
phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $blog->getHeaderImageURI(),
|
||||
'class' => 'phame-header-image',
|
||||
)));
|
||||
}
|
||||
|
||||
$title = phutil_tag_div('phame-header-title', $blog->getName());
|
||||
$subtitle = null;
|
||||
if ($blog->getSubtitle()) {
|
||||
$subtitle = phutil_tag_div('phame-header-subtitle', $blog->getSubtitle());
|
||||
}
|
||||
|
||||
return phutil_tag_div(
|
||||
'phame-mega-header', array($image, $title, $subtitle));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
final class PhamePostArchiveController extends PhamePostController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$id = $request->getURIData('id');
|
||||
$post = id(new PhamePostQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$post) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$cancel_uri = $post->getViewURI();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$xactions = array();
|
||||
|
||||
$new_value = PhameConstants::VISIBILITY_ARCHIVED;
|
||||
$xactions[] = id(new PhamePostTransaction())
|
||||
->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY)
|
||||
->setNewValue($new_value);
|
||||
|
||||
id(new PhamePostEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($post, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($cancel_uri);
|
||||
}
|
||||
|
||||
$title = pht('Archive Post');
|
||||
$body = pht(
|
||||
'This post will revert to archived status and no longer be visible '.
|
||||
'to other users or members of this blog.');
|
||||
$button = pht('Archive Post');
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle($title)
|
||||
->appendParagraph($body)
|
||||
->addSubmitButton($button)
|
||||
->addCancelButton($cancel_uri);
|
||||
}
|
||||
|
||||
}
|
|
@ -48,6 +48,16 @@ final class PhamePostViewController
|
|||
'Use "Publish" to publish this post.')));
|
||||
}
|
||||
|
||||
if ($post->isArchived()) {
|
||||
$document->appendChild(
|
||||
id(new PHUIInfoView())
|
||||
->setSeverity(PHUIInfoView::SEVERITY_ERROR)
|
||||
->setTitle(pht('Archived Post'))
|
||||
->appendChild(
|
||||
pht('Only you can see this archived post until you publish it. '.
|
||||
'Use "Publish" to publish this post.')));
|
||||
}
|
||||
|
||||
if (!$post->getBlog()) {
|
||||
$document->appendChild(
|
||||
id(new PHUIInfoView())
|
||||
|
@ -92,6 +102,8 @@ final class PhamePostViewController
|
|||
$date = phabricator_datetime($post->getDatePublished(), $viewer);
|
||||
if ($post->isDraft()) {
|
||||
$subtitle = pht('Unpublished draft by %s.', $author);
|
||||
} else if ($post->isArchived()) {
|
||||
$subtitle = pht('Archived post by %s.', $author);
|
||||
} else {
|
||||
$subtitle = pht('Written by %s on %s.', $author, $date);
|
||||
}
|
||||
|
@ -207,6 +219,21 @@ final class PhamePostViewController
|
|||
->setName(pht('Publish'))
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true));
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-ban')
|
||||
->setHref($this->getApplicationURI('post/archive/'.$id.'/'))
|
||||
->setName(pht('Archive'))
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true));
|
||||
} else if ($post->isArchived()) {
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-eye')
|
||||
->setHref($this->getApplicationURI('post/publish/'.$id.'/'))
|
||||
->setName(pht('Publish'))
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true));
|
||||
} else {
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
|
@ -215,6 +242,13 @@ final class PhamePostViewController
|
|||
->setName(pht('Unpublish'))
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true));
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-ban')
|
||||
->setHref($this->getApplicationURI('post/archive/'.$id.'/'))
|
||||
->setName(pht('Archive'))
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true));
|
||||
}
|
||||
|
||||
if ($post->isDraft()) {
|
||||
|
@ -223,12 +257,14 @@ final class PhamePostViewController
|
|||
$live_name = pht('View Live');
|
||||
}
|
||||
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setUser($viewer)
|
||||
->setIcon('fa-globe')
|
||||
->setHref($post->getLiveURI())
|
||||
->setName($live_name));
|
||||
if (!$post->isArchived()) {
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setUser($viewer)
|
||||
->setIcon('fa-globe')
|
||||
->setHref($post->getLiveURI())
|
||||
->setName($live_name));
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
@ -255,7 +291,7 @@ final class PhamePostViewController
|
|||
|
||||
$query = id(new PhamePostQuery())
|
||||
->setViewer($viewer)
|
||||
->withVisibility(PhameConstants::VISIBILITY_PUBLISHED)
|
||||
->withVisibility(array(PhameConstants::VISIBILITY_PUBLISHED))
|
||||
->withBlogPHIDs(array($post->getBlog()->getPHID()))
|
||||
->setLimit(1);
|
||||
|
||||
|
|
|
@ -77,6 +77,14 @@ final class PhameBlogEditEngine
|
|||
->setConduitTypeDescription(pht('New blog title.'))
|
||||
->setTransactionType(PhameBlogTransaction::TYPE_NAME)
|
||||
->setValue($object->getName()),
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('subtitle')
|
||||
->setLabel(pht('Subtitle'))
|
||||
->setDescription(pht('Blog subtitle.'))
|
||||
->setConduitDescription(pht('Change the blog subtitle.'))
|
||||
->setConduitTypeDescription(pht('New blog subtitle.'))
|
||||
->setTransactionType(PhameBlogTransaction::TYPE_SUBTITLE)
|
||||
->setValue($object->getSubtitle()),
|
||||
id(new PhabricatorRemarkupEditField())
|
||||
->setKey('description')
|
||||
->setLabel(pht('Description'))
|
||||
|
|
|
@ -15,6 +15,7 @@ final class PhameBlogEditor
|
|||
$types = parent::getTransactionTypes();
|
||||
|
||||
$types[] = PhameBlogTransaction::TYPE_NAME;
|
||||
$types[] = PhameBlogTransaction::TYPE_SUBTITLE;
|
||||
$types[] = PhameBlogTransaction::TYPE_DESCRIPTION;
|
||||
$types[] = PhameBlogTransaction::TYPE_DOMAIN;
|
||||
$types[] = PhameBlogTransaction::TYPE_STATUS;
|
||||
|
@ -31,6 +32,8 @@ final class PhameBlogEditor
|
|||
switch ($xaction->getTransactionType()) {
|
||||
case PhameBlogTransaction::TYPE_NAME:
|
||||
return $object->getName();
|
||||
case PhameBlogTransaction::TYPE_SUBTITLE:
|
||||
return $object->getSubtitle();
|
||||
case PhameBlogTransaction::TYPE_DESCRIPTION:
|
||||
return $object->getDescription();
|
||||
case PhameBlogTransaction::TYPE_DOMAIN:
|
||||
|
@ -46,6 +49,7 @@ final class PhameBlogEditor
|
|||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhameBlogTransaction::TYPE_NAME:
|
||||
case PhameBlogTransaction::TYPE_SUBTITLE:
|
||||
case PhameBlogTransaction::TYPE_DESCRIPTION:
|
||||
case PhameBlogTransaction::TYPE_STATUS:
|
||||
return $xaction->getNewValue();
|
||||
|
@ -65,6 +69,8 @@ final class PhameBlogEditor
|
|||
switch ($xaction->getTransactionType()) {
|
||||
case PhameBlogTransaction::TYPE_NAME:
|
||||
return $object->setName($xaction->getNewValue());
|
||||
case PhameBlogTransaction::TYPE_SUBTITLE:
|
||||
return $object->setSubtitle($xaction->getNewValue());
|
||||
case PhameBlogTransaction::TYPE_DESCRIPTION:
|
||||
return $object->setDescription($xaction->getNewValue());
|
||||
case PhameBlogTransaction::TYPE_DOMAIN:
|
||||
|
@ -82,6 +88,7 @@ final class PhameBlogEditor
|
|||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhameBlogTransaction::TYPE_NAME:
|
||||
case PhameBlogTransaction::TYPE_SUBTITLE:
|
||||
case PhameBlogTransaction::TYPE_DESCRIPTION:
|
||||
case PhameBlogTransaction::TYPE_DOMAIN:
|
||||
case PhameBlogTransaction::TYPE_STATUS:
|
||||
|
@ -227,7 +234,7 @@ final class PhameBlogEditor
|
|||
|
||||
|
||||
protected function supportsSearch() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function shouldApplyHeraldRules(
|
||||
|
|
|
@ -66,6 +66,9 @@ final class PhamePostEditor
|
|||
case PhamePostTransaction::TYPE_VISIBILITY:
|
||||
if ($xaction->getNewValue() == PhameConstants::VISIBILITY_DRAFT) {
|
||||
$object->setDatePublished(0);
|
||||
} else if ($xaction->getNewValue() ==
|
||||
PhameConstants::VISIBILITY_ARCHIVED) {
|
||||
$object->setDatePublished(0);
|
||||
} else {
|
||||
$object->setDatePublished(PhabricatorTime::getNow());
|
||||
}
|
||||
|
@ -168,7 +171,7 @@ final class PhamePostEditor
|
|||
protected function shouldSendMail(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
if ($object->isDraft()) {
|
||||
if ($object->isDraft() || ($object->isArchived())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -177,7 +180,7 @@ final class PhamePostEditor
|
|||
protected function shouldPublishFeedStory(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
if ($object->isDraft()) {
|
||||
if ($object->isDraft() || $object->isArchived()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -228,7 +231,7 @@ final class PhamePostEditor
|
|||
foreach ($xactions as $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhamePostTransaction::TYPE_VISIBILITY:
|
||||
if (!$object->isDraft()) {
|
||||
if (!$object->isDraft() && !$object->isArchived()) {
|
||||
$body->addRemarkupSection(null, $object->getBody());
|
||||
}
|
||||
break;
|
||||
|
@ -261,7 +264,7 @@ final class PhamePostEditor
|
|||
}
|
||||
|
||||
protected function supportsSearch() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function shouldApplyHeraldRules(
|
||||
|
|
|
@ -34,6 +34,11 @@ final class PhabricatorPhameBlogPHIDType extends PhabricatorPHIDType {
|
|||
$handle->setName($blog->getName());
|
||||
$handle->setFullName($blog->getName());
|
||||
$handle->setURI('/phame/blog/view/'.$blog->getID().'/');
|
||||
|
||||
if ($blog->isArchived()) {
|
||||
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,13 @@ final class PhabricatorPhamePostPHIDType extends PhabricatorPHIDType {
|
|||
$handle->setName($post->getTitle());
|
||||
$handle->setFullName($post->getTitle());
|
||||
$handle->setURI('/phame/post/view/'.$post->getID().'/');
|
||||
|
||||
if ($post->isArchived()) {
|
||||
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
|
||||
private $needBloggers;
|
||||
private $needProfileImage;
|
||||
private $needHeaderImage;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
|
@ -35,6 +36,11 @@ final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function needHeaderImage($need) {
|
||||
$this->needHeaderImage = $need;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new PhameBlog();
|
||||
}
|
||||
|
@ -107,6 +113,28 @@ final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
$blog->attachProfileImageFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->needHeaderImage) {
|
||||
$file_phids = mpull($blogs, 'getHeaderImagePHID');
|
||||
$file_phids = array_filter($file_phids);
|
||||
if ($file_phids) {
|
||||
$files = id(new PhabricatorFileQuery())
|
||||
->setParentQuery($this)
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs($file_phids)
|
||||
->execute();
|
||||
$files = mpull($files, null, 'getPHID');
|
||||
} else {
|
||||
$files = array();
|
||||
}
|
||||
|
||||
foreach ($blogs as $blog) {
|
||||
$file = idx($files, $blog->getHeaderImagePHID());
|
||||
if ($file) {
|
||||
$blog->attachHeaderImageFile($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $blogs;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withVisibility($visibility) {
|
||||
public function withVisibility(array $visibility) {
|
||||
$this->visibility = $visibility;
|
||||
return $this;
|
||||
}
|
||||
|
@ -98,10 +98,10 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
$this->bloggerPHIDs);
|
||||
}
|
||||
|
||||
if ($this->visibility !== null) {
|
||||
if ($this->visibility) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'visibility = %d',
|
||||
'visibility IN (%Ld)',
|
||||
$this->visibility);
|
||||
}
|
||||
|
||||
|
@ -122,6 +122,36 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
return $where;
|
||||
}
|
||||
|
||||
public function getBuiltinOrders() {
|
||||
return array(
|
||||
'datePublished' => array(
|
||||
'vector' => array('datePublished', 'id'),
|
||||
'name' => pht('Publish Date'),
|
||||
),
|
||||
) + parent::getBuiltinOrders();
|
||||
}
|
||||
|
||||
public function getOrderableColumns() {
|
||||
return parent::getOrderableColumns() + array(
|
||||
'datePublished' => array(
|
||||
'column' => 'datePublished',
|
||||
'type' => 'int',
|
||||
'reverse' => false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getPagingValueMap($cursor, array $keys) {
|
||||
$post = $this->loadCursorObject($cursor);
|
||||
|
||||
$map = array(
|
||||
'datePublished' => $post->getDatePublished(),
|
||||
'id' => $post->getID(),
|
||||
);
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
// TODO: Does setting this break public blogs?
|
||||
return null;
|
||||
|
|
|
@ -19,7 +19,7 @@ final class PhamePostSearchEngine
|
|||
$query = $this->newQuery();
|
||||
|
||||
if (strlen($map['visibility'])) {
|
||||
$query->withVisibility($map['visibility']);
|
||||
$query->withVisibility(array($map['visibility']));
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
@ -35,6 +35,7 @@ final class PhamePostSearchEngine
|
|||
'' => pht('All'),
|
||||
PhameConstants::VISIBILITY_PUBLISHED => pht('Published'),
|
||||
PhameConstants::VISIBILITY_DRAFT => pht('Draft'),
|
||||
PhameConstants::VISIBILITY_ARCHIVED => pht('Archived'),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
@ -48,6 +49,7 @@ final class PhamePostSearchEngine
|
|||
'all' => pht('All Posts'),
|
||||
'live' => pht('Published Posts'),
|
||||
'draft' => pht('Draft Posts'),
|
||||
'archived' => pht('Archived Posts'),
|
||||
);
|
||||
return $names;
|
||||
}
|
||||
|
@ -65,6 +67,9 @@ final class PhamePostSearchEngine
|
|||
case 'draft':
|
||||
return $query->setParameter(
|
||||
'visibility', PhameConstants::VISIBILITY_DRAFT);
|
||||
case 'archived':
|
||||
return $query->setParameter(
|
||||
'visibility', PhameConstants::VISIBILITY_ARCHIVED);
|
||||
}
|
||||
|
||||
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||
|
@ -99,11 +104,19 @@ final class PhamePostSearchEngine
|
|||
if ($post->isDraft()) {
|
||||
$item->setStatusIcon('fa-star-o grey');
|
||||
$item->setDisabled(true);
|
||||
$item->addIcon('none', pht('Draft Post'));
|
||||
$item->addIcon('fa-star-o', pht('Draft Post'));
|
||||
} else if ($post->isArchived()) {
|
||||
$item->setStatusIcon('fa-ban grey');
|
||||
$item->setDisabled(true);
|
||||
$item->addIcon('fa-ban', pht('Archived Post'));
|
||||
} else {
|
||||
$date = $post->getDatePublished();
|
||||
$item->setEpoch($date);
|
||||
}
|
||||
$item->addAction(
|
||||
id(new PHUIListItemView())
|
||||
->setIcon('fa-pencil')
|
||||
->setHref($post->getEditURI()));
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
|
|
28
src/applications/phame/search/PhameBlogFulltextEngine.php
Normal file
28
src/applications/phame/search/PhameBlogFulltextEngine.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
final class PhameBlogFulltextEngine
|
||||
extends PhabricatorFulltextEngine {
|
||||
|
||||
protected function buildAbstractDocument(
|
||||
PhabricatorSearchAbstractDocument $document,
|
||||
$object) {
|
||||
|
||||
$blog = $object;
|
||||
|
||||
$document->setDocumentTitle($blog->getName());
|
||||
|
||||
$document->addField(
|
||||
PhabricatorSearchDocumentFieldType::FIELD_BODY,
|
||||
$blog->getDescription());
|
||||
|
||||
$document->addRelationship(
|
||||
$blog->isArchived()
|
||||
? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED
|
||||
: PhabricatorSearchRelationship::RELATIONSHIP_OPEN,
|
||||
$blog->getPHID(),
|
||||
PhabricatorPhameBlogPHIDType::TYPECONST,
|
||||
PhabricatorTime::getNow());
|
||||
|
||||
}
|
||||
|
||||
}
|
34
src/applications/phame/search/PhamePostFulltextEngine.php
Normal file
34
src/applications/phame/search/PhamePostFulltextEngine.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
final class PhamePostFulltextEngine
|
||||
extends PhabricatorFulltextEngine {
|
||||
|
||||
protected function buildAbstractDocument(
|
||||
PhabricatorSearchAbstractDocument $document,
|
||||
$object) {
|
||||
|
||||
$post = $object;
|
||||
|
||||
$document->setDocumentTitle($post->getTitle());
|
||||
|
||||
$document->addField(
|
||||
PhabricatorSearchDocumentFieldType::FIELD_BODY,
|
||||
$post->getBody());
|
||||
|
||||
$document->addRelationship(
|
||||
PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR,
|
||||
$post->getBloggerPHID(),
|
||||
PhabricatorPeopleUserPHIDType::TYPECONST,
|
||||
$post->getDateCreated());
|
||||
|
||||
$document->addRelationship(
|
||||
$post->isArchived()
|
||||
? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED
|
||||
: PhabricatorSearchRelationship::RELATIONSHIP_OPEN,
|
||||
$post->getPHID(),
|
||||
PhabricatorPhamePostPHIDType::TYPECONST,
|
||||
PhabricatorTime::getNow());
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -9,11 +9,13 @@ final class PhameBlog extends PhameDAO
|
|||
PhabricatorProjectInterface,
|
||||
PhabricatorDestructibleInterface,
|
||||
PhabricatorApplicationTransactionInterface,
|
||||
PhabricatorConduitResultInterface {
|
||||
PhabricatorConduitResultInterface,
|
||||
PhabricatorFulltextInterface {
|
||||
|
||||
const MARKUP_FIELD_DESCRIPTION = 'markup:description';
|
||||
|
||||
protected $name;
|
||||
protected $subtitle;
|
||||
protected $description;
|
||||
protected $domain;
|
||||
protected $configData;
|
||||
|
@ -23,8 +25,10 @@ final class PhameBlog extends PhameDAO
|
|||
protected $status;
|
||||
protected $mailKey;
|
||||
protected $profileImagePHID;
|
||||
protected $headerImagePHID;
|
||||
|
||||
private $profileImageFile = self::ATTACHABLE;
|
||||
private $headerImageFile = self::ATTACHABLE;
|
||||
|
||||
const STATUS_ACTIVE = 'active';
|
||||
const STATUS_ARCHIVED = 'archived';
|
||||
|
@ -37,11 +41,13 @@ final class PhameBlog extends PhameDAO
|
|||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'name' => 'text64',
|
||||
'subtitle' => 'text64',
|
||||
'description' => 'text',
|
||||
'domain' => 'text128?',
|
||||
'status' => 'text32',
|
||||
'mailKey' => 'bytes20',
|
||||
'profileImagePHID' => 'phid?',
|
||||
'headerImagePHID' => 'phid?',
|
||||
|
||||
// T6203/NULLABILITY
|
||||
// These policies should always be non-null.
|
||||
|
@ -211,6 +217,19 @@ final class PhameBlog extends PhameDAO
|
|||
return $this->assertAttached($this->profileImageFile);
|
||||
}
|
||||
|
||||
public function getHeaderImageURI() {
|
||||
return $this->getHeaderImageFile()->getBestURI();
|
||||
}
|
||||
|
||||
public function attachHeaderImageFile(PhabricatorFile $file) {
|
||||
$this->headerImageFile = $file;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHeaderImageFile() {
|
||||
return $this->assertAttached($this->headerImageFile);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
|
||||
|
||||
|
@ -370,4 +389,10 @@ final class PhameBlog extends PhameDAO
|
|||
}
|
||||
|
||||
|
||||
/* -( PhabricatorFulltextInterface )--------------------------------------- */
|
||||
|
||||
public function newFulltextEngine() {
|
||||
return new PhameBlogFulltextEngine();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ final class PhameBlogTransaction
|
|||
extends PhabricatorApplicationTransaction {
|
||||
|
||||
const TYPE_NAME = 'phame.blog.name';
|
||||
const TYPE_SUBTITLE = 'phame.blog.subtitle';
|
||||
const TYPE_DESCRIPTION = 'phame.blog.description';
|
||||
const TYPE_DOMAIN = 'phame.blog.domain';
|
||||
const TYPE_STATUS = 'phame.blog.status';
|
||||
|
@ -80,6 +81,7 @@ final class PhameBlogTransaction
|
|||
$tags[] = self::MAILTAG_SUBSCRIBERS;
|
||||
break;
|
||||
case self::TYPE_NAME:
|
||||
case self::TYPE_SUBTITLE:
|
||||
case self::TYPE_DESCRIPTION:
|
||||
case self::TYPE_DOMAIN:
|
||||
$tags[] = self::MAILTAG_DETAILS;
|
||||
|
@ -116,6 +118,19 @@ final class PhameBlogTransaction
|
|||
$new);
|
||||
}
|
||||
break;
|
||||
case self::TYPE_SUBTITLE:
|
||||
if ($old === null) {
|
||||
return pht(
|
||||
'%s set this blog\'s subtitle to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$new);
|
||||
} else {
|
||||
return pht(
|
||||
'%s updated the blog\'s subtitle to "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$new);
|
||||
}
|
||||
break;
|
||||
case self::TYPE_DESCRIPTION:
|
||||
return pht(
|
||||
'%s updated the blog\'s description.',
|
||||
|
@ -166,6 +181,19 @@ final class PhameBlogTransaction
|
|||
$this->renderHandleLink($object_phid));
|
||||
}
|
||||
break;
|
||||
case self::TYPE_SUBTITLE:
|
||||
if ($old === null) {
|
||||
return pht(
|
||||
'%s set the subtitle for %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
} else {
|
||||
return pht(
|
||||
'%s updated the subtitle for %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
}
|
||||
break;
|
||||
case self::TYPE_DESCRIPTION:
|
||||
return pht(
|
||||
'%s updated the description for %s.',
|
||||
|
|
|
@ -10,7 +10,8 @@ final class PhamePost extends PhameDAO
|
|||
PhabricatorSubscribableInterface,
|
||||
PhabricatorDestructibleInterface,
|
||||
PhabricatorTokenReceiverInterface,
|
||||
PhabricatorConduitResultInterface {
|
||||
PhabricatorConduitResultInterface,
|
||||
PhabricatorFulltextInterface {
|
||||
|
||||
const MARKUP_FIELD_BODY = 'markup:body';
|
||||
const MARKUP_FIELD_SUMMARY = 'markup:summary';
|
||||
|
@ -53,7 +54,8 @@ final class PhamePost extends PhameDAO
|
|||
public function getLiveURI() {
|
||||
$blog = $this->getBlog();
|
||||
$is_draft = $this->isDraft();
|
||||
if (strlen($blog->getDomain()) && !$is_draft) {
|
||||
$is_archived = $this->isArchived();
|
||||
if (strlen($blog->getDomain()) && !$is_draft && !$is_archived) {
|
||||
return $this->getExternalLiveURI();
|
||||
} else {
|
||||
return $this->getInternalLiveURI();
|
||||
|
@ -92,6 +94,10 @@ final class PhamePost extends PhameDAO
|
|||
return ($this->getVisibility() == PhameConstants::VISIBILITY_DRAFT);
|
||||
}
|
||||
|
||||
public function isArchived() {
|
||||
return ($this->getVisibility() == PhameConstants::VISIBILITY_ARCHIVED);
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
|
@ -165,7 +171,7 @@ final class PhamePost extends PhameDAO
|
|||
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||
if (!$this->isDraft() && $this->getBlog()) {
|
||||
if (!$this->isDraft() && !$this->isArchived() && $this->getBlog()) {
|
||||
return $this->getBlog()->getViewPolicy();
|
||||
} else if ($this->getBlog()) {
|
||||
return $this->getBlog()->getEditPolicy();
|
||||
|
@ -319,6 +325,8 @@ final class PhamePost extends PhameDAO
|
|||
public function getFieldValuesForConduit() {
|
||||
if ($this->isDraft()) {
|
||||
$date_published = null;
|
||||
} else if ($this->isArchived()) {
|
||||
$date_published = null;
|
||||
} else {
|
||||
$date_published = (int)$this->getDatePublished();
|
||||
}
|
||||
|
@ -337,4 +345,11 @@ final class PhamePost extends PhameDAO
|
|||
return array();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorFulltextInterface )--------------------------------------- */
|
||||
|
||||
public function newFulltextEngine() {
|
||||
return new PhamePostFulltextEngine();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -73,6 +73,8 @@ final class PhamePostTransaction
|
|||
case self::TYPE_VISIBILITY:
|
||||
if ($new == PhameConstants::VISIBILITY_PUBLISHED) {
|
||||
return 'fa-globe';
|
||||
} else if ($new == PhameConstants::VISIBILITY_ARCHIVED) {
|
||||
return 'fa-ban';
|
||||
} else {
|
||||
return 'fa-eye-slash';
|
||||
}
|
||||
|
@ -144,6 +146,10 @@ final class PhamePostTransaction
|
|||
return pht(
|
||||
'%s marked this post as a draft.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
} else if ($new == PhameConstants::VISIBILITY_ARCHIVED) {
|
||||
return pht(
|
||||
'%s archived this post.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
} else {
|
||||
return pht(
|
||||
'%s published this post.',
|
||||
|
@ -201,6 +207,11 @@ final class PhamePostTransaction
|
|||
'%s marked %s as a draft.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
} else if ($new == PhameConstants::VISIBILITY_ARCHIVED) {
|
||||
return pht(
|
||||
'%s marked %s as archived.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($object_phid));
|
||||
} else {
|
||||
return pht(
|
||||
'%s published %s.',
|
||||
|
|
|
@ -1099,6 +1099,18 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
|
|||
$column = $this->refreshColumn($user, $column);
|
||||
$this->assertTrue((bool)$column);
|
||||
|
||||
// This test has been failing randomly in a way that doesn't reproduce
|
||||
// on any host, so add some extra assertions to try to nail it down.
|
||||
$board = $this->refreshProject($board, $user, true);
|
||||
$this->assertTrue((bool)$board);
|
||||
$this->assertTrue($board->isUserMember($user->getPHID()));
|
||||
|
||||
$can_view = PhabricatorPolicyFilter::hasCapability(
|
||||
$user,
|
||||
$column,
|
||||
PhabricatorPolicyCapability::CAN_VIEW);
|
||||
$this->assertTrue($can_view);
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$user,
|
||||
$column,
|
||||
|
|
|
@ -11,14 +11,20 @@ final class PhabricatorGitGraphStream
|
|||
|
||||
public function __construct(
|
||||
PhabricatorRepository $repository,
|
||||
$start_commit) {
|
||||
$start_commit = null) {
|
||||
|
||||
$this->repository = $repository;
|
||||
|
||||
$future = $repository->getLocalCommandFuture(
|
||||
'log --format=%s %s --',
|
||||
'%H%x01%P%x01%ct',
|
||||
$start_commit);
|
||||
if ($start_commit !== null) {
|
||||
$future = $repository->getLocalCommandFuture(
|
||||
'log --format=%s %s --',
|
||||
'%H%x01%P%x01%ct',
|
||||
$start_commit);
|
||||
} else {
|
||||
$future = $repository->getLocalCommandFuture(
|
||||
'log --format=%s --all --',
|
||||
'%H%x01%P%x01%ct');
|
||||
}
|
||||
|
||||
$this->iterator = new LinesOfALargeExecFuture($future);
|
||||
$this->iterator->setDelimiter("\n");
|
||||
|
|
|
@ -105,6 +105,8 @@ final class PhabricatorRepositoryDiscoveryEngine
|
|||
$this->commitCache[$ref->getIdentifier()] = true;
|
||||
}
|
||||
|
||||
$this->markUnreachableCommits($repository);
|
||||
|
||||
$version = $this->getObservedVersion($repository);
|
||||
if ($version !== null) {
|
||||
id(new DiffusionRepositoryClusterEngine())
|
||||
|
@ -130,39 +132,35 @@ final class PhabricatorRepositoryDiscoveryEngine
|
|||
$this->verifyGitOrigin($repository);
|
||||
}
|
||||
|
||||
// TODO: This should also import tags, but some of the logic is still
|
||||
// branch-specific today.
|
||||
|
||||
$branches = id(new DiffusionLowLevelGitRefQuery())
|
||||
$heads = id(new DiffusionLowLevelGitRefQuery())
|
||||
->setRepository($repository)
|
||||
->withRefTypes(
|
||||
array(
|
||||
PhabricatorRepositoryRefCursor::TYPE_BRANCH,
|
||||
))
|
||||
->execute();
|
||||
|
||||
if (!$branches) {
|
||||
// This repository has no branches at all, so we don't need to do
|
||||
if (!$heads) {
|
||||
// This repository has no heads at all, so we don't need to do
|
||||
// anything. Generally, this means the repository is empty.
|
||||
return array();
|
||||
}
|
||||
|
||||
$branches = $this->sortBranches($branches);
|
||||
$branches = mpull($branches, 'getCommitIdentifier', 'getShortName');
|
||||
$heads = $this->sortRefs($heads);
|
||||
$head_commits = mpull($heads, 'getCommitIdentifier');
|
||||
|
||||
$this->log(
|
||||
pht(
|
||||
'Discovering commits in repository "%s".',
|
||||
$repository->getDisplayName()));
|
||||
|
||||
$this->fillCommitCache(array_values($branches));
|
||||
$this->fillCommitCache($head_commits);
|
||||
|
||||
$refs = array();
|
||||
foreach ($branches as $name => $commit) {
|
||||
$this->log(pht('Examining branch "%s", at "%s".', $name, $commit));
|
||||
foreach ($heads as $ref) {
|
||||
$name = $ref->getShortName();
|
||||
$commit = $ref->getCommitIdentifier();
|
||||
|
||||
if (!$repository->shouldTrackBranch($name)) {
|
||||
$this->log(pht('Skipping, branch is untracked.'));
|
||||
$this->log(pht('Examining ref "%s", at "%s".', $name, $commit));
|
||||
|
||||
if (!$repository->shouldTrackRef($ref)) {
|
||||
$this->log(pht('Skipping, ref is untracked.'));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -173,14 +171,14 @@ final class PhabricatorRepositoryDiscoveryEngine
|
|||
|
||||
$this->log(pht('Looking for new commits.'));
|
||||
|
||||
$branch_refs = $this->discoverStreamAncestry(
|
||||
$head_refs = $this->discoverStreamAncestry(
|
||||
new PhabricatorGitGraphStream($repository, $commit),
|
||||
$commit,
|
||||
$repository->shouldAutocloseBranch($name));
|
||||
$repository->shouldAutocloseRef($ref));
|
||||
|
||||
$this->didDiscoverRefs($branch_refs);
|
||||
$this->didDiscoverRefs($head_refs);
|
||||
|
||||
$refs[] = $branch_refs;
|
||||
$refs[] = $head_refs;
|
||||
}
|
||||
|
||||
return array_mergev($refs);
|
||||
|
@ -448,10 +446,17 @@ final class PhabricatorRepositoryDiscoveryEngine
|
|||
return;
|
||||
}
|
||||
|
||||
// When filling the cache we ignore commits which have been marked as
|
||||
// unreachable, treating them as though they do not exist. When recording
|
||||
// commits later we'll revive commits that exist but are unreachable.
|
||||
|
||||
$commits = id(new PhabricatorRepositoryCommit())->loadAllWhere(
|
||||
'repositoryID = %d AND commitIdentifier IN (%Ls)',
|
||||
'repositoryID = %d AND commitIdentifier IN (%Ls)
|
||||
AND (importStatus & %d) != %d',
|
||||
$this->getRepository()->getID(),
|
||||
$identifiers);
|
||||
$identifiers,
|
||||
PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE,
|
||||
PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE);
|
||||
|
||||
foreach ($commits as $commit) {
|
||||
$this->commitCache[$commit->getCommitIdentifier()] = true;
|
||||
|
@ -469,25 +474,23 @@ final class PhabricatorRepositoryDiscoveryEngine
|
|||
*
|
||||
* @task internal
|
||||
*
|
||||
* @param list<DiffusionRepositoryRef> List of branch heads.
|
||||
* @return list<DiffusionRepositoryRef> Sorted list of branch heads.
|
||||
* @param list<DiffusionRepositoryRef> List of refs.
|
||||
* @return list<DiffusionRepositoryRef> Sorted list of refs.
|
||||
*/
|
||||
private function sortBranches(array $branches) {
|
||||
private function sortRefs(array $refs) {
|
||||
$repository = $this->getRepository();
|
||||
|
||||
$head_branches = array();
|
||||
$tail_branches = array();
|
||||
foreach ($branches as $branch) {
|
||||
$name = $branch->getShortName();
|
||||
|
||||
if ($repository->shouldAutocloseBranch($name)) {
|
||||
$head_branches[] = $branch;
|
||||
$head_refs = array();
|
||||
$tail_refs = array();
|
||||
foreach ($refs as $ref) {
|
||||
if ($repository->shouldAutocloseRef($ref)) {
|
||||
$head_refs[] = $ref;
|
||||
} else {
|
||||
$tail_branches[] = $branch;
|
||||
$tail_refs[] = $ref;
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($head_branches, $tail_branches);
|
||||
return array_merge($head_refs, $tail_refs);
|
||||
}
|
||||
|
||||
|
||||
|
@ -499,6 +502,30 @@ final class PhabricatorRepositoryDiscoveryEngine
|
|||
array $parents) {
|
||||
|
||||
$commit = new PhabricatorRepositoryCommit();
|
||||
$conn_w = $repository->establishConnection('w');
|
||||
|
||||
// First, try to revive an existing unreachable commit (if one exists) by
|
||||
// removing the "unreachable" flag. If we succeed, we don't need to do
|
||||
// anything else: we already discovered this commit some time ago.
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'UPDATE %T SET importStatus = (importStatus & ~%d)
|
||||
WHERE repositoryID = %d AND commitIdentifier = %s',
|
||||
$commit->getTableName(),
|
||||
PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE,
|
||||
$repository->getID(),
|
||||
$commit_identifier);
|
||||
if ($conn_w->getAffectedRows()) {
|
||||
$commit = $commit->loadOneWhere(
|
||||
'repositoryID = %d AND commitIdentifier = %s',
|
||||
$repository->getID(),
|
||||
$commit_identifier);
|
||||
|
||||
// After reviving a commit, schedule new daemons for it.
|
||||
$this->didDiscoverCommit($repository, $commit, $epoch);
|
||||
return;
|
||||
}
|
||||
|
||||
$commit->setRepositoryID($repository->getID());
|
||||
$commit->setCommitIdentifier($commit_identifier);
|
||||
$commit->setEpoch($epoch);
|
||||
|
@ -508,10 +535,7 @@ final class PhabricatorRepositoryDiscoveryEngine
|
|||
|
||||
$data = new PhabricatorRepositoryCommitData();
|
||||
|
||||
$conn_w = $repository->establishConnection('w');
|
||||
|
||||
try {
|
||||
|
||||
// If this commit has parents, look up their IDs. The parent commits
|
||||
// should always exist already.
|
||||
|
||||
|
@ -559,21 +583,7 @@ final class PhabricatorRepositoryDiscoveryEngine
|
|||
}
|
||||
$commit->saveTransaction();
|
||||
|
||||
$this->insertTask($repository, $commit);
|
||||
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'INSERT INTO %T (repositoryID, size, lastCommitID, epoch)
|
||||
VALUES (%d, 1, %d, %d)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
size = size + 1,
|
||||
lastCommitID =
|
||||
IF(VALUES(epoch) > epoch, VALUES(lastCommitID), lastCommitID),
|
||||
epoch = IF(VALUES(epoch) > epoch, VALUES(epoch), epoch)',
|
||||
PhabricatorRepository::TABLE_SUMMARY,
|
||||
$repository->getID(),
|
||||
$commit->getID(),
|
||||
$epoch);
|
||||
$this->didDiscoverCommit($repository, $commit, $epoch);
|
||||
|
||||
if ($this->repairMode) {
|
||||
// Normally, the query should throw a duplicate key exception. If we
|
||||
|
@ -589,8 +599,6 @@ final class PhabricatorRepositoryDiscoveryEngine
|
|||
'commit' => $commit,
|
||||
)));
|
||||
|
||||
|
||||
|
||||
} catch (AphrontDuplicateKeyQueryException $ex) {
|
||||
$commit->killTransaction();
|
||||
// Ignore. This can happen because we discover the same new commit
|
||||
|
@ -600,6 +608,29 @@ final class PhabricatorRepositoryDiscoveryEngine
|
|||
}
|
||||
}
|
||||
|
||||
private function didDiscoverCommit(
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit,
|
||||
$epoch) {
|
||||
|
||||
$this->insertTask($repository, $commit);
|
||||
|
||||
// Update the repository summary table.
|
||||
queryfx(
|
||||
$commit->establishConnection('w'),
|
||||
'INSERT INTO %T (repositoryID, size, lastCommitID, epoch)
|
||||
VALUES (%d, 1, %d, %d)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
size = size + 1,
|
||||
lastCommitID =
|
||||
IF(VALUES(epoch) > epoch, VALUES(lastCommitID), lastCommitID),
|
||||
epoch = IF(VALUES(epoch) > epoch, VALUES(epoch), epoch)',
|
||||
PhabricatorRepository::TABLE_SUMMARY,
|
||||
$repository->getID(),
|
||||
$commit->getID(),
|
||||
$epoch);
|
||||
}
|
||||
|
||||
private function didDiscoverRefs(array $refs) {
|
||||
foreach ($refs as $ref) {
|
||||
$this->workingSet[$ref->getIdentifier()] = true;
|
||||
|
@ -702,4 +733,136 @@ final class PhabricatorRepositoryDiscoveryEngine
|
|||
return (int)$version['version'];
|
||||
}
|
||||
|
||||
private function markUnreachableCommits(PhabricatorRepository $repository) {
|
||||
// For now, this is only supported for Git.
|
||||
if (!$repository->isGit()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find older versions of refs which we haven't processed yet. We're going
|
||||
// to make sure their commits are still reachable.
|
||||
$old_refs = id(new PhabricatorRepositoryOldRef())->loadAllWhere(
|
||||
'repositoryPHID = %s',
|
||||
$repository->getPHID());
|
||||
|
||||
// We can share a single graph stream across all the checks we need to do.
|
||||
$stream = new PhabricatorGitGraphStream($repository);
|
||||
|
||||
foreach ($old_refs as $old_ref) {
|
||||
$identifier = $old_ref->getCommitIdentifier();
|
||||
$this->markUnreachableFrom($repository, $stream, $identifier);
|
||||
|
||||
// If nothing threw an exception, we're all done with this ref.
|
||||
$old_ref->delete();
|
||||
}
|
||||
}
|
||||
|
||||
private function markUnreachableFrom(
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorGitGraphStream $stream,
|
||||
$identifier) {
|
||||
|
||||
$unreachable = array();
|
||||
|
||||
$commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
|
||||
'repositoryID = %s AND commitIdentifier = %s',
|
||||
$repository->getID(),
|
||||
$identifier);
|
||||
if (!$commit) {
|
||||
return;
|
||||
}
|
||||
|
||||
$look = array($commit);
|
||||
$seen = array();
|
||||
while ($look) {
|
||||
$target = array_pop($look);
|
||||
|
||||
// If we've already checked this commit (for example, because history
|
||||
// branches and then merges) we don't need to check it again.
|
||||
$target_identifier = $target->getCommitIdentifier();
|
||||
if (isset($seen[$target_identifier])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$seen[$target_identifier] = true;
|
||||
|
||||
try {
|
||||
$stream->getCommitDate($target_identifier);
|
||||
$reachable = true;
|
||||
} catch (Exception $ex) {
|
||||
$reachable = false;
|
||||
}
|
||||
|
||||
if ($reachable) {
|
||||
// This commit is reachable, so we don't need to go any further
|
||||
// down this road.
|
||||
continue;
|
||||
}
|
||||
|
||||
$unreachable[] = $target;
|
||||
|
||||
// Find the commit's parents and check them for reachability, too. We
|
||||
// have to look in the database since we no may longer have the commit
|
||||
// in the repository.
|
||||
$rows = queryfx_all(
|
||||
$commit->establishConnection('w'),
|
||||
'SELECT commit.* FROM %T commit
|
||||
JOIN %T parents ON commit.id = parents.parentCommitID
|
||||
WHERE parents.childCommitID = %d',
|
||||
$commit->getTableName(),
|
||||
PhabricatorRepository::TABLE_PARENTS,
|
||||
$target->getID());
|
||||
if (!$rows) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parents = id(new PhabricatorRepositoryCommit())
|
||||
->loadAllFromArray($rows);
|
||||
foreach ($parents as $parent) {
|
||||
$look[] = $parent;
|
||||
}
|
||||
}
|
||||
|
||||
$unreachable = array_reverse($unreachable);
|
||||
|
||||
$flag = PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE;
|
||||
foreach ($unreachable as $unreachable_commit) {
|
||||
$unreachable_commit->writeImportStatusFlag($flag);
|
||||
}
|
||||
|
||||
// If anything was unreachable, just rebuild the whole summary table.
|
||||
// We can't really update it incrementally when a commit becomes
|
||||
// unreachable.
|
||||
if ($unreachable) {
|
||||
$this->rebuildSummaryTable($repository);
|
||||
}
|
||||
}
|
||||
|
||||
private function rebuildSummaryTable(PhabricatorRepository $repository) {
|
||||
$conn_w = $repository->establishConnection('w');
|
||||
|
||||
$data = queryfx_one(
|
||||
$conn_w,
|
||||
'SELECT COUNT(*) N, MAX(id) id, MAX(epoch) epoch
|
||||
FROM %T WHERE repositoryID = %d AND (importStatus & %d) != %d',
|
||||
id(new PhabricatorRepositoryCommit())->getTableName(),
|
||||
$repository->getID(),
|
||||
PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE,
|
||||
PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE);
|
||||
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'INSERT INTO %T (repositoryID, size, lastCommitID, epoch)
|
||||
VALUES (%d, %d, %d, %d)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
size = VALUES(size),
|
||||
lastCommitID = VALUES(lastCommitID),
|
||||
epoch = VALUES(epoch)',
|
||||
PhabricatorRepository::TABLE_SUMMARY,
|
||||
$repository->getID(),
|
||||
$data['N'],
|
||||
$data['id'],
|
||||
$data['epoch']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -347,7 +347,7 @@ final class PhabricatorRepositoryPullEngine
|
|||
// For bare working copies, we need this magic incantation.
|
||||
$future = $repository->getRemoteCommandFuture(
|
||||
'fetch origin %s --prune',
|
||||
'+refs/heads/*:refs/heads/*');
|
||||
'+refs/*:refs/*');
|
||||
} else {
|
||||
$future = $repository->getRemoteCommandFuture(
|
||||
'fetch --all --prune');
|
||||
|
|
|
@ -25,29 +25,31 @@ final class PhabricatorRepositoryRefEngine
|
|||
switch ($vcs) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||
// No meaningful refs of any type in Subversion.
|
||||
$branches = array();
|
||||
$bookmarks = array();
|
||||
$tags = array();
|
||||
$maps = array();
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
$branches = $this->loadMercurialBranchPositions($repository);
|
||||
$bookmarks = $this->loadMercurialBookmarkPositions($repository);
|
||||
$tags = array();
|
||||
$maps = array(
|
||||
PhabricatorRepositoryRefCursor::TYPE_BRANCH => $branches,
|
||||
PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => $bookmarks,
|
||||
);
|
||||
|
||||
$branches_may_close = true;
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
$branches = $this->loadGitBranchPositions($repository);
|
||||
$bookmarks = array();
|
||||
$tags = $this->loadGitTagPositions($repository);
|
||||
$maps = $this->loadGitRefPositions($repository);
|
||||
break;
|
||||
default:
|
||||
throw new Exception(pht('Unknown VCS "%s"!', $vcs));
|
||||
}
|
||||
|
||||
$maps = array(
|
||||
PhabricatorRepositoryRefCursor::TYPE_BRANCH => $branches,
|
||||
PhabricatorRepositoryRefCursor::TYPE_TAG => $tags,
|
||||
PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => $bookmarks,
|
||||
// Fill in any missing types with empty lists.
|
||||
$maps = $maps + array(
|
||||
PhabricatorRepositoryRefCursor::TYPE_BRANCH => array(),
|
||||
PhabricatorRepositoryRefCursor::TYPE_TAG => array(),
|
||||
PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => array(),
|
||||
PhabricatorRepositoryRefCursor::TYPE_REF => array(),
|
||||
);
|
||||
|
||||
$all_cursors = id(new PhabricatorRepositoryRefCursorQuery())
|
||||
|
@ -83,6 +85,13 @@ final class PhabricatorRepositoryRefEngine
|
|||
$ref->save();
|
||||
}
|
||||
foreach ($this->deadRefs as $ref) {
|
||||
// Shove this ref into the old refs table so the discovery engine
|
||||
// can check if any commits have been rendered unreachable.
|
||||
id(new PhabricatorRepositoryOldRef())
|
||||
->setRepositoryPHID($repository->getPHID())
|
||||
->setCommitIdentifier($ref->getCommitIdentifier())
|
||||
->save();
|
||||
|
||||
$ref->delete();
|
||||
}
|
||||
$repository->saveTransaction();
|
||||
|
@ -91,6 +100,7 @@ final class PhabricatorRepositoryRefEngine
|
|||
$this->deadRefs = array();
|
||||
}
|
||||
|
||||
$branches = $maps[PhabricatorRepositoryRefCursor::TYPE_BRANCH];
|
||||
if ($branches && $branches_may_close) {
|
||||
$this->updateBranchStates($repository, $branches);
|
||||
}
|
||||
|
@ -449,28 +459,12 @@ final class PhabricatorRepositoryRefEngine
|
|||
/**
|
||||
* @task git
|
||||
*/
|
||||
private function loadGitBranchPositions(PhabricatorRepository $repository) {
|
||||
return id(new DiffusionLowLevelGitRefQuery())
|
||||
private function loadGitRefPositions(PhabricatorRepository $repository) {
|
||||
$refs = id(new DiffusionLowLevelGitRefQuery())
|
||||
->setRepository($repository)
|
||||
->withRefTypes(
|
||||
array(
|
||||
PhabricatorRepositoryRefCursor::TYPE_BRANCH,
|
||||
))
|
||||
->execute();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task git
|
||||
*/
|
||||
private function loadGitTagPositions(PhabricatorRepository $repository) {
|
||||
return id(new DiffusionLowLevelGitRefQuery())
|
||||
->setRepository($repository)
|
||||
->withRefTypes(
|
||||
array(
|
||||
PhabricatorRepositoryRefCursor::TYPE_TAG,
|
||||
))
|
||||
->execute();
|
||||
return mgroup($refs, 'getRefType');
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorRepositoryManagementMarkReachableWorkflow
|
||||
extends PhabricatorRepositoryManagementWorkflow {
|
||||
|
||||
private $untouchedCount = 0;
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->setName('mark-reachable')
|
||||
->setExamples('**mark-reachable** [__options__] __repository__ ...')
|
||||
->setSynopsis(
|
||||
pht(
|
||||
'Rebuild "unreachable" flags for commits in __repository__.'))
|
||||
->setArguments(
|
||||
array(
|
||||
array(
|
||||
'name' => 'repos',
|
||||
'wildcard' => true,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$repos = $this->loadRepositories($args, 'repos');
|
||||
if (!$repos) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Specify one or more repositories to correct reachability status '.
|
||||
'for.'));
|
||||
}
|
||||
|
||||
foreach ($repos as $repo) {
|
||||
$this->markReachable($repo);
|
||||
}
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Examined %s commits already in the correct state.',
|
||||
new PhutilNumber($this->untouchedCount)));
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht('Done.'));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function markReachable(PhabricatorRepository $repository) {
|
||||
if (!$repository->isGit()) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Only Git repositories are supported, this repository ("%s") is '.
|
||||
'not a Git repository.',
|
||||
$repository->getDisplayName()));
|
||||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$commits = id(new DiffusionCommitQuery())
|
||||
->setViewer($viewer)
|
||||
->withRepository($repository)
|
||||
->execute();
|
||||
|
||||
$flag = PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE;
|
||||
|
||||
$graph = new PhabricatorGitGraphStream($repository);
|
||||
foreach ($commits as $commit) {
|
||||
$identifier = $commit->getCommitIdentifier();
|
||||
|
||||
try {
|
||||
$graph->getCommitDate($identifier);
|
||||
$unreachable = false;
|
||||
} catch (Exception $ex) {
|
||||
$unreachable = true;
|
||||
}
|
||||
|
||||
// The commit has proper reachability, so do nothing.
|
||||
if ($commit->isUnreachable() === $unreachable) {
|
||||
$this->untouchedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($unreachable) {
|
||||
echo tsprintf(
|
||||
"%s: %s\n",
|
||||
$commit->getMonogram(),
|
||||
pht('Marking commit unreachable.'));
|
||||
|
||||
$commit->writeImportStatusFlag($flag);
|
||||
} else {
|
||||
echo tsprintf(
|
||||
"%s: %s\n",
|
||||
$commit->getMonogram(),
|
||||
pht('Marking commit reachable.'));
|
||||
|
||||
$commit->clearImportStatusFlag($flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -46,6 +46,10 @@ final class PhabricatorRepositoryCommitPHIDType extends PhabricatorPHIDType {
|
|||
$handle->setFullName($full_name);
|
||||
$handle->setURI($commit->getURI());
|
||||
$handle->setTimestamp($commit->getEpoch());
|
||||
|
||||
if ($commit->isUnreachable()) {
|
||||
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -910,6 +910,21 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
return null;
|
||||
}
|
||||
|
||||
public function shouldTrackRef(DiffusionRepositoryRef $ref) {
|
||||
// At least for now, don't track the staging area tags.
|
||||
if ($ref->isTag()) {
|
||||
if (preg_match('(^phabricator/)', $ref->getShortName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$ref->isBranch()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->shouldTrackBranch($ref->getShortName());
|
||||
}
|
||||
|
||||
public function shouldTrackBranch($branch) {
|
||||
return $this->isBranchInFilter($branch, 'branch-filter');
|
||||
}
|
||||
|
@ -1020,6 +1035,14 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
/* -( Autoclose )---------------------------------------------------------- */
|
||||
|
||||
|
||||
public function shouldAutocloseRef(DiffusionRepositoryRef $ref) {
|
||||
if (!$ref->isBranch()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->shouldAutocloseBranch($ref->getShortName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if autoclose is active for a branch.
|
||||
*
|
||||
|
|
|
@ -32,6 +32,7 @@ final class PhabricatorRepositoryCommit
|
|||
const IMPORTED_ALL = 15;
|
||||
|
||||
const IMPORTED_CLOSEABLE = 1024;
|
||||
const IMPORTED_UNREACHABLE = 2048;
|
||||
|
||||
private $commitData = self::ATTACHABLE;
|
||||
private $audits = self::ATTACHABLE;
|
||||
|
@ -58,14 +59,43 @@ final class PhabricatorRepositoryCommit
|
|||
return $this->isPartiallyImported(self::IMPORTED_ALL);
|
||||
}
|
||||
|
||||
public function isUnreachable() {
|
||||
return $this->isPartiallyImported(self::IMPORTED_UNREACHABLE);
|
||||
}
|
||||
|
||||
public function writeImportStatusFlag($flag) {
|
||||
queryfx(
|
||||
$this->establishConnection('w'),
|
||||
'UPDATE %T SET importStatus = (importStatus | %d) WHERE id = %d',
|
||||
$this->getTableName(),
|
||||
$flag,
|
||||
$this->getID());
|
||||
$this->setImportStatus($this->getImportStatus() | $flag);
|
||||
return $this->adjustImportStatusFlag($flag, true);
|
||||
}
|
||||
|
||||
public function clearImportStatusFlag($flag) {
|
||||
return $this->adjustImportStatusFlag($flag, false);
|
||||
}
|
||||
|
||||
private function adjustImportStatusFlag($flag, $set) {
|
||||
$conn_w = $this->establishConnection('w');
|
||||
$table_name = $this->getTableName();
|
||||
$id = $this->getID();
|
||||
|
||||
if ($set) {
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'UPDATE %T SET importStatus = (importStatus | %d) WHERE id = %d',
|
||||
$table_name,
|
||||
$flag,
|
||||
$id);
|
||||
|
||||
$this->setImportStatus($this->getImportStatus() | $flag);
|
||||
} else {
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'UPDATE %T SET importStatus = (importStatus & ~%d) WHERE id = %d',
|
||||
$table_name,
|
||||
$flag,
|
||||
$id);
|
||||
|
||||
$this->setImportStatus($this->getImportStatus() & ~$flag);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Stores outdated refs which need to be checked for reachability.
|
||||
*
|
||||
* When a branch is deleted, the old HEAD ends up here and the discovery
|
||||
* engine marks all the commits that previously appeared on it as unreachable.
|
||||
*/
|
||||
final class PhabricatorRepositoryOldRef
|
||||
extends PhabricatorRepositoryDAO
|
||||
implements PhabricatorPolicyInterface {
|
||||
|
||||
protected $repositoryPHID;
|
||||
protected $commitIdentifier;
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_TIMESTAMPS => false,
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'commitIdentifier' => 'text40',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_repository' => array(
|
||||
'columns' => array('repositoryPHID'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
return PhabricatorPolicies::getMostOpenPolicy();
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ final class PhabricatorRepositoryRefCursor
|
|||
const TYPE_BRANCH = 'branch';
|
||||
const TYPE_TAG = 'tag';
|
||||
const TYPE_BOOKMARK = 'bookmark';
|
||||
const TYPE_REF = 'ref';
|
||||
|
||||
protected $repositoryPHID;
|
||||
protected $refType;
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
final class PhabricatorRepositoryCommitHeraldWorker
|
||||
extends PhabricatorRepositoryCommitParserWorker {
|
||||
|
||||
protected function getImportStepFlag() {
|
||||
return PhabricatorRepositoryCommit::IMPORTED_HERALD;
|
||||
}
|
||||
|
||||
public function getRequiredLeaseTime() {
|
||||
// Herald rules may take a long time to process.
|
||||
return phutil_units('4 hours in seconds');
|
||||
|
@ -12,6 +16,12 @@ final class PhabricatorRepositoryCommitHeraldWorker
|
|||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit) {
|
||||
|
||||
if ($this->shouldSkipImportStep()) {
|
||||
// This worker has no followup tasks, so we can just bail out
|
||||
// right away without queueing anything.
|
||||
return;
|
||||
}
|
||||
|
||||
// Reload the commit to pull commit data and audit requests.
|
||||
$commit = id(new DiffusionCommitQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
|
|
|
@ -3,14 +3,18 @@
|
|||
final class PhabricatorRepositoryCommitOwnersWorker
|
||||
extends PhabricatorRepositoryCommitParserWorker {
|
||||
|
||||
protected function getImportStepFlag() {
|
||||
return PhabricatorRepositoryCommit::IMPORTED_OWNERS;
|
||||
}
|
||||
|
||||
protected function parseCommit(
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit) {
|
||||
|
||||
$this->triggerOwnerAudits($repository, $commit);
|
||||
|
||||
$commit->writeImportStatusFlag(
|
||||
PhabricatorRepositoryCommit::IMPORTED_OWNERS);
|
||||
if (!$this->shouldSkipImportStep()) {
|
||||
$this->triggerOwnerAudits($repository, $commit);
|
||||
$commit->writeImportStatusFlag($this->getImportStepFlag());
|
||||
}
|
||||
|
||||
if ($this->shouldQueueFollowupTasks()) {
|
||||
$this->queueTask(
|
||||
|
|
|
@ -26,6 +26,14 @@ abstract class PhabricatorRepositoryCommitParserWorker
|
|||
pht('Commit "%s" does not exist.', $commit_id));
|
||||
}
|
||||
|
||||
if ($commit->isUnreachable()) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Commit "%s" has been deleted: it is no longer reachable from '.
|
||||
'any ref.',
|
||||
$commit_id));
|
||||
}
|
||||
|
||||
$this->commit = $commit;
|
||||
|
||||
return $commit;
|
||||
|
@ -44,6 +52,42 @@ abstract class PhabricatorRepositoryCommitParserWorker
|
|||
return !idx($this->getTaskData(), 'only');
|
||||
}
|
||||
|
||||
protected function getImportStepFlag() {
|
||||
return null;
|
||||
}
|
||||
|
||||
final protected function shouldSkipImportStep() {
|
||||
// If this step has already been performed and this is a "natural" task
|
||||
// which was queued by the normal daemons, decline to do the work again.
|
||||
// This mitigates races if commits are rapidly deleted and revived.
|
||||
$flag = $this->getImportStepFlag();
|
||||
if (!$flag) {
|
||||
// This step doesn't have an associated flag.
|
||||
return false;
|
||||
}
|
||||
|
||||
$commit = $this->commit;
|
||||
if (!$commit->isPartiallyImported($flag)) {
|
||||
// This commit doesn't have the flag set yet.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!$this->shouldQueueFollowupTasks()) {
|
||||
// This task was queued by administrative tools, so do the work even
|
||||
// if it duplicates existing work.
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->log(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Skipping import step; this step was previously completed for '.
|
||||
'this commit.'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract protected function parseCommit(
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit);
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
abstract class PhabricatorRepositoryCommitChangeParserWorker
|
||||
extends PhabricatorRepositoryCommitParserWorker {
|
||||
|
||||
protected function getImportStepFlag() {
|
||||
return PhabricatorRepositoryCommit::IMPORTED_CHANGE;
|
||||
}
|
||||
|
||||
public function getRequiredLeaseTime() {
|
||||
// It can take a very long time to parse commits; some commits in the
|
||||
// Facebook repository affect many millions of paths. Acquire 24h leases.
|
||||
|
@ -23,9 +27,15 @@ abstract class PhabricatorRepositoryCommitChangeParserWorker
|
|||
return;
|
||||
}
|
||||
|
||||
$results = $this->parseCommitChanges($repository, $commit);
|
||||
if ($results) {
|
||||
$this->writeCommitChanges($repository, $commit, $results);
|
||||
if (!$this->shouldSkipImportStep()) {
|
||||
$results = $this->parseCommitChanges($repository, $commit);
|
||||
if ($results) {
|
||||
$this->writeCommitChanges($repository, $commit, $results);
|
||||
}
|
||||
|
||||
$commit->writeImportStatusFlag($this->getImportStepFlag());
|
||||
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing($commit->getPHID());
|
||||
}
|
||||
|
||||
$this->finishParse();
|
||||
|
@ -85,12 +95,6 @@ abstract class PhabricatorRepositoryCommitChangeParserWorker
|
|||
|
||||
protected function finishParse() {
|
||||
$commit = $this->commit;
|
||||
|
||||
$commit->writeImportStatusFlag(
|
||||
PhabricatorRepositoryCommit::IMPORTED_CHANGE);
|
||||
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing($commit->getPHID());
|
||||
|
||||
if ($this->shouldQueueFollowupTasks()) {
|
||||
$this->queueTask(
|
||||
'PhabricatorRepositoryCommitOwnersWorker',
|
||||
|
|
|
@ -3,42 +3,52 @@
|
|||
abstract class PhabricatorRepositoryCommitMessageParserWorker
|
||||
extends PhabricatorRepositoryCommitParserWorker {
|
||||
|
||||
abstract protected function parseCommitWithRef(
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit,
|
||||
DiffusionCommitRef $ref);
|
||||
protected function getImportStepFlag() {
|
||||
return PhabricatorRepositoryCommit::IMPORTED_MESSAGE;
|
||||
}
|
||||
|
||||
abstract protected function getFollowupTaskClass();
|
||||
|
||||
final protected function parseCommit(
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit) {
|
||||
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
if (!$this->shouldSkipImportStep()) {
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
|
||||
$refs_raw = DiffusionQuery::callConduitWithDiffusionRequest(
|
||||
$viewer,
|
||||
DiffusionRequest::newFromDictionary(
|
||||
$refs_raw = DiffusionQuery::callConduitWithDiffusionRequest(
|
||||
$viewer,
|
||||
DiffusionRequest::newFromDictionary(
|
||||
array(
|
||||
'repository' => $repository,
|
||||
'user' => $viewer,
|
||||
)),
|
||||
'diffusion.querycommits',
|
||||
array(
|
||||
'repository' => $repository,
|
||||
'user' => $viewer,
|
||||
)),
|
||||
'diffusion.querycommits',
|
||||
array(
|
||||
'repositoryPHID' => $repository->getPHID(),
|
||||
'phids' => array($commit->getPHID()),
|
||||
'bypassCache' => true,
|
||||
'needMessages' => true,
|
||||
));
|
||||
'repositoryPHID' => $repository->getPHID(),
|
||||
'phids' => array($commit->getPHID()),
|
||||
'bypassCache' => true,
|
||||
'needMessages' => true,
|
||||
));
|
||||
|
||||
if (empty($refs_raw['data'])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unable to retrieve details for commit "%s"!',
|
||||
$commit->getPHID()));
|
||||
if (empty($refs_raw['data'])) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unable to retrieve details for commit "%s"!',
|
||||
$commit->getPHID()));
|
||||
}
|
||||
|
||||
$ref = DiffusionCommitRef::newFromConduitResult(head($refs_raw['data']));
|
||||
$this->updateCommitData($ref);
|
||||
}
|
||||
|
||||
$ref = DiffusionCommitRef::newFromConduitResult(head($refs_raw['data']));
|
||||
|
||||
$this->parseCommitWithRef($repository, $commit, $ref);
|
||||
if ($this->shouldQueueFollowupTasks()) {
|
||||
$this->queueTask(
|
||||
$this->getFollowupTaskClass(),
|
||||
array(
|
||||
'commitID' => $commit->getID(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
final protected function updateCommitData(DiffusionCommitRef $ref) {
|
||||
|
|
|
@ -3,20 +3,8 @@
|
|||
final class PhabricatorRepositoryGitCommitMessageParserWorker
|
||||
extends PhabricatorRepositoryCommitMessageParserWorker {
|
||||
|
||||
protected function parseCommitWithRef(
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit,
|
||||
DiffusionCommitRef $ref) {
|
||||
|
||||
$this->updateCommitData($ref);
|
||||
|
||||
if ($this->shouldQueueFollowupTasks()) {
|
||||
$this->queueTask(
|
||||
'PhabricatorRepositoryGitCommitChangeParserWorker',
|
||||
array(
|
||||
'commitID' => $commit->getID(),
|
||||
));
|
||||
}
|
||||
protected function getFollowupTaskClass() {
|
||||
return 'PhabricatorRepositoryGitCommitChangeParserWorker';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,20 +3,8 @@
|
|||
final class PhabricatorRepositoryMercurialCommitMessageParserWorker
|
||||
extends PhabricatorRepositoryCommitMessageParserWorker {
|
||||
|
||||
protected function parseCommitWithRef(
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit,
|
||||
DiffusionCommitRef $ref) {
|
||||
|
||||
$this->updateCommitData($ref);
|
||||
|
||||
if ($this->shouldQueueFollowupTasks()) {
|
||||
$this->queueTask(
|
||||
'PhabricatorRepositoryMercurialCommitChangeParserWorker',
|
||||
array(
|
||||
'commitID' => $commit->getID(),
|
||||
));
|
||||
}
|
||||
protected function getFollowupTaskClass() {
|
||||
return 'PhabricatorRepositoryMercurialCommitChangeParserWorker';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,20 +3,8 @@
|
|||
final class PhabricatorRepositorySvnCommitMessageParserWorker
|
||||
extends PhabricatorRepositoryCommitMessageParserWorker {
|
||||
|
||||
protected function parseCommitWithRef(
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit,
|
||||
DiffusionCommitRef $ref) {
|
||||
|
||||
$this->updateCommitData($ref);
|
||||
|
||||
if ($this->shouldQueueFollowupTasks()) {
|
||||
$this->queueTask(
|
||||
'PhabricatorRepositorySvnCommitChangeParserWorker',
|
||||
array(
|
||||
'commitID' => $commit->getID(),
|
||||
));
|
||||
}
|
||||
protected function getFollowupTaskClass() {
|
||||
return 'PhabricatorRepositorySvnCommitChangeParserWorker';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue