1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-02-20 10:48:40 +01:00

Promote 2023.17 to stable

This commit is contained in:
Aviv Eyal 2023-04-24 23:53:52 -07:00
commit aa27cccc87
81 changed files with 1456 additions and 1032 deletions

File diff suppressed because it is too large Load diff

View file

@ -9,10 +9,10 @@ return array(
'names' => array(
'conpherence.pkg.css' => '0e3cf785',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => '80481fe6',
'core.pkg.js' => 'd2de90d9',
'core.pkg.css' => 'b239afaa',
'core.pkg.js' => '66c49ca1',
'dark-console.pkg.js' => '187792c2',
'differential.pkg.css' => 'ffb69e3d',
'differential.pkg.css' => '609e63d4',
'differential.pkg.js' => 'c60bec1b',
'diffusion.pkg.css' => '42c75c37',
'diffusion.pkg.js' => '78c9885d',
@ -45,7 +45,7 @@ return array(
'rsrc/css/application/chatlog/chatlog.css' => 'abdc76ee',
'rsrc/css/application/conduit/conduit-api.css' => 'ce2cfc41',
'rsrc/css/application/config/config-options.css' => '16c920ae',
'rsrc/css/application/config/config-template.css' => '20babf50',
'rsrc/css/application/config/config-template.css' => 'e689dbbd',
'rsrc/css/application/config/setup-issue.css' => '5eed85b2',
'rsrc/css/application/config/unhandled-exception.css' => '9ecfc00d',
'rsrc/css/application/conpherence/color.css' => 'b17746b0',
@ -63,9 +63,9 @@ return array(
'rsrc/css/application/diff/diff-tree-view.css' => 'e2d3e222',
'rsrc/css/application/diff/inline-comment-summary.css' => '81eb368d',
'rsrc/css/application/differential/add-comment.css' => '7e5900d9',
'rsrc/css/application/differential/changeset-view.css' => '60c3d405',
'rsrc/css/application/differential/changeset-view.css' => 'bf159129',
'rsrc/css/application/differential/core.css' => '7300a73e',
'rsrc/css/application/differential/phui-inline-comment.css' => '9863a85e',
'rsrc/css/application/differential/phui-inline-comment.css' => 'a864426f',
'rsrc/css/application/differential/revision-comment.css' => '7dbc8d1d',
'rsrc/css/application/differential/revision-history.css' => '237a2979',
'rsrc/css/application/differential/revision-list.css' => '93d2df7d',
@ -77,7 +77,7 @@ return array(
'rsrc/css/application/feed/feed.css' => 'd8b6e3f8',
'rsrc/css/application/files/global-drag-and-drop.css' => '1d2713a4',
'rsrc/css/application/flag/flag.css' => '2b77be8d',
'rsrc/css/application/harbormaster/harbormaster.css' => '8dfe16b2',
'rsrc/css/application/harbormaster/harbormaster.css' => 'd98decda',
'rsrc/css/application/herald/herald-test.css' => '7e7bbdae',
'rsrc/css/application/herald/herald.css' => '648d39e2',
'rsrc/css/application/maniphest/report.css' => '3d53188b',
@ -101,15 +101,15 @@ return array(
'rsrc/css/application/policy/policy-transaction-detail.css' => 'c02b8384',
'rsrc/css/application/policy/policy.css' => 'ceb56a08',
'rsrc/css/application/ponder/ponder-view.css' => '05a09d0a',
'rsrc/css/application/project/project-card-view.css' => 'a9f2c2dd',
'rsrc/css/application/project/project-card-view.css' => 'c1200da7',
'rsrc/css/application/project/project-triggers.css' => 'cd9c8bb9',
'rsrc/css/application/project/project-view.css' => '567858b3',
'rsrc/css/application/project/project-view.css' => '2f7caa20',
'rsrc/css/application/search/application-search-view.css' => '0f7c06d8',
'rsrc/css/application/search/search-results.css' => '9ea70ace',
'rsrc/css/application/slowvote/slowvote.css' => '1694baed',
'rsrc/css/application/tokens/tokens.css' => 'ce5a50bd',
'rsrc/css/application/uiexample/example.css' => 'b4795059',
'rsrc/css/core/core.css' => 'b3ebd90d',
'rsrc/css/core/core.css' => 'a708bd25',
'rsrc/css/core/remarkup.css' => '5baa3bd9',
'rsrc/css/core/syntax.css' => '548567f6',
'rsrc/css/core/z-index.css' => 'ac3bfcd4',
@ -121,10 +121,10 @@ return array(
'rsrc/css/fuel/fuel-handle-list.css' => '2c4cbeca',
'rsrc/css/fuel/fuel-map.css' => 'd6e31510',
'rsrc/css/fuel/fuel-menu.css' => '21f5d199',
'rsrc/css/layout/phabricator-source-code-view.css' => '03d7ac28',
'rsrc/css/layout/phabricator-source-code-view.css' => '6b31244f',
'rsrc/css/phui/button/phui-button-bar.css' => 'a4aa75c4',
'rsrc/css/phui/button/phui-button-simple.css' => '1ff278aa',
'rsrc/css/phui/button/phui-button.css' => 'ea704902',
'rsrc/css/phui/button/phui-button.css' => 'e434f171',
'rsrc/css/phui/calendar/phui-calendar-day.css' => '9597d706',
'rsrc/css/phui/calendar/phui-calendar-list.css' => 'ccd7e4e2',
'rsrc/css/phui/calendar/phui-calendar-month.css' => 'cb758c42',
@ -142,7 +142,7 @@ return array(
'rsrc/css/phui/phui-big-info-view.css' => '362ad37b',
'rsrc/css/phui/phui-box.css' => '5ed3b8cb',
'rsrc/css/phui/phui-bulk-editor.css' => '374d5e30',
'rsrc/css/phui/phui-chart.css' => '14df9ae3',
'rsrc/css/phui/phui-chart.css' => 'fe8f87a7',
'rsrc/css/phui/phui-cms.css' => '8c05c41e',
'rsrc/css/phui/phui-comment-form.css' => '68a2d99a',
'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0',
@ -154,8 +154,8 @@ return array(
'rsrc/css/phui/phui-document.css' => '52b748a5',
'rsrc/css/phui/phui-feed-story.css' => 'a0c05029',
'rsrc/css/phui/phui-fontkit.css' => '1ec937e5',
'rsrc/css/phui/phui-form-view.css' => '01b796c0',
'rsrc/css/phui/phui-form.css' => '1f177cb7',
'rsrc/css/phui/phui-form-view.css' => '7536aef9',
'rsrc/css/phui/phui-form.css' => 'd1adb52c',
'rsrc/css/phui/phui-formation-view.css' => 'd2dec8ed',
'rsrc/css/phui/phui-head-thing.css' => 'd7f293df',
'rsrc/css/phui/phui-header-view.css' => '36c86a58',
@ -168,7 +168,7 @@ return array(
'rsrc/css/phui/phui-left-right.css' => '68513c34',
'rsrc/css/phui/phui-lightbox.css' => '4ebf22da',
'rsrc/css/phui/phui-list.css' => '0c04affd',
'rsrc/css/phui/phui-object-box.css' => 'b8d7eea0',
'rsrc/css/phui/phui-object-box.css' => 'fdffed5c',
'rsrc/css/phui/phui-pager.css' => 'd022c7ad',
'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8',
'rsrc/css/phui/phui-policy-section-view.css' => '139fdc64',
@ -178,13 +178,13 @@ return array(
'rsrc/css/phui/phui-spacing.css' => 'b05cadc3',
'rsrc/css/phui/phui-status.css' => '293b5dad',
'rsrc/css/phui/phui-tag-view.css' => 'fb811341',
'rsrc/css/phui/phui-timeline-view.css' => '2d32d7a9',
'rsrc/css/phui/phui-timeline-view.css' => '7f8659ec',
'rsrc/css/phui/phui-two-column-view.css' => 'f96d319f',
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'e86de308',
'rsrc/css/phui/workboards/phui-workboard-color.css' => '3a1c21ff',
'rsrc/css/phui/workboards/phui-workboard.css' => '74fc9d98',
'rsrc/css/phui/workboards/phui-workcard.css' => '913441b6',
'rsrc/css/phui/workboards/phui-workpanel.css' => '3ae89b20',
'rsrc/css/sprite-login.css' => '18b368a6',
'rsrc/css/sprite-login.css' => '35d1510c',
'rsrc/css/sprite-tokens.css' => 'f1896dc5',
'rsrc/css/syntax/syntax-default.css' => '055fc231',
'rsrc/externals/d3/d3.min.js' => '9d068042',
@ -257,7 +257,7 @@ return array(
'rsrc/externals/javelin/lib/URI.js' => '2e255291',
'rsrc/externals/javelin/lib/Vector.js' => 'e9c80beb',
'rsrc/externals/javelin/lib/WebSocket.js' => 'fdc13e4e',
'rsrc/externals/javelin/lib/Workflow.js' => '945ff654',
'rsrc/externals/javelin/lib/Workflow.js' => 'cc1553f3',
'rsrc/externals/javelin/lib/__tests__/Cookie.js' => 'ca686f71',
'rsrc/externals/javelin/lib/__tests__/DOM.js' => '4566e249',
'rsrc/externals/javelin/lib/__tests__/JSON.js' => '710377ae',
@ -265,7 +265,7 @@ return array(
'rsrc/externals/javelin/lib/__tests__/behavior.js' => '8426ebeb',
'rsrc/externals/javelin/lib/behavior.js' => '1b6acc2a',
'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '89a1ae3a',
'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => 'a4356cde',
'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => '0507519c',
'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => 'a241536a',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '22ee68a5',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '23387297',
@ -342,8 +342,8 @@ return array(
'rsrc/image/phrequent_active.png' => 'de66dc50',
'rsrc/image/phrequent_inactive.png' => '79c61baf',
'rsrc/image/resize.png' => '9cc83373',
'rsrc/image/sprite-login-X2.png' => '604545f6',
'rsrc/image/sprite-login.png' => '7a001a9a',
'rsrc/image/sprite-login-X2.png' => '269800ec',
'rsrc/image/sprite-login.png' => 'a843f146',
'rsrc/image/sprite-tokens-X2.png' => '21621dd9',
'rsrc/image/sprite-tokens.png' => 'bede2580',
'rsrc/image/texture/card-gradient.png' => 'e6892cb4',
@ -423,7 +423,7 @@ return array(
'rsrc/js/application/projects/WorkboardCard.js' => '0392a5d8',
'rsrc/js/application/projects/WorkboardCardTemplate.js' => '84f82dad',
'rsrc/js/application/projects/WorkboardColumn.js' => 'c3d24e63',
'rsrc/js/application/projects/WorkboardController.js' => 'b9d0c2f3',
'rsrc/js/application/projects/WorkboardController.js' => '7474d31f',
'rsrc/js/application/projects/WorkboardDropEffect.js' => '8e0aa661',
'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d',
'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'ebe83a6b',
@ -453,7 +453,7 @@ return array(
'rsrc/js/core/DragAndDropFileUpload.js' => '4370900d',
'rsrc/js/core/DraggableList.js' => '0169e425',
'rsrc/js/core/Favicon.js' => '7930776a',
'rsrc/js/core/FileUpload.js' => 'ab85e184',
'rsrc/js/core/FileUpload.js' => '331676ea',
'rsrc/js/core/Hovercard.js' => '6199f752',
'rsrc/js/core/HovercardList.js' => 'de4b4919',
'rsrc/js/core/KeyboardShortcut.js' => '1a844c06',
@ -559,7 +559,7 @@ return array(
'conpherence-transaction-css' => '3a3f5e7e',
'd3' => '9d068042',
'diff-tree-view-css' => 'e2d3e222',
'differential-changeset-view-css' => '60c3d405',
'differential-changeset-view-css' => 'bf159129',
'differential-core-view-css' => '7300a73e',
'differential-revision-add-comment-css' => '7e5900d9',
'differential-revision-comment-css' => '7dbc8d1d',
@ -578,7 +578,7 @@ return array(
'fuel-map-css' => 'd6e31510',
'fuel-menu-css' => '21f5d199',
'global-drag-and-drop-css' => '1d2713a4',
'harbormaster-css' => '8dfe16b2',
'harbormaster-css' => 'd98decda',
'herald-css' => '648d39e2',
'herald-rule-editor' => '2633bef7',
'herald-test-css' => '7e7bbdae',
@ -731,7 +731,7 @@ return array(
'javelin-sound' => 'd4cc2d2a',
'javelin-stratcom' => '0889b835',
'javelin-tokenizer' => '89a1ae3a',
'javelin-typeahead' => 'a4356cde',
'javelin-typeahead' => '0507519c',
'javelin-typeahead-composite-source' => '22ee68a5',
'javelin-typeahead-normalizer' => 'a241536a',
'javelin-typeahead-ondemand-source' => '23387297',
@ -751,12 +751,12 @@ return array(
'javelin-workboard-card' => '0392a5d8',
'javelin-workboard-card-template' => '84f82dad',
'javelin-workboard-column' => 'c3d24e63',
'javelin-workboard-controller' => 'b9d0c2f3',
'javelin-workboard-controller' => '7474d31f',
'javelin-workboard-drop-effect' => '8e0aa661',
'javelin-workboard-header' => '111bfd2d',
'javelin-workboard-header-template' => 'ebe83a6b',
'javelin-workboard-order-template' => '03e8891f',
'javelin-workflow' => '945ff654',
'javelin-workflow' => 'cc1553f3',
'maniphest-report-css' => '3d53188b',
'maniphest-task-edit-css' => '272daa84',
'maniphest-task-summary-css' => '61d1667e',
@ -771,7 +771,7 @@ return array(
'phabricator-busy' => '5202e831',
'phabricator-chatlog-css' => 'abdc76ee',
'phabricator-content-source-view-css' => 'cdf0d579',
'phabricator-core-css' => 'b3ebd90d',
'phabricator-core-css' => 'a708bd25',
'phabricator-countdown-css' => 'bff8012f',
'phabricator-darklog' => '3b869402',
'phabricator-darkmessage' => '26cd4b73',
@ -784,10 +784,10 @@ return array(
'phabricator-diff-tree-view' => '5d83623b',
'phabricator-drag-and-drop-file-upload' => '4370900d',
'phabricator-draggable-list' => '0169e425',
'phabricator-fatal-config-template-css' => '20babf50',
'phabricator-fatal-config-template-css' => 'e689dbbd',
'phabricator-favicon' => '7930776a',
'phabricator-feed-css' => 'd8b6e3f8',
'phabricator-file-upload' => 'ab85e184',
'phabricator-file-upload' => '331676ea',
'phabricator-flag-css' => '2b77be8d',
'phabricator-keyboard-shortcut' => '1a844c06',
'phabricator-keyboard-shortcut-manager' => '81debc48',
@ -803,7 +803,7 @@ return array(
'phabricator-search-results-css' => '9ea70ace',
'phabricator-shaped-request' => '995f5102',
'phabricator-slowvote-css' => '1694baed',
'phabricator-source-code-view-css' => '03d7ac28',
'phabricator-source-code-view-css' => '6b31244f',
'phabricator-standard-page-view' => 'a374f94c',
'phabricator-textareautils' => 'f340a484',
'phabricator-title' => '43bc9360',
@ -827,13 +827,13 @@ return array(
'phui-box-css' => '5ed3b8cb',
'phui-bulk-editor-css' => '374d5e30',
'phui-button-bar-css' => 'a4aa75c4',
'phui-button-css' => 'ea704902',
'phui-button-css' => 'e434f171',
'phui-button-simple-css' => '1ff278aa',
'phui-calendar-css' => 'f11073aa',
'phui-calendar-day-css' => '9597d706',
'phui-calendar-list-css' => 'ccd7e4e2',
'phui-calendar-month-css' => 'cb758c42',
'phui-chart-css' => '14df9ae3',
'phui-chart-css' => 'fe8f87a7',
'phui-cms-css' => '8c05c41e',
'phui-comment-form-css' => '68a2d99a',
'phui-comment-panel-css' => 'ec4e31c0',
@ -846,8 +846,8 @@ return array(
'phui-feed-story-css' => 'a0c05029',
'phui-font-icon-base-css' => '303c9b87',
'phui-fontkit-css' => '1ec937e5',
'phui-form-css' => '1f177cb7',
'phui-form-view-css' => '01b796c0',
'phui-form-css' => 'd1adb52c',
'phui-form-view-css' => '7536aef9',
'phui-formation-view-css' => 'd2dec8ed',
'phui-head-thing-view-css' => 'd7f293df',
'phui-header-view-css' => '36c86a58',
@ -858,12 +858,12 @@ return array(
'phui-icon-view-css' => '4cbc684a',
'phui-image-mask-css' => '62c7f4d2',
'phui-info-view-css' => 'a10a909b',
'phui-inline-comment-view-css' => '9863a85e',
'phui-inline-comment-view-css' => 'a864426f',
'phui-invisible-character-view-css' => 'c694c4a4',
'phui-left-right-css' => '68513c34',
'phui-lightbox-css' => '4ebf22da',
'phui-list-view-css' => '0c04affd',
'phui-object-box-css' => 'b8d7eea0',
'phui-object-box-css' => 'fdffed5c',
'phui-oi-big-ui-css' => 'fa74cc35',
'phui-oi-color-css' => 'b517bfa0',
'phui-oi-drag-ui-css' => 'da15d3dc',
@ -880,9 +880,9 @@ return array(
'phui-status-list-view-css' => '293b5dad',
'phui-tag-view-css' => 'fb811341',
'phui-theme-css' => '35883b37',
'phui-timeline-view-css' => '2d32d7a9',
'phui-timeline-view-css' => '7f8659ec',
'phui-two-column-view-css' => 'f96d319f',
'phui-workboard-color-css' => 'e86de308',
'phui-workboard-color-css' => '3a1c21ff',
'phui-workboard-view-css' => '74fc9d98',
'phui-workcard-view-css' => '913441b6',
'phui-workpanel-view-css' => '3ae89b20',
@ -900,11 +900,11 @@ return array(
'policy-edit-css' => '8794e2ed',
'policy-transaction-detail-css' => 'c02b8384',
'ponder-view-css' => '05a09d0a',
'project-card-view-css' => 'a9f2c2dd',
'project-card-view-css' => 'c1200da7',
'project-triggers-css' => 'cd9c8bb9',
'project-view-css' => '567858b3',
'project-view-css' => '2f7caa20',
'setup-issue-css' => '5eed85b2',
'sprite-login-css' => '18b368a6',
'sprite-login-css' => '35d1510c',
'sprite-tokens-css' => 'f1896dc5',
'syntax-default-css' => '055fc231',
'syntax-highlighting-css' => '548567f6',
@ -965,6 +965,12 @@ return array(
'javelin-dom',
'javelin-workflow',
),
'0507519c' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
'javelin-util',
),
'05d290ef' => array(
'javelin-install',
'javelin-util',
@ -1207,6 +1213,11 @@ return array(
'javelin-stratcom',
'javelin-dom',
),
'331676ea' => array(
'javelin-install',
'javelin-dom',
'phabricator-notification',
),
34450586 => array(
'javelin-color',
'javelin-install',
@ -1512,9 +1523,6 @@ return array(
'5faf27b9' => array(
'phuix-form-control-view',
),
'60c3d405' => array(
'phui-inline-comment-view-css',
),
'60cd9241' => array(
'javelin-behavior',
),
@ -1592,6 +1600,16 @@ return array(
'javelin-behavior',
'javelin-dom',
),
'7474d31f' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'phabricator-drag-and-drop-file-upload',
'javelin-workboard-board',
),
'78bc5d94' => array(
'javelin-behavior',
'javelin-uri',
@ -1747,17 +1765,6 @@ return array(
'javelin-typeahead-preloaded-source',
'javelin-util',
),
'945ff654' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
),
'9623adc1' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1814,12 +1821,6 @@ return array(
'javelin-workflow',
'phabricator-draggable-list',
),
'a4356cde' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
'javelin-util',
),
'a43ae2ae' => array(
'javelin-install',
'javelin-dom',
@ -1876,11 +1877,6 @@ return array(
'javelin-json',
'phuix-form-control-view',
),
'ab85e184' => array(
'javelin-install',
'javelin-dom',
'phabricator-notification',
),
'ac10c917' => array(
'javelin-behavior',
'javelin-dom',
@ -2003,15 +1999,8 @@ return array(
'javelin-uri',
'phabricator-notification',
),
'b9d0c2f3' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'phabricator-drag-and-drop-file-upload',
'javelin-workboard-board',
'bf159129' => array(
'phui-inline-comment-view-css',
),
'c03f2fb4' => array(
'javelin-install',
@ -2051,6 +2040,17 @@ return array(
'javelin-workflow',
'javelin-json',
),
'cc1553f3' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
),
'cc2c5de5' => array(
'javelin-install',
'phuix-button-view',

View file

@ -119,12 +119,14 @@ if ($is_svnrevprop) {
exit($err);
} else if ($repository->isGit() || $repository->isHg()) {
$username = getenv(DiffusionCommitHookEngine::ENV_USER);
if (!strlen($username)) {
throw new Exception(
pht(
'No Direct Pushes: You are pushing directly to a hosted repository. '.
'This will not work. See "No Direct Pushes" in the documentation '.
'for more information.'));
if ($username !== false) {
if (!phutil_nonempty_string($username)) {
throw new Exception(
pht(
'No Direct Pushes: You are pushing directly to a hosted repository. '.
'This will not work. See "No Direct Pushes" in the documentation '.
'for more information.'));
}
}
if ($repository->isHg()) {
@ -181,18 +183,24 @@ $engine->setStdin($stdin);
$engine->setOriginalArgv(array_slice($argv, 2));
$remote_address = getenv(DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS);
if (strlen($remote_address)) {
$engine->setRemoteAddress($remote_address);
if ($remote_address !== false) {
if (phutil_nonempty_string($remote_address)) {
$engine->setRemoteAddress($remote_address);
}
}
$remote_protocol = getenv(DiffusionCommitHookEngine::ENV_REMOTE_PROTOCOL);
if (strlen($remote_protocol)) {
$engine->setRemoteProtocol($remote_protocol);
if ($remote_protocol !== false) {
if (phutil_nonempty_string($remote_protocol)) {
$engine->setRemoteProtocol($remote_protocol);
}
}
$request_identifier = getenv(DiffusionCommitHookEngine::ENV_REQUEST);
if (strlen($request_identifier)) {
$engine->setRequestIdentifier($request_identifier);
if ($request_identifier !== false) {
if (phutil_nonempty_string($request_identifier)) {
$engine->setRequestIdentifier($request_identifier);
}
}
try {

View file

@ -36,7 +36,7 @@ $authstruct_raw = $cache->getKey($authstruct_key);
$authstruct = null;
if (strlen($authstruct_raw)) {
if (phutil_nonempty_string($authstruct_raw)) {
try {
$authstruct = phutil_json_decode($authstruct_raw);
} catch (Exception $ex) {
@ -82,13 +82,13 @@ if ($authstruct === null) {
// Strip out newlines and other nonsense from the key type and key body.
$type = $ssh_key->getKeyType();
$type = preg_replace('@[\x00-\x20]+@', '', $type);
if (!strlen($type)) {
if (!phutil_nonempty_string($type)) {
continue;
}
$key = $ssh_key->getKeyBody();
$key = preg_replace('@[\x00-\x20]+@', '', $key);
if (!strlen($key)) {
if (!phutil_nonempty_string($key)) {
continue;
}
@ -135,7 +135,7 @@ foreach ($authstruct['keys'] as $key_struct) {
$cmd = csprintf('%s %Ls', $bin, $key_argv);
if (strlen($instance)) {
if (phutil_nonempty_string($instance)) {
$cmd = csprintf('PHABRICATOR_INSTANCE=%s %C', $instance, $cmd);
}

View file

@ -103,7 +103,7 @@ try {
'--phabricator-ssh-device',
$user_name,
$device_name));
} else if (strlen($user_name)) {
} else if (phutil_nonempty_string($user_name)) {
$user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withUsernames(array($user_name))
@ -117,7 +117,7 @@ try {
id(new PhabricatorAuthSessionEngine())
->willServeRequestForUser($user);
} else if (strlen($device_name)) {
} else if (phutil_nonempty_string($device_name)) {
if (!$remote_address) {
throw new Exception(
pht(

View file

@ -39,7 +39,7 @@ $data = array();
$futures = array();
foreach (explode("\n", trim($input)) as $file) {
if (!strlen($file)) {
if (!phutil_nonempty_string($file)) {
continue;
}

View file

@ -27,7 +27,7 @@ $data = array();
$futures = array();
foreach (explode("\n", trim($input)) as $file) {
if (!strlen($file)) {
if (!phutil_nonempty_string($file)) {
continue;
}

View file

@ -5843,6 +5843,7 @@ phutil_register_library_map(array(
'PonderQuestionSearchEngine' => 'applications/ponder/query/PonderQuestionSearchEngine.php',
'PonderQuestionStatus' => 'applications/ponder/constants/PonderQuestionStatus.php',
'PonderQuestionStatusController' => 'applications/ponder/controller/PonderQuestionStatusController.php',
'PonderQuestionStatusTestCase' => 'applications/ponder/storage/__tests__/PonderQuestionStatusTestCase.php',
'PonderQuestionStatusTransaction' => 'applications/ponder/xaction/PonderQuestionStatusTransaction.php',
'PonderQuestionTitleTransaction' => 'applications/ponder/xaction/PonderQuestionTitleTransaction.php',
'PonderQuestionTransaction' => 'applications/ponder/storage/PonderQuestionTransaction.php',
@ -8401,6 +8402,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorFlaggableInterface',
'PhabricatorMentionableInterface',
'PhabricatorSubscribableInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSpacesInterface',
@ -12756,6 +12758,7 @@ phutil_register_library_map(array(
'PonderQuestionSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PonderQuestionStatus' => 'PonderConstants',
'PonderQuestionStatusController' => 'PonderController',
'PonderQuestionStatusTestCase' => 'PhutilTestCase',
'PonderQuestionStatusTransaction' => 'PonderQuestionTransactionType',
'PonderQuestionTitleTransaction' => 'PonderQuestionTransactionType',
'PonderQuestionTransaction' => 'PhabricatorModularTransaction',

View file

@ -66,7 +66,7 @@ final class AphrontRequest extends Phobject {
}
public static function parseURILineRange($range, $limit) {
if (!strlen($range)) {
if (!phutil_nonempty_string($range)) {
return null;
}
@ -234,7 +234,7 @@ final class AphrontRequest extends Phobject {
$raw_data = phutil_string_cast($this->requestData[$name]);
$raw_data = trim($raw_data);
if (!strlen($raw_data)) {
if (!phutil_nonempty_string($raw_data)) {
return $default;
}
@ -499,7 +499,7 @@ final class AphrontRequest extends Phobject {
// domain is. This makes setup easier, and we'll tell administrators to
// configure a base domain during the setup process.
$base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
if (!strlen($base_uri)) {
if (!phutil_nonempty_string($base_uri)) {
return new PhutilURI('http://'.$host.'/');
}
@ -956,7 +956,7 @@ final class AphrontRequest extends Phobject {
$submit_cookie = PhabricatorCookies::COOKIE_SUBMIT;
$submit_key = $this->getCookie($submit_cookie);
if (strlen($submit_key)) {
if (phutil_nonempty_string($submit_key)) {
$this->clearCookie($submit_cookie);
$this->submitKey = $submit_key;
}

View file

@ -823,7 +823,7 @@ final class AphrontApplicationConfiguration
$raw_input = PhabricatorStartup::getRawInput();
$parser = new PhutilQueryStringParser();
if (strlen($raw_input)) {
if (phutil_nonempty_string($raw_input)) {
$content_type = idx($_SERVER, 'CONTENT_TYPE');
$is_multipart = preg_match('@^multipart/form-data@i', $content_type);
if ($is_multipart) {

View file

@ -64,7 +64,7 @@ final class AphrontAjaxResponse extends AphrontResponse {
if ($viewer) {
$postprocessor_key = $viewer->getUserSetting(
PhabricatorAccessibilitySetting::SETTINGKEY);
if (strlen($postprocessor_key)) {
if (phutil_nonempty_string($postprocessor_key)) {
$response->setPostprocessorKey($postprocessor_key);
}
}

View file

@ -19,7 +19,7 @@ final class AphrontFileResponse extends AphrontResponse {
}
public function setDownload($download) {
if (!strlen($download)) {
if (!phutil_nonempty_string($download)) {
$download = 'untitled';
}
$this->download = $download;

View file

@ -21,7 +21,7 @@ final class AphrontWebpageResponse extends AphrontHTMLResponse {
public function buildResponseString() {
$unexpected_output = $this->getUnexpectedOutput();
if (strlen($unexpected_output)) {
if (phutil_nonempty_string($unexpected_output)) {
$style = array(
'background: linear-gradient(180deg, #eeddff, #ddbbff);',
'white-space: pre-wrap;',

View file

@ -8,7 +8,7 @@ final class AphrontPHPHTTPSink extends AphrontHTTPSink {
protected function emitHTTPStatus($code, $message = '') {
if ($code != 200) {
$header = "HTTP/1.0 {$code}";
if (strlen($message)) {
if (phutil_nonempty_string($message)) {
$header .= " {$message}";
}
header($header);

View file

@ -15,7 +15,7 @@ abstract class AphrontSite extends Phobject {
protected function isHostMatch($host, array $uris) {
foreach ($uris as $uri) {
if (!strlen($uri)) {
if (!phutil_nonempty_string($uri)) {
continue;
}

View file

@ -14,7 +14,7 @@ final class PhabricatorPlatformSite extends PhabricatorSite {
// If no base URI has been configured yet, match this site so the user
// can follow setup instructions.
$base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
if (!strlen($base_uri)) {
if (!phutil_nonempty_string($base_uri)) {
return new PhabricatorPlatformSite();
}

View file

@ -14,7 +14,7 @@ final class PhabricatorResourceSite extends PhabricatorSite {
$host = $request->getHost();
$uri = PhabricatorEnv::getEnvConfig('security.alternate-file-domain');
if (!strlen($uri)) {
if (!phutil_nonempty_string($uri)) {
return null;
}

View file

@ -14,7 +14,7 @@ final class PhabricatorShortSite extends PhabricatorSite {
$host = $request->getHost();
$uri = PhabricatorEnv::getEnvConfig('phurl.short-uri');
if (!strlen($uri)) {
if (!phutil_nonempty_string($uri)) {
return null;
}

View file

@ -14,7 +14,7 @@ final class PhutilTwitchAuthAdapter extends PhutilOAuthAuthAdapter {
}
public function getAccountID() {
return $this->getOAuthAccountData('_id');
return $this->getOAuthAccountData('id');
}
public function getAccountEmail() {
@ -22,11 +22,11 @@ final class PhutilTwitchAuthAdapter extends PhutilOAuthAuthAdapter {
}
public function getAccountName() {
return $this->getOAuthAccountData('name');
return $this->getOAuthAccountData('login');
}
public function getAccountImageURI() {
return $this->getOAuthAccountData('logo');
return $this->getOAuthAccountData('profile_image_url');
}
public function getAccountURI() {
@ -42,11 +42,11 @@ final class PhutilTwitchAuthAdapter extends PhutilOAuthAuthAdapter {
}
protected function getAuthenticateBaseURI() {
return 'https://api.twitch.tv/kraken/oauth2/authorize';
return 'https://id.twitch.tv/oauth2/authorize';
}
protected function getTokenBaseURI() {
return 'https://api.twitch.tv/kraken/oauth2/token';
return 'https://id.twitch.tv/oauth2/token';
}
public function getScope() {
@ -69,7 +69,7 @@ final class PhutilTwitchAuthAdapter extends PhutilOAuthAuthAdapter {
return id(new PhutilTwitchFuture())
->setClientID($this->getClientID())
->setAccessToken($this->getAccessToken())
->setRawTwitchQuery('user')
->setRawTwitchQuery('users')
->resolve();
}

View file

@ -4,7 +4,7 @@ final class PhabricatorAuthPasswordException
extends Exception {
private $passwordError;
private $confirmErorr;
private $confirmError;
public function __construct(
$message,

View file

@ -8,7 +8,22 @@ final class PhabricatorCalendarImportEditController
->setController($this);
$id = $request->getURIData('id');
if (!$id) {
if ($id) {
// edit a specific entry
$calendar_import = self::queryImportByID($request, $id);
if (!$calendar_import) {
return new Aphront404Response();
}
// pass the correct import engine to build the response
$engine->setImportEngine($calendar_import->getEngine());
} else {
// create an entry
$list_uri = $this->getApplicationURI('import/');
$import_type = $request->getStr('importType');
@ -27,6 +42,18 @@ final class PhabricatorCalendarImportEditController
return $engine->buildResponse();
}
private static function queryImportByID(AphrontRequest $request, int $id) {
return id(new PhabricatorCalendarImportQuery())
->setViewer($request->getViewer())
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
}
private function buildEngineTypeResponse($cancel_uri) {
$import_engines = PhabricatorCalendarImportEngine::getAllImportEngines();

View file

@ -83,6 +83,12 @@ final class PhabricatorCalendarImportEditEngine
$engine = $object->getEngine();
$can_trigger = $engine->supportsTriggers($object);
// calendar URI import
// note that it can contains a secret token
// if we are here you have enough privileges to edit and see the value
$uri_key = PhabricatorCalendarImportICSURITransaction::PARAMKEY_URI;
$uri = $object->getParameter($uri_key);
$fields = array(
id(new PhabricatorTextEditField())
->setKey('name')
@ -94,6 +100,15 @@ final class PhabricatorCalendarImportEditEngine
->setConduitTypeDescription(pht('New import name.'))
->setPlaceholder($object->getDisplayName())
->setValue($object->getName()),
id(new PhabricatorTextEditField())
->setKey('uri')
->setLabel(pht('URI'))
->setDescription(pht('URI to import.'))
->setTransactionType(
PhabricatorCalendarImportICSURITransaction::TRANSACTIONTYPE)
->setConduitDescription(pht('URI to import.'))
->setConduitTypeDescription(pht('New URI.'))
->setValue($uri),
id(new PhabricatorBoolEditField())
->setKey('disabled')
->setOptions(pht('Active'), pht('Disabled'))

View file

@ -222,12 +222,16 @@ final class PhabricatorCalendarEventQuery
$limit = $this->getRecurrenceLimit($event, $raw_limit);
// note that this can be NULL for some imported events
$set = $event->newRecurrenceSet();
$recurrences = $set->getEventsBetween(
$start_date,
$end_date,
$limit + 1);
$recurrences = array();
if ($set) {
$recurrences = $set->getEventsBetween(
$start_date,
$end_date,
$limit + 1);
}
// We're generating events from the beginning and then filtering them
// here (instead of only generating events starting at the start date)

View file

@ -15,13 +15,13 @@ final class CelerityDarkModePostprocessor
return array(
// Fonts
'basefont' => "13px 'Segoe UI', 'Segoe UI Emoji', ".
"'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ".
"Helvetica, Arial, sans-serif",
'basefont' => "13px -apple-system, system-ui, BlinkMacSystemFont, ".
"'Segoe UI', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Lato', ".
"'Helvetica Neue', Helvetica, Arial, sans-serif",
'fontfamily' => "'Segoe UI', 'Segoe UI Emoji', ".
"'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ".
"Helvetica, Arial, sans-serif",
'fontfamily' => "-apple-system, system-ui, BlinkMacSystemFont, ".
"'Segoe UI', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Lato', ".
"'Helvetica Neue', Helvetica, Arial, sans-serif",
// Drop Shadow
'dropshadow' => '0 2px 12px rgba(0, 0, 0, .20)',

View file

@ -20,13 +20,13 @@ final class CelerityDefaultPostprocessor
public function buildVariables() {
return array(
// Fonts
'basefont' => "13px 'Segoe UI', 'Segoe UI Emoji', ".
"'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ".
"Helvetica, Arial, sans-serif",
'basefont' => "13px -apple-system, system-ui, BlinkMacSystemFont, ".
"'Segoe UI', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Lato', ".
"'Helvetica Neue', Helvetica, Arial, sans-serif",
'fontfamily' => "'Segoe UI', 'Segoe UI Emoji', ".
"'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ".
"Helvetica, Arial, sans-serif",
'fontfamily' => "-apple-system, system-ui, BlinkMacSystemFont, ".
"'Segoe UI', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Lato', ".
"'Helvetica Neue', Helvetica, Arial, sans-serif",
// Drop Shadow
'dropshadow' => '0 2px 12px rgba(0, 0, 0, .20)',

View file

@ -14,9 +14,9 @@ final class CelerityLargeFontPostprocessor
public function buildVariables() {
return array(
'basefont' => "14px 'Segoe UI', 'Segoe UI Web Regular', ".
"'Segoe UI Symbol', 'Lato', 'Helvetica Neue', Helvetica, ".
"Arial, sans-serif",
'basefont' => "14px -apple-system, system-ui, BlinkMacSystemFont, ".
"'Segoe UI', 'Segoe UI Web Regular', 'Segoe UI Symbol', 'Lato', ".
"'Helvetica Neue', Helvetica, Arial, sans-serif",
// Font Sizes
'biggestfontsize' => '16px',

View file

@ -247,6 +247,10 @@ final class PhabricatorConduitConsoleController
));
}
$view->addProperty(
pht('Summary'),
$method->getMethodSummary());
$view->addProperty(
pht('Returns'),
$method->getReturnType());

View file

@ -151,19 +151,19 @@ final class PhabricatorStorageSetupCheck extends PhabricatorSetupCheck {
$how_many = 0;
if (strlen($access_key)) {
if (phutil_nonempty_string($access_key)) {
$how_many++;
}
if (strlen($secret_key)) {
if (phutil_nonempty_string($secret_key)) {
$how_many++;
}
if (strlen($region)) {
if (phutil_nonempty_string($region)) {
$how_many++;
}
if (strlen($endpoint)) {
if (phutil_nonempty_string($endpoint)) {
$how_many++;
}

View file

@ -178,6 +178,7 @@ final class PhabricatorConfigConsoleController
'('.
implode('|', array(
'we\.phorge\.it/',
'github\.com/phorgeit/',
'github\.com/phacility/',
'secure\.phabricator\.com/',
)).

View file

@ -60,8 +60,20 @@ final class PhabricatorConfigClusterSearchController
foreach ($service->getHosts() as $host) {
try {
// Default status icon
//
// At the moment the default status is shown also when
// you just use MySQL as search server. So, on MySQL it
// shows "Unknown" even if probably it should says "Active".
// If you have time, please improve the MySQL getConnectionStatus()
// to return something more useful than this default.
$default_status = array(
'icon' => 'fa-question-circle',
'color' => 'blue',
'label' => pht('Unknown'),
);
$status = $host->getConnectionStatus();
$status = idx($status_map, $status, array());
$status = idx($status_map, $status, $default_status);
} catch (Exception $ex) {
$status['icon'] = 'fa-times';
$status['label'] = pht('Connection Error');

View file

@ -23,6 +23,17 @@ final class DifferentialRevisionTitleTransaction
}
public function getTitleForFeed() {
$obj = $this->getObject();
// To avoid verbose messages we mention the current title just once
if ($obj && $obj->getTitle() === $this->getNewValue()) {
return pht(
'%s retitled %s from %s',
$this->renderAuthor(),
$this->renderObject(),
$this->renderOldValue());
}
return pht(
'%s retitled %s from %s to %s.',
$this->renderAuthor(),

View file

@ -55,7 +55,7 @@ final class DiffusionHistoryQueryConduitAPIMethod
$limit = $request->getValue('limit');
if (strlen($against_hash)) {
$commit_range = "${against_hash}..${commit_hash}";
$commit_range = "{$against_hash}..{$commit_hash}";
} else {
$commit_range = $commit_hash;
}

View file

@ -31,8 +31,7 @@ final class DiffusionRepositoryBasicsManagementPanel
$repository = $this->getRepository();
$viewer = $this->getViewer();
$action_list = id(new PhabricatorActionListView())
->setViewer($viewer);
$action_list = $this->newActionList();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,

View file

@ -111,6 +111,9 @@ abstract class DiffusionRepositoryManagementPanel
final protected function newActionList() {
$viewer = $this->getViewer();
// Generating this ID allows to spawn the "Actions" menu
// on mobile on the header
$action_id = celerity_generate_unique_node_id();
return id(new PhabricatorActionListView())

View file

@ -31,11 +31,11 @@ final class PhabricatorS3FileStorageEngine
$endpoint = PhabricatorEnv::getEnvConfig('amazon-s3.endpoint');
$region = PhabricatorEnv::getEnvConfig('amazon-s3.region');
return (strlen($bucket) &&
strlen($access_key) &&
strlen($secret_key) &&
strlen($endpoint) &&
strlen($region));
return phutil_nonempty_string($bucket) &&
phutil_nonempty_string($access_key) &&
phutil_nonempty_string($secret_key) &&
phutil_nonempty_string($endpoint) &&
phutil_nonempty_string($region);
}
@ -57,7 +57,7 @@ final class PhabricatorS3FileStorageEngine
$parts[] = 'phabricator';
$instance_name = PhabricatorEnv::getEnvConfig('cluster.instance');
if (strlen($instance_name)) {
if (phutil_nonempty_string($instance_name)) {
$parts[] = $instance_name;
}

View file

@ -856,7 +856,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
// instance identity in the path allows us to distinguish between requests
// originating from different instances but served through the same CDN.
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
if (strlen($instance)) {
if (phutil_nonempty_string($instance)) {
$parts[] = '@'.$instance;
}
@ -903,7 +903,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
$parts[] = 'xform';
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
if (strlen($instance)) {
if (phutil_nonempty_string($instance)) {
$parts[] = '@'.$instance;
}

View file

@ -333,8 +333,8 @@ final class HeraldNewController extends HeraldController {
$cancel_params = $params;
unset($cancel_params['type']);
$cancel_uri = $this->getApplicationURI('new/');
$cancel_uri = new PhutilURI($cancel_uri, $params);
$cancel_uri = $this->getApplicationURI('create/');
$cancel_uri = new PhutilURI($cancel_uri, $cancel_params);
$form->appendChild(
id(new AphrontFormSubmitControl())

View file

@ -35,7 +35,7 @@ final class HeraldTranscriptPHIDType extends PhabricatorPHIDType {
$id = $xscript->getID();
$handle->setName(pht('Transcript %s', $id));
$handle->setURI("/herald/transcript/${id}/");
$handle->setURI("/herald/transcript/$id/");
}
}

View file

@ -507,7 +507,7 @@ final class PhabricatorMailEmailEngine
public function newDefaultEmailAddress() {
$raw_address = PhabricatorEnv::getEnvConfig('metamta.default-address');
if (!strlen($raw_address)) {
if (!$raw_address) {
$domain = $this->newMailDomain();
$raw_address = "noreply@{$domain}";
}
@ -527,7 +527,7 @@ final class PhabricatorMailEmailEngine
private function newMailDomain() {
$domain = PhabricatorEnv::getEnvConfig('metamta.reply-handler-domain');
if (strlen($domain)) {
if ($domain) {
return $domain;
}

View file

@ -14,7 +14,8 @@ final class PhabricatorMetaMTAReceivedMailProcessingException
$this->statusCode = $args[0];
$args = array_slice($args, 1);
call_user_func_array(array('parent', '__construct'), $args);
$parent = get_parent_class($this);
call_user_func_array(array($parent, '__construct'), $args);
}
}

View file

@ -152,7 +152,7 @@ final class PhabricatorNotificationServerRef
->setPath($full_path);
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
if (strlen($instance)) {
if (phutil_nonempty_string($instance)) {
$uri->replaceQueryParam('instance', $instance);
}

View file

@ -45,6 +45,12 @@ final class PassphraseCredentialEditController extends PassphraseController {
// Prefill username if provided.
$credential->setUsername((string)$request->getStr('username'));
// Prefill name if provided.
$credential->setName((string)$request->getStr('name'));
// Prefill description if provided.
$credential->setDescription((string)$request->getStr('description'));
if (!$request->getStr('isInitialized')) {
$type->didInitializeNewCredential($viewer, $credential);
}

View file

@ -5,6 +5,7 @@ final class PassphraseCredential extends PassphraseDAO
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorFlaggableInterface,
PhabricatorMentionableInterface,
PhabricatorSubscribableInterface,
PhabricatorDestructibleInterface,
PhabricatorSpacesInterface,

View file

@ -110,9 +110,11 @@ final class PholioImageFileTransaction
$new_phids = $value;
$file_phids = array();
foreach ($new_phids as $phid) {
$file_phids[] = $editor->loadPholioImage($object, $phid)
->getFilePHID();
foreach ($new_phids as $phids) {
foreach ($phids as $phid) {
$file_phids[] = $editor->loadPholioImage($object, $phid)
->getFilePHID();
}
}
return $file_phids;

View file

@ -18,6 +18,9 @@ final class PhrictionDocumentDatasource
public function loadResults() {
$viewer = $this->getViewer();
$app_type = pht('Wiki Document');
$mid_dot = "\xC2\xB7";
$query = id(new PhrictionDocumentQuery())
->setViewer($viewer)
->needContent(true);
@ -34,15 +37,25 @@ final class PhrictionDocumentDatasource
foreach ($documents as $document) {
$content = $document->getContent();
if (!$document->isActive()) {
$closed = $document->getStatusDisplayName();
} else {
if ($document->isActive()) {
$closed = null;
} else {
$closed = $document->getStatusDisplayName();
}
$slug = $document->getSlug();
$title = $content->getTitle();
// For some time the search result was
// just mentioning the document slug.
// Now, it also mentions the application type.
// Example: "Wiki Document - /foo/bar"
$display_type = sprintf(
'%s %s %s',
$app_type,
$mid_dot,
$slug);
$sprite = 'phabricator-search-icon phui-font-fa phui-icon-view fa-book';
$autocomplete = '[[ '.$slug.' ]]';
@ -51,7 +64,7 @@ final class PhrictionDocumentDatasource
->setDisplayName($title)
->setURI($document->getURI())
->setPHID($document->getPHID())
->setDisplayType($slug)
->setDisplayType($display_type)
->setPriorityType('wiki')
->setImageSprite($sprite)
->setAutocomplete($autocomplete)

View file

@ -86,4 +86,14 @@ final class PonderQuestionStatus extends PonderConstants {
);
}
/**
* Check whenever a Ponder question status is Closed
*
* @param $status string
* @return bool
*/
public static function isQuestionStatusClosed($status) {
return in_array($status, self::getQuestionStatusClosedMap(), true);
}
}

View file

@ -157,6 +157,9 @@ final class PonderQuestionSearchEngine
'Asked by %s',
$handles[$question->getAuthorPHID()]->renderLink()));
// Render Closed Questions as striked in query results
$item->setDisabled($question->isStatusClosed());
$item->addAttribute(
pht(
'%s Answer(s)',

View file

@ -137,6 +137,14 @@ final class PonderQuestion extends PonderDAO
return self::MARKUP_FIELD_CONTENT;
}
/**
* Check whenever this Question has whatever closed status
*
* @return bool
*/
public function isStatusClosed() {
return PonderQuestionStatus::isQuestionStatusClosed($this->status);
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */

View file

@ -0,0 +1,25 @@
<?php
final class PonderQuestionStatusTestCase extends PhutilTestCase {
public function testClosedStatuses() {
$statuses = PonderQuestionStatus::getQuestionStatusClosedMap();
foreach ($statuses as $status) {
$question = new PonderQuestion();
$question->setStatus($status);
$this->assertEqual(true, $question->isStatusClosed());
}
}
public function testOpenedStatuses() {
$statuses = PonderQuestionStatus::getQuestionStatusOpenMap();
foreach ($statuses as $status) {
$question = new PonderQuestion();
$question->setStatus($status);
$this->assertEqual(false, $question->isStatusClosed());
}
}
}

View file

@ -693,10 +693,19 @@ final class PhabricatorProjectBoardViewController
->newCreateActionSpecifications(array());
foreach ($specs as $spec) {
// Prefill tags= when you open the Column menu
// https://we.phorge.it/T15147
$spec_href = new PhutilURI($spec['uri']);
$spec_slug = $project->getPrimarySlug();
if ($spec_slug !== null) {
$spec_href->replaceQueryParam('tags', $spec_slug);
}
$column_items[] = id(new PhabricatorActionView())
->setIcon($spec['icon'])
->setName($spec['name'])
->setHref($spec['uri'])
->setHref($spec_href)
->setDisabled($spec['disabled'])
->addSigil('column-add-task')
->setMetadata(

View file

@ -43,7 +43,7 @@ final class PhabricatorProjectProjectPHIDType extends PhabricatorPHIDType {
$handle->setName($name);
if (strlen($slug)) {
if (phutil_nonempty_string($slug)) {
$handle->setObjectName('#'.$slug);
$handle->setMailStampName('#'.$slug);
$handle->setURI("/tag/{$slug}/");

View file

@ -238,7 +238,7 @@ final class PhabricatorRepositoryPullEngine
$identifier = $repository->getPHID();
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
if (strlen($instance)) {
if (phutil_nonempty_string($instance)) {
$identifier = "{$identifier}:{$instance}";
}

View file

@ -2480,7 +2480,8 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
$has_https = false;
}
$has_ssh = (bool)strlen(PhabricatorEnv::getEnvConfig('phd.user'));
$phd_user = PhabricatorEnv::getEnvConfig('phd.user');
$has_ssh = phutil_nonempty_string($phd_user);
$protocol_map = array(
PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH => $has_ssh,

View file

@ -48,7 +48,7 @@ final class PhabricatorSearchApplicationStorageEnginePanel
}
$instructions = pht(
'To configure the search engines, edit [[ %s | `%s` ]] configuration. '.
'To configure the search engines, edit [[ %s | %s ]] configuration. '.
'See **[[ %s | %s ]]** for documentation.',
'/config/edit/cluster.search/',
'cluster.search',

View file

@ -16,7 +16,7 @@ final class PhabricatorSystemReadOnlyController
case PhabricatorEnv::READONLY_CONFIG:
$title = pht('Administrative Read-Only Mode');
$body[] = pht(
'An administrator has placed this server into read-only mode.');
'An Administrator has placed this server into read-only mode.');
$body[] = pht(
'This mode may be used to perform temporary maintenance, test '.
'configuration, or archive an installation permanently.');
@ -26,7 +26,7 @@ final class PhabricatorSystemReadOnlyController
'has been turned on by rolling your chair away from your desk and '.
'yelling "Hey! Why is %s in read-only mode??!" using '.
'your very loudest outside voice.',
PlatformSymbols::getPlatformServerSymbol());
PlatformSymbols::getPlatformServerName());
$body[] = pht(
'This mode is active because it is enabled in the configuration '.
'option "%s".',
@ -110,7 +110,7 @@ final class PhabricatorSystemReadOnlyController
if ($viewer->getIsAdmin()) {
$body[] = pht(
'As an administrator, you can review status information from the '.
'As an Administrator, you can review status information from the '.
'%s control panel. This may provide more information about the '.
'current state of affairs.',
phutil_tag(

View file

@ -539,7 +539,7 @@ abstract class PhabricatorApplicationTransaction
case PhabricatorTransactions::TYPE_COMMENT;
$comment = $this->getComment();
if ($comment && $comment->getIsRemoved()) {
return 'black';
return 'grey';
}
break;
case PhabricatorTransactions::TYPE_EDGE:

View file

@ -493,6 +493,11 @@ class PhabricatorApplicationTransactionView extends AphrontView {
$xaction->getComment() &&
$xaction->getComment()->getIsRemoved();
// Make designers happy to make CSS customizations
if ($has_removed_comment) {
$event->addClass('phui-timeline-shell-removed');
}
if ($xaction->getCommentVersion() > 1 && !$has_removed_comment) {
$event->setIsEdited(true);
}

View file

@ -21,7 +21,7 @@
],
"groups": {
"javascript": {
"name": "Javascript"
"name": "JavaScript"
},
"lore": {
"name": "Phorge Lore"

View file

@ -1,6 +1,6 @@
{
"name": "phorge",
"title": "Phorge User Documentation",
"title": "Phorge Administrator and User Documentation",
"short": "User Docs",
"preface": "Instructions for installing, configuring, and using Phorge.",
"root": "../../../",

View file

@ -1,13 +1,13 @@
@title Javascript Object and Array
@title JavaScript Object and Array
@group javascript
This document describes the behaviors of Object and Array in Javascript, and
This document describes the behaviors of Object and Array in JavaScript, and
a specific approach to their use which produces basically reasonable language
behavior.
= Primitives =
Javascript has two native datatype primitives, Object and Array. Both are
JavaScript has two native datatype primitives, Object and Array. Both are
classes, so you can use `new` to instantiate new objects and arrays:
COUNTEREXAMPLE
@ -43,11 +43,11 @@ and Array are both classes, but "object" is also a primitive type. Object is
= Objects are Maps, Arrays are Lists =
PHP has a single `array` datatype which behaves like as both map and a list,
and a common mistake is to treat Javascript arrays (or objects) in the same way.
and a common mistake is to treat JavaScript arrays (or objects) in the same way.
**Don't do this.** It sort of works until it doesn't. Instead, learn how
Javascript's native datatypes work and use them properly.
JavaScript's native datatypes work and use them properly.
In Javascript, you should think of Objects as maps ("dictionaries") and Arrays
In JavaScript, you should think of Objects as maps ("dictionaries") and Arrays
as lists ("vectors").
You store keys-value pairs in a map, and store ordered values in a list. So,
@ -58,7 +58,13 @@ store key-value pairs in Objects.
species: 'zebra'
};
console.log(o.name);
o.paws = 4;
o['numberOfEars'] = 2;
console.log(o.name);
console.log(o.paws);
console.log(o.numberOfEars);
...and store ordered values in Arrays.
@ -71,8 +77,14 @@ Don't store key-value pairs in Arrays and don't expect Objects to be ordered.
var a = [];
a['name'] = 'Hubert'; // No! Don't do this!
This technically works because Arrays are Objects and you think everything is
fine and dandy, but it won't do what you want and will burn you.
Technically, both work because Arrays //are// Objects and you think everything
is fine and dandy, but it won't do what you want and will burn you. For example,
using `.length` will play tricks on you.
In short, trust me:
* use `[]` only to create a stack of consecutive elements numerically indexed
* use `{}` to create associative maps ("associative arrays")
= Iterating over Maps and Lists =
@ -140,7 +152,7 @@ The correct way to deal with this is:
continue;
}
f(list[ii]);
}
}
Avoid sparse arrays if possible.

View file

@ -1,12 +1,12 @@
@title Javascript Pitfalls
@title JavaScript Pitfalls
@group javascript
This document discusses pitfalls and flaws in the Javascript language, and how
This document discusses pitfalls and flaws in the JavaScript language, and how
to avoid, work around, or at least understand them.
= Implicit Semicolons =
Javascript tries to insert semicolons if you forgot them. This is a pretty
JavaScript tries to insert semicolons if you forgot them. This is a pretty
horrible idea. Notably, it can mask syntax errors by transforming subexpressions
on their own lines into statements with no effect:
@ -46,11 +46,11 @@ you can pass `arguments` to Function.prototype.apply() without converting it.
There is essentially only one reasonable, consistent way to use these primitives
but it is not obvious. Navigate these troubled waters with
@{article:Javascript Object and Array}.
@{article:JavaScript Object and Array}.
= typeof null == "object" =
This statement is true in Javascript:
This statement is true in JavaScript:
typeof null == 'object'
@ -58,9 +58,9 @@ This is pretty much a bug in the language that can never be fixed now.
= Number, String, and Boolean objects =
Like Java, Javascript has primitive versions of number, string, and boolean,
Like Java, JavaScript has primitive versions of number, string, and boolean,
and object versions. In Java, there's some argument for this distinction. In
Javascript, it's pretty much completely worthless and the behavior of these
JavaScript, it's pretty much completely worthless and the behavior of these
objects is wrong. String and Boolean in particular are essentially unusable:
lang=js
@ -83,5 +83,5 @@ Number.prototype, etc.) and their logical behavior is at best absurd and at
worst strictly wrong.
**Never use** `new Number()`, `new String()` or `new Boolean()` unless
your Javascript is God Tier and you are absolutely sure you know what you are
your JavaScript is God Tier and you are absolutely sure you know what you are
doing.

View file

@ -256,7 +256,7 @@ echo $obj->flavor; // Outputs 'coconut'.
echo get_class($obj); // Outputs 'stdClass'.
```
This is occasionally useful, mostly to force an object to become a Javascript
This is occasionally useful, mostly to force an object to become a JavaScript
dictionary (vs a list) when passed to `json_encode()`.
= Invoking `new` With an Argument Vector is Really Hard =

View file

@ -34,7 +34,7 @@ it also gained a lot of performance problems, usability issues, and bugs.
Through 2007 and 2008 Evan worked mostly on frontend and support infrastructure;
among other things, he wrote a static resource management system called Haste.
In 2009 Evan worked on the Facebook Lite site, where he built the Javelin
Javascript library and an MVC-flavored framework called Alite.
JavaScript library and an MVC-flavored framework called Alite.
But by early 2010, Diffcamp was in pretty bad shape. Two years of having random
features grafted onto it without real direction had left it slow and difficult

View file

@ -11,6 +11,7 @@ If you haven't, see @{article:Installation Guide}.
The next steps are:
- Configure your webserver (Apache, nginx, or lighttpd).
- Configure the databases.
- Access Phorge with your browser.
- Follow the instructions to complete setup.

View file

@ -15,10 +15,17 @@ Phorge, you will need:
- a domain name (like `phorge.example.com`);
- basic sysadmin skills;
- Apache, nginx, or another webserver;
- PHP, MySQL, and Git.
- PHP;
- MySQL (you will need a server with multiple databases);
- git
The remainder of this document details these requirements.
You may be interested also in preparing these optional stuff:
- have valid SMTP parameters for outgoing email notifications;
- having nothing listening on port 22, to then setup a SSH+git server
Installation Requirements
=========================
@ -71,7 +78,11 @@ Beyond an operating system, you will need **a webserver**.
You will also need:
- **MySQL**: You need MySQL. We strongly recommend MySQL 5.5 or newer.
- **PHP**: You need PHP 5.5 or newer.
You will need a server with multiple databases.
- **PHP**: You need PHP 5.5 or newer. Note that PHP 8.1 and above are not
fully supported.
- **git**: You need git 2.5.0 or newer on the server.
No particular version is needed on your clients.
You'll probably also need a **domain name**. In particular, you should read this
note:
@ -106,7 +117,7 @@ Here's a general description of what you need to install:
- git (usually called "git" in package management systems)
- Apache (usually "httpd" or "apache2") (or nginx)
- MySQL Server (usually "mysqld" or "mysql-server")
- MySQL Server (usually "mysqld" or "mysql-server" or "mariadb-server")
- PHP (usually "php")
- Required PHP extensions: mbstring, iconv, mysql (or mysqli), curl, pcntl
(these might be something like "php-mysql" or "php5-mysqlnd")

View file

@ -229,7 +229,7 @@ final class PhabricatorDatabaseRef
$host = $this->getHost();
$port = $this->getPort();
if (strlen($port)) {
if ($port) {
return "{$host}:{$port}";
}

View file

@ -125,7 +125,7 @@ final class PhabricatorEnv extends Phobject {
// If an instance identifier is defined, write it into the environment so
// it's available to subprocesses.
$instance = self::getEnvConfig('cluster.instance');
if (strlen($instance)) {
if (phutil_nonempty_string($instance)) {
putenv('PHABRICATOR_INSTANCE='.$instance);
$_ENV['PHABRICATOR_INSTANCE'] = $instance;
}
@ -440,7 +440,7 @@ final class PhabricatorEnv extends Phobject {
$uri = new PhutilURI($raw_uri);
$host = $uri->getDomain();
if (!strlen($host)) {
if (!phutil_nonempty_string($host)) {
return false;
}
@ -463,7 +463,7 @@ final class PhabricatorEnv extends Phobject {
$self_map = array();
foreach ($self_uris as $self_uri) {
$host = id(new PhutilURI($self_uri))->getDomain();
if (!strlen($host)) {
if (!phutil_nonempty_string($host)) {
continue;
}
@ -669,7 +669,7 @@ final class PhabricatorEnv extends Phobject {
public static function isValidLocalURIForLink($uri) {
$uri = (string)$uri;
if (!strlen($uri)) {
if (!phutil_nonempty_string($uri)) {
return false;
}
@ -734,7 +734,7 @@ final class PhabricatorEnv extends Phobject {
$uri = new PhutilURI($raw_uri);
$proto = $uri->getProtocol();
if (!strlen($proto)) {
if (!$proto) {
throw new Exception(
pht(
'URI "%s" is not a valid linkable resource. A valid linkable '.
@ -753,7 +753,7 @@ final class PhabricatorEnv extends Phobject {
}
$domain = $uri->getDomain();
if (!strlen($domain)) {
if (!$domain) {
throw new Exception(
pht(
'URI "%s" is not a valid linkable resource. A valid linkable '.
@ -801,7 +801,7 @@ final class PhabricatorEnv extends Phobject {
$uri = new PhutilURI($raw_uri);
$proto = $uri->getProtocol();
if (!strlen($proto)) {
if (!$proto) {
throw new Exception(
pht(
'URI "%s" is not a valid fetchable resource. A valid fetchable '.
@ -820,7 +820,7 @@ final class PhabricatorEnv extends Phobject {
}
$domain = $uri->getDomain();
if (!strlen($domain)) {
if (!$domain) {
throw new Exception(
pht(
'URI "%s" is not a valid fetchable resource. A valid fetchable '.

View file

@ -24,7 +24,7 @@ final class PhabricatorSSHLog extends Phobject {
);
$sudo_user = PhabricatorEnv::getEnvConfig('phd.user');
if (strlen($sudo_user)) {
if (phutil_nonempty_string($sudo_user)) {
$data['S'] = $sudo_user;
}

View file

@ -0,0 +1,123 @@
#!/usr/bin/env php
<?php
/**
* /startup/ is not a Phutil library, so it can't use the phutil test fixture.
* This script will just run the tests directly.
*
* NOTE: This test file will not run as part of `arc unit` run!
*/
final class PreambleUtilsTestCase {
public function testTrustXForwardValues() {
// For specific values of `$_SERVER['HTTP_X_FORWARDED_FOR']`,
// `$_SERVER['REMOTE_ADDR']` will be updated with the result.
$undefined = 'SPECIAL::UNDEFINED';
$null_value = 'SPECIAL::NULL';
$test_cases = array(
'abc' => 'abc',
$null_value => $undefined,
'' => $undefined,
// Strange, unexpected cases:
144 => '144',
);
foreach ($test_cases as $input => $expected) {
switch ($input) {
case $undefined:
unset($_SERVER['HTTP_X_FORWARDED_FOR']);
break;
case $null_value:
$_SERVER['HTTP_X_FORWARDED_FOR'] = null;
break;
default:
$_SERVER['HTTP_X_FORWARDED_FOR'] = $input;
break;
}
unset($_SERVER['REMOTE_ADDR']);
preamble_trust_x_forwarded_for_header();
if (!isset($_SERVER['REMOTE_ADDR'])) {
if ($expected === $undefined) {
// test pass
continue;
} else {
$this->failTest("Failed for input {$input} - result is not defined!");
}
}
$actual = $_SERVER['REMOTE_ADDR'];
if ($actual !== $expected) {
var_dump($actual);
$this->failTest(
"Failed for input {$input} - actual output is {$actual}");
}
}
}
private function failTest($message = null) {
echo $message;
echo "\n";
throw new Exception();
}
/**
* Run all tests in this class.
*
* Return: True if all tests passed; False if any test failed.
*/
final public function run() {
$reflection = new ReflectionClass($this);
$methods = $reflection->getMethods();
$any_fail = false;
// Try to ensure that poorly-written tests which depend on execution order
// (and are thus not properly isolated) will fail.
shuffle($methods);
foreach ($methods as $method) {
$name = $method->getName();
if (!preg_match('/^test/', $name)) {
continue;
}
try {
call_user_func_array(
array($this, $name),
array());
echo "Test passed: {$name}\n";
} catch (Throwable $ex) {
$any_fail = true;
echo "Failed test: {$name}\n";
}
}
return !$any_fail;
}
}
require_once dirname(__DIR__).'/preamble-utils.php';
$test_case = new PreambleUtilsTestCase();
$good = $test_case->run();
if (!$good) {
exit(3);
}

View file

@ -6,7 +6,8 @@ body {
background: #f9f9f9;
margin: 0;
padding: 0;
font: 13px/1.231 'Segoe UI', 'Segoe UI Web Regular', 'Segoe UI Symbol',
font: 13px/1.231 -apple-system, system-ui, BlinkMacSystemFont,
'Segoe UI', 'Segoe UI Web Regular', 'Segoe UI Symbol',
'Helvetica Neue', Helvetica, Arial, sans-serif;
text-align: left;
-webkit-text-size-adjust: none;

View file

@ -106,7 +106,8 @@
.harbormaster-log-expand-table td {
vertical-align: middle;
font: 13px 'Segoe UI', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Lato',
font: 13px -apple-system, system-ui, BlinkMacSystemFont,
'Segoe UI', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Lato',
'Helvetica Neue', Helvetica, Arial, sans-serif;
}

View file

@ -25,6 +25,8 @@
border-right: 1px solid {$paste.border};
color: {$sh-yellowtext};
white-space: nowrap;
-webkit-user-select: none;
user-select: none;
}
.phabricator-source-line > a::before {

View file

@ -525,7 +525,7 @@ properly, and submit values. */
}
.aphront-form-preview-hidden {
opacity: 0.5;
display: none;
}
.aphront-form-error .phui-icon-view {

View file

@ -189,9 +189,30 @@
overflow-x: auto;
}
/*
* Start Customization for removed comments
* https://we.phorge.it/T15192
*/
.phui-timeline-core-content .comment-deleted {
font-style: italic;
}
.phui-timeline-shell-removed .phui-timeline-image,
.phui-timeline-shell-removed .phui-timeline-badges {
opacity: 0.5;
}
.phui-timeline-shell-removed,
.phui-timeline-shell-removed a,
.phui-timeline-shell-removed .phui-timeline-title {
color:#888; /* grey */
}
.phui-timeline-shell-removed
.phui-timeline-major-event
.phui-timeline-content .phui-timeline-core-content {
padding:4px 16px; /* reduce vertical space from 16px */
}
/* End Customization for removed comments */
.device .phui-timeline-event-view {
min-height: 23px;

View file

@ -27,7 +27,7 @@
}
.phui-workboard-color .phuix-dropdown-menu {
background-color: rgba({$alphawhite},.9);
background-color: rgba({$alphawhite},.95);
}
.phui-workboard-color .phui-workpanel-view .phui-box-grey {

View file

@ -403,6 +403,20 @@ JX.install('Workflow', {
JX.$E('Response to workflow request went unhandled.');
}
}
// Only when the response is a Dialog, check if the user
// is quitting with pending changes
if (this._root) {
var form = JX.DOM.scry(this._root, 'form', 'jx-dialog');
if (form.length) {
JX.DOM.listen(form[0], 'keydown', null, function(e) {
if (e.getSpecialKey()) {
return;
}
JX.Stratcom.addSigil(form[0], 'dialog-keydown');
});
}
}
},
_push : function() {
if (!this._pushed) {
@ -536,6 +550,22 @@ JX.install('Workflow', {
return;
}
// Only when the response is a Dialog, check if the user
// is quitting with pending changes
if (active._root) {
var form = JX.DOM.scry(active._root, 'form', 'jx-dialog');
var confirmMsg =
'Form data may have changed. ' +
'Are you sure you want to close this dialog?';
if (
form.length &&
JX.Stratcom.hasSigil(form[0], 'dialog-keydown') &&
!window.confirm(confirmMsg)
) {
return;
}
}
JX.Workflow._pop();
e.prevent();
}

View file

@ -84,8 +84,19 @@ JX.install('Typeahead', {
'mousedown',
'tag:a',
JX.bind(this, function(e) {
if (!e.isRightButton()) {
if (e.isNormalMouseEvent()) {
this._choose(e.getNode('tag:a'));
} else {
// fix the middle-click and any non-normal mouse event
// in order to have an "open in a new tab" that just works natively
// or any other browser action that is supposed to be there.
//
// Probably this is one of the specific cases where kill() has
// sense instead of just stop(), since there are not much chances
// that another event listener had anything else to do
// during non-normal mousedown/click events.
// https://we.phorge.it/T15149
e.kill();
}
}));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -151,6 +151,13 @@ JX.install('WorkboardController', {
},
_onaddcard: function(e) {
// Allow CTRL+click and maybe other actions
if(!e.isNormalMouseEvent()) {
e.stop();
return;
}
// We want the 'boards-dropdown-menu' behavior to see this event and
// close the dropdown, but don't want to follow the link.
e.prevent();
@ -176,6 +183,13 @@ JX.install('WorkboardController', {
},
_oneditcard: function(e) {
// Allow CTRL+click and maybe other actions
if(!e.isNormalMouseEvent()) {
e.stop();
return;
}
e.kill();
var column_node = e.getNode('project-column');

View file

@ -38,6 +38,20 @@ JX.install('PhabricatorFileUpload', {
return this;
},
/**
* Get the File Monogram - like 'F123'
*/
getMonogram: function() {
return 'F' + this.getID();
},
/**
* Get the File page URI - like '/F123'
*/
getPageURI: function() {
return '/' + this.getMonogram();
},
setChunks: function(chunks) {
var chunk;
for (var ii = 0; ii < chunks.length; ii++) {
@ -101,7 +115,19 @@ JX.install('PhabricatorFileUpload', {
switch (this.getStatus()) {
case 'done':
var link = JX.$N('a', {href: this.getURI()}, 'F' + this.getID());
// In this case the File upload was successful
var linkAttr = {};
linkAttr.href = this.getPageURI();
// External links are evil as default.
// Believe it or not, but some Phorge users brainstormed
// for one hour for this specific target="_blank".
// https://we.phorge.it/T15172
linkAttr.target = '_blank';
var link = JX.$N('a', linkAttr, this.getMonogram());
content = [
JX.$N('strong', {}, ['Upload Complete (', link, ')']),