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:
commit
8c5aceec60
54 changed files with 2273 additions and 202 deletions
|
@ -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',
|
||||
|
|
|
@ -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);
|
8
resources/sql/autopatches/20180322.lock.02.wait.sql
Normal file
8
resources/sql/autopatches/20180322.lock.02.wait.sql
Normal 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;
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -279,7 +279,7 @@ final class DarkConsoleServicesPlugin extends DarkConsolePlugin {
|
|||
$analysis,
|
||||
);
|
||||
|
||||
if ($row['trace']) {
|
||||
if (isset($row['trace'])) {
|
||||
$rows[] = array(
|
||||
null,
|
||||
null,
|
||||
|
|
|
@ -80,7 +80,7 @@ abstract class DifferentialRevisionActionTransaction
|
|||
DifferentialRevision $revision) {
|
||||
return array(
|
||||
array(),
|
||||
null,
|
||||
array(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,5 +3,6 @@
|
|||
interface DiffusionRepositoryClusterEngineLogInterface {
|
||||
|
||||
public function writeClusterEngineLogMessage($message);
|
||||
public function writeClusterEngineLogProperty($key, $value);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
214
src/applications/files/document/PhabricatorDocumentEngine.php
Normal file
214
src/applications/files/document/PhabricatorDocumentEngine.php
Normal 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),
|
||||
));
|
||||
}
|
||||
|
||||
}
|
134
src/applications/files/document/PhabricatorDocumentRef.php
Normal file
134
src/applications/files/document/PhabricatorDocumentRef.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
|
|
20
webroot/rsrc/externals/javelin/lib/Workflow.js
vendored
20
webroot/rsrc/externals/javelin/lib/Workflow.js
vendored
|
@ -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();
|
||||
|
|
143
webroot/rsrc/js/application/files/behavior-document-engine.js
Normal file
143
webroot/rsrc/js/application/files/behavior-document-engine.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue