1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-08 13:51:02 +01:00

(stable) Promote 2018 Week 12

This commit is contained in:
epriestley 2018-03-23 14:49:13 -07:00
commit 8c5aceec60
54 changed files with 2273 additions and 202 deletions

View file

@ -9,8 +9,8 @@ return array(
'names' => array(
'conpherence.pkg.css' => 'e68cf1fa',
'conpherence.pkg.js' => '15191c65',
'core.pkg.css' => 'c218ed53',
'core.pkg.js' => '8581cd02',
'core.pkg.css' => '3fd3b7b8',
'core.pkg.js' => 'b9b4a943',
'differential.pkg.css' => '113e692c',
'differential.pkg.js' => 'f6d809c0',
'diffusion.pkg.css' => 'a2d17c7d',
@ -112,7 +112,7 @@ return array(
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
'rsrc/css/application/uiexample/example.css' => '528b19de',
'rsrc/css/core/core.css' => '62fa3ace',
'rsrc/css/core/remarkup.css' => '97dc3523',
'rsrc/css/core/remarkup.css' => 'b375546d',
'rsrc/css/core/syntax.css' => 'cae95e89',
'rsrc/css/core/z-index.css' => '9d8f7c4b',
'rsrc/css/diviner/diviner-shared.css' => '896f1d43',
@ -120,7 +120,7 @@ return array(
'rsrc/css/font/font-lato.css' => 'c7ccd872',
'rsrc/css/font/phui-font-icon-base.css' => '870a7360',
'rsrc/css/layout/phabricator-filetree-view.css' => 'b912ad97',
'rsrc/css/layout/phabricator-source-code-view.css' => '926ced2d',
'rsrc/css/layout/phabricator-source-code-view.css' => '31ee3c83',
'rsrc/css/phui/button/phui-button-bar.css' => 'f1ff5494',
'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68',
'rsrc/css/phui/button/phui-button.css' => '1863cc6e',
@ -168,7 +168,7 @@ return array(
'rsrc/css/phui/phui-object-box.css' => '9cff003c',
'rsrc/css/phui/phui-pager.css' => 'edcbc226',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
'rsrc/css/phui/phui-property-list-view.css' => '2dc7993f',
'rsrc/css/phui/phui-property-list-view.css' => 'de4754d8',
'rsrc/css/phui/phui-remarkup-preview.css' => '54a34863',
'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892',
'rsrc/css/phui/phui-spacing.css' => '042804d6',
@ -253,7 +253,7 @@ return array(
'rsrc/externals/javelin/lib/URI.js' => 'c989ade3',
'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8',
'rsrc/externals/javelin/lib/WebSocket.js' => '3ffe32d6',
'rsrc/externals/javelin/lib/Workflow.js' => '0eb1db0c',
'rsrc/externals/javelin/lib/Workflow.js' => '6a726c55',
'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8',
'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b',
'rsrc/externals/javelin/lib/__tests__/JSON.js' => '837a7d68',
@ -392,6 +392,7 @@ return array(
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc',
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70',
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef',
'rsrc/js/application/files/behavior-document-engine.js' => 'd3f8623c',
'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab',
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909',
@ -472,7 +473,7 @@ return array(
'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0',
'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0',
'rsrc/js/core/behavior-lightbox-attachments.js' => '6b31879a',
'rsrc/js/core/behavior-line-linker.js' => 'a9b946f8',
'rsrc/js/core/behavior-line-linker.js' => '13e39479',
'rsrc/js/core/behavior-more.js' => 'a80d0378',
'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0',
'rsrc/js/core/behavior-oncopy.js' => '2926fff2',
@ -507,7 +508,7 @@ return array(
'rsrc/js/phui/behavior-phui-submenu.js' => 'a6f7a73b',
'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9',
'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
'rsrc/js/phuix/PHUIXActionView.js' => '442efd08',
'rsrc/js/phuix/PHUIXActionView.js' => 'ed18356a',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '7fa5c915',
'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03',
@ -606,6 +607,7 @@ return array(
'javelin-behavior-diffusion-jump-to' => '73d09eef',
'javelin-behavior-diffusion-locate-file' => '6d3e1947',
'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc',
'javelin-behavior-document-engine' => 'd3f8623c',
'javelin-behavior-doorkeeper-tag' => '1db13e70',
'javelin-behavior-drydock-live-operation-status' => '901935ef',
'javelin-behavior-durable-column' => '2ae077e1',
@ -636,7 +638,7 @@ return array(
'javelin-behavior-phabricator-gesture-example' => '558829c2',
'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0',
'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0',
'javelin-behavior-phabricator-line-linker' => 'a9b946f8',
'javelin-behavior-phabricator-line-linker' => '13e39479',
'javelin-behavior-phabricator-nav' => '836f966d',
'javelin-behavior-phabricator-notification-example' => '8ce821c5',
'javelin-behavior-phabricator-object-selector' => '77c1f0b0',
@ -737,7 +739,7 @@ return array(
'javelin-workboard-card' => 'c587b80f',
'javelin-workboard-column' => '758b4758',
'javelin-workboard-controller' => '26167537',
'javelin-workflow' => '0eb1db0c',
'javelin-workflow' => '6a726c55',
'maniphest-report-css' => '9b9580b7',
'maniphest-task-edit-css' => 'fda62a9b',
'maniphest-task-summary-css' => '11cc5344',
@ -778,11 +780,11 @@ return array(
'phabricator-object-selector-css' => '85ee8ce6',
'phabricator-phtize' => 'd254d646',
'phabricator-prefab' => '77b0ae28',
'phabricator-remarkup-css' => '97dc3523',
'phabricator-remarkup-css' => 'b375546d',
'phabricator-search-results-css' => '505dd8cf',
'phabricator-shaped-request' => '7cbe244b',
'phabricator-slowvote-css' => 'a94b7230',
'phabricator-source-code-view-css' => '926ced2d',
'phabricator-source-code-view-css' => '31ee3c83',
'phabricator-standard-page-view' => '34ee718b',
'phabricator-textareautils' => '320810c8',
'phabricator-title' => '485aaa6c',
@ -848,7 +850,7 @@ return array(
'phui-oi-simple-ui-css' => 'a8beebea',
'phui-pager-css' => 'edcbc226',
'phui-pinboard-view-css' => '2495140e',
'phui-property-list-view-css' => '2dc7993f',
'phui-property-list-view-css' => 'de4754d8',
'phui-remarkup-preview-css' => '54a34863',
'phui-segment-bar-view-css' => 'b1d1b892',
'phui-spacing-css' => '042804d6',
@ -862,7 +864,7 @@ return array(
'phui-workcard-view-css' => 'cca5fa92',
'phui-workpanel-view-css' => 'a3a63478',
'phuix-action-list-view' => 'b5c256b8',
'phuix-action-view' => '442efd08',
'phuix-action-view' => 'ed18356a',
'phuix-autocomplete' => '7fa5c915',
'phuix-button-view' => '8a91e1ac',
'phuix-dropdown-menu' => '04b2ae03',
@ -958,21 +960,16 @@ return array(
'javelin-dom',
'javelin-router',
),
'0eb1db0c' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
),
'0f764c35' => array(
'javelin-install',
'javelin-util',
),
'13e39479' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-history',
),
'15d5ff71' => array(
'aphront-typeahead-control-css',
'phui-tag-view-css',
@ -1182,11 +1179,6 @@ return array(
'javelin-workflow',
'javelin-workboard-controller',
),
'442efd08' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
),
'44959b73' => array(
'javelin-util',
'javelin-uri',
@ -1434,6 +1426,17 @@ return array(
'69adf288' => array(
'javelin-install',
),
'6a726c55' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
),
'6b31879a' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1749,12 +1752,6 @@ return array(
'javelin-uri',
'phabricator-keyboard-shortcut',
),
'a9b946f8' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-history',
),
'a9f88de2' => array(
'javelin-behavior',
'javelin-dom',
@ -2005,6 +2002,11 @@ return array(
'd254d646' => array(
'javelin-util',
),
'd3f8623c' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'd4505101' => array(
'javelin-stratcom',
'javelin-install',
@ -2123,6 +2125,11 @@ return array(
'javelin-stratcom',
'javelin-vector',
),
'ed18356a' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
),
'edf8a145' => array(
'javelin-behavior',
'javelin-uri',

View file

@ -0,0 +1,5 @@
ALTER TABLE {$NAMESPACE}_repository.repository_pushevent
ADD requestIdentifier VARBINARY(12);
ALTER TABLE {$NAMESPACE}_repository.repository_pushevent
ADD UNIQUE KEY `key_request` (requestIdentifier);

View file

@ -0,0 +1,8 @@
ALTER TABLE {$NAMESPACE}_repository.repository_pushevent
ADD writeWait BIGINT UNSIGNED;
ALTER TABLE {$NAMESPACE}_repository.repository_pushevent
ADD readWait BIGINT UNSIGNED;
ALTER TABLE {$NAMESPACE}_repository.repository_pushevent
ADD hostWait BIGINT UNSIGNED;

View file

@ -187,6 +187,11 @@ if (strlen($remote_protocol)) {
$engine->setRemoteProtocol($remote_protocol);
}
$request_identifier = getenv(DiffusionCommitHookEngine::ENV_REQUEST);
if (strlen($request_identifier)) {
$engine->setRequestIdentifier($request_identifier);
}
try {
$err = $engine->execute();
} catch (DiffusionCommitHookRejectException $ex) {

View file

@ -8,6 +8,12 @@ require_once $root.'/scripts/__init_script__.php';
$ssh_log = PhabricatorSSHLog::getLog();
$request_identifier = Filesystem::readRandomCharacters(12);
$ssh_log->setData(
array(
'Q' => $request_identifier,
));
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('execute SSH requests'));
$args->setSynopsis(<<<EOSYNOPSIS
@ -248,6 +254,7 @@ try {
$workflow->setSSHUser($user);
$workflow->setOriginalArguments($original_argv);
$workflow->setIsClusterRequest($is_cluster_request);
$workflow->setRequestIdentifier($request_identifier);
$sock_stdin = fopen('php://stdin', 'r');
if (!$sock_stdin) {

View file

@ -2066,6 +2066,7 @@ phutil_register_library_map(array(
'PhabricatorAsanaConfigOptions' => 'applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php',
'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorAsanaSubtaskHasObjectEdgeType.php',
'PhabricatorAsanaTaskHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorAsanaTaskHasObjectEdgeType.php',
'PhabricatorAudioDocumentEngine' => 'applications/files/document/PhabricatorAudioDocumentEngine.php',
'PhabricatorAuditActionConstants' => 'applications/audit/constants/PhabricatorAuditActionConstants.php',
'PhabricatorAuditApplication' => 'applications/audit/application/PhabricatorAuditApplication.php',
'PhabricatorAuditCommentEditor' => 'applications/audit/editor/PhabricatorAuditCommentEditor.php',
@ -2808,6 +2809,8 @@ phutil_register_library_map(array(
'PhabricatorDividerEditField' => 'applications/transactions/editfield/PhabricatorDividerEditField.php',
'PhabricatorDividerProfileMenuItem' => 'applications/search/menuitem/PhabricatorDividerProfileMenuItem.php',
'PhabricatorDivinerApplication' => 'applications/diviner/application/PhabricatorDivinerApplication.php',
'PhabricatorDocumentEngine' => 'applications/files/document/PhabricatorDocumentEngine.php',
'PhabricatorDocumentRef' => 'applications/files/document/PhabricatorDocumentRef.php',
'PhabricatorDoorkeeperApplication' => 'applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php',
'PhabricatorDraft' => 'applications/draft/storage/PhabricatorDraft.php',
'PhabricatorDraftDAO' => 'applications/draft/storage/PhabricatorDraftDAO.php',
@ -2998,6 +3001,7 @@ phutil_register_library_map(array(
'PhabricatorFileDataController' => 'applications/files/controller/PhabricatorFileDataController.php',
'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php',
'PhabricatorFileDeleteTransaction' => 'applications/files/xaction/PhabricatorFileDeleteTransaction.php',
'PhabricatorFileDocumentController' => 'applications/files/controller/PhabricatorFileDocumentController.php',
'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php',
'PhabricatorFileEditController' => 'applications/files/controller/PhabricatorFileEditController.php',
'PhabricatorFileEditEngine' => 'applications/files/editor/PhabricatorFileEditEngine.php',
@ -3011,7 +3015,6 @@ phutil_register_library_map(array(
'PhabricatorFileImageMacro' => 'applications/macro/storage/PhabricatorFileImageMacro.php',
'PhabricatorFileImageProxyController' => 'applications/files/controller/PhabricatorFileImageProxyController.php',
'PhabricatorFileImageTransform' => 'applications/files/transform/PhabricatorFileImageTransform.php',
'PhabricatorFileInfoController' => 'applications/files/controller/PhabricatorFileInfoController.php',
'PhabricatorFileIntegrityException' => 'applications/files/exception/PhabricatorFileIntegrityException.php',
'PhabricatorFileLightboxController' => 'applications/files/controller/PhabricatorFileLightboxController.php',
'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php',
@ -3047,6 +3050,7 @@ phutil_register_library_map(array(
'PhabricatorFileUploadException' => 'applications/files/exception/PhabricatorFileUploadException.php',
'PhabricatorFileUploadSource' => 'applications/files/uploadsource/PhabricatorFileUploadSource.php',
'PhabricatorFileUploadSourceByteLimitException' => 'applications/files/uploadsource/PhabricatorFileUploadSourceByteLimitException.php',
'PhabricatorFileViewController' => 'applications/files/controller/PhabricatorFileViewController.php',
'PhabricatorFileinfoSetupCheck' => 'applications/config/check/PhabricatorFileinfoSetupCheck.php',
'PhabricatorFilesApplication' => 'applications/files/application/PhabricatorFilesApplication.php',
'PhabricatorFilesApplicationStorageEnginePanel' => 'applications/files/applicationpanel/PhabricatorFilesApplicationStorageEnginePanel.php',
@ -3137,6 +3141,7 @@ phutil_register_library_map(array(
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php',
'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php',
'PhabricatorHeraldContentSource' => 'applications/herald/contentsource/PhabricatorHeraldContentSource.php',
'PhabricatorHexdumpDocumentEngine' => 'applications/files/document/PhabricatorHexdumpDocumentEngine.php',
'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php',
'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php',
'PhabricatorHomeConstants' => 'applications/home/constants/PhabricatorHomeConstants.php',
@ -3155,6 +3160,7 @@ phutil_register_library_map(array(
'PhabricatorIconSet' => 'applications/files/iconset/PhabricatorIconSet.php',
'PhabricatorIconSetEditField' => 'applications/transactions/editfield/PhabricatorIconSetEditField.php',
'PhabricatorIconSetIcon' => 'applications/files/iconset/PhabricatorIconSetIcon.php',
'PhabricatorImageDocumentEngine' => 'applications/files/document/PhabricatorImageDocumentEngine.php',
'PhabricatorImageMacroRemarkupRule' => 'applications/macro/markup/PhabricatorImageMacroRemarkupRule.php',
'PhabricatorImageRemarkupRule' => 'applications/files/markup/PhabricatorImageRemarkupRule.php',
'PhabricatorImageTransformer' => 'applications/files/PhabricatorImageTransformer.php',
@ -3181,9 +3187,11 @@ phutil_register_library_map(array(
'PhabricatorIteratorFileUploadSource' => 'applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php',
'PhabricatorJIRAAuthProvider' => 'applications/auth/provider/PhabricatorJIRAAuthProvider.php',
'PhabricatorJSONConfigType' => 'applications/config/type/PhabricatorJSONConfigType.php',
'PhabricatorJSONDocumentEngine' => 'applications/files/document/PhabricatorJSONDocumentEngine.php',
'PhabricatorJSONExportFormat' => 'infrastructure/export/format/PhabricatorJSONExportFormat.php',
'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php',
'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php',
'PhabricatorJupyterDocumentEngine' => 'applications/files/document/PhabricatorJupyterDocumentEngine.php',
'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php',
'PhabricatorKeyValueSerializingCacheProxy' => 'applications/cache/PhabricatorKeyValueSerializingCacheProxy.php',
'PhabricatorKeyboardRemarkupRule' => 'infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php',
@ -3513,6 +3521,7 @@ phutil_register_library_map(array(
'PhabricatorOwnersPathsSearchEngineAttachment' => 'applications/owners/engineextension/PhabricatorOwnersPathsSearchEngineAttachment.php',
'PhabricatorOwnersSchemaSpec' => 'applications/owners/storage/PhabricatorOwnersSchemaSpec.php',
'PhabricatorOwnersSearchField' => 'applications/owners/searchfield/PhabricatorOwnersSearchField.php',
'PhabricatorPDFDocumentEngine' => 'applications/files/document/PhabricatorPDFDocumentEngine.php',
'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php',
'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php',
'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php',
@ -3961,6 +3970,7 @@ phutil_register_library_map(array(
'PhabricatorRemarkupCowsayBlockInterpreter' => 'infrastructure/markup/interpreter/PhabricatorRemarkupCowsayBlockInterpreter.php',
'PhabricatorRemarkupCustomBlockRule' => 'infrastructure/markup/rule/PhabricatorRemarkupCustomBlockRule.php',
'PhabricatorRemarkupCustomInlineRule' => 'infrastructure/markup/rule/PhabricatorRemarkupCustomInlineRule.php',
'PhabricatorRemarkupDocumentEngine' => 'applications/files/document/PhabricatorRemarkupDocumentEngine.php',
'PhabricatorRemarkupEditField' => 'applications/transactions/editfield/PhabricatorRemarkupEditField.php',
'PhabricatorRemarkupFigletBlockInterpreter' => 'infrastructure/markup/interpreter/PhabricatorRemarkupFigletBlockInterpreter.php',
'PhabricatorRemarkupUIExample' => 'applications/uiexample/examples/PhabricatorRemarkupUIExample.php',
@ -4213,6 +4223,7 @@ phutil_register_library_map(array(
'PhabricatorSlug' => 'infrastructure/util/PhabricatorSlug.php',
'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php',
'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php',
'PhabricatorSourceDocumentEngine' => 'applications/files/document/PhabricatorSourceDocumentEngine.php',
'PhabricatorSpaceEditField' => 'applications/transactions/editfield/PhabricatorSpaceEditField.php',
'PhabricatorSpacesApplication' => 'applications/spaces/application/PhabricatorSpacesApplication.php',
'PhabricatorSpacesArchiveController' => 'applications/spaces/controller/PhabricatorSpacesArchiveController.php',
@ -4354,6 +4365,7 @@ phutil_register_library_map(array(
'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php',
'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php',
'PhabricatorTextConfigType' => 'applications/config/type/PhabricatorTextConfigType.php',
'PhabricatorTextDocumentEngine' => 'applications/files/document/PhabricatorTextDocumentEngine.php',
'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php',
'PhabricatorTextExportFormat' => 'infrastructure/export/format/PhabricatorTextExportFormat.php',
'PhabricatorTextListConfigType' => 'applications/config/type/PhabricatorTextListConfigType.php',
@ -4481,7 +4493,9 @@ phutil_register_library_map(array(
'PhabricatorVCSResponse' => 'applications/repository/response/PhabricatorVCSResponse.php',
'PhabricatorVersionedDraft' => 'applications/draft/storage/PhabricatorVersionedDraft.php',
'PhabricatorVeryWowEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php',
'PhabricatorVideoDocumentEngine' => 'applications/files/document/PhabricatorVideoDocumentEngine.php',
'PhabricatorViewerDatasource' => 'applications/people/typeahead/PhabricatorViewerDatasource.php',
'PhabricatorVoidDocumentEngine' => 'applications/files/document/PhabricatorVoidDocumentEngine.php',
'PhabricatorWatcherHasObjectEdgeType' => 'applications/transactions/edges/PhabricatorWatcherHasObjectEdgeType.php',
'PhabricatorWebContentSource' => 'infrastructure/contentsource/PhabricatorWebContentSource.php',
'PhabricatorWebServerSetupCheck' => 'applications/config/check/PhabricatorWebServerSetupCheck.php',
@ -7497,6 +7511,7 @@ phutil_register_library_map(array(
'PhabricatorAsanaConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorAsanaTaskHasObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorAudioDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorAuditActionConstants' => 'Phobject',
'PhabricatorAuditApplication' => 'PhabricatorApplication',
'PhabricatorAuditCommentEditor' => 'PhabricatorEditor',
@ -8362,6 +8377,8 @@ phutil_register_library_map(array(
'PhabricatorDividerEditField' => 'PhabricatorEditField',
'PhabricatorDividerProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorDivinerApplication' => 'PhabricatorApplication',
'PhabricatorDocumentEngine' => 'Phobject',
'PhabricatorDocumentRef' => 'Phobject',
'PhabricatorDoorkeeperApplication' => 'PhabricatorApplication',
'PhabricatorDraft' => 'PhabricatorDraftDAO',
'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
@ -8581,6 +8598,7 @@ phutil_register_library_map(array(
'PhabricatorFileDataController' => 'PhabricatorFileController',
'PhabricatorFileDeleteController' => 'PhabricatorFileController',
'PhabricatorFileDeleteTransaction' => 'PhabricatorFileTransactionType',
'PhabricatorFileDocumentController' => 'PhabricatorFileController',
'PhabricatorFileDropUploadController' => 'PhabricatorFileController',
'PhabricatorFileEditController' => 'PhabricatorFileController',
'PhabricatorFileEditEngine' => 'PhabricatorEditEngine',
@ -8604,7 +8622,6 @@ phutil_register_library_map(array(
),
'PhabricatorFileImageProxyController' => 'PhabricatorFileController',
'PhabricatorFileImageTransform' => 'PhabricatorFileTransform',
'PhabricatorFileInfoController' => 'PhabricatorFileController',
'PhabricatorFileIntegrityException' => 'Exception',
'PhabricatorFileLightboxController' => 'PhabricatorFileController',
'PhabricatorFileLinkView' => 'AphrontTagView',
@ -8640,6 +8657,7 @@ phutil_register_library_map(array(
'PhabricatorFileUploadException' => 'Exception',
'PhabricatorFileUploadSource' => 'Phobject',
'PhabricatorFileUploadSourceByteLimitException' => 'Exception',
'PhabricatorFileViewController' => 'PhabricatorFileController',
'PhabricatorFileinfoSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorFilesApplication' => 'PhabricatorApplication',
'PhabricatorFilesApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel',
@ -8738,6 +8756,7 @@ phutil_register_library_map(array(
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
'PhabricatorHeraldApplication' => 'PhabricatorApplication',
'PhabricatorHeraldContentSource' => 'PhabricatorContentSource',
'PhabricatorHexdumpDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorHomeApplication' => 'PhabricatorApplication',
'PhabricatorHomeConstants' => 'PhabricatorHomeController',
@ -8756,6 +8775,7 @@ phutil_register_library_map(array(
'PhabricatorIconSet' => 'Phobject',
'PhabricatorIconSetEditField' => 'PhabricatorEditField',
'PhabricatorIconSetIcon' => 'Phobject',
'PhabricatorImageDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorImageMacroRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorImageRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorImageTransformer' => 'Phobject',
@ -8781,9 +8801,11 @@ phutil_register_library_map(array(
'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource',
'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider',
'PhabricatorJSONConfigType' => 'PhabricatorTextConfigType',
'PhabricatorJSONDocumentEngine' => 'PhabricatorTextDocumentEngine',
'PhabricatorJSONExportFormat' => 'PhabricatorExportFormat',
'PhabricatorJavelinLinter' => 'ArcanistLinter',
'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorJupyterDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache',
'PhabricatorKeyValueSerializingCacheProxy' => 'PhutilKeyValueCacheProxy',
'PhabricatorKeyboardRemarkupRule' => 'PhutilRemarkupRule',
@ -9154,6 +9176,7 @@ phutil_register_library_map(array(
'PhabricatorOwnersPathsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorOwnersSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorOwnersSearchField' => 'PhabricatorSearchTokenizerField',
'PhabricatorPDFDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPHID' => 'Phobject',
'PhabricatorPHIDConstants' => 'Phobject',
@ -9696,6 +9719,7 @@ phutil_register_library_map(array(
'PhabricatorRemarkupCowsayBlockInterpreter' => 'PhutilRemarkupBlockInterpreter',
'PhabricatorRemarkupCustomBlockRule' => 'PhutilRemarkupBlockRule',
'PhabricatorRemarkupCustomInlineRule' => 'PhutilRemarkupRule',
'PhabricatorRemarkupDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorRemarkupEditField' => 'PhabricatorEditField',
'PhabricatorRemarkupFigletBlockInterpreter' => 'PhutilRemarkupBlockInterpreter',
'PhabricatorRemarkupUIExample' => 'PhabricatorUIExample',
@ -10021,6 +10045,7 @@ phutil_register_library_map(array(
'PhabricatorSlug' => 'Phobject',
'PhabricatorSlugTestCase' => 'PhabricatorTestCase',
'PhabricatorSourceCodeView' => 'AphrontView',
'PhabricatorSourceDocumentEngine' => 'PhabricatorTextDocumentEngine',
'PhabricatorSpaceEditField' => 'PhabricatorEditField',
'PhabricatorSpacesApplication' => 'PhabricatorApplication',
'PhabricatorSpacesArchiveController' => 'PhabricatorSpacesController',
@ -10168,6 +10193,7 @@ phutil_register_library_map(array(
'PhabricatorTestWorker' => 'PhabricatorWorker',
'PhabricatorTextAreaEditField' => 'PhabricatorEditField',
'PhabricatorTextConfigType' => 'PhabricatorConfigType',
'PhabricatorTextDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorTextEditField' => 'PhabricatorEditField',
'PhabricatorTextExportFormat' => 'PhabricatorExportFormat',
'PhabricatorTextListConfigType' => 'PhabricatorTextConfigType',
@ -10330,7 +10356,9 @@ phutil_register_library_map(array(
'PhabricatorVCSResponse' => 'AphrontResponse',
'PhabricatorVersionedDraft' => 'PhabricatorDraftDAO',
'PhabricatorVeryWowEnglishTranslation' => 'PhutilTranslation',
'PhabricatorVideoDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorViewerDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorVoidDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorWatcherHasObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorWebContentSource' => 'PhabricatorContentSource',
'PhabricatorWebServerSetupCheck' => 'PhabricatorSetupCheck',

View file

@ -28,6 +28,7 @@ abstract class AphrontResponse extends Phobject {
'connect-src' => array(),
'frame-src' => array(),
'form-action' => array(),
'object-src' => array(),
);
}
@ -163,8 +164,10 @@ abstract class AphrontResponse extends Phobject {
$csp[] = "frame-ancestors 'none'";
}
// Block relics of the old world: Flash, Java applets, and so on.
$csp[] = "object-src 'none'";
// Block relics of the old world: Flash, Java applets, and so on. Note
// that Chrome prevents the user from viewing PDF documents if they are
// served with a policy which excludes the domain they are served from.
$csp[] = $this->newContentSecurityPolicy('object-src', "'none'");
// Don't allow forms to submit offsite.

View file

@ -47,6 +47,10 @@ final class PhabricatorAccessLogConfigOptions
's' => pht('The system user.'),
'S' => pht('The system sudo user.'),
'k' => pht('ID of the SSH key used to authenticate the request.'),
// TODO: This is a reasonable thing to support in the HTTP access
// log, too.
'Q' => pht('A random, unique string which identifies the request.'),
);
$http_desc = pht(

View file

@ -279,7 +279,7 @@ final class DarkConsoleServicesPlugin extends DarkConsolePlugin {
$analysis,
);
if ($row['trace']) {
if (isset($row['trace'])) {
$rows[] = array(
null,
null,

View file

@ -80,7 +80,7 @@ abstract class DifferentialRevisionActionTransaction
DifferentialRevision $revision) {
return array(
array(),
null,
array(),
);
}

View file

@ -12,6 +12,7 @@ final class DiffusionCommitHookEngine extends Phobject {
const ENV_REPOSITORY = 'PHABRICATOR_REPOSITORY';
const ENV_USER = 'PHABRICATOR_USER';
const ENV_REQUEST = 'PHABRICATOR_REQUEST';
const ENV_REMOTE_ADDRESS = 'PHABRICATOR_REMOTE_ADDRESS';
const ENV_REMOTE_PROTOCOL = 'PHABRICATOR_REMOTE_PROTOCOL';
@ -25,6 +26,7 @@ final class DiffusionCommitHookEngine extends Phobject {
private $subversionRepository;
private $remoteAddress;
private $remoteProtocol;
private $requestIdentifier;
private $transactionKey;
private $mercurialHook;
private $mercurialCommits = array();
@ -58,6 +60,15 @@ final class DiffusionCommitHookEngine extends Phobject {
return $this->remoteAddress;
}
public function setRequestIdentifier($request_identifier) {
$this->requestIdentifier = $request_identifier;
return $this;
}
public function getRequestIdentifier() {
return $this->requestIdentifier;
}
public function setSubversionTransactionInfo($transaction, $repository) {
$this->subversionTransaction = $transaction;
$this->subversionRepository = $repository;
@ -620,6 +631,7 @@ final class DiffusionCommitHookEngine extends Phobject {
$env = array(
self::ENV_REPOSITORY => $this->getRepository()->getPHID(),
self::ENV_USER => $this->getViewer()->getUsername(),
self::ENV_REQUEST => $this->getRequestIdentifier(),
self::ENV_REMOTE_PROTOCOL => $this->getRemoteProtocol(),
self::ENV_REMOTE_ADDRESS => $this->getRemoteAddress(),
);
@ -1081,16 +1093,24 @@ final class DiffusionCommitHookEngine extends Phobject {
->setDevicePHID($device_phid)
->setRepositoryPHID($this->getRepository()->getPHID())
->attachRepository($this->getRepository())
->setEpoch(time());
->setEpoch(PhabricatorTime::getNow());
}
private function newPushEvent() {
$viewer = $this->getViewer();
return PhabricatorRepositoryPushEvent::initializeNewEvent($viewer)
$event = PhabricatorRepositoryPushEvent::initializeNewEvent($viewer)
->setRepositoryPHID($this->getRepository()->getPHID())
->setRemoteAddress($this->getRemoteAddress())
->setRemoteProtocol($this->getRemoteProtocol())
->setEpoch(time());
->setEpoch(PhabricatorTime::getNow());
$identifier = $this->getRequestIdentifier();
if (strlen($identifier)) {
$event->setRequestIdentifier($identifier);
}
return $event;
}
private function rejectEnormousChanges(array $content_updates) {

View file

@ -135,6 +135,11 @@ abstract class DiffusionCommandEngine extends Phobject {
$future->setEnv($env);
// See T13108. By default, don't let any cluster command run indefinitely
// to try to avoid cases where `git fetch` hangs for some reason and we're
// left sitting with a held lock forever.
$future->setTimeout(phutil_units('15 minutes in seconds'));
return $future;
}

View file

@ -151,8 +151,8 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
$this->logLine(
pht(
'Waiting up to %s second(s) for a cluster read lock on "%s"...',
new PhutilNumber($lock_wait),
'Acquiring read lock for repository "%s" on device "%s"...',
$repository->getDisplayName(),
$device->getName()));
try {
@ -308,18 +308,37 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
$write_lock->useSpecificConnection($locked_connection);
$lock_wait = phutil_units('2 minutes in seconds');
$this->logLine(
pht(
'Waiting up to %s second(s) for a cluster write lock...',
new PhutilNumber($lock_wait)));
'Acquiring write lock for repository "%s"...',
$repository->getDisplayName()));
$lock_wait = phutil_units('2 minutes in seconds');
try {
$start = PhabricatorTime::getNow();
$write_lock->lock($lock_wait);
$waited = (PhabricatorTime::getNow() - $start);
$write_wait_start = microtime(true);
$start = PhabricatorTime::getNow();
$step_wait = 1;
while (true) {
try {
$write_lock->lock((int)floor($step_wait));
$write_wait_end = microtime(true);
break;
} catch (PhutilLockException $ex) {
$waited = (PhabricatorTime::getNow() - $start);
if ($waited > $lock_wait) {
throw $ex;
}
$this->logActiveWriter($viewer, $repository);
}
// Wait a little longer before the next message we print.
$step_wait = $step_wait + 0.5;
$step_wait = min($step_wait, 3);
}
$waited = (PhabricatorTime::getNow() - $start);
if ($waited) {
$this->logLine(
pht(
@ -354,12 +373,14 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
'documentation for instructions.'));
}
$read_wait_start = microtime(true);
try {
$max_version = $this->synchronizeWorkingCopyBeforeRead();
} catch (Exception $ex) {
$write_lock->unlock();
throw $ex;
}
$read_wait_end = microtime(true);
$pid = getmypid();
$hash = Filesystem::readRandomCharacters(12);
@ -378,6 +399,15 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
$this->clusterWriteVersion = $max_version;
$this->clusterWriteLock = $write_lock;
$write_wait = ($write_wait_end - $write_wait_start);
$read_wait = ($read_wait_end - $read_wait_start);
$log = $this->logger;
if ($log) {
$log->writeClusterEngineLogProperty('readWait', $read_wait);
$log->writeClusterEngineLogProperty('writeWait', $write_wait);
}
}
@ -763,4 +793,32 @@ final class DiffusionRepositoryClusterEngine extends Phobject {
}
}
private function logActiveWriter(
PhabricatorUser $viewer,
PhabricatorRepository $repository) {
$writer = PhabricatorRepositoryWorkingCopyVersion::loadWriter(
$repository->getPHID());
if (!$writer) {
$this->logLine(pht('Waiting on another user to finish writing...'));
return;
}
$user_phid = $writer->getWriteProperty('userPHID');
$device_phid = $writer->getWriteProperty('devicePHID');
$epoch = $writer->getWriteProperty('epoch');
$phids = array($user_phid, $device_phid);
$handles = $viewer->loadHandles($phids);
$duration = (PhabricatorTime::getNow() - $epoch) + 1;
$this->logLine(
pht(
'Waiting for %s to finish writing (on device "%s" for %ss)...',
$handles[$user_phid]->getName(),
$handles[$device_phid]->getName(),
new PhutilNumber($duration)));
}
}

View file

@ -3,5 +3,6 @@
interface DiffusionRepositoryClusterEngineLogInterface {
public function writeClusterEngineLogMessage($message);
public function writeClusterEngineLogProperty($key, $value);
}

View file

@ -14,6 +14,8 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
}
protected function executeRepositoryOperations() {
$host_wait_start = microtime(true);
$repository = $this->getRepository();
$viewer = $this->getSSHUser();
$device = AlmanacKeys::getLiveDevice();
@ -71,6 +73,14 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
PhabricatorRepositoryStatusMessage::CODE_OKAY);
$this->waitForGitClient();
$host_wait_end = microtime(true);
$this->updatePushLogWithTimingInformation(
$this->getClusterEngineLogProperty('writeWait'),
$this->getClusterEngineLogProperty('readWait'),
($host_wait_end - $host_wait_start));
}
return $err;
@ -89,4 +99,37 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
->execute();
}
private function updatePushLogWithTimingInformation(
$write_wait,
$read_wait,
$host_wait) {
if ($write_wait !== null) {
$write_wait = (int)(1000000 * $write_wait);
}
if ($read_wait !== null) {
$read_wait = (int)(1000000 * $read_wait);
}
if ($host_wait !== null) {
$host_wait = (int)(1000000 * $host_wait);
}
$identifier = $this->getRequestIdentifier();
$event = new PhabricatorRepositoryPushEvent();
$conn = $event->establishConnection('w');
queryfx(
$conn,
'UPDATE %T SET writeWait = %nd, readWait = %nd, hostWait = %nd
WHERE requestIdentifier = %s',
$event->getTableName(),
$write_wait,
$read_wait,
$host_wait,
$identifier);
}
}

View file

@ -4,6 +4,8 @@ abstract class DiffusionGitSSHWorkflow
extends DiffusionSSHWorkflow
implements DiffusionRepositoryClusterEngineLogInterface {
private $engineLogProperties = array();
protected function writeError($message) {
// Git assumes we'll add our own newlines.
return parent::writeError($message."\n");
@ -14,6 +16,14 @@ abstract class DiffusionGitSSHWorkflow
$this->getErrorChannel()->update();
}
public function writeClusterEngineLogProperty($key, $value) {
$this->engineLogProperties[$key] = $value;
}
protected function getClusterEngineLogProperty($key, $default = null) {
return idx($this->engineLogProperties, $key, $default);
}
protected function identifyRepository() {
$args = $this->getArgs();
$path = head($args->getArg('dir'));

View file

@ -30,6 +30,11 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
DiffusionCommitHookEngine::ENV_REMOTE_PROTOCOL => 'ssh',
);
$identifier = $this->getRequestIdentifier();
if ($identifier !== null) {
$env[DiffusionCommitHookEngine::ENV_REQUEST] = $identifier;
}
$remote_address = $this->getSSHRemoteAddress();
if ($remote_address !== null) {
$env[DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS] = $remote_address;

View file

@ -12,6 +12,7 @@ final class PhabricatorFeedStoryPublisher extends Phobject {
private $mailRecipientPHIDs = array();
private $notifyAuthor;
private $mailTags = array();
private $unexpandablePHIDs = array();
public function setMailTags(array $mail_tags) {
$this->mailTags = $mail_tags;
@ -46,6 +47,15 @@ final class PhabricatorFeedStoryPublisher extends Phobject {
return $this;
}
public function setUnexpandablePHIDs(array $unexpandable_phids) {
$this->unexpandablePHIDs = $unexpandable_phids;
return $this;
}
public function getUnexpandablePHIDs() {
return $this->unexpandablePHIDs;
}
public function setStoryType($story_type) {
$this->storyType = $story_type;
return $this;
@ -254,10 +264,36 @@ final class PhabricatorFeedStoryPublisher extends Phobject {
}
private function expandRecipients(array $phids) {
return id(new PhabricatorMetaMTAMemberQuery())
$expanded_phids = id(new PhabricatorMetaMTAMemberQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs($phids)
->executeExpansion();
// Filter out unexpandable PHIDs from the results. The typical case for
// this is that resigned reviewers should not be notified just because
// they are a member of some project or package reviewer.
$original_map = array_fuse($phids);
$unexpandable_map = array_fuse($this->unexpandablePHIDs);
foreach ($expanded_phids as $key => $phid) {
// We can keep this expanded PHID if it was present originally.
if (isset($original_map[$phid])) {
continue;
}
// We can also keep it if it isn't marked as unexpandable.
if (!isset($unexpandable_map[$phid])) {
continue;
}
// If it's unexpandable and we produced it by expanding recipients,
// throw it away.
unset($expanded_phids[$key]);
}
$expanded_phids = array_values($expanded_phids);
return $expanded_phids;
}
/**

View file

@ -69,9 +69,15 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
public function getRoutes() {
return array(
'/F(?P<id>[1-9]\d*)' => 'PhabricatorFileInfoController',
'/F(?P<id>[1-9]\d*)(?:\$(?P<lines>\d+(?:-\d+)?))?'
=> 'PhabricatorFileViewController',
'/file/' => array(
'(query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorFileListController',
'view/(?P<id>[^/]+)/'.
'(?:(?P<engineKey>[^/]+)/)?'.
'(?:\$(?P<lines>\d+(?:-\d+)?))?'
=> 'PhabricatorFileViewController',
'info/(?P<phid>[^/]+)/' => 'PhabricatorFileViewController',
'upload/' => 'PhabricatorFileUploadController',
'dropupload/' => 'PhabricatorFileDropUploadController',
'compose/' => 'PhabricatorFileComposeController',
@ -80,7 +86,6 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorFileDeleteController',
$this->getEditRoutePattern('edit/')
=> 'PhabricatorFileEditController',
'info/(?P<phid>[^/]+)/' => 'PhabricatorFileInfoController',
'imageproxy/' => 'PhabricatorFileImageProxyController',
'transforms/(?P<id>[1-9]\d*)/' =>
'PhabricatorFileTransformListController',
@ -89,6 +94,8 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
'iconset/(?P<key>[^/]+)/' => array(
'select/' => 'PhabricatorFileIconSetSelectController',
),
'document/(?P<engineKey>[^/]+)/(?P<phid>[^/]+)/'
=> 'PhabricatorFileDocumentController',
) + $this->getResourceSubroutes(),
);
}

View file

@ -45,6 +45,8 @@ final class PhabricatorFilesConfigOptions
'video/ogg' => 'video/ogg',
'video/webm' => 'video/webm',
'video/quicktime' => 'video/quicktime',
'application/pdf' => 'application/pdf',
);
$image_default = array(

View file

@ -73,11 +73,14 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
list($begin, $end) = $response->parseHTTPRange($range);
}
$is_viewable = $file->isViewableInBrowser();
if (!$file->isViewableInBrowser()) {
$is_download = true;
}
$request_type = $request->getHTTPHeader('X-Phabricator-Request-Type');
$is_lfs = ($request_type == 'git-lfs');
if ($is_viewable && !$is_download) {
if (!$is_download) {
$response->setMimeType($file->getViewableMimeType());
} else {
$is_post = $request->isHTTPPost();
@ -109,6 +112,19 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
$response->setContentLength($file->getByteSize());
$response->setContentIterator($iterator);
// In Chrome, we must permit this domain in "object-src" CSP when serving a
// PDF or the browser will refuse to render it.
if (!$is_download && $file->isPDF()) {
$request_uri = id(clone $request->getAbsoluteRequestURI())
->setPath(null)
->setFragment(null)
->setQueryParams(array());
$response->addContentSecurityPolicyURI(
'object-src',
(string)$request_uri);
}
return $response;
}

View file

@ -0,0 +1,113 @@
<?php
final class PhabricatorFileDocumentController
extends PhabricatorFileController {
private $file;
private $engine;
private $ref;
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$file_phid = $request->getURIData('phid');
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phid))
->executeOne();
if (!$file) {
return $this->newErrorResponse(
pht(
'This file ("%s") does not exist or could not be loaded.',
$file_phid));
}
$this->file = $file;
$ref = id(new PhabricatorDocumentRef())
->setFile($file);
$this->ref = $ref;
$engines = PhabricatorDocumentEngine::getEnginesForRef($viewer, $ref);
$engine_key = $request->getURIData('engineKey');
if (!isset($engines[$engine_key])) {
return $this->newErrorResponse(
pht(
'The engine ("%s") is unknown, or unable to render this document.',
$engine_key));
}
$engine = $engines[$engine_key];
$this->engine = $engine;
try {
$content = $engine->newDocument($ref);
} catch (Exception $ex) {
return $this->newErrorResponse($ex->getMessage());
}
return $this->newContentResponse($content);
}
private function newErrorResponse($message) {
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-error',
),
array(
id(new PHUIIconView())
->setIcon('fa-exclamation-triangle red'),
' ',
$message,
));
return $this->newContentResponse($container);
}
private function newContentResponse($content) {
$viewer = $this->getViewer();
$request = $this->getRequest();
$file = $this->file;
$engine = $this->engine;
$ref = $this->ref;
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())
->setContent(
array(
'markup' => hsprintf('%s', $content),
));
}
$crumbs = $this->buildApplicationCrumbs();
if ($file) {
$crumbs->addTextCrumb($file->getMonogram(), $file->getInfoURI());
}
$label = $engine->getViewAsLabel($ref);
if ($label) {
$crumbs->addTextCrumb($label);
}
$crumbs->setBorder(true);
$content_frame = id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($content);
$page_frame = id(new PHUITwoColumnView())
->setFooter($content_frame);
return $this->newPage()
->setCrumbs($crumbs)
->setTitle(
array(
$ref->getName(),
pht('Standalone'),
))
->appendChild($page_frame);
}
}

View file

@ -1,6 +1,6 @@
<?php
final class PhabricatorFileInfoController extends PhabricatorFileController {
final class PhabricatorFileViewController extends PhabricatorFileController {
public function shouldAllowPublic() {
return true;
@ -23,6 +23,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
}
return id(new AphrontRedirectResponse())->setURI($file->getInfoURI());
}
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withIDs(array($id))
@ -62,31 +63,34 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
$timeline = $this->buildTransactionView($file);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(
'F'.$file->getID(),
$this->getApplicationURI("/info/{$phid}/"));
$file->getMonogram(),
$file->getInfoURI());
$crumbs->setBorder(true);
$object_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('File'))
->setHeaderText(pht('File Metadata'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
$this->buildPropertyViews($object_box, $file);
$title = $file->getName();
$file_content = $this->newFileContent($file);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(array(
$object_box,
$timeline,
));
->setMainColumn(
array(
$object_box,
$file_content,
$timeline,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($file->getPHID()))
->appendChild($view);
}
private function buildTransactionView(PhabricatorFile $file) {
@ -325,61 +329,6 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
$viewer->renderHandleList($phids));
}
if ($file->isViewableImage()) {
$image = phutil_tag(
'img',
array(
'src' => $file->getViewURI(),
'class' => 'phui-property-list-image',
));
$linked_image = phutil_tag(
'a',
array(
'href' => $file->getViewURI(),
),
$image);
$media = id(new PHUIPropertyListView())
->addImageContent($linked_image);
$box->addPropertyList($media);
} else if ($file->isVideo()) {
$video = phutil_tag(
'video',
array(
'controls' => 'controls',
'class' => 'phui-property-list-video',
),
phutil_tag(
'source',
array(
'src' => $file->getViewURI(),
'type' => $file->getMimeType(),
)));
$media = id(new PHUIPropertyListView())
->addImageContent($video);
$box->addPropertyList($media);
} else if ($file->isAudio()) {
$audio = phutil_tag(
'audio',
array(
'controls' => 'controls',
'class' => 'phui-property-list-audio',
),
phutil_tag(
'source',
array(
'src' => $file->getViewURI(),
'type' => $file->getMimeType(),
)));
$media = id(new PHUIPropertyListView())
->addImageContent($audio);
$box->addPropertyList($media);
}
$engine = $this->loadStorageEngine($file);
if ($engine) {
if ($engine->isChunkEngine()) {
@ -453,5 +402,99 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
return $engine;
}
private function newFileContent(PhabricatorFile $file) {
$viewer = $this->getViewer();
$request = $this->getRequest();
$ref = id(new PhabricatorDocumentRef())
->setFile($file);
$engines = PhabricatorDocumentEngine::getEnginesForRef($viewer, $ref);
$engine_key = $request->getURIData('engineKey');
if (!isset($engines[$engine_key])) {
$engine_key = head_key($engines);
}
$engine = $engines[$engine_key];
$lines = $request->getURILineRange('lines', 1000);
if ($lines) {
$engine->setHighlightedLines(range($lines[0], $lines[1]));
}
$views = array();
foreach ($engines as $candidate_key => $candidate_engine) {
$label = $candidate_engine->getViewAsLabel($ref);
if ($label === null) {
continue;
}
$view_uri = '/file/view/'.$file->getID().'/'.$candidate_key.'/';
$view_icon = $candidate_engine->getViewAsIconIcon($ref);
$view_color = $candidate_engine->getViewAsIconColor($ref);
$loading = $candidate_engine->newLoadingContent($ref);
$views[] = array(
'viewKey' => $candidate_engine->getDocumentEngineKey(),
'icon' => $view_icon,
'color' => $view_color,
'name' => $label,
'engineURI' => $candidate_engine->getRenderURI($ref),
'viewURI' => $view_uri,
'loadingMarkup' => hsprintf('%s', $loading),
);
}
$viewport_id = celerity_generate_unique_node_id();
$control_id = celerity_generate_unique_node_id();
$icon = $engine->newDocumentIcon($ref);
if ($engine->shouldRenderAsync($ref)) {
$content = $engine->newLoadingContent($ref);
$config = array(
'renderControlID' => $control_id,
);
} else {
$content = $engine->newDocument($ref);
$config = array();
}
Javelin::initBehavior('document-engine', $config);
$viewport = phutil_tag(
'div',
array(
'id' => $viewport_id,
),
$content);
$meta = array(
'viewportID' => $viewport_id,
'viewKey' => $engine->getDocumentEngineKey(),
'views' => $views,
'standaloneURI' => $engine->getRenderURI($ref),
);
$view_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('View Options'))
->setIcon('fa-file-image-o')
->setColor(PHUIButtonView::GREY)
->setID($control_id)
->setMetadata($meta)
->setDropdown(true)
->addSigil('document-engine-view-dropdown');
$header = id(new PHUIHeaderView())
->setHeaderIcon($icon)
->setHeader($ref->getName())
->addActionLink($view_button);
return id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setHeader($header)
->appendChild($viewport);
}
}

View file

@ -0,0 +1,69 @@
<?php
final class PhabricatorAudioDocumentEngine
extends PhabricatorDocumentEngine {
const ENGINEKEY = 'audio';
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
return pht('View as Audio');
}
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-file-sound-o';
}
protected function getByteLengthLimit() {
return null;
}
protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
$file = $ref->getFile();
if ($file) {
return $file->isAudio();
}
$viewable_types = PhabricatorEnv::getEnvConfig('files.viewable-mime-types');
$viewable_types = array_keys($viewable_types);
$audio_types = PhabricatorEnv::getEnvConfig('files.audio-mime-types');
$audio_types = array_keys($audio_types);
return
$ref->hasAnyMimeType($viewable_types) &&
$ref->hasAnyMimeType($audio_types);
}
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
$file = $ref->getFile();
if ($file) {
$source_uri = $file->getViewURI();
} else {
throw new PhutilMethodNotImplementedException();
}
$mime_type = $ref->getMimeType();
$audio = phutil_tag(
'audio',
array(
'controls' => 'controls',
),
phutil_tag(
'source',
array(
'src' => $source_uri,
'type' => $mime_type,
)));
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-audio',
),
$audio);
return $container;
}
}

View file

@ -0,0 +1,214 @@
<?php
abstract class PhabricatorDocumentEngine
extends Phobject {
private $viewer;
private $highlightedLines = array();
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
final public function setHighlightedLines(array $highlighted_lines) {
$this->highlightedLines = $highlighted_lines;
return $this;
}
final public function getHighlightedLines() {
return $this->highlightedLines;
}
final public function canRenderDocument(PhabricatorDocumentRef $ref) {
return $this->canRenderDocumentType($ref);
}
public function shouldRenderAsync(PhabricatorDocumentRef $ref) {
return false;
}
abstract protected function canRenderDocumentType(
PhabricatorDocumentRef $ref);
final public function newDocument(PhabricatorDocumentRef $ref) {
$can_complete = $this->canRenderCompleteDocument($ref);
$can_partial = $this->canRenderPartialDocument($ref);
if (!$can_complete && !$can_partial) {
return $this->newMessage(
pht(
'This document is too large to be rendered inline. (The document '.
'is %s bytes, the limit for this engine is %s bytes.)',
new PhutilNumber($ref->getByteLength()),
new PhutilNumber($this->getByteLengthLimit())));
}
return $this->newDocumentContent($ref);
}
final public function newDocumentIcon(PhabricatorDocumentRef $ref) {
return id(new PHUIIconView())
->setIcon($this->getDocumentIconIcon($ref));
}
abstract protected function newDocumentContent(
PhabricatorDocumentRef $ref);
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-file-o';
}
protected function getDocumentRenderingText(PhabricatorDocumentRef $ref) {
return pht('Loading...');
}
final public function getDocumentEngineKey() {
return $this->getPhobjectClassConstant('ENGINEKEY');
}
final public static function getAllEngines() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getDocumentEngineKey')
->execute();
}
final public function newSortVector(PhabricatorDocumentRef $ref) {
$content_score = $this->getContentScore($ref);
// Prefer engines which can render the entire file over engines which
// can only render a header, and engines which can render a header over
// engines which can't render anything.
if ($this->canRenderCompleteDocument($ref)) {
$limit_score = 0;
} else if ($this->canRenderPartialDocument($ref)) {
$limit_score = 1;
} else {
$limit_score = 2;
}
return id(new PhutilSortVector())
->addInt($limit_score)
->addInt(-$content_score);
}
protected function getContentScore(PhabricatorDocumentRef $ref) {
return 2000;
}
abstract public function getViewAsLabel(PhabricatorDocumentRef $ref);
public function getViewAsIconIcon(PhabricatorDocumentRef $ref) {
$can_complete = $this->canRenderCompleteDocument($ref);
$can_partial = $this->canRenderPartialDocument($ref);
if (!$can_complete && !$can_partial) {
return 'fa-times';
}
return $this->getDocumentIconIcon($ref);
}
public function getViewAsIconColor(PhabricatorDocumentRef $ref) {
$can_complete = $this->canRenderCompleteDocument($ref);
if (!$can_complete) {
return 'grey';
}
return null;
}
public function getRenderURI(PhabricatorDocumentRef $ref) {
$file = $ref->getFile();
if (!$file) {
throw new PhutilMethodNotImplementedException();
}
$engine_key = $this->getDocumentEngineKey();
$file_phid = $file->getPHID();
return "/file/document/{$engine_key}/{$file_phid}/";
}
final public static function getEnginesForRef(
PhabricatorUser $viewer,
PhabricatorDocumentRef $ref) {
$engines = self::getAllEngines();
foreach ($engines as $key => $engine) {
$engine = id(clone $engine)
->setViewer($viewer);
if (!$engine->canRenderDocument($ref)) {
unset($engines[$key]);
continue;
}
$engines[$key] = $engine;
}
if (!$engines) {
throw new Exception(pht('No content engine can render this document.'));
}
$vectors = array();
foreach ($engines as $key => $usable_engine) {
$vectors[$key] = $usable_engine->newSortVector($ref);
}
$vectors = msortv($vectors, 'getSelf');
return array_select_keys($engines, array_keys($vectors));
}
protected function getByteLengthLimit() {
return (1024 * 1024 * 8);
}
protected function canRenderCompleteDocument(PhabricatorDocumentRef $ref) {
$limit = $this->getByteLengthLimit();
if ($limit) {
$length = $ref->getByteLength();
if ($length > $limit) {
return false;
}
}
return true;
}
protected function canRenderPartialDocument(PhabricatorDocumentRef $ref) {
return false;
}
protected function newMessage($message) {
return phutil_tag(
'div',
array(
'class' => 'document-engine-error',
),
$message);
}
final public function newLoadingContent(PhabricatorDocumentRef $ref) {
$spinner = id(new PHUIIconView())
->setIcon('fa-gear')
->addClass('ph-spin');
return phutil_tag(
'div',
array(
'class' => 'document-engine-loading',
),
array(
$spinner,
$this->getDocumentRenderingText($ref),
));
}
}

View file

@ -0,0 +1,134 @@
<?php
final class PhabricatorDocumentRef
extends Phobject {
private $name;
private $mimeType;
private $file;
private $byteLength;
private $snippet;
public function setFile(PhabricatorFile $file) {
$this->file = $file;
return $this;
}
public function getFile() {
return $this->file;
}
public function setMimeType($mime_type) {
$this->mimeType = $mime_type;
return $this;
}
public function getMimeType() {
if ($this->mimeType !== null) {
return $this->mimeType;
}
if ($this->file) {
return $this->file->getMimeType();
}
return null;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
if ($this->name !== null) {
return $this->name;
}
if ($this->file) {
return $this->file->getName();
}
return null;
}
public function setByteLength($length) {
$this->byteLength = $length;
return $this;
}
public function getByteLength() {
if ($this->byteLength !== null) {
return $this->byteLength;
}
if ($this->file) {
return (int)$this->file->getByteSize();
}
return null;
}
public function loadData($begin = null, $end = null) {
if ($this->file) {
$iterator = $this->file->getFileDataIterator($begin, $end);
$result = '';
foreach ($iterator as $chunk) {
$result .= $chunk;
}
return $result;
}
throw new PhutilMethodNotImplementedException();
}
public function hasAnyMimeType(array $candidate_types) {
$mime_full = $this->getMimeType();
$mime_parts = explode(';', $mime_full);
$mime_type = head($mime_parts);
$mime_type = $this->normalizeMimeType($mime_type);
foreach ($candidate_types as $candidate_type) {
if ($this->normalizeMimeType($candidate_type) === $mime_type) {
return true;
}
}
return false;
}
private function normalizeMimeType($mime_type) {
$mime_type = trim($mime_type);
$mime_type = phutil_utf8_strtolower($mime_type);
return $mime_type;
}
public function isProbablyText() {
$snippet = $this->getSnippet();
return (strpos($snippet, "\0") === false);
}
public function isProbablyJSON() {
if (!$this->isProbablyText()) {
return false;
}
$snippet = $this->getSnippet();
if (!preg_match('/^\s*[{[]/', $snippet)) {
return false;
}
return phutil_is_utf8($snippet);
}
public function getSnippet() {
if ($this->snippet === null) {
$this->snippet = $this->loadData(null, (1024 * 1024 * 1));
}
return $this->snippet;
}
}

View file

@ -0,0 +1,114 @@
<?php
final class PhabricatorHexdumpDocumentEngine
extends PhabricatorDocumentEngine {
const ENGINEKEY = 'hexdump';
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
return pht('View as Hexdump');
}
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-microchip';
}
protected function getByteLengthLimit() {
return (1024 * 1024 * 1);
}
protected function getContentScore(PhabricatorDocumentRef $ref) {
return 500;
}
protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
return true;
}
protected function canRenderPartialDocument(PhabricatorDocumentRef $ref) {
return true;
}
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
$limit = $this->getByteLengthLimit();
$length = $ref->getByteLength();
$is_partial = false;
if ($limit) {
if ($length > $limit) {
$is_partial = true;
$length = $limit;
}
}
$content = $ref->loadData(null, $length);
$output = array();
$offset = 0;
$lines = str_split($content, 16);
foreach ($lines as $line) {
$output[] = sprintf(
'%08x %- 23s %- 23s %- 16s',
$offset,
$this->renderHex(substr($line, 0, 8)),
$this->renderHex(substr($line, 8)),
$this->renderBytes($line));
$offset += 16;
}
$output = implode("\n", $output);
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-hexdump PhabricatorMonospaced',
),
$output);
$message = null;
if ($is_partial) {
$message = $this->newMessage(
pht(
'This document is too large to be completely rendered inline. The '.
'first %s bytes are shown.',
new PhutilNumber($limit)));
}
return array(
$message,
$container,
);
}
private function renderHex($bytes) {
$length = strlen($bytes);
$output = array();
for ($ii = 0; $ii < $length; $ii++) {
$output[] = sprintf('%02x', ord($bytes[$ii]));
}
return implode(' ', $output);
}
private function renderBytes($bytes) {
$length = strlen($bytes);
$output = array();
for ($ii = 0; $ii < $length; $ii++) {
$chr = $bytes[$ii];
$ord = ord($chr);
if ($ord < 0x20 || $ord >= 0x7F) {
$chr = '.';
}
$output[] = $chr;
}
return implode('', $output);
}
}

View file

@ -0,0 +1,71 @@
<?php
final class PhabricatorImageDocumentEngine
extends PhabricatorDocumentEngine {
const ENGINEKEY = 'image';
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
return pht('View as Image');
}
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-file-image-o';
}
protected function getByteLengthLimit() {
return (1024 * 1024 * 64);
}
protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
$file = $ref->getFile();
if ($file) {
return $file->isViewableImage();
}
$viewable_types = PhabricatorEnv::getEnvConfig('files.viewable-mime-types');
$viewable_types = array_keys($viewable_types);
$image_types = PhabricatorEnv::getEnvConfig('files.image-mime-types');
$image_types = array_keys($image_types);
return
$ref->hasAnyMimeType($viewable_types) &&
$ref->hasAnyMimeType($image_types);
}
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
$file = $ref->getFile();
if ($file) {
$source_uri = $file->getViewURI();
} else {
// We could use a "data:" URI here. It's not yet clear if or when we'll
// have a ref but no backing file.
throw new PhutilMethodNotImplementedException();
}
$image = phutil_tag(
'img',
array(
'src' => $source_uri,
));
$linked_image = phutil_tag(
'a',
array(
'href' => $source_uri,
'rel' => 'noreferrer',
),
$image);
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-image',
),
$linked_image);
return $container;
}
}

View file

@ -0,0 +1,59 @@
<?php
final class PhabricatorJSONDocumentEngine
extends PhabricatorTextDocumentEngine {
const ENGINEKEY = 'json';
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
return pht('View as JSON');
}
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-database';
}
protected function getContentScore(PhabricatorDocumentRef $ref) {
if (preg_match('/\.json\z/', $ref->getName())) {
return 2000;
}
if ($ref->isProbablyJSON()) {
return 1750;
}
return 500;
}
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
$raw_data = $this->loadTextData($ref);
try {
$data = phutil_json_decode($raw_data);
if (preg_match('/^\s*\[/', $raw_data)) {
$content = id(new PhutilJSON())->encodeAsList($data);
} else {
$content = id(new PhutilJSON())->encodeFormatted($data);
}
$message = null;
$content = PhabricatorSyntaxHighlighter::highlightWithLanguage(
'json',
$content);
} catch (PhutilJSONParserException $ex) {
$message = $this->newMessage(
pht(
'This document is not valid JSON: %s',
$ex->getMessage()));
$content = $raw_data;
}
return array(
$message,
$this->newTextDocumentContent($content),
);
}
}

View file

@ -0,0 +1,313 @@
<?php
final class PhabricatorJupyterDocumentEngine
extends PhabricatorDocumentEngine {
const ENGINEKEY = 'jupyter';
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
return pht('View as Jupyter Notebook');
}
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-sun-o';
}
protected function getDocumentRenderingText(PhabricatorDocumentRef $ref) {
return pht('Rendering Jupyter Notebook...');
}
public function shouldRenderAsync(PhabricatorDocumentRef $ref) {
return true;
}
protected function getContentScore(PhabricatorDocumentRef $ref) {
$name = $ref->getName();
if (preg_match('/\\.ipynb\z/i', $name)) {
return 2000;
}
return 500;
}
protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
return $ref->isProbablyJSON();
}
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
$viewer = $this->getViewer();
$content = $ref->loadData();
try {
$data = phutil_json_decode($content);
} catch (PhutilJSONParserException $ex) {
return $this->newMessage(
pht(
'This is not a valid JSON document and can not be rendered as '.
'a Jupyter notebook: %s.',
$ex->getMessage()));
}
if (!is_array($data)) {
return $this->newMessage(
pht(
'This document does not encode a valid JSON object and can not '.
'be rendered as a Jupyter notebook.'));
}
$nbformat = idx($data, 'nbformat');
if (!strlen($nbformat)) {
return $this->newMessage(
pht(
'This document is missing an "nbformat" field. Jupyter notebooks '.
'must have this field.'));
}
if ($nbformat !== 4) {
return $this->newMessage(
pht(
'This Jupyter notebook uses an unsupported version of the file '.
'format (found version %s, expected version 4).',
$nbformat));
}
$cells = idx($data, 'cells');
if (!is_array($cells)) {
return $this->newMessage(
pht(
'This Jupyter notebook does not specify a list of "cells".'));
}
if (!$cells) {
return $this->newMessage(
pht(
'This Jupyter notebook does not specify any notebook cells.'));
}
$rows = array();
foreach ($cells as $cell) {
$rows[] = $this->renderJupyterCell($viewer, $cell);
}
$notebook_table = phutil_tag(
'table',
array(
'class' => 'jupyter-notebook',
),
$rows);
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-jupyter',
),
$notebook_table);
return $container;
}
private function renderJupyterCell(
PhabricatorUser $viewer,
array $cell) {
list($label, $content) = $this->renderJupyterCellContent($viewer, $cell);
$label_cell = phutil_tag(
'th',
array(),
$label);
$content_cell = phutil_tag(
'td',
array(),
$content);
return phutil_tag(
'tr',
array(),
array(
$label_cell,
$content_cell,
));
}
private function renderJupyterCellContent(
PhabricatorUser $viewer,
array $cell) {
$cell_type = idx($cell, 'cell_type');
switch ($cell_type) {
case 'markdown':
return $this->newMarkdownCell($cell);
case 'code':
return $this->newCodeCell($cell);
}
return $this->newRawCell(id(new PhutilJSON())->encodeFormatted($cell));
}
private function newRawCell($content) {
return array(
null,
phutil_tag(
'div',
array(
'class' => 'jupyter-cell-raw PhabricatorMonospaced',
),
$content),
);
}
private function newMarkdownCell(array $cell) {
$content = idx($cell, 'source');
if (!is_array($content)) {
$content = array();
}
$content = implode('', $content);
$content = phutil_escape_html_newlines($content);
return array(
null,
phutil_tag(
'div',
array(
'class' => 'jupyter-cell-markdown',
),
$content),
);
}
private function newCodeCell(array $cell) {
$execution_count = idx($cell, 'execution_count');
if ($execution_count) {
$label = 'In ['.$execution_count.']:';
} else {
$label = null;
}
$content = idx($cell, 'source');
if (!is_array($content)) {
$content = array();
}
$content = implode('', $content);
$content = PhabricatorSyntaxHighlighter::highlightWithLanguage(
'python',
$content);
$outputs = array();
$output_list = idx($cell, 'outputs');
if (is_array($output_list)) {
foreach ($output_list as $output) {
$outputs[] = $this->newOutput($output);
}
}
return array(
$label,
array(
phutil_tag(
'div',
array(
'class' => 'jupyter-cell-code PhabricatorMonospaced remarkup-code',
),
array(
$content,
)),
$outputs,
),
);
}
private function newOutput(array $output) {
if (!is_array($output)) {
return pht('<Invalid Output>');
}
$classes = array(
'jupyter-output',
'PhabricatorMonospaced',
);
$output_name = idx($output, 'name');
switch ($output_name) {
case 'stderr':
$classes[] = 'jupyter-output-stderr';
break;
}
$output_type = idx($output, 'output_type');
switch ($output_type) {
case 'execute_result':
case 'display_data':
$data = idx($output, 'data');
$image_formats = array(
'image/png',
'image/jpeg',
'image/jpg',
'image/gif',
);
foreach ($image_formats as $image_format) {
if (!isset($data[$image_format])) {
continue;
}
$raw_data = $data[$image_format];
if (!is_array($raw_data)) {
continue;
}
$raw_data = implode('', $raw_data);
$content = phutil_tag(
'img',
array(
'src' => 'data:'.$image_format.';base64,'.$raw_data,
));
break 2;
}
if (isset($data['text/html'])) {
$content = $data['text/html'];
$classes[] = 'jupyter-output-html';
break;
}
if (isset($data['application/javascript'])) {
$content = $data['application/javascript'];
$classes[] = 'jupyter-output-html';
break;
}
if (isset($data['text/plain'])) {
$content = $data['text/plain'];
break;
}
break;
case 'stream':
default:
$content = idx($output, 'text');
if (!is_array($content)) {
$content = array();
}
$content = implode('', $content);
break;
}
return phutil_tag(
'div',
array(
'class' => implode(' ', $classes),
),
$content);
}
}

View file

@ -0,0 +1,57 @@
<?php
final class PhabricatorPDFDocumentEngine
extends PhabricatorDocumentEngine {
const ENGINEKEY = 'pdf';
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
return pht('View as PDF');
}
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-file-pdf-o';
}
protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
// Since we just render a link to the document anyway, we don't need to
// check anything fancy in config to see if the MIME type is actually
// viewable.
return $ref->hasAnyMimeType(
array(
'application/pdf',
));
}
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
$viewer = $this->getViewer();
$file = $ref->getFile();
if ($file) {
$source_uri = $file->getViewURI();
} else {
throw new PhutilMethodNotImplementedException();
}
$name = $ref->getName();
$length = $ref->getByteLength();
$link = id(new PhabricatorFileLinkView())
->setViewer($viewer)
->setFileName($name)
->setFileViewURI($source_uri)
->setFileViewable(true)
->setFileSize(phutil_format_bytes($length));
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-pdf',
),
$link);
return $container;
}
}

View file

@ -0,0 +1,47 @@
<?php
final class PhabricatorRemarkupDocumentEngine
extends PhabricatorDocumentEngine {
const ENGINEKEY = 'remarkup';
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
return pht('View as Remarkup');
}
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-file-text-o';
}
protected function getContentScore(PhabricatorDocumentRef $ref) {
$name = $ref->getName();
if (preg_match('/\\.remarkup\z/i', $name)) {
return 2000;
}
return 500;
}
protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
return $ref->isProbablyText();
}
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
$viewer = $this->getViewer();
$content = $ref->loadData();
$content = phutil_utf8ize($content);
$remarkup = new PHUIRemarkupView($viewer, $content);
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-remarkup',
),
$remarkup);
return $container;
}
}

View file

@ -0,0 +1,30 @@
<?php
final class PhabricatorSourceDocumentEngine
extends PhabricatorTextDocumentEngine {
const ENGINEKEY = 'source';
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
return pht('View as Source');
}
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-code';
}
protected function getContentScore(PhabricatorDocumentRef $ref) {
return 1500;
}
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
$content = $this->loadTextData($ref);
$content = PhabricatorSyntaxHighlighter::highlightWithFilename(
$ref->getName(),
$content);
return $this->newTextDocumentContent($content);
}
}

View file

@ -0,0 +1,33 @@
<?php
abstract class PhabricatorTextDocumentEngine
extends PhabricatorDocumentEngine {
protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
return $ref->isProbablyText();
}
protected function newTextDocumentContent($content) {
$lines = phutil_split_lines($content);
$view = id(new PhabricatorSourceCodeView())
->setHighlights($this->getHighlightedLines())
->setLines($lines);
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-text',
),
$view);
return $container;
}
protected function loadTextData(PhabricatorDocumentRef $ref) {
$content = $ref->loadData();
$content = phutil_utf8ize($content);
return $content;
}
}

View file

@ -0,0 +1,75 @@
<?php
final class PhabricatorVideoDocumentEngine
extends PhabricatorDocumentEngine {
const ENGINEKEY = 'video';
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
return pht('View as Video');
}
protected function getContentScore(PhabricatorDocumentRef $ref) {
// Some video documents can be rendered as either video or audio, but we
// want to prefer video.
return 2500;
}
protected function getByteLengthLimit() {
return null;
}
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-film';
}
protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
$file = $ref->getFile();
if ($file) {
return $file->isVideo();
}
$viewable_types = PhabricatorEnv::getEnvConfig('files.viewable-mime-types');
$viewable_types = array_keys($viewable_types);
$video_types = PhabricatorEnv::getEnvConfig('files.video-mime-types');
$video_types = array_keys($video_types);
return
$ref->hasAnyMimeType($viewable_types) &&
$ref->hasAnyMimeType($video_types);
}
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
$file = $ref->getFile();
if ($file) {
$source_uri = $file->getViewURI();
} else {
throw new PhutilMethodNotImplementedException();
}
$mime_type = $ref->getMimeType();
$video = phutil_tag(
'video',
array(
'controls' => 'controls',
),
phutil_tag(
'source',
array(
'src' => $source_uri,
'type' => $mime_type,
)));
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-video',
),
$video);
return $container;
}
}

View file

@ -0,0 +1,42 @@
<?php
final class PhabricatorVoidDocumentEngine
extends PhabricatorDocumentEngine {
const ENGINEKEY = 'void';
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
return null;
}
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-file';
}
protected function getContentScore(PhabricatorDocumentRef $ref) {
return 1000;
}
protected function getByteLengthLimit() {
return null;
}
protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
return true;
}
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
$message = pht(
'No document engine can render the contents of this file.');
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-message',
),
$message);
return $container;
}
}

View file

@ -930,6 +930,19 @@ final class PhabricatorFile extends PhabricatorFileDAO
return idx($mime_map, $mime_type);
}
public function isPDF() {
if (!$this->isViewableInBrowser()) {
return false;
}
$mime_map = array(
'application/pdf' => 'application/pdf',
);
$mime_type = $this->getMimeType();
return idx($mime_map, $mime_type);
}
public function isTransformableImage() {
// NOTE: The way the 'gd' extension works in PHP is that you can install it
// with support for only some file types, so it might be able to handle

View file

@ -98,54 +98,66 @@ final class PhabricatorRepositoryPushLogSearchEngine
$viewer = $this->requireViewer();
$fields = array(
$fields[] = id(new PhabricatorIDExportField())
id(new PhabricatorIDExportField())
->setKey('pushID')
->setLabel(pht('Push ID')),
$fields[] = id(new PhabricatorStringExportField())
id(new PhabricatorStringExportField())
->setKey('unique')
->setLabel(pht('Unique')),
id(new PhabricatorStringExportField())
->setKey('protocol')
->setLabel(pht('Protocol')),
$fields[] = id(new PhabricatorPHIDExportField())
id(new PhabricatorPHIDExportField())
->setKey('repositoryPHID')
->setLabel(pht('Repository PHID')),
$fields[] = id(new PhabricatorStringExportField())
id(new PhabricatorStringExportField())
->setKey('repository')
->setLabel(pht('Repository')),
$fields[] = id(new PhabricatorPHIDExportField())
id(new PhabricatorPHIDExportField())
->setKey('pusherPHID')
->setLabel(pht('Pusher PHID')),
$fields[] = id(new PhabricatorStringExportField())
id(new PhabricatorStringExportField())
->setKey('pusher')
->setLabel(pht('Pusher')),
$fields[] = id(new PhabricatorPHIDExportField())
id(new PhabricatorPHIDExportField())
->setKey('devicePHID')
->setLabel(pht('Device PHID')),
$fields[] = id(new PhabricatorStringExportField())
id(new PhabricatorStringExportField())
->setKey('device')
->setLabel(pht('Device')),
$fields[] = id(new PhabricatorStringExportField())
id(new PhabricatorStringExportField())
->setKey('type')
->setLabel(pht('Ref Type')),
$fields[] = id(new PhabricatorStringExportField())
id(new PhabricatorStringExportField())
->setKey('name')
->setLabel(pht('Ref Name')),
$fields[] = id(new PhabricatorStringExportField())
id(new PhabricatorStringExportField())
->setKey('old')
->setLabel(pht('Ref Old')),
$fields[] = id(new PhabricatorStringExportField())
id(new PhabricatorStringExportField())
->setKey('new')
->setLabel(pht('Ref New')),
$fields[] = id(new PhabricatorIntExportField())
id(new PhabricatorIntExportField())
->setKey('flags')
->setLabel(pht('Flags')),
$fields[] = id(new PhabricatorStringListExportField())
id(new PhabricatorStringListExportField())
->setKey('flagNames')
->setLabel(pht('Flag Names')),
$fields[] = id(new PhabricatorIntExportField())
id(new PhabricatorIntExportField())
->setKey('result')
->setLabel(pht('Result')),
$fields[] = id(new PhabricatorStringExportField())
id(new PhabricatorStringExportField())
->setKey('resultName')
->setLabel(pht('Result Name')),
id(new PhabricatorIntExportField())
->setKey('writeWait')
->setLabel(pht('Write Wait (us)')),
id(new PhabricatorIntExportField())
->setKey('readWait')
->setLabel(pht('Read Wait (us)')),
id(new PhabricatorIntExportField())
->setKey('hostWait')
->setLabel(pht('Host Wait (us)')),
);
if ($viewer->getIsAdmin()) {
@ -209,6 +221,7 @@ final class PhabricatorRepositoryPushLogSearchEngine
$map = array(
'pushID' => $event->getID(),
'unique' => $event->getRequestIdentifier(),
'protocol' => $event->getRemoteProtocol(),
'repositoryPHID' => $repository_phid,
'repository' => $repository_name,
@ -224,6 +237,9 @@ final class PhabricatorRepositoryPushLogSearchEngine
'flagNames' => $flag_names,
'result' => $result,
'resultName' => $result_name,
'writeWait' => $event->getWriteWait(),
'readWait' => $event->getReadWait(),
'hostWait' => $event->getHostWait(),
);
if ($viewer->getIsAdmin()) {

View file

@ -11,10 +11,14 @@ final class PhabricatorRepositoryPushEvent
protected $repositoryPHID;
protected $epoch;
protected $pusherPHID;
protected $requestIdentifier;
protected $remoteAddress;
protected $remoteProtocol;
protected $rejectCode;
protected $rejectDetails;
protected $writeWait;
protected $readWait;
protected $hostWait;
private $repository = self::ATTACHABLE;
private $logs = self::ATTACHABLE;
@ -29,15 +33,23 @@ final class PhabricatorRepositoryPushEvent
self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'requestIdentifier' => 'bytes12?',
'remoteAddress' => 'ipaddress?',
'remoteProtocol' => 'text32?',
'rejectCode' => 'uint32',
'rejectDetails' => 'text64?',
'writeWait' => 'uint64?',
'readWait' => 'uint64?',
'hostWait' => 'uint64?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_repository' => array(
'columns' => array('repositoryPHID'),
),
'key_request' => array(
'columns' => array('requestIdentifier'),
'unique' => true,
),
),
) + parent::getConfiguration();
}

View file

@ -28,6 +28,17 @@ final class PhabricatorRepositoryWorkingCopyVersion
) + parent::getConfiguration();
}
public function getWriteProperty($key, $default = null) {
// The "writeProperties" don't currently get automatically serialized or
// deserialized. Perhaps they should.
try {
$properties = phutil_json_decode($this->writeProperties);
return idx($properties, $key, $default);
} catch (Exception $ex) {
return null;
}
}
public static function loadVersions($repository_phid) {
$version = new self();
$conn_w = $version->establishConnection('w');
@ -43,6 +54,27 @@ final class PhabricatorRepositoryWorkingCopyVersion
return $version->loadAllFromArray($rows);
}
public static function loadWriter($repository_phid) {
$version = new self();
$conn_w = $version->establishConnection('w');
$table = $version->getTableName();
// We're forcing this read to go to the master.
$row = queryfx_one(
$conn_w,
'SELECT * FROM %T WHERE repositoryPHID = %s AND isWriting = 1
LIMIT 1',
$table,
$repository_phid);
if (!$row) {
return null;
}
return $version->loadFromArray($row);
}
public static function getReadLock($repository_phid, $device_phid) {
$repository_hash = PhabricatorHash::digestForIndex($repository_phid);
$device_hash = PhabricatorHash::digestForIndex($device_phid);

View file

@ -104,7 +104,7 @@ final class PhabricatorFerretFulltextStorageEngine
// Reorder the results so that the highest-ranking results come first,
// no matter which object types they belong to.
$metadata = msort($metadata, 'getRelevanceSortVector');
$metadata = msortv($metadata, 'getRelevanceSortVector');
$list = array_select_keys($list, array_keys($metadata)) + $list;
$result_slice = array_slice($list, $offset, $limit, true);

View file

@ -3199,6 +3199,11 @@ abstract class PhabricatorApplicationTransactionEditor
$story_type = $this->getFeedStoryType();
$story_data = $this->getFeedStoryData($object, $xactions);
$unexpandable_phids = $this->mailUnexpandablePHIDs;
if (!is_array($unexpandable_phids)) {
$unexpandable_phids = array();
}
id(new PhabricatorFeedStoryPublisher())
->setStoryType($story_type)
->setStoryData($story_data)
@ -3207,6 +3212,7 @@ abstract class PhabricatorApplicationTransactionEditor
->setRelatedPHIDs($related_phids)
->setPrimaryObjectPHID($object->getPHID())
->setSubscribedPHIDs($subscribed_phids)
->setUnexpandablePHIDs($unexpandable_phids)
->setMailRecipientPHIDs($mailed_phids)
->setMailTags($this->getMailTags($object, $xactions))
->publish();

View file

@ -530,7 +530,7 @@ This renders: {icon camera color=blue}
For a list of available icons and colors, check the UIExamples application.
(The icons are sourced from
[[ http://fortawesome.github.io/Font-Awesome/ | FontAwesome ]], so you can also
[[ https://fontawesome.com/v4.7.0/icons/ | FontAwesome ]], so you can also
browse the collection there.)
You can add `spin` to make the icon spin:

View file

@ -13,6 +13,7 @@ abstract class PhabricatorSSHWorkflow
private $errorChannel;
private $isClusterRequest;
private $originalArguments;
private $requestIdentifier;
public function isExecutable() {
return false;
@ -89,6 +90,15 @@ abstract class PhabricatorSSHWorkflow
return $this->originalArguments;
}
public function setRequestIdentifier($request_identifier) {
$this->requestIdentifier = $request_identifier;
return $this;
}
public function getRequestIdentifier() {
return $this->requestIdentifier;
}
public function getSSHRemoteAddress() {
$ssh_client = getenv('SSH_CLIENT');
if (!strlen($ssh_client)) {

View file

@ -101,26 +101,39 @@ final class PhabricatorFileLinkView extends AphrontTagView {
}
protected function getTagName() {
return 'div';
if ($this->getFileDownloadURI()) {
return 'div';
} else {
return 'a';
}
}
protected function getTagAttributes() {
$mustcapture = true;
$sigil = 'lightboxable';
$meta = $this->getMeta();
$class = 'phabricator-remarkup-embed-layout-link';
if ($this->getCustomClass()) {
$class = $this->getCustomClass();
}
return array(
'href' => $this->getFileViewURI(),
'class' => $class,
'sigil' => $sigil,
'meta' => $meta,
'mustcapture' => $mustcapture,
$attributes = array(
'href' => $this->getFileViewURI(),
'target' => '_blank',
'rel' => 'noreferrer',
'class' => $class,
);
if ($this->getFilePHID()) {
$mustcapture = true;
$sigil = 'lightboxable';
$meta = $this->getMeta();
$attributes += array(
'sigil' => $sigil,
'meta' => $meta,
'mustcapture' => $mustcapture,
);
}
return $attributes;
}
protected function getTagContent() {
@ -131,16 +144,21 @@ final class PhabricatorFileLinkView extends AphrontTagView {
->setIcon($this->getFileIcon())
->addClass('phabricator-remarkup-embed-layout-icon');
$dl_icon = id(new PHUIIconView())
->setIcon('fa-download');
$download_link = null;
$download_link = phutil_tag(
'a',
array(
'class' => 'phabricator-remarkup-embed-layout-download',
'href' => $this->getFileDownloadURI(),
),
pht('Download'));
$download_uri = $this->getFileDownloadURI();
if ($download_uri) {
$dl_icon = id(new PHUIIconView())
->setIcon('fa-download');
$download_link = phutil_tag(
'a',
array(
'class' => 'phabricator-remarkup-embed-layout-download',
'href' => $download_uri,
),
pht('Download'));
}
$info = phutil_tag(
'span',

View file

@ -85,7 +85,11 @@ final class PhabricatorSourceCodeView extends AphrontView {
}
if ($this->canClickHighlight) {
$line_href = $base_uri.'$'.$line_number;
if ($base_uri) {
$line_href = $base_uri.'$'.$line_number;
} else {
$line_href = null;
}
$tag_number = phutil_tag(
'a',

View file

@ -307,9 +307,14 @@ final class PHUIHeaderView extends AphrontTagView {
$icon = null;
if ($this->headerIcon) {
$icon = id(new PHUIIconView())
->setIcon($this->headerIcon)
->addClass('phui-header-icon');
if ($this->headerIcon instanceof PHUIIconView) {
$icon = id(clone $this->headerIcon)
->addClass('phui-header-icon');
} else {
$icon = id(new PHUIIconView())
->setIcon($this->headerIcon)
->addClass('phui-header-icon');
}
}
$header_content = $this->header;

View file

@ -405,8 +405,9 @@ video.phabricator-media {
color: {$blacktext};
min-width: 256px;
position: relative;
/*height: 22px;*/
line-height: 20px;
overflow: hidden;
min-height: 38px;
}
.phabricator-remarkup-embed-layout-icon {
@ -426,6 +427,9 @@ video.phabricator-media {
.phabricator-remarkup-embed-layout-link:hover {
border-color: {$violet};
cursor: pointer;
}
.device-desktop .phabricator-remarkup-embed-layout-link:hover {
text-decoration: none;
}

View file

@ -14,14 +14,6 @@
margin-left: 8px;
}
.phabricator-source-code-view tr:first-child * {
padding-top: 8px;
}
.phabricator-source-code-view tr:last-child * {
padding-bottom: 8px;
}
.phabricator-source-code {
white-space: pre-wrap;
padding: 2px 8px 1px;
@ -45,12 +37,16 @@
white-space: nowrap;
}
th.phabricator-source-line a {
color: {$darkbluetext};
th.phabricator-source-line a,
th.phabricator-source-line span {
display: block;
padding: 2px 6px 1px 12px;
}
th.phabricator-source-line a {
color: {$darkbluetext};
}
th.phabricator-source-line a:hover {
background: {$paste.border};
text-decoration: none;
@ -60,10 +56,6 @@ th.phabricator-source-line a:hover {
background: {$paste.highlight};
}
.phabricator-source-highlight th.phabricator-source-line {
background: {$paste.border};
}
.phabricator-source-code-summary {
padding-bottom: 8px;
}

View file

@ -149,24 +149,6 @@ div.phui-property-list-stacked .phui-property-list-properties
}
.phui-property-list-image {
margin: auto;
max-width: 95%;
}
.phui-property-list-audio {
display: block;
margin: 16px auto;
width: 50%;
min-width: 240px;
}
.phui-property-list-video {
display: block;
margin: 0 auto;
max-width: 95%;
}
/* When tags appear in property lists, give them a little more vertical
spacing. */
.phui-property-list-value .phui-tag-view {
@ -220,3 +202,126 @@ div.phui-property-list-stacked .phui-property-list-properties
border-right: 1px solid {$lightblueborder};
border-bottom: 1px solid {$blueborder};
}
.document-engine-image img {
margin: 20px auto;
background: url('/rsrc/image/checker_light.png');
}
.device-desktop .document-engine-image img:hover {
background: url('/rsrc/image/checker_dark.png');
}
.document-engine-video video {
margin: 20px auto;
display: block;
max-width: 95%;
}
.document-engine-audio audio {
display: block;
margin: 16px auto;
width: 50%;
min-width: 240px;
}
.document-engine-message {
margin: 20px auto;
text-align: center;
color: {$greytext};
}
.document-engine-error {
margin: 20px;
padding: 12px;
text-align: center;
color: {$redtext};
background: {$sh-redbackground};
}
.document-engine-hexdump {
margin: 20px;
white-space: pre;
}
.document-engine-remarkup {
margin: 20px;
}
.document-engine-pdf {
margin: 20px;
text-align: center;
}
.document-engine-pdf .phabricator-remarkup-embed-layout-link {
text-align: left;
}
.document-engine-text .phabricator-source-code-container {
border: none;
}
.document-engine-jupyter {
overflow: hidden;
margin: 20px;
}
.document-engine-in-flight {
opacity: 0.25;
}
.document-engine-loading {
margin: 20px;
text-align: center;
color: {$lightgreytext};
}
.document-engine-loading .phui-icon-view {
display: block;
font-size: 48px;
color: {$lightgreyborder};
padding: 8px;
}
.jupyter-cell-raw {
white-space: pre-wrap;
background: {$lightgreybackground};
color: {$greytext};
padding: 8px;
}
.jupyter-cell-code {
white-space: pre-wrap;
background: {$lightgreybackground};
padding: 8px;
border: 1px solid {$lightgreyborder};
border-radius: 2px;
}
.jupyter-notebook > tbody > tr > th,
.jupyter-notebook > tbody > tr > td {
padding: 8px;
}
.jupyter-notebook > tbody > tr > th {
white-space: nowrap;
text-align: right;
min-width: 48px;
font-weight: bold;
}
.jupyter-output {
margin: 4px 0;
padding: 8px;
white-space: pre-wrap;
word-break: break-all;
}
.jupyter-output-stderr {
background: {$sh-redbackground};
}
.jupyter-output-html {
background: {$sh-indigobackground};
}

View file

@ -59,12 +59,15 @@ JX.install('Workflow', {
workflow.setDataWithListOfPairs(pairs);
workflow.setMethod(form.getAttribute('method'));
workflow.listen('finally', function() {
// Re-enable form elements
for (var ii = 0; ii < inputs.length; ii++) {
inputs[ii] && (inputs[ii].disabled = false);
var onfinally = JX.bind(workflow, function() {
if (!this._keepControlsDisabled) {
for (var ii = 0; ii < inputs.length; ii++) {
inputs[ii] && (inputs[ii].disabled = false);
}
}
});
workflow.listen('finally', onfinally);
return workflow;
},
@ -148,6 +151,11 @@ JX.install('Workflow', {
// NOTE: Don't remove the current dialog yet because additional
// handlers may still want to access the nodes.
// Disable whatever button the user clicked to prevent duplicate
// submission mistakes when you accidentally click a button multiple
// times. See T11145.
button.disabled = true;
active
.setURI(form.getAttribute('action') || active.getURI())
.setDataWithListOfPairs(data)
@ -242,6 +250,7 @@ JX.install('Workflow', {
_form: null,
_paused: 0,
_nextCallback: null,
_keepControlsDisabled: false,
getSourceForm: function() {
return this._form;
@ -283,6 +292,9 @@ JX.install('Workflow', {
this._pop();
}
// If we're redirecting, don't re-enable for controls.
this._keepControlsDisabled = true;
JX.$U(r.redirect).go();
} else if (r && r.dialog) {
this._push();

View file

@ -0,0 +1,143 @@
/**
* @provides javelin-behavior-document-engine
* @requires javelin-behavior
* javelin-dom
* javelin-stratcom
*/
JX.behavior('document-engine', function(config, statics) {
function onmenu(e) {
var node = e.getNode('document-engine-view-dropdown');
var data = JX.Stratcom.getData(node);
if (data.menu) {
return;
}
e.prevent();
var menu = new JX.PHUIXDropdownMenu(node);
var list = new JX.PHUIXActionListView();
var view;
var engines = [];
for (var ii = 0; ii < data.views.length; ii++) {
var spec = data.views[ii];
view = new JX.PHUIXActionView()
.setName(spec.name)
.setIcon(spec.icon)
.setIconColor(spec.color)
.setHref(spec.engineURI);
view.setHandler(JX.bind(null, function(spec, e) {
if (!e.isNormalClick()) {
return;
}
e.prevent();
menu.close();
onview(data, spec, false);
}, spec));
list.addItem(view);
engines.push({
spec: spec,
view: view
});
}
menu.setContent(list.getNode());
menu.listen('open', function() {
for (var ii = 0; ii < engines.length; ii++) {
var engine = engines[ii];
// Highlight the current rendering engine.
var is_selected = (engine.spec.viewKey == data.viewKey);
engine.view.setSelected(is_selected);
}
});
data.menu = menu;
menu.open();
}
function onview(data, spec, immediate) {
data.sequence = (data.sequence || 0) + 1;
var handler = JX.bind(null, onrender, data, data.sequence);
data.viewKey = spec.viewKey;
JX.History.replace(spec.viewURI);
new JX.Request(spec.engineURI, handler)
.send();
if (data.loadingView) {
// If we're already showing "Loading...", immediately change it to
// show the new document type.
onloading(data, spec);
} else if (!immediate) {
// Otherwise, grey out the document and show "Loading..." after a
// short delay. This prevents the content from flickering when rendering
// is fast.
var viewport = JX.$(data.viewportID);
JX.DOM.alterClass(viewport, 'document-engine-in-flight', true);
var load = JX.bind(null, onloading, data, spec);
data.loadTimer = setTimeout(load, 333);
}
}
function onloading(data, spec) {
data.loadingView = true;
var viewport = JX.$(data.viewportID);
JX.DOM.alterClass(viewport, 'document-engine-in-flight', false);
JX.DOM.setContent(viewport, JX.$H(spec.loadingMarkup));
}
function onrender(data, sequence, r) {
// If this isn't the most recent request we sent, throw it away. This can
// happen if the user makes multiple selections from the menu while we are
// still rendering the first view.
if (sequence != data.sequence) {
return;
}
if (data.loadTimer) {
clearTimeout(data.loadTimer);
data.loadTimer = null;
}
var viewport = JX.$(data.viewportID);
JX.DOM.alterClass(viewport, 'document-engine-in-flight', false);
data.loadingView = false;
JX.DOM.setContent(viewport, JX.$H(r.markup));
}
if (!statics.initialized) {
JX.Stratcom.listen('click', 'document-engine-view-dropdown', onmenu);
statics.initialized = true;
}
if (config.renderControlID) {
var control = JX.$(config.renderControlID);
var data = JX.Stratcom.getData(control);
for (var ii = 0; ii < data.views.length; ii++) {
if (data.views[ii].viewKey == data.viewKey) {
onview(data, data.views[ii], true);
break;
}
}
}
});

View file

@ -145,6 +145,10 @@ JX.behavior('phabricator-line-linker', function() {
var t = getRowNumber(target);
var uri = JX.Stratcom.getData(root).uri;
if (!uri) {
uri = ('' + window.location).split('$')[0];
}
origin = null;
target = null;
root = null;

View file

@ -12,6 +12,7 @@ JX.install('PHUIXActionView', {
_node: null,
_name: null,
_icon: 'none',
_iconColor: null,
_disabled: false,
_label: false,
_handler: null,
@ -79,6 +80,12 @@ JX.install('PHUIXActionView', {
return this;
},
setIconColor: function(color) {
this._iconColor = color;
this._buildIconNode(true);
return this;
},
setHref: function(href) {
this._href = href;
this._buildNameNode(true);
@ -129,6 +136,10 @@ JX.install('PHUIXActionView', {
icon_class = icon_class + ' grey';
}
if (this._iconColor) {
icon_class = icon_class + ' ' + this._iconColor;
}
JX.DOM.alterClass(node, icon_class, true);
if (this._iconNode && this._iconNode.parentNode) {