From a16e8773f009daaed9e058b902b544bcdbcf724f Mon Sep 17 00:00:00 2001 From: Matthew Bowker Date: Wed, 21 Sep 2022 11:25:51 -0600 Subject: [PATCH 01/43] Show confirmation dialog when closing a modal if form contents have been changed Summary: Honestly I did not realize that Differential can do this. Anyway this is related to T15034 ... Originally opened at https://secure.phabricator.com/T12676 Test Plan: 1) Start creating a task via a Workboard in Manifest, type many words, press `ESC` 2) Start creating a task via a Workboard in Manifest, type no words, press `ESC` Reviewers: O1 Blessed Committers, Ekubischta, speck Reviewed By: O1 Blessed Committers, Ekubischta, speck Subscribers: Leon95, 20after4, avivey, Ekubischta, speck, tobiaswiese Tags: #maniphest Differential Revision: https://we.phorge.it/D25015 --- .../rsrc/externals/javelin/lib/Workflow.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/webroot/rsrc/externals/javelin/lib/Workflow.js b/webroot/rsrc/externals/javelin/lib/Workflow.js index 25de547deb..7d63a3408a 100644 --- a/webroot/rsrc/externals/javelin/lib/Workflow.js +++ b/webroot/rsrc/externals/javelin/lib/Workflow.js @@ -403,6 +403,16 @@ JX.install('Workflow', { JX.$E('Response to workflow request went unhandled.'); } } + + 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 +546,15 @@ JX.install('Workflow', { return; } + var form = JX.DOM.scry(active._root, 'form', 'jx-dialog'); + if ( + form.length && + JX.Stratcom.hasSigil(form[0], 'dialog-keydown') && + !confirm('Form data may have changed. Are you sure you want to close this dialog?') + ) { + return; + } + JX.Workflow._pop(); e.prevent(); } From 69b2710af9c0485cefae6078249b891ef3aa712f Mon Sep 17 00:00:00 2001 From: Leon Eckardt Date: Wed, 21 Sep 2022 11:31:37 -0600 Subject: [PATCH 02/43] Prevent Line Numbers in Diffusion being copied as Tabs Summary: Marks Line Numbers in Diffusion File Preview as unselectable Test Plan: - Select multiple Lines from a File Preview in Diffusion - Copy them into a Text Editor - The Leading Tabs should no longer included Reviewers: O1 Blessed Committers, speck, Ekubischta Reviewed By: O1 Blessed Committers, speck, Ekubischta Subscribers: Ekubischta, speck, tobiaswiese, valerio.bozzolan Differential Revision: https://we.phorge.it/D25024 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/layout/phabricator-source-code-view.css | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 819dc37463..2985c19563 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -121,7 +121,7 @@ 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', @@ -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', diff --git a/webroot/rsrc/css/layout/phabricator-source-code-view.css b/webroot/rsrc/css/layout/phabricator-source-code-view.css index 9b61425d63..71e3516003 100644 --- a/webroot/rsrc/css/layout/phabricator-source-code-view.css +++ b/webroot/rsrc/css/layout/phabricator-source-code-view.css @@ -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 { From f8ffa393c4427f8b9026075e6dc490990a12df12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vedran=20Mileti=C4=87?= Date: Sun, 9 Oct 2022 03:13:05 -0700 Subject: [PATCH 03/43] Added cross-platform default fonts Summary: `system-ui` is provided by modern browsers as [a cross-platform default font](https://caniuse.com/font-family-system-ui). Using this font [was a controversial choice back in 2017](https://infinnie.github.io/blog/2017/systemui.html). because it did not address i18n well and therefore it was removed from Bootstrap [twbs/bootstrap#22377](https://github.com/twbs/bootstrap/pull/22377). However, it was added back in Bootstrap v5 [twbs/bootstrap#30561](https://github.com/twbs/bootstrap/pull/30561) since it got better over time. Fixes T15049. Test Plan: Tested locally for now. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: Matthew, avivey, Cigaryno, 20after4, Ekubischta, speck, tobiaswiese, valerio.bozzolan Maniphest Tasks: T15049 Differential Revision: https://we.phorge.it/D25021 --- resources/celerity/map.php | 82 +++++++++---------- .../CelerityDarkModePostprocessor.php | 12 +-- .../CelerityDefaultPostprocessor.php | 12 +-- .../CelerityLargeFontPostprocessor.php | 6 +- .../application/config/config-template.css | 3 +- .../application/harbormaster/harbormaster.css | 3 +- 6 files changed, 60 insertions(+), 58 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2985c19563..3815362684 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -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' => 'd95915b7', + 'core.pkg.js' => '256dfd7b', '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', @@ -124,7 +124,7 @@ return array( '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', @@ -155,7 +155,7 @@ return array( '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.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', @@ -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' => 'd7ba6915', 'rsrc/externals/javelin/lib/__tests__/Cookie.js' => 'ca686f71', 'rsrc/externals/javelin/lib/__tests__/DOM.js' => '4566e249', 'rsrc/externals/javelin/lib/__tests__/JSON.js' => '710377ae', @@ -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', @@ -756,7 +756,7 @@ return array( 'javelin-workboard-header' => '111bfd2d', 'javelin-workboard-header-template' => 'ebe83a6b', 'javelin-workboard-order-template' => '03e8891f', - 'javelin-workflow' => '945ff654', + 'javelin-workflow' => 'd7ba6915', '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,7 +784,7 @@ 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', @@ -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,7 +846,7 @@ return array( 'phui-feed-story-css' => 'a0c05029', 'phui-font-icon-base-css' => '303c9b87', 'phui-fontkit-css' => '1ec937e5', - 'phui-form-css' => '1f177cb7', + 'phui-form-css' => 'd1adb52c', 'phui-form-view-css' => '01b796c0', 'phui-formation-view-css' => 'd2dec8ed', 'phui-head-thing-view-css' => 'd7f293df', @@ -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', @@ -900,9 +900,9 @@ 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-tokens-css' => 'f1896dc5', @@ -1512,9 +1512,6 @@ return array( '5faf27b9' => array( 'phuix-form-control-view', ), - '60c3d405' => array( - 'phui-inline-comment-view-css', - ), '60cd9241' => array( 'javelin-behavior', ), @@ -1747,17 +1744,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', @@ -2013,6 +1999,9 @@ return array( 'phabricator-drag-and-drop-file-upload', 'javelin-workboard-board', ), + 'bf159129' => array( + 'phui-inline-comment-view-css', + ), 'c03f2fb4' => array( 'javelin-install', ), @@ -2080,6 +2069,17 @@ return array( 'd4cc2d2a' => array( 'javelin-install', ), + 'd7ba6915' => array( + 'javelin-stratcom', + 'javelin-request', + 'javelin-dom', + 'javelin-vector', + 'javelin-install', + 'javelin-util', + 'javelin-mask', + 'javelin-uri', + 'javelin-routable', + ), 'd7d3ba75' => array( 'javelin-dom', 'javelin-util', diff --git a/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php b/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php index 39ee4be57a..31932352ef 100644 --- a/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php @@ -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)', diff --git a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php index be039772c1..ff7e7f3668 100644 --- a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php @@ -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)', diff --git a/src/applications/celerity/postprocessor/CelerityLargeFontPostprocessor.php b/src/applications/celerity/postprocessor/CelerityLargeFontPostprocessor.php index 4dbe561951..1bc4e1cb3c 100644 --- a/src/applications/celerity/postprocessor/CelerityLargeFontPostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityLargeFontPostprocessor.php @@ -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', diff --git a/webroot/rsrc/css/application/config/config-template.css b/webroot/rsrc/css/application/config/config-template.css index 13ce56488a..8b212520fd 100644 --- a/webroot/rsrc/css/application/config/config-template.css +++ b/webroot/rsrc/css/application/config/config-template.css @@ -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; diff --git a/webroot/rsrc/css/application/harbormaster/harbormaster.css b/webroot/rsrc/css/application/harbormaster/harbormaster.css index 9fdad43a42..2fa5af7b22 100644 --- a/webroot/rsrc/css/application/harbormaster/harbormaster.css +++ b/webroot/rsrc/css/application/harbormaster/harbormaster.css @@ -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; } From 4aedc206c727b88fb071fdcc6c8488b58b8cdfc0 Mon Sep 17 00:00:00 2001 From: ekubischta Date: Sun, 9 Oct 2022 03:17:22 -0700 Subject: [PATCH 04/43] Update PassphraseCredential to support Mentions Summary: This revision allows us to see where passphrase credentials are mentioned in other parts of phabricator This is very useful to "What is this credential for?" If we can see where it was been mentioned Test Plan: Mentioned a passphrase and saw it in the transaction list Reviewers: O1 Blessed Committers, Matthew Reviewed By: O1 Blessed Committers, Matthew Subscribers: Matthew, Cigaryno, speck, tobiaswiese, valerio.bozzolan Differential Revision: https://we.phorge.it/D25032 --- src/__phutil_library_map__.php | 1 + src/applications/passphrase/storage/PassphraseCredential.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 02353b32c1..fec9ac13dc 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -8401,6 +8401,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorFlaggableInterface', + 'PhabricatorMentionableInterface', 'PhabricatorSubscribableInterface', 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', diff --git a/src/applications/passphrase/storage/PassphraseCredential.php b/src/applications/passphrase/storage/PassphraseCredential.php index c470ea661f..d180fc6b31 100644 --- a/src/applications/passphrase/storage/PassphraseCredential.php +++ b/src/applications/passphrase/storage/PassphraseCredential.php @@ -5,6 +5,7 @@ final class PassphraseCredential extends PassphraseDAO PhabricatorApplicationTransactionInterface, PhabricatorPolicyInterface, PhabricatorFlaggableInterface, + PhabricatorMentionableInterface, PhabricatorSubscribableInterface, PhabricatorDestructibleInterface, PhabricatorSpacesInterface, From abb693962d327550215789e9ee0827943ca05cbe Mon Sep 17 00:00:00 2001 From: MacFan4000 Date: Fri, 11 Nov 2022 13:03:46 -0700 Subject: [PATCH 05/43] Update sprite sheets Summary: This fully updates some login provider icons. Somehow this didn't work fully on previous updates. I used --force this time. Test Plan: None just ran the script which regenerated the spritesheets. Reviewers: O1 Blessed Committers, #blessed_committers, avivey Reviewed By: O1 Blessed Committers, #blessed_committers, avivey Subscribers: avivey, speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Tags: #auth Differential Revision: https://we.phorge.it/D25055 --- webroot/rsrc/image/sprite-login-X2.png | Bin 29253 -> 37744 bytes webroot/rsrc/image/sprite-login.png | Bin 12792 -> 16010 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/webroot/rsrc/image/sprite-login-X2.png b/webroot/rsrc/image/sprite-login-X2.png index 2d3c1187456bb482a0aa5895e05947eb9eb355b9..24b3ed02b18d8cee35dd5d3d382763a2543993d0 100644 GIT binary patch literal 37744 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 literal 29253 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 Date: Fri, 11 Nov 2022 13:06:34 -0700 Subject: [PATCH 06/43] Fix broken file PHID extraction that causes Pholio uploads to crash Summary: A commit earlier this year modified the structure of the file upload transaction data value, by nesting the array of file upload PHIDs in another array. The `extractFilePHIDs` method was not updated to deal with that change though, therefore new mock uploads via Pholio would crash. This patch fixes that method so it can process the updated transaction data. Resolves T15105 Test Plan: Patched my live Phabricator installation with this fix and successfully uploaded new Pholio mockups. Reviewers: O1 Blessed Committers, Cigaryno, Matthew Reviewed By: O1 Blessed Committers, Cigaryno, Matthew Subscribers: Matthew, speck, tobiaswiese, valerio.bozzolan, Cigaryno Tags: #pholio Maniphest Tasks: T15105 Differential Revision: https://we.phorge.it/D25058 --- .../pholio/xaction/PholioImageFileTransaction.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/applications/pholio/xaction/PholioImageFileTransaction.php b/src/applications/pholio/xaction/PholioImageFileTransaction.php index e18fca28e2..ce1a0e9773 100644 --- a/src/applications/pholio/xaction/PholioImageFileTransaction.php +++ b/src/applications/pholio/xaction/PholioImageFileTransaction.php @@ -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; From dc558b5538cd9c4872eacbfa5ca108f1c6e4f40d Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Thu, 8 Dec 2022 15:32:03 -0700 Subject: [PATCH 07/43] Fix NULL pointer exception in some circumstances from Calendar's homepage Summary: After importing specific weird events, for example from Google Calendar (bleah), it can happen that the Calendar's homepage becomes broken. This was the Exception error shown to video: "Call to a member function getEventsBetween() on null" It was happening since this method can return NULL: PhabricatorCalendarEventQuery#newRecurrenceSet() This changeset verifies this condition from the Calendar's homepage. Closes T15136 Test Plan: I tried in my server. I've executed the syntax lint. On my local machine I was not able to run "arc diff" since it tries to connect to root@localhost for some reasons. Reviewers: O1 Blessed Committers, 20after4 Reviewed By: O1 Blessed Committers, 20after4 Subscribers: 0, Cigaryno, 20after4, speck, tobiaswiese, Matthew Tags: #calendar Maniphest Tasks: T15136 Differential Revision: https://we.phorge.it/D25060 --- .../calendar/query/PhabricatorCalendarEventQuery.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index db50bb4d77..d20250e711 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -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) From d5040f9a8f4978ba21160c6ccf6c35b28a3497b2 Mon Sep 17 00:00:00 2001 From: k__nard Date: Thu, 8 Dec 2022 15:40:54 -0700 Subject: [PATCH 08/43] updating twitch to latest api (Helix) Summary: api doc : https://dev.twitch.tv/docs/api/reference oauth2 doc : https://dev.twitch.tv/docs/authentication Test Plan: I have successfully setup OAuth2 authentication against Twitch Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Tags: #auth Maniphest Tasks: T15122 Differential Revision: https://we.phorge.it/D25057 --- .../auth/adapter/PhutilTwitchAuthAdapter.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/applications/auth/adapter/PhutilTwitchAuthAdapter.php b/src/applications/auth/adapter/PhutilTwitchAuthAdapter.php index dce2c7e2f0..364ac55eac 100644 --- a/src/applications/auth/adapter/PhutilTwitchAuthAdapter.php +++ b/src/applications/auth/adapter/PhutilTwitchAuthAdapter.php @@ -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(); } From 02a4f8b0c8f1279fc0040ad8077942fd8b0d948b Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Mon, 28 Nov 2022 21:54:54 +0100 Subject: [PATCH 09/43] Fix editing a Calendar import ICS URI Summary: Before this change it was only possible to create a Calendar import ICS URI. After this change it's possible to also edit already-existing elements. This change fixes this specific exception when visiting similar pages: /calendar/import/edit/5/ Argument 2 passed to PhabricatorCalendarImport::initializeNewCalendarImport() must be an instance of PhabricatorCalendarImportEngine, null given, called in phorge/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php on line 45 Before this change it was only theorically possible to edit the name, policies, etc. but not the URI. This change also introduces the ability to edit the specific ICS URI, in order to change legitimate parts of the URI, like the secret token. Closes T15137 Test Plan: I tested in my own installation with success lints. To test, visit the Calendar, create an import ICS URI, and then try to edit it again. It will work only after this change. I was not able to conclude the "arc diff" since it tries to connect to an unexisting database owned by root. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, Cigaryno, speck, tobiaswiese, Matthew Maniphest Tasks: T15137 Differential Revision: https://we.phorge.it/D25061 --- ...habricatorCalendarImportEditController.php | 29 ++++++++++++++++++- .../PhabricatorCalendarImportEditEngine.php | 15 ++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarImportEditController.php b/src/applications/calendar/controller/PhabricatorCalendarImportEditController.php index 0af9fa1540..9acbd0f2d2 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarImportEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarImportEditController.php @@ -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(); diff --git a/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php index 7be3969671..9a4a7c0107 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php @@ -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')) From 6a563a68b511f9eddcbbf941803cec970abcbab7 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Thu, 19 Jan 2023 01:58:09 -0800 Subject: [PATCH 10/43] Celerify Summary: Fix unit tests in master #cowboycommit Test Plan: arc unit Reviewers: O1 Blessed Committers! Subscribers: speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25062 --- resources/celerity/map.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 3815362684..80ba37f78e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => 'd95915b7', + 'core.pkg.css' => 'f538846d', 'core.pkg.js' => '256dfd7b', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '609e63d4', @@ -184,7 +184,7 @@ return array( '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', @@ -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', @@ -904,7 +904,7 @@ return array( 'project-triggers-css' => 'cd9c8bb9', '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', From 83672ba949fb3d72b9d9452c2687f70a23aa9cd4 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Fri, 10 Feb 2023 20:39:33 +0100 Subject: [PATCH 11/43] Allow to prefill name and description fields of a new Passphrase Summary: Allow to prefill name and description fields of a new Passphrase Closes T15142 Test Plan: I've visited this URL in my local test installation: /passphrase/edit/?type=password&username=user&name=super&description=mario And I was able to see the Name and Description fields prefilled, just like the username. I also tried to put the query string on an already-existing element and it was working as expected, so, without any prefilled value from the query string. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15142 Differential Revision: https://we.phorge.it/D25065 --- .../controller/PassphraseCredentialEditController.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/passphrase/controller/PassphraseCredentialEditController.php b/src/applications/passphrase/controller/PassphraseCredentialEditController.php index a3a346c20f..7247321648 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php @@ -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); } From 20fb93d1a4be471f5e3e401c8ee4d2b9755aa556 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Sat, 11 Feb 2023 11:09:39 +0100 Subject: [PATCH 12/43] Add small documentation details Summary: Add some small Diviner documentation details like: * mention PHP 8.1 (thanks https://we.phorge.it/Q18) * mention the fact that you need multiple databases (it may be not obvious from the startup) * mention MariaDB and not just MySQL (thanks MariaDB community!) * mention "Administrator" and not just "User" for the "Instructions for installing, configuring, and using Phorge." I was not bold enough to do more changes. Test Plan: I tested locally and Diviner does not look bad. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, speck, tobiaswiese, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25064 --- src/docs/book/user.book | 2 +- .../configuration/configuration_guide.diviner | 1 + src/docs/user/installation_guide.diviner | 15 ++++++++++++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/docs/book/user.book b/src/docs/book/user.book index 449acd4ffc..b5b6dac7b1 100644 --- a/src/docs/book/user.book +++ b/src/docs/book/user.book @@ -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": "../../../", diff --git a/src/docs/user/configuration/configuration_guide.diviner b/src/docs/user/configuration/configuration_guide.diviner index 7798ff0e0b..1fde967a95 100644 --- a/src/docs/user/configuration/configuration_guide.diviner +++ b/src/docs/user/configuration/configuration_guide.diviner @@ -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. diff --git a/src/docs/user/installation_guide.diviner b/src/docs/user/installation_guide.diviner index ded0e4c93a..6db0df4d7c 100644 --- a/src/docs/user/installation_guide.diviner +++ b/src/docs/user/installation_guide.diviner @@ -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,9 @@ 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. You'll probably also need a **domain name**. In particular, you should read this note: @@ -106,7 +115,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") From 36dba82224de1b64bf571ebae5e944ce8a02e7c2 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Sat, 11 Feb 2023 11:10:58 +0100 Subject: [PATCH 13/43] Show the API summary on the top of each Conduit API page Summary: Show the API summary on the top of each Conduit API page. Before this change, the summary was only displayed from the Conduit APIs list. Closes T15141 Test Plan: I've opened 20 random Conduit API pages from the web interface and I checked that now there is an amazing "Summary" field. I also double-checked that there were not green peppers. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15141 Differential Revision: https://we.phorge.it/D25063 --- .../controller/PhabricatorConduitConsoleController.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php index 70a2253084..b3dfa805a2 100644 --- a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php +++ b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php @@ -247,6 +247,10 @@ final class PhabricatorConduitConsoleController )); } + $view->addProperty( + pht('Summary'), + $method->getMethodSummary()); + $view->addProperty( pht('Returns'), $method->getReturnType()); From 53c31b7b135cb17bd2d14169b81743931f1a6542 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Fri, 3 Mar 2023 12:12:01 +0100 Subject: [PATCH 14/43] Fix middle-click, CTRL+click, right-click etc. on Typehead search results Summary: Fix middle-click, CTRL+click, right-click etc. on Typehead search results. Closes T15149 Test Plan: Try the following actions on various typeheads: - right-click - middle-click - CTRL+click - normal click Demonstration video (2M) showing After patch (on localhost) and Before patch (here on we.phorge.it), where I middle-click and normal-click on menu entries: {F256610} Notes: - the middle click now works (opening in new tab) - the CTRL+click (or "command" key + click) now works (opening in new tab) - the right click now opens the context menu (previously broken) - the normal click should just click (as usual) Try on: - search results while typing in main search bar - search results when editing a Task Tags / assigned to, etc. - try to click on other weird places - $$$ Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15149 Differential Revision: https://we.phorge.it/D25069 --- resources/celerity/map.php | 18 +++++++++--------- .../javelin/lib/control/typeahead/Typeahead.js | 13 ++++++++++++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 80ba37f78e..71761a45e6 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', 'core.pkg.css' => 'f538846d', - 'core.pkg.js' => '256dfd7b', + 'core.pkg.js' => '6a2c22c2', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '609e63d4', 'differential.pkg.js' => 'c60bec1b', @@ -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' => 'd96e47a4', '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', @@ -731,7 +731,7 @@ return array( 'javelin-sound' => 'd4cc2d2a', 'javelin-stratcom' => '0889b835', 'javelin-tokenizer' => '89a1ae3a', - 'javelin-typeahead' => 'a4356cde', + 'javelin-typeahead' => 'd96e47a4', 'javelin-typeahead-composite-source' => '22ee68a5', 'javelin-typeahead-normalizer' => 'a241536a', 'javelin-typeahead-ondemand-source' => '23387297', @@ -1800,12 +1800,6 @@ return array( 'javelin-workflow', 'phabricator-draggable-list', ), - 'a4356cde' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-vector', - 'javelin-util', - ), 'a43ae2ae' => array( 'javelin-install', 'javelin-dom', @@ -2100,6 +2094,12 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), + 'd96e47a4' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-vector', + 'javelin-util', + ), 'da15d3dc' => array( 'phui-oi-list-view-css', ), diff --git a/webroot/rsrc/externals/javelin/lib/control/typeahead/Typeahead.js b/webroot/rsrc/externals/javelin/lib/control/typeahead/Typeahead.js index 5875bf9ea6..e971ae1495 100644 --- a/webroot/rsrc/externals/javelin/lib/control/typeahead/Typeahead.js +++ b/webroot/rsrc/externals/javelin/lib/control/typeahead/Typeahead.js @@ -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(); } })); From 66192a5b832cff2856809736ab6e4696595a0934 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Fri, 3 Mar 2023 12:15:48 +0100 Subject: [PATCH 15/43] Fix "Undefined index: icon" when visiting Search Servers using MySQL Summary: Fix "Undefined index: icon" when visiting Search Servers using MySQL NOTE: This patch just fixes the exception at my best but this section probably deserves more improvement to show a better default. Closes T15155 Test Plan: - use the default Search Server configuration (that is MySQL) - open the page Search Servers (/config/cluster/search/) - verify that it does not explode anymore but it displays something unuseful Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15155 Differential Revision: https://we.phorge.it/D25070 --- .../PhabricatorConfigClusterSearchController.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/applications/config/controller/services/PhabricatorConfigClusterSearchController.php b/src/applications/config/controller/services/PhabricatorConfigClusterSearchController.php index 5e877d6b95..706d6b4fa9 100644 --- a/src/applications/config/controller/services/PhabricatorConfigClusterSearchController.php +++ b/src/applications/config/controller/services/PhabricatorConfigClusterSearchController.php @@ -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'); From 9623e667458821f696eb92e9dac5a2c9720af7af Mon Sep 17 00:00:00 2001 From: MacFan4000 Date: Fri, 3 Mar 2023 19:27:30 +0100 Subject: [PATCH 16/43] replace usage of each() with foreach() for smtp Summary: this replaces uses with warning suppression of each() (depreccated in PHP7, removed in PHP8) with foreach Test Plan: can verify that these chages do work (tested on my own install) Reviewers: O1 Blessed Committers, #blessed_committers, dsadad, avivey, valerio.bozzolan Reviewed By: O1 Blessed Committers, #blessed_committers, dsadad, avivey, valerio.bozzolan Subscribers: avivey, Cigaryno, speck, tobiaswiese, valerio.bozzolan, Matthew Differential Revision: https://we.phorge.it/D25059 --- externals/phpmailer/class.smtp.php | 1626 ++++++++++++++-------------- 1 file changed, 813 insertions(+), 813 deletions(-) diff --git a/externals/phpmailer/class.smtp.php b/externals/phpmailer/class.smtp.php index c2ca1cb3b8..1b238eec95 100644 --- a/externals/phpmailer/class.smtp.php +++ b/externals/phpmailer/class.smtp.php @@ -1,814 +1,814 @@ -smtp_conn = 0; - $this->error = null; - $this->helo_rply = null; - - $this->do_debug = 0; - } - - ///////////////////////////////////////////////// - // CONNECTION FUNCTIONS - ///////////////////////////////////////////////// - - /** - * Connect to the server specified on the port specified. - * If the port is not specified use the default SMTP_PORT. - * If tval is specified then a connection will try and be - * established with the server for that number of seconds. - * If tval is not specified the default is 30 seconds to - * try on the connection. - * - * SMTP CODE SUCCESS: 220 - * SMTP CODE FAILURE: 421 - * @access public - * @return bool - */ - public function Connect($host, $port = 0, $tval = 30) { - // set the error val to null so there is no confusion - $this->error = null; - - // make sure we are __not__ connected - if($this->connected()) { - // already connected, generate error - $this->error = array("error" => "Already connected to a server"); - return false; - } - - if(empty($port)) { - $port = $this->SMTP_PORT; - } - - // connect to the smtp server - $this->smtp_conn = @fsockopen($host, // the host of the server - $port, // the port to use - $errno, // error number if any - $errstr, // error message if any - $tval); // give up after ? secs - // verify we connected properly - if(empty($this->smtp_conn)) { - $this->error = array("error" => "Failed to connect to server", - "errno" => $errno, - "errstr" => $errstr); - if($this->do_debug >= 1) { - echo "SMTP -> ERROR: " . $this->error["error"] . ": $errstr ($errno)" . $this->CRLF . '
'; - } - return false; - } - - // SMTP server can take longer to respond, give longer timeout for first read - // Windows does not have support for this timeout function - if(substr(PHP_OS, 0, 3) != "WIN") - socket_set_timeout($this->smtp_conn, $tval, 0); - - // get any announcement - $announce = $this->get_lines(); - - if($this->do_debug >= 2) { - echo "SMTP -> FROM SERVER:" . $announce . $this->CRLF . '
'; - } - - return true; - } - - /** - * Initiate a TLS communication with the server. - * - * SMTP CODE 220 Ready to start TLS - * SMTP CODE 501 Syntax error (no parameters allowed) - * SMTP CODE 454 TLS not available due to temporary reason - * @access public - * @return bool success - */ - public function StartTLS() { - $this->error = null; # to avoid confusion - - if(!$this->connected()) { - $this->error = array("error" => "Called StartTLS() without being connected"); - return false; - } - - fputs($this->smtp_conn,"STARTTLS" . $this->CRLF); - - $rply = $this->get_lines(); - $code = substr($rply,0,3); - - if($this->do_debug >= 2) { - echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; - } - - if($code != 220) { - $this->error = - array("error" => "STARTTLS not accepted from server", - "smtp_code" => $code, - "smtp_msg" => substr($rply,4)); - if($this->do_debug >= 1) { - echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; - } - return false; - } - - // Begin encrypted connection - if(!stream_socket_enable_crypto($this->smtp_conn, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { - return false; - } - - return true; - } - - /** - * Performs SMTP authentication. Must be run after running the - * Hello() method. Returns true if successfully authenticated. - * @access public - * @return bool - */ - public function Authenticate($username, $password) { - // Start authentication - fputs($this->smtp_conn,"AUTH LOGIN" . $this->CRLF); - - $rply = $this->get_lines(); - $code = substr($rply,0,3); - - if($code != 334) { - $this->error = - array("error" => "AUTH not accepted from server", - "smtp_code" => $code, - "smtp_msg" => substr($rply,4)); - if($this->do_debug >= 1) { - echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; - } - return false; - } - - // Send encoded username - fputs($this->smtp_conn, base64_encode($username) . $this->CRLF); - - $rply = $this->get_lines(); - $code = substr($rply,0,3); - - if($code != 334) { - $this->error = - array("error" => "Username not accepted from server", - "smtp_code" => $code, - "smtp_msg" => substr($rply,4)); - if($this->do_debug >= 1) { - echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; - } - return false; - } - - // Send encoded password - fputs($this->smtp_conn, base64_encode($password) . $this->CRLF); - - $rply = $this->get_lines(); - $code = substr($rply,0,3); - - if($code != 235) { - $this->error = - array("error" => "Password not accepted from server", - "smtp_code" => $code, - "smtp_msg" => substr($rply,4)); - if($this->do_debug >= 1) { - echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; - } - return false; - } - - return true; - } - - /** - * Returns true if connected to a server otherwise false - * @access public - * @return bool - */ - public function Connected() { - if(!empty($this->smtp_conn)) { - $sock_status = socket_get_status($this->smtp_conn); - if($sock_status["eof"]) { - // the socket is valid but we are not connected - if($this->do_debug >= 1) { - echo "SMTP -> NOTICE:" . $this->CRLF . "EOF caught while checking if connected"; - } - $this->Close(); - return false; - } - return true; // everything looks good - } - return false; - } - - /** - * Closes the socket and cleans up the state of the class. - * It is not considered good to use this function without - * first trying to use QUIT. - * @access public - * @return void - */ - public function Close() { - $this->error = null; // so there is no confusion - $this->helo_rply = null; - if(!empty($this->smtp_conn)) { - // close the connection and cleanup - fclose($this->smtp_conn); - $this->smtp_conn = 0; - } - } - - ///////////////////////////////////////////////// - // SMTP COMMANDS - ///////////////////////////////////////////////// - - /** - * Issues a data command and sends the msg_data to the server - * finializing the mail transaction. $msg_data is the message - * that is to be send with the headers. Each header needs to be - * on a single line followed by a with the message headers - * and the message body being seperated by and additional . - * - * Implements rfc 821: DATA - * - * SMTP CODE INTERMEDIATE: 354 - * [data] - * . - * SMTP CODE SUCCESS: 250 - * SMTP CODE FAILURE: 552,554,451,452 - * SMTP CODE FAILURE: 451,554 - * SMTP CODE ERROR : 500,501,503,421 - * @access public - * @return bool - */ - public function Data($msg_data) { - $this->error = null; // so no confusion is caused - - if(!$this->connected()) { - $this->error = array( - "error" => "Called Data() without being connected"); - return false; - } - - fputs($this->smtp_conn,"DATA" . $this->CRLF); - - $rply = $this->get_lines(); - $code = substr($rply,0,3); - - if($this->do_debug >= 2) { - echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; - } - - if($code != 354) { - $this->error = - array("error" => "DATA command not accepted from server", - "smtp_code" => $code, - "smtp_msg" => substr($rply,4)); - if($this->do_debug >= 1) { - echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; - } - return false; - } - - /* the server is ready to accept data! - * according to rfc 821 we should not send more than 1000 - * including the CRLF - * characters on a single line so we will break the data up - * into lines by \r and/or \n then if needed we will break - * each of those into smaller lines to fit within the limit. - * in addition we will be looking for lines that start with - * a period '.' and append and additional period '.' to that - * line. NOTE: this does not count towards limit. - */ - - // normalize the line breaks so we know the explode works - $msg_data = str_replace("\r\n","\n",$msg_data); - $msg_data = str_replace("\r","\n",$msg_data); - $lines = explode("\n",$msg_data); - - /* we need to find a good way to determine is headers are - * in the msg_data or if it is a straight msg body - * currently I am assuming rfc 822 definitions of msg headers - * and if the first field of the first line (':' sperated) - * does not contain a space then it _should_ be a header - * and we can process all lines before a blank "" line as - * headers. - */ - - $field = substr($lines[0],0,strpos($lines[0],":")); - $in_headers = false; - if(!empty($field) && !strstr($field," ")) { - $in_headers = true; - } - - $max_line_length = 998; // used below; set here for ease in change - - while(list(,$line) = @each($lines)) { - $lines_out = null; - if($line == "" && $in_headers) { - $in_headers = false; - } - // ok we need to break this line up into several smaller lines - while(strlen($line) > $max_line_length) { - $pos = strrpos(substr($line,0,$max_line_length)," "); - - // Patch to fix DOS attack - if(!$pos) { - $pos = $max_line_length - 1; - $lines_out[] = substr($line,0,$pos); - $line = substr($line,$pos); - } else { - $lines_out[] = substr($line,0,$pos); - $line = substr($line,$pos + 1); - } - - /* if processing headers add a LWSP-char to the front of new line - * rfc 822 on long msg headers - */ - if($in_headers) { - $line = "\t" . $line; - } - } - $lines_out[] = $line; - - // send the lines to the server - while(list(,$line_out) = @each($lines_out)) { - if(strlen($line_out) > 0) - { - if(substr($line_out, 0, 1) == ".") { - $line_out = "." . $line_out; - } - } - fputs($this->smtp_conn,$line_out . $this->CRLF); - } - } - - // message data has been sent - fputs($this->smtp_conn, $this->CRLF . "." . $this->CRLF); - - $rply = $this->get_lines(); - $code = substr($rply,0,3); - - if($this->do_debug >= 2) { - echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; - } - - if($code != 250) { - $this->error = - array("error" => "DATA not accepted from server", - "smtp_code" => $code, - "smtp_msg" => substr($rply,4)); - if($this->do_debug >= 1) { - echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; - } - return false; - } - return true; - } - - /** - * Sends the HELO command to the smtp server. - * This makes sure that we and the server are in - * the same known state. - * - * Implements from rfc 821: HELO - * - * SMTP CODE SUCCESS: 250 - * SMTP CODE ERROR : 500, 501, 504, 421 - * @access public - * @return bool - */ - public function Hello($host = '') { - $this->error = null; // so no confusion is caused - - if(!$this->connected()) { - $this->error = array( - "error" => "Called Hello() without being connected"); - return false; - } - - // if hostname for HELO was not specified send default - if(empty($host)) { - // determine appropriate default to send to server - $host = "localhost"; - } - - // Send extended hello first (RFC 2821) - if(!$this->SendHello("EHLO", $host)) { - if(!$this->SendHello("HELO", $host)) { - return false; - } - } - - return true; - } - - /** - * Sends a HELO/EHLO command. - * @access private - * @return bool - */ - private function SendHello($hello, $host) { - fputs($this->smtp_conn, $hello . " " . $host . $this->CRLF); - - $rply = $this->get_lines(); - $code = substr($rply,0,3); - - if($this->do_debug >= 2) { - echo "SMTP -> FROM SERVER: " . $rply . $this->CRLF . '
'; - } - - if($code != 250) { - $this->error = - array("error" => $hello . " not accepted from server", - "smtp_code" => $code, - "smtp_msg" => substr($rply,4)); - if($this->do_debug >= 1) { - echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; - } - return false; - } - - $this->helo_rply = $rply; - - return true; - } - - /** - * Starts a mail transaction from the email address specified in - * $from. Returns true if successful or false otherwise. If True - * the mail transaction is started and then one or more Recipient - * commands may be called followed by a Data command. - * - * Implements rfc 821: MAIL FROM: - * - * SMTP CODE SUCCESS: 250 - * SMTP CODE SUCCESS: 552,451,452 - * SMTP CODE SUCCESS: 500,501,421 - * @access public - * @return bool - */ - public function Mail($from) { - $this->error = null; // so no confusion is caused - - if(!$this->connected()) { - $this->error = array( - "error" => "Called Mail() without being connected"); - return false; - } - - $useVerp = ($this->do_verp ? "XVERP" : ""); - fputs($this->smtp_conn,"MAIL FROM:<" . $from . ">" . $useVerp . $this->CRLF); - - $rply = $this->get_lines(); - $code = substr($rply,0,3); - - if($this->do_debug >= 2) { - echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; - } - - if($code != 250) { - $this->error = - array("error" => "MAIL not accepted from server", - "smtp_code" => $code, - "smtp_msg" => substr($rply,4)); - if($this->do_debug >= 1) { - echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; - } - return false; - } - return true; - } - - /** - * Sends the quit command to the server and then closes the socket - * if there is no error or the $close_on_error argument is true. - * - * Implements from rfc 821: QUIT - * - * SMTP CODE SUCCESS: 221 - * SMTP CODE ERROR : 500 - * @access public - * @return bool - */ - public function Quit($close_on_error = true) { - $this->error = null; // so there is no confusion - - if(!$this->connected()) { - $this->error = array( - "error" => "Called Quit() without being connected"); - return false; - } - - // send the quit command to the server - fputs($this->smtp_conn,"quit" . $this->CRLF); - - // get any good-bye messages - $byemsg = $this->get_lines(); - - if($this->do_debug >= 2) { - echo "SMTP -> FROM SERVER:" . $byemsg . $this->CRLF . '
'; - } - - $rval = true; - $e = null; - - $code = substr($byemsg,0,3); - if($code != 221) { - // use e as a tmp var cause Close will overwrite $this->error - $e = array("error" => "SMTP server rejected quit command", - "smtp_code" => $code, - "smtp_rply" => substr($byemsg,4)); - $rval = false; - if($this->do_debug >= 1) { - echo "SMTP -> ERROR: " . $e["error"] . ": " . $byemsg . $this->CRLF . '
'; - } - } - - if(empty($e) || $close_on_error) { - $this->Close(); - } - - return $rval; - } - - /** - * Sends the command RCPT to the SMTP server with the TO: argument of $to. - * Returns true if the recipient was accepted false if it was rejected. - * - * Implements from rfc 821: RCPT TO: - * - * SMTP CODE SUCCESS: 250,251 - * SMTP CODE FAILURE: 550,551,552,553,450,451,452 - * SMTP CODE ERROR : 500,501,503,421 - * @access public - * @return bool - */ - public function Recipient($to) { - $this->error = null; // so no confusion is caused - - if(!$this->connected()) { - $this->error = array( - "error" => "Called Recipient() without being connected"); - return false; - } - - fputs($this->smtp_conn,"RCPT TO:<" . $to . ">" . $this->CRLF); - - $rply = $this->get_lines(); - $code = substr($rply,0,3); - - if($this->do_debug >= 2) { - echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; - } - - if($code != 250 && $code != 251) { - $this->error = - array("error" => "RCPT not accepted from server", - "smtp_code" => $code, - "smtp_msg" => substr($rply,4)); - if($this->do_debug >= 1) { - echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; - } - return false; - } - return true; - } - - /** - * Sends the RSET command to abort and transaction that is - * currently in progress. Returns true if successful false - * otherwise. - * - * Implements rfc 821: RSET - * - * SMTP CODE SUCCESS: 250 - * SMTP CODE ERROR : 500,501,504,421 - * @access public - * @return bool - */ - public function Reset() { - $this->error = null; // so no confusion is caused - - if(!$this->connected()) { - $this->error = array( - "error" => "Called Reset() without being connected"); - return false; - } - - fputs($this->smtp_conn,"RSET" . $this->CRLF); - - $rply = $this->get_lines(); - $code = substr($rply,0,3); - - if($this->do_debug >= 2) { - echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; - } - - if($code != 250) { - $this->error = - array("error" => "RSET failed", - "smtp_code" => $code, - "smtp_msg" => substr($rply,4)); - if($this->do_debug >= 1) { - echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; - } - return false; - } - - return true; - } - - /** - * Starts a mail transaction from the email address specified in - * $from. Returns true if successful or false otherwise. If True - * the mail transaction is started and then one or more Recipient - * commands may be called followed by a Data command. This command - * will send the message to the users terminal if they are logged - * in and send them an email. - * - * Implements rfc 821: SAML FROM: - * - * SMTP CODE SUCCESS: 250 - * SMTP CODE SUCCESS: 552,451,452 - * SMTP CODE SUCCESS: 500,501,502,421 - * @access public - * @return bool - */ - public function SendAndMail($from) { - $this->error = null; // so no confusion is caused - - if(!$this->connected()) { - $this->error = array( - "error" => "Called SendAndMail() without being connected"); - return false; - } - - fputs($this->smtp_conn,"SAML FROM:" . $from . $this->CRLF); - - $rply = $this->get_lines(); - $code = substr($rply,0,3); - - if($this->do_debug >= 2) { - echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; - } - - if($code != 250) { - $this->error = - array("error" => "SAML not accepted from server", - "smtp_code" => $code, - "smtp_msg" => substr($rply,4)); - if($this->do_debug >= 1) { - echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; - } - return false; - } - return true; - } - - /** - * This is an optional command for SMTP that this class does not - * support. This method is here to make the RFC821 Definition - * complete for this class and __may__ be implimented in the future - * - * Implements from rfc 821: TURN - * - * SMTP CODE SUCCESS: 250 - * SMTP CODE FAILURE: 502 - * SMTP CODE ERROR : 500, 503 - * @access public - * @return bool - */ - public function Turn() { - $this->error = array("error" => "This method, TURN, of the SMTP ". - "is not implemented"); - if($this->do_debug >= 1) { - echo "SMTP -> NOTICE: " . $this->error["error"] . $this->CRLF . '
'; - } - return false; - } - - /** - * Get the current error - * @access public - * @return array - */ - public function getError() { - return $this->error; - } - - ///////////////////////////////////////////////// - // INTERNAL FUNCTIONS - ///////////////////////////////////////////////// - - /** - * Read in as many lines as possible - * either before eof or socket timeout occurs on the operation. - * With SMTP we can tell if we have more lines to read if the - * 4th character is '-' symbol. If it is a space then we don't - * need to read anything else. - * @access private - * @return string - */ - private function get_lines() { - $data = ""; - while($str = @fgets($this->smtp_conn,515)) { - if($this->do_debug >= 4) { - echo "SMTP -> get_lines(): \$data was \"$data\"" . $this->CRLF . '
'; - echo "SMTP -> get_lines(): \$str is \"$str\"" . $this->CRLF . '
'; - } - $data .= $str; - if($this->do_debug >= 4) { - echo "SMTP -> get_lines(): \$data is \"$data\"" . $this->CRLF . '
'; - } - // if 4th character is a space, we are done reading, break the loop - if(substr($str,3,1) == " ") { break; } - } - return $data; - } - -} - +smtp_conn = 0; + $this->error = null; + $this->helo_rply = null; + + $this->do_debug = 0; + } + + ///////////////////////////////////////////////// + // CONNECTION FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Connect to the server specified on the port specified. + * If the port is not specified use the default SMTP_PORT. + * If tval is specified then a connection will try and be + * established with the server for that number of seconds. + * If tval is not specified the default is 30 seconds to + * try on the connection. + * + * SMTP CODE SUCCESS: 220 + * SMTP CODE FAILURE: 421 + * @access public + * @return bool + */ + public function Connect($host, $port = 0, $tval = 30) { + // set the error val to null so there is no confusion + $this->error = null; + + // make sure we are __not__ connected + if($this->connected()) { + // already connected, generate error + $this->error = array("error" => "Already connected to a server"); + return false; + } + + if(empty($port)) { + $port = $this->SMTP_PORT; + } + + // connect to the smtp server + $this->smtp_conn = @fsockopen($host, // the host of the server + $port, // the port to use + $errno, // error number if any + $errstr, // error message if any + $tval); // give up after ? secs + // verify we connected properly + if(empty($this->smtp_conn)) { + $this->error = array("error" => "Failed to connect to server", + "errno" => $errno, + "errstr" => $errstr); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": $errstr ($errno)" . $this->CRLF . '
'; + } + return false; + } + + // SMTP server can take longer to respond, give longer timeout for first read + // Windows does not have support for this timeout function + if(substr(PHP_OS, 0, 3) != "WIN") + socket_set_timeout($this->smtp_conn, $tval, 0); + + // get any announcement + $announce = $this->get_lines(); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $announce . $this->CRLF . '
'; + } + + return true; + } + + /** + * Initiate a TLS communication with the server. + * + * SMTP CODE 220 Ready to start TLS + * SMTP CODE 501 Syntax error (no parameters allowed) + * SMTP CODE 454 TLS not available due to temporary reason + * @access public + * @return bool success + */ + public function StartTLS() { + $this->error = null; # to avoid confusion + + if(!$this->connected()) { + $this->error = array("error" => "Called StartTLS() without being connected"); + return false; + } + + fputs($this->smtp_conn,"STARTTLS" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 220) { + $this->error = + array("error" => "STARTTLS not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + // Begin encrypted connection + if(!stream_socket_enable_crypto($this->smtp_conn, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + return false; + } + + return true; + } + + /** + * Performs SMTP authentication. Must be run after running the + * Hello() method. Returns true if successfully authenticated. + * @access public + * @return bool + */ + public function Authenticate($username, $password) { + // Start authentication + fputs($this->smtp_conn,"AUTH LOGIN" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($code != 334) { + $this->error = + array("error" => "AUTH not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + // Send encoded username + fputs($this->smtp_conn, base64_encode($username) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($code != 334) { + $this->error = + array("error" => "Username not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + // Send encoded password + fputs($this->smtp_conn, base64_encode($password) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($code != 235) { + $this->error = + array("error" => "Password not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + return true; + } + + /** + * Returns true if connected to a server otherwise false + * @access public + * @return bool + */ + public function Connected() { + if(!empty($this->smtp_conn)) { + $sock_status = socket_get_status($this->smtp_conn); + if($sock_status["eof"]) { + // the socket is valid but we are not connected + if($this->do_debug >= 1) { + echo "SMTP -> NOTICE:" . $this->CRLF . "EOF caught while checking if connected"; + } + $this->Close(); + return false; + } + return true; // everything looks good + } + return false; + } + + /** + * Closes the socket and cleans up the state of the class. + * It is not considered good to use this function without + * first trying to use QUIT. + * @access public + * @return void + */ + public function Close() { + $this->error = null; // so there is no confusion + $this->helo_rply = null; + if(!empty($this->smtp_conn)) { + // close the connection and cleanup + fclose($this->smtp_conn); + $this->smtp_conn = 0; + } + } + + ///////////////////////////////////////////////// + // SMTP COMMANDS + ///////////////////////////////////////////////// + + /** + * Issues a data command and sends the msg_data to the server + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being seperated by and additional . + * + * Implements rfc 821: DATA + * + * SMTP CODE INTERMEDIATE: 354 + * [data] + * . + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 552,554,451,452 + * SMTP CODE FAILURE: 451,554 + * SMTP CODE ERROR : 500,501,503,421 + * @access public + * @return bool + */ + public function Data($msg_data) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Data() without being connected"); + return false; + } + + fputs($this->smtp_conn,"DATA" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 354) { + $this->error = + array("error" => "DATA command not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + /* the server is ready to accept data! + * according to rfc 821 we should not send more than 1000 + * including the CRLF + * characters on a single line so we will break the data up + * into lines by \r and/or \n then if needed we will break + * each of those into smaller lines to fit within the limit. + * in addition we will be looking for lines that start with + * a period '.' and append and additional period '.' to that + * line. NOTE: this does not count towards limit. + */ + + // normalize the line breaks so we know the explode works + $msg_data = str_replace("\r\n","\n",$msg_data); + $msg_data = str_replace("\r","\n",$msg_data); + $lines = explode("\n",$msg_data); + + /* we need to find a good way to determine is headers are + * in the msg_data or if it is a straight msg body + * currently I am assuming rfc 822 definitions of msg headers + * and if the first field of the first line (':' sperated) + * does not contain a space then it _should_ be a header + * and we can process all lines before a blank "" line as + * headers. + */ + + $field = substr($lines[0],0,strpos($lines[0],":")); + $in_headers = false; + if(!empty($field) && !strstr($field," ")) { + $in_headers = true; + } + + $max_line_length = 998; // used below; set here for ease in change + + foreach($lines as $line) { + $lines_out = null; + if($line == "" && $in_headers) { + $in_headers = false; + } + // ok we need to break this line up into several smaller lines + while(strlen($line) > $max_line_length) { + $pos = strrpos(substr($line,0,$max_line_length)," "); + + // Patch to fix DOS attack + if(!$pos) { + $pos = $max_line_length - 1; + $lines_out[] = substr($line,0,$pos); + $line = substr($line,$pos); + } else { + $lines_out[] = substr($line,0,$pos); + $line = substr($line,$pos + 1); + } + + /* if processing headers add a LWSP-char to the front of new line + * rfc 822 on long msg headers + */ + if($in_headers) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + // send the lines to the server + foreach($lines_out as $line_out) { + if(strlen($line_out) > 0) + { + if(substr($line_out, 0, 1) == ".") { + $line_out = "." . $line_out; + } + } + fputs($this->smtp_conn,$line_out . $this->CRLF); + } + } + + // message data has been sent + fputs($this->smtp_conn, $this->CRLF . "." . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => "DATA not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + return true; + } + + /** + * Sends the HELO command to the smtp server. + * This makes sure that we and the server are in + * the same known state. + * + * Implements from rfc 821: HELO + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500, 501, 504, 421 + * @access public + * @return bool + */ + public function Hello($host = '') { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Hello() without being connected"); + return false; + } + + // if hostname for HELO was not specified send default + if(empty($host)) { + // determine appropriate default to send to server + $host = "localhost"; + } + + // Send extended hello first (RFC 2821) + if(!$this->SendHello("EHLO", $host)) { + if(!$this->SendHello("HELO", $host)) { + return false; + } + } + + return true; + } + + /** + * Sends a HELO/EHLO command. + * @access private + * @return bool + */ + private function SendHello($hello, $host) { + fputs($this->smtp_conn, $hello . " " . $host . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER: " . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => $hello . " not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + $this->helo_rply = $rply; + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. + * + * Implements rfc 821: MAIL FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552,451,452 + * SMTP CODE SUCCESS: 500,501,421 + * @access public + * @return bool + */ + public function Mail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Mail() without being connected"); + return false; + } + + $useVerp = ($this->do_verp ? "XVERP" : ""); + fputs($this->smtp_conn,"MAIL FROM:<" . $from . ">" . $useVerp . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => "MAIL not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + return true; + } + + /** + * Sends the quit command to the server and then closes the socket + * if there is no error or the $close_on_error argument is true. + * + * Implements from rfc 821: QUIT + * + * SMTP CODE SUCCESS: 221 + * SMTP CODE ERROR : 500 + * @access public + * @return bool + */ + public function Quit($close_on_error = true) { + $this->error = null; // so there is no confusion + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Quit() without being connected"); + return false; + } + + // send the quit command to the server + fputs($this->smtp_conn,"quit" . $this->CRLF); + + // get any good-bye messages + $byemsg = $this->get_lines(); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $byemsg . $this->CRLF . '
'; + } + + $rval = true; + $e = null; + + $code = substr($byemsg,0,3); + if($code != 221) { + // use e as a tmp var cause Close will overwrite $this->error + $e = array("error" => "SMTP server rejected quit command", + "smtp_code" => $code, + "smtp_rply" => substr($byemsg,4)); + $rval = false; + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $e["error"] . ": " . $byemsg . $this->CRLF . '
'; + } + } + + if(empty($e) || $close_on_error) { + $this->Close(); + } + + return $rval; + } + + /** + * Sends the command RCPT to the SMTP server with the TO: argument of $to. + * Returns true if the recipient was accepted false if it was rejected. + * + * Implements from rfc 821: RCPT TO: + * + * SMTP CODE SUCCESS: 250,251 + * SMTP CODE FAILURE: 550,551,552,553,450,451,452 + * SMTP CODE ERROR : 500,501,503,421 + * @access public + * @return bool + */ + public function Recipient($to) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Recipient() without being connected"); + return false; + } + + fputs($this->smtp_conn,"RCPT TO:<" . $to . ">" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250 && $code != 251) { + $this->error = + array("error" => "RCPT not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + return true; + } + + /** + * Sends the RSET command to abort and transaction that is + * currently in progress. Returns true if successful false + * otherwise. + * + * Implements rfc 821: RSET + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500,501,504,421 + * @access public + * @return bool + */ + public function Reset() { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Reset() without being connected"); + return false; + } + + fputs($this->smtp_conn,"RSET" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => "RSET failed", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * + * Implements rfc 821: SAML FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552,451,452 + * SMTP CODE SUCCESS: 500,501,502,421 + * @access public + * @return bool + */ + public function SendAndMail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called SendAndMail() without being connected"); + return false; + } + + fputs($this->smtp_conn,"SAML FROM:" . $from . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => "SAML not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + return true; + } + + /** + * This is an optional command for SMTP that this class does not + * support. This method is here to make the RFC821 Definition + * complete for this class and __may__ be implimented in the future + * + * Implements from rfc 821: TURN + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 502 + * SMTP CODE ERROR : 500, 503 + * @access public + * @return bool + */ + public function Turn() { + $this->error = array("error" => "This method, TURN, of the SMTP ". + "is not implemented"); + if($this->do_debug >= 1) { + echo "SMTP -> NOTICE: " . $this->error["error"] . $this->CRLF . '
'; + } + return false; + } + + /** + * Get the current error + * @access public + * @return array + */ + public function getError() { + return $this->error; + } + + ///////////////////////////////////////////////// + // INTERNAL FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Read in as many lines as possible + * either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * @access private + * @return string + */ + private function get_lines() { + $data = ""; + while($str = @fgets($this->smtp_conn,515)) { + if($this->do_debug >= 4) { + echo "SMTP -> get_lines(): \$data was \"$data\"" . $this->CRLF . '
'; + echo "SMTP -> get_lines(): \$str is \"$str\"" . $this->CRLF . '
'; + } + $data .= $str; + if($this->do_debug >= 4) { + echo "SMTP -> get_lines(): \$data is \"$data\"" . $this->CRLF . '
'; + } + // if 4th character is a space, we are done reading, break the loop + if(substr($str,3,1) == " ") { break; } + } + return $data; + } + +} + ?> \ No newline at end of file From 3487ee444a885de558c8586f137cd7d477e27d65 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Tue, 14 Mar 2023 20:24:32 +0100 Subject: [PATCH 17/43] Fix regression in new confirmation Dialog Summary: Fix a JavaScript regression encapsulating the problematic part into an `if`. Other minor changes: - dedicate a variable for the confirmation messages to improve i18n in the future (but also to avoid 80 characters and make lint happy) - replace `confirm` with `window.confirm` (to make lint happy) Ref T15034 Ref D25015 Test Plan: - surf on your local Phorge - no JavaScript errors in console Reviewers: bekay, Ekubischta, O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15034 Differential Revision: https://we.phorge.it/D25076 --- .../rsrc/externals/javelin/lib/Workflow.js | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/webroot/rsrc/externals/javelin/lib/Workflow.js b/webroot/rsrc/externals/javelin/lib/Workflow.js index 7d63a3408a..6d91682dbc 100644 --- a/webroot/rsrc/externals/javelin/lib/Workflow.js +++ b/webroot/rsrc/externals/javelin/lib/Workflow.js @@ -404,14 +404,18 @@ JX.install('Workflow', { } } - 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'); - }); + // 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() { @@ -546,13 +550,20 @@ JX.install('Workflow', { return; } - var form = JX.DOM.scry(active._root, 'form', 'jx-dialog'); - if ( - form.length && - JX.Stratcom.hasSigil(form[0], 'dialog-keydown') && - !confirm('Form data may have changed. Are you sure you want to close this dialog?') - ) { - 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(); From 5cba56182f39a983ce7fd2a6a6b280d28b2619da Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Thu, 16 Mar 2023 15:27:34 +0100 Subject: [PATCH 18/43] Diviner: fix "Javascript" -> "JavaScript" and minor change Test Plan: - look at Diviner and say "Java..." - Diviner will look at you saying "...Script" - you land, satisfied If Diviner says "...script" instead (lowercase "S"), abandon the ship. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25075 --- src/docs/book/flavor.book | 2 +- .../flavor/javascript_object_array.diviner | 32 +++++++++++++------ src/docs/flavor/javascript_pitfalls.diviner | 16 +++++----- src/docs/flavor/php_pitfalls.diviner | 2 +- src/docs/flavor/project_history.diviner | 2 +- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/docs/book/flavor.book b/src/docs/book/flavor.book index 4404e94bf7..c9a80fae92 100644 --- a/src/docs/book/flavor.book +++ b/src/docs/book/flavor.book @@ -21,7 +21,7 @@ ], "groups": { "javascript": { - "name": "Javascript" + "name": "JavaScript" }, "lore": { "name": "Phorge Lore" diff --git a/src/docs/flavor/javascript_object_array.diviner b/src/docs/flavor/javascript_object_array.diviner index cdba029e9a..113dbb1000 100644 --- a/src/docs/flavor/javascript_object_array.diviner +++ b/src/docs/flavor/javascript_object_array.diviner @@ -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. diff --git a/src/docs/flavor/javascript_pitfalls.diviner b/src/docs/flavor/javascript_pitfalls.diviner index 9276ae4712..fbc2eae05c 100644 --- a/src/docs/flavor/javascript_pitfalls.diviner +++ b/src/docs/flavor/javascript_pitfalls.diviner @@ -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. diff --git a/src/docs/flavor/php_pitfalls.diviner b/src/docs/flavor/php_pitfalls.diviner index 3f4be45dd7..e15ca9e2bb 100644 --- a/src/docs/flavor/php_pitfalls.diviner +++ b/src/docs/flavor/php_pitfalls.diviner @@ -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 = diff --git a/src/docs/flavor/project_history.diviner b/src/docs/flavor/project_history.diviner index 1159304cda..6b51e203ed 100644 --- a/src/docs/flavor/project_history.diviner +++ b/src/docs/flavor/project_history.diviner @@ -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 From 4f356838beda6e03bbb05c94144cbc2df25ed38e Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Fri, 17 Mar 2023 08:29:35 +0100 Subject: [PATCH 19/43] FileUpload: show File page instead of binary file Summary: This modification improves a single link shown to the user. The goal is to make it easier to change file permissions or file name or whatever just after the upload of the file itself. Before this change, doing that was difficult because clicking on the popup would send you back to the binary file only. Closes T15165 Test Plan: - upload a file using drag & drop (for example in a comment) - click on the popup's link - you see the File page, not the binary file Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15165 Differential Revision: https://we.phorge.it/D25074 --- webroot/rsrc/js/core/FileUpload.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/webroot/rsrc/js/core/FileUpload.js b/webroot/rsrc/js/core/FileUpload.js index 22ba33f97a..e2c3530cbd 100644 --- a/webroot/rsrc/js/core/FileUpload.js +++ b/webroot/rsrc/js/core/FileUpload.js @@ -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,7 @@ JX.install('PhabricatorFileUpload', { switch (this.getStatus()) { case 'done': - var link = JX.$N('a', {href: this.getURI()}, 'F' + this.getID()); + var link = JX.$N('a', {href: this.getPageURI()}, this.getMonogram()); content = [ JX.$N('strong', {}, ['Upload Complete (', link, ')']), From b33e373503c6f64118ec77bb34dc8224d54da4e3 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Fri, 17 Mar 2023 13:13:13 +0100 Subject: [PATCH 20/43] Drag & Drop: set a link as external Summary: Rest assured: external links remain evil, by default. Don't adopt them randomly by induction. Whether you believe it or not, this specific external link merited some deep thoughts on Phorge: - https://we.phorge.it/T15172 So, whenever you use a mouse, a finger, or whenever we have a confirmation dialog or not to prevent onblur disasters, this change is probably consistent with common expectations. Having said, external links remain evil - by default. Closes T15172 Test Plan: - Drag & Drop a File on a Remarkup text - click on the link inside the popup - it opens in a new tab (without risk of form loss) Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15172 Differential Revision: https://we.phorge.it/D25077 --- webroot/rsrc/js/core/FileUpload.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/webroot/rsrc/js/core/FileUpload.js b/webroot/rsrc/js/core/FileUpload.js index e2c3530cbd..ef5089a56e 100644 --- a/webroot/rsrc/js/core/FileUpload.js +++ b/webroot/rsrc/js/core/FileUpload.js @@ -115,7 +115,19 @@ JX.install('PhabricatorFileUpload', { switch (this.getStatus()) { case 'done': - var link = JX.$N('a', {href: this.getPageURI()}, this.getMonogram()); + + // 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, ')']), From c6f56b8221bae80c316a81e0b6cf7f45b9eaba85 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Sat, 18 Mar 2023 14:57:15 +0100 Subject: [PATCH 21/43] Workboard: fix CTRL+click on "Create Task" and "Edit Task" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: The CTRL+click now opens the links in a new tab, like any other normal link opened with CTRL+click. Note that the middle-click was already working. Closes T15157 Test Plan: - visit a Workboard - column > menu > mouse on "Create Task" - CTRL+click: open in new tab (โ†’ now works) - normal click: open the pop-up (โ†’ still works) - middle-click: open in new tab (โ†’ still works) - column > single Task > mouse on "Edit" - CTRL+click: open in new tab (โ†’ now works) - normal click: open the pop-up (โ†’ still works) - middle-click: open in new tab (โ†’ still works) Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15157 Differential Revision: https://we.phorge.it/D25072 --- .../js/application/projects/WorkboardController.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/webroot/rsrc/js/application/projects/WorkboardController.js b/webroot/rsrc/js/application/projects/WorkboardController.js index da5d177bb9..1b38638f3d 100644 --- a/webroot/rsrc/js/application/projects/WorkboardController.js +++ b/webroot/rsrc/js/application/projects/WorkboardController.js @@ -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'); From 9bc3c16b6e7599ad513aec7347c61cc6677fe6d5 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Sat, 25 Mar 2023 12:06:22 +0100 Subject: [PATCH 22/43] Fix information page about Read-only status Summary: This change fixes the following page on your Phorge/Phabricator: /readonly/config/ If you visit that page (with or without read-only mode), the following crash will happen: Call to undefined method PlatformSymbols::getPlatformServerSymbol() Fixes T15185 Probably the regression was introduced in this point: rP4d3f0dc7c727e6fdc0e8cba0e8e8f2b8ef1bd16c This change also renamed "administrator" to "Administrator" in the messages from that specific page, both because I have great respect for Administrators and also because that is how this word is spelled in most other places in Phorge. Test Plan: - Visit the page /readonly/config/ - It does not crash anymore and you can see that beautiful message that - it seems - no person in the world has ever seen since a whole year. Take a good look at this page: it is a precious treasure. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15185 Differential Revision: https://we.phorge.it/D25091 --- resources/celerity/map.php | 82 +++++++++---------- .../PhabricatorSystemReadOnlyController.php | 6 +- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 71761a45e6..9323cc070d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', 'core.pkg.css' => 'f538846d', - 'core.pkg.js' => '6a2c22c2', + 'core.pkg.js' => '66c49ca1', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '609e63d4', 'differential.pkg.js' => 'c60bec1b', @@ -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' => 'd7ba6915', + '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' => 'd96e47a4', + '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', @@ -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', @@ -731,7 +731,7 @@ return array( 'javelin-sound' => 'd4cc2d2a', 'javelin-stratcom' => '0889b835', 'javelin-tokenizer' => '89a1ae3a', - 'javelin-typeahead' => 'd96e47a4', + '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' => 'd7ba6915', + 'javelin-workflow' => 'cc1553f3', 'maniphest-report-css' => '3d53188b', 'maniphest-task-edit-css' => '272daa84', 'maniphest-task-summary-css' => '61d1667e', @@ -787,7 +787,7 @@ return array( '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', @@ -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', @@ -1589,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', @@ -1856,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', @@ -1983,16 +1999,6 @@ 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', ), @@ -2034,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', @@ -2063,17 +2080,6 @@ return array( 'd4cc2d2a' => array( 'javelin-install', ), - 'd7ba6915' => array( - 'javelin-stratcom', - 'javelin-request', - 'javelin-dom', - 'javelin-vector', - 'javelin-install', - 'javelin-util', - 'javelin-mask', - 'javelin-uri', - 'javelin-routable', - ), 'd7d3ba75' => array( 'javelin-dom', 'javelin-util', @@ -2094,12 +2100,6 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), - 'd96e47a4' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-vector', - 'javelin-util', - ), 'da15d3dc' => array( 'phui-oi-list-view-css', ), diff --git a/src/applications/system/controller/PhabricatorSystemReadOnlyController.php b/src/applications/system/controller/PhabricatorSystemReadOnlyController.php index e83fe67b0e..2d00d1f405 100644 --- a/src/applications/system/controller/PhabricatorSystemReadOnlyController.php +++ b/src/applications/system/controller/PhabricatorSystemReadOnlyController.php @@ -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( From 42a8b73adbaa1bb241abb71e9fe52f368223c952 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Sat, 25 Mar 2023 14:20:53 +0100 Subject: [PATCH 23/43] Feed Differential retitled: do not repeat the title twice Summary: This is a cosmetic modification for a specific Feed. https://we.phorge.it/T15183 If you retitle a Diff (and you do nothing else), its Feed is somehow too much verbose: ... retitled NEW_TITLE from OLD_TITLE to NEW_TITLE In this specific case, the Feed now becomes shorter so that the NEW_TITLE is not repeated twice: ... retitled NEW_TITLE from OLD_TITLE Note that, if the title changes again, the Feed naturally comes back as before so to mention the change and obviously without any repetition. Example: ... retitled CURRENT_TITLE from OLD_TITLE to NEW_TITLE Closes T15183 Test Plan: - take a Diff named "OLD_TITLE" and retitle to "NEW_TITLE" - visit the homepage and see "retitled NEW_TITLE from OLD_TITLE" (this means the new behavior works) - retitle the Diff from "NEW_TITLE" to "CURRENT_TITLE" - visit the homepage again and see "retitled CURRENT_TITLE from OLD_TITLE to NEW_TITLE" (this means the old behavior still works) Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15183 Differential Revision: https://we.phorge.it/D25094 --- .../xaction/DifferentialRevisionTitleTransaction.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/applications/differential/xaction/DifferentialRevisionTitleTransaction.php b/src/applications/differential/xaction/DifferentialRevisionTitleTransaction.php index 50d00e1892..485aec0b5c 100644 --- a/src/applications/differential/xaction/DifferentialRevisionTitleTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionTitleTransaction.php @@ -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(), From 03c9bf575e4314f86a70276e2d3764bbc4f0bd79 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Sun, 26 Mar 2023 22:08:05 +0200 Subject: [PATCH 24/43] PHP 8.2: fixes for deprecated use of ${var} in strings Summary: Note that PHP 8.2 implemented this deprecation: Using ${var} in strings is deprecated, use {$var} instead This change fixes some known cases. Ref T15196 Test Plan: - I checked with my big eyes that everything is as it should be Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15196 Differential Revision: https://we.phorge.it/D25098 --- .../diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php | 2 +- src/applications/herald/phid/HeraldTranscriptPHIDType.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php index c6ae6dbd2f..5eb20d2f7e 100644 --- a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php @@ -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; } diff --git a/src/applications/herald/phid/HeraldTranscriptPHIDType.php b/src/applications/herald/phid/HeraldTranscriptPHIDType.php index 8ba3a0254c..12625d0c1a 100644 --- a/src/applications/herald/phid/HeraldTranscriptPHIDType.php +++ b/src/applications/herald/phid/HeraldTranscriptPHIDType.php @@ -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/"); } } From d98c47041f137e6a69978f5a4d6a14c708c53648 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Mon, 27 Mar 2023 07:06:27 +0200 Subject: [PATCH 25/43] PHP 8.2: fix deprecated creation of dynamic properties Summary: This change fixes a typo that, in PHP 8.2, causes this exception: Creation of dynamic property PhabricatorAuthPasswordException::$confirmError is deprecated Closes T15201 Test Plan: - I checked that "error" was spelled with two "r" Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15201 Differential Revision: https://we.phorge.it/D25100 --- .../auth/password/PhabricatorAuthPasswordException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/auth/password/PhabricatorAuthPasswordException.php b/src/applications/auth/password/PhabricatorAuthPasswordException.php index d4b13bb10c..fa749722af 100644 --- a/src/applications/auth/password/PhabricatorAuthPasswordException.php +++ b/src/applications/auth/password/PhabricatorAuthPasswordException.php @@ -4,7 +4,7 @@ final class PhabricatorAuthPasswordException extends Exception { private $passwordError; - private $confirmErorr; + private $confirmError; public function __construct( $message, From 5e2b3677157889104a7e540d7772a04f13164037 Mon Sep 17 00:00:00 2001 From: dylsss Date: Mon, 27 Mar 2023 08:18:32 +0200 Subject: [PATCH 26/43] Workboard: improve the Create Task link to mention the Project slug Summary: This expands a specific link in a specific menu of Workboard Columns. You may never notice this difference unless you like to open links in another tab. If you go to a Workboard, and you open its context menu, and you hover your mouse on the Create Task action, you see this URL or a similar one: http://example.com/maniphest/task/edit/form/default/ After this change, you see this URL or a similar one: http://example.com/maniphest/task/edit/form/default/?tags=test You see that the PhutilURI class was used to add the Project slug to the 'tags' query param so that users can still open the URL in a new tab and have the form prefilled with the Project Tag. Closes T15147 Test Plan: - visit a Workboard - open the context menu of a Column (the pencil icon) - see that the Create Task link has the Project slug is in the URL of its prefilled form Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15147 Differential Revision: https://we.phorge.it/D25068 --- .../controller/PhabricatorProjectBoardViewController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 13a75c5a73..d254936c1a 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -696,7 +696,8 @@ final class PhabricatorProjectBoardViewController $column_items[] = id(new PhabricatorActionView()) ->setIcon($spec['icon']) ->setName($spec['name']) - ->setHref($spec['uri']) + ->setHref(id(new PhutilURI($spec['uri'])) + ->replaceQueryParam('tags', $project->getPrimarySlug())) ->setDisabled($spec['disabled']) ->addSigil('column-add-task') ->setMetadata( From 38e3692d7bda187b724b5355d11120c1bbfb23e3 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Mon, 27 Mar 2023 20:50:37 +0200 Subject: [PATCH 27/43] Workboard: in some cases, increase menu opacity (accessibility) Summary: This change contains a very minimal graphical change for Workboards with a custom background. https://we.phorge.it/T15186 After this change, if your Workboard has a custom background color, the opacity of the floating menu of the Column is not 0.9 but 0.95, so it's increased a bit. The problem with the previous value, is that when you open the menu, you see too much text underneath, and this could worsen the readability. Now also probably, but less I hope. Closes T15186 Test Plan: - Workboard > Change Background Color > Pick a nice color - Backlog > Edit - Enjoy the extra 0.05 of opacity of that menu - NEVER ACCEPT IF YOUR EYES DETECT JUST A 0.04999998 OF EXTRA OPACITY Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15186 Differential Revision: https://we.phorge.it/D25088 --- webroot/rsrc/css/phui/workboards/phui-workboard-color.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webroot/rsrc/css/phui/workboards/phui-workboard-color.css b/webroot/rsrc/css/phui/workboards/phui-workboard-color.css index 9973d64979..5aae48647a 100644 --- a/webroot/rsrc/css/phui/workboards/phui-workboard-color.css +++ b/webroot/rsrc/css/phui/workboards/phui-workboard-color.css @@ -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 { From f0618d54e8b5a483e95fd4c488cae15a29be0500 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Mon, 27 Mar 2023 20:51:04 +0200 Subject: [PATCH 28/43] Installation Guide: mention that git 2.5.0 (2015) is needed server-side Summary: Note that the git version 2.5.0 is surely already assumed as required as implicit fact, so it's just better to clarify this in the documentation to prevent weird situations and unclear related exceptions. Note that if you have seen this Phabricator/Phorge error in your installation: This path was a submodule at R123:5378198ea123asdlol This is probably that kind of weird errors that will be fixed just updating your git version to 2.5.0, so that the git escape sequence ('--') is correctly parsed by git. Example of command that will fail with outdated git versions: git cat-file -t -- : If you do not want to update git, but you want to fix, you can convert that command to this one, manually patching your installation: git cat-file -t : Related material: - https://we.phorge.it/T15179 - https://unix.stackexchange.com/a/740621/85666 - https://github.com/git/git/blob/6f9504c48ea59951a2aa3ee17e7c7fc36285e225/Documentation/RelNotes/2.5.0.txt Wontfix T15179 Test Plan: - open Diviner page "Installation Guide" - see that the git version is not explicit - yup! Reviewers: O1 Blessed Committers, Cigaryno, avivey Reviewed By: O1 Blessed Committers, Cigaryno, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15179 Differential Revision: https://we.phorge.it/D25081 --- src/docs/user/installation_guide.diviner | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/docs/user/installation_guide.diviner b/src/docs/user/installation_guide.diviner index 6db0df4d7c..7306155917 100644 --- a/src/docs/user/installation_guide.diviner +++ b/src/docs/user/installation_guide.diviner @@ -81,6 +81,8 @@ You will also need: 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: From 780e86acf83b709f6cf10a1a393a4cc022c3d922 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Mon, 27 Mar 2023 20:51:34 +0200 Subject: [PATCH 29/43] Herald Rule Creation Page: fix Back button in some cases Summary: If you want to create a new Herald Rule, sometime, like for new "Object" Herald Rules, the Back button just sends you to the very same page you already were. The risk is that an employee could receive a specific instruction, such as "Hey Alfreda, Go back, now" and at this point Alfreda goes into an incredible loop continuing to click the Back button until the duration of the universe (since Phorge is so stable that it's able to handle that "Back spike" forever - really). https://we.phorge.it/T15184 Note that this also tries to avoid to change the base URI to just go Back. For example, before this change, the Back button was trying to send from /create/ to /new/, but apparently they are just aliases. Closes T15184 Test Plan: - Visit the page /herald/create/?adapter=commit - Click on "Object" - Click on "Back" - Verify that you only went back one screen, and not just forward in time Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15184 Differential Revision: https://we.phorge.it/D25087 --- src/applications/herald/controller/HeraldNewController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/herald/controller/HeraldNewController.php b/src/applications/herald/controller/HeraldNewController.php index 09b7e62c6e..93d4503754 100644 --- a/src/applications/herald/controller/HeraldNewController.php +++ b/src/applications/herald/controller/HeraldNewController.php @@ -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()) From 5eac69b79dbb37b5efa29c3d7383605ea64426e0 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Mon, 27 Mar 2023 20:54:59 +0200 Subject: [PATCH 30/43] Ponder: strike code of resolved questions in query results Summary: After this change, search results with resolved questions are striked, just like any Closed Maniphest Tasks is already striked, etc. https://we.phorge.it/T15166 There is still the same thing to be done at Remarkup. Ref T15166 Test Plan: - go in the page /ponder/query/all/ and see that closed questions are now striked Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15166 Differential Revision: https://we.phorge.it/D25086 --- src/__phutil_library_map__.php | 2 ++ .../ponder/constants/PonderQuestionStatus.php | 10 ++++++++ .../query/PonderQuestionSearchEngine.php | 3 +++ .../ponder/storage/PonderQuestion.php | 8 ++++++ .../PonderQuestionStatusTestCase.php | 25 +++++++++++++++++++ 5 files changed, 48 insertions(+) create mode 100644 src/applications/ponder/storage/__tests__/PonderQuestionStatusTestCase.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fec9ac13dc..6a6bf37aed 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', @@ -12757,6 +12758,7 @@ phutil_register_library_map(array( 'PonderQuestionSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PonderQuestionStatus' => 'PonderConstants', 'PonderQuestionStatusController' => 'PonderController', + 'PonderQuestionStatusTestCase' => 'PhutilTestCase', 'PonderQuestionStatusTransaction' => 'PonderQuestionTransactionType', 'PonderQuestionTitleTransaction' => 'PonderQuestionTransactionType', 'PonderQuestionTransaction' => 'PhabricatorModularTransaction', diff --git a/src/applications/ponder/constants/PonderQuestionStatus.php b/src/applications/ponder/constants/PonderQuestionStatus.php index 55e433bf25..52497bef37 100644 --- a/src/applications/ponder/constants/PonderQuestionStatus.php +++ b/src/applications/ponder/constants/PonderQuestionStatus.php @@ -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); + } + } diff --git a/src/applications/ponder/query/PonderQuestionSearchEngine.php b/src/applications/ponder/query/PonderQuestionSearchEngine.php index 4cba28b93b..ecca1a91e5 100644 --- a/src/applications/ponder/query/PonderQuestionSearchEngine.php +++ b/src/applications/ponder/query/PonderQuestionSearchEngine.php @@ -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)', diff --git a/src/applications/ponder/storage/PonderQuestion.php b/src/applications/ponder/storage/PonderQuestion.php index 40ec9fbee8..b5034f94d8 100644 --- a/src/applications/ponder/storage/PonderQuestion.php +++ b/src/applications/ponder/storage/PonderQuestion.php @@ -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 )------------------------- */ diff --git a/src/applications/ponder/storage/__tests__/PonderQuestionStatusTestCase.php b/src/applications/ponder/storage/__tests__/PonderQuestionStatusTestCase.php new file mode 100644 index 0000000000..0057dd03d4 --- /dev/null +++ b/src/applications/ponder/storage/__tests__/PonderQuestionStatusTestCase.php @@ -0,0 +1,25 @@ +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()); + } + } + +} From 6036079e883aefd1707e05a127372d84e757cfc2 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Wed, 29 Mar 2023 20:36:57 +0200 Subject: [PATCH 31/43] PHP 8.2: fix deprecated use of "parent" in callables Summary: Closes T15200 Test Plan: Test 1: I was able to run `arc unit --everything` without the error thrown from T15200 Test 2: I also tried this and it still works: ``` php -a require 'src/applications/metamta/exception/PhabricatorMetaMTAReceivedMailProcessingException.php'; $asd = new PhabricatorMetaMTAReceivedMailProcessingException("1", "TEST MESSAGE"); var_dump($asd->getMessage()); ``` You get: ``` string(12) "TEST MESSAGE" ``` Reviewers: O1 Blessed Committers, Matthew Reviewed By: O1 Blessed Committers, Matthew Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15200 Differential Revision: https://we.phorge.it/D25099 --- resources/celerity/map.php | 4 ++-- .../PhabricatorMetaMTAReceivedMailProcessingException.php | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 9323cc070d..0c05232751 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -180,7 +180,7 @@ return array( 'rsrc/css/phui/phui-tag-view.css' => 'fb811341', 'rsrc/css/phui/phui-timeline-view.css' => '2d32d7a9', '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', @@ -882,7 +882,7 @@ return array( 'phui-theme-css' => '35883b37', 'phui-timeline-view-css' => '2d32d7a9', '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', diff --git a/src/applications/metamta/exception/PhabricatorMetaMTAReceivedMailProcessingException.php b/src/applications/metamta/exception/PhabricatorMetaMTAReceivedMailProcessingException.php index 5fb1209885..cc77af9595 100644 --- a/src/applications/metamta/exception/PhabricatorMetaMTAReceivedMailProcessingException.php +++ b/src/applications/metamta/exception/PhabricatorMetaMTAReceivedMailProcessingException.php @@ -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); } } From 821df3364ec4f41f4148a28de786422b230211e6 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Thu, 30 Mar 2023 12:11:11 +0200 Subject: [PATCH 32/43] Hide the "hidden" fields on custom form previews Summary: Hide the "hidden" fields on custom form previews. Before this change, Phabricator's custom form preview doesn't actually hide the hidden fields, instead it shows them at 50% opacity. It looks cluttered and doesn't provide a very useful "preview" at all. This just hides some fields via CSS. Cherry picked from: https://phabricator.wikimedia.org/rPHABbba62cf5243538af9e37cc1211a01d247294f9f7 Upstream Task: https://phabricator.wikimedia.org/T209743 Test Plan: Tested in Wikimedia's fork. I believe it results in better UX. Reviewers: #blessed_committers, O1 Blessed Committers, Cigaryno, avivey, Matthew, valerio.bozzolan Reviewed By: #blessed_committers, O1 Blessed Committers, Cigaryno, avivey, valerio.bozzolan Subscribers: avivey, Cigaryno, speck, tobiaswiese, valerio.bozzolan, Matthew Maniphest Tasks: T15081 Differential Revision: https://we.phorge.it/D25037 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-form-view.css | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0c05232751..d11174a1d2 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => 'f538846d', + 'core.pkg.css' => 'bb377509', 'core.pkg.js' => '66c49ca1', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '609e63d4', @@ -154,7 +154,7 @@ 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-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', @@ -847,7 +847,7 @@ return array( 'phui-font-icon-base-css' => '303c9b87', 'phui-fontkit-css' => '1ec937e5', 'phui-form-css' => 'd1adb52c', - 'phui-form-view-css' => '01b796c0', + 'phui-form-view-css' => '7536aef9', 'phui-formation-view-css' => 'd2dec8ed', 'phui-head-thing-view-css' => 'd7f293df', 'phui-header-view-css' => '36c86a58', diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css index accce86819..47f7d1bb2c 100644 --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -525,7 +525,7 @@ properly, and submit values. */ } .aphront-form-preview-hidden { - opacity: 0.5; + display: none; } .aphront-form-error .phui-icon-view { From fc9bbb9949f8792de009376ed030045aa9eaef9c Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Fri, 31 Mar 2023 10:40:45 +0200 Subject: [PATCH 33/43] Workboard: fix regression caused by tags= prefiller Summary: This change fixes a regression introduced here: rP5e2b3677157889104a7e540d7772a04f13164037 Thank you to the user @dadalha for auditing that commit and sharing a stack trace to easily fix the issue. In short, if you see this exception you are affected: ``` EXCEPTION: (InvalidArgumentException) Value provided to "replaceQueryParam()" for key "tags" is NULL. Use "removeQueryParam()" to remove a query parameter. at [/src/parser/PhutilURI.php:341] ``` This change just fixes that specific problem. Closes T15221 Test Plan: 1. Create a new Project 2. Create a new Milestone inside 3. Create the Milestone's Workboard 4. Visit the Milestone's Workboard and note that now it works Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno, dadalha Tags: #workboard Maniphest Tasks: T15221 Differential Revision: https://we.phorge.it/D25103 --- .../PhabricatorProjectBoardViewController.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index d254936c1a..a24c911b9f 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -693,11 +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(id(new PhutilURI($spec['uri'])) - ->replaceQueryParam('tags', $project->getPrimarySlug())) + ->setHref($spec_href) ->setDisabled($spec['disabled']) ->addSigil('column-add-task') ->setMetadata( From d25d630fe5dd5bf9c1521bb7b067a8810aca114b Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Fri, 31 Mar 2023 22:01:56 +0200 Subject: [PATCH 34/43] PHP 8.2: fixes for strlen() not accepting NULL anymore, part 1 Summary: This change avoids some unnecessary uses of the strlen() function, actually fixing some deprecation warnings in PHP 8.2. In short, this is the suggested universal replace: -if(strlen($v)) +if(phutil_nonempty_string($v)) And, if you know PHP, this is also another adoptable replace, but only for cases where you are sure that the string "0" is not useful: -if(strlen($v)) +if($v)) As usual the optimal solution depends on the contest. Other similar patches will probably follow. Closes T15222 Ref T15190 Test Plan: - for the first time in my life, with this change, the unit tests are passed in PHP 8.2 - check with your big eyes that there are no obvious typos Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15199, T15190, T15222 Differential Revision: https://we.phorge.it/D25104 --- .../check/PhabricatorStorageSetupCheck.php | 8 ++++---- .../engine/PhabricatorS3FileStorageEngine.php | 12 ++++++------ .../files/storage/PhabricatorFile.php | 4 ++-- .../engine/PhabricatorMailEmailEngine.php | 4 ++-- .../client/PhabricatorNotificationServerRef.php | 2 +- .../engine/PhabricatorRepositoryPullEngine.php | 2 +- .../repository/storage/PhabricatorRepository.php | 3 ++- .../cluster/PhabricatorDatabaseRef.php | 2 +- src/infrastructure/env/PhabricatorEnv.php | 16 ++++++++-------- src/infrastructure/log/PhabricatorSSHLog.php | 2 +- 10 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/applications/config/check/PhabricatorStorageSetupCheck.php b/src/applications/config/check/PhabricatorStorageSetupCheck.php index 86aa1f2ebd..75639f2f43 100644 --- a/src/applications/config/check/PhabricatorStorageSetupCheck.php +++ b/src/applications/config/check/PhabricatorStorageSetupCheck.php @@ -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++; } diff --git a/src/applications/files/engine/PhabricatorS3FileStorageEngine.php b/src/applications/files/engine/PhabricatorS3FileStorageEngine.php index 443a5a9ddc..95cdfc737b 100644 --- a/src/applications/files/engine/PhabricatorS3FileStorageEngine.php +++ b/src/applications/files/engine/PhabricatorS3FileStorageEngine.php @@ -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; } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index cc1b701dcd..355aa2d215 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -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; } diff --git a/src/applications/metamta/engine/PhabricatorMailEmailEngine.php b/src/applications/metamta/engine/PhabricatorMailEmailEngine.php index 6c9cf1b356..4278aa96a1 100644 --- a/src/applications/metamta/engine/PhabricatorMailEmailEngine.php +++ b/src/applications/metamta/engine/PhabricatorMailEmailEngine.php @@ -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; } diff --git a/src/applications/notification/client/PhabricatorNotificationServerRef.php b/src/applications/notification/client/PhabricatorNotificationServerRef.php index 714ad5f5d7..c5b0f2f8aa 100644 --- a/src/applications/notification/client/PhabricatorNotificationServerRef.php +++ b/src/applications/notification/client/PhabricatorNotificationServerRef.php @@ -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); } diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index 12aee9bc51..5bcf4554fd 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -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}"; } diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index c68eb22668..5c7c9ff33d 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -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, diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRef.php b/src/infrastructure/cluster/PhabricatorDatabaseRef.php index 1eb232ad86..e526eb1639 100644 --- a/src/infrastructure/cluster/PhabricatorDatabaseRef.php +++ b/src/infrastructure/cluster/PhabricatorDatabaseRef.php @@ -229,7 +229,7 @@ final class PhabricatorDatabaseRef $host = $this->getHost(); $port = $this->getPort(); - if (strlen($port)) { + if ($port) { return "{$host}:{$port}"; } diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php index ca478ed0a5..617fb57af9 100644 --- a/src/infrastructure/env/PhabricatorEnv.php +++ b/src/infrastructure/env/PhabricatorEnv.php @@ -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; } @@ -432,7 +432,7 @@ final class PhabricatorEnv extends Phobject { $uri = new PhutilURI($raw_uri); $host = $uri->getDomain(); - if (!strlen($host)) { + if (!phutil_nonempty_string($host)) { return false; } @@ -455,7 +455,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; } @@ -661,7 +661,7 @@ final class PhabricatorEnv extends Phobject { public static function isValidLocalURIForLink($uri) { $uri = (string)$uri; - if (!strlen($uri)) { + if (!phutil_nonempty_string($uri)) { return false; } @@ -726,7 +726,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 '. @@ -745,7 +745,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 '. @@ -793,7 +793,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 '. @@ -812,7 +812,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 '. diff --git a/src/infrastructure/log/PhabricatorSSHLog.php b/src/infrastructure/log/PhabricatorSSHLog.php index de4c270f72..e3c15de6bf 100644 --- a/src/infrastructure/log/PhabricatorSSHLog.php +++ b/src/infrastructure/log/PhabricatorSSHLog.php @@ -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; } From 96ae4ba13acbf0e2f8932e950a92af0495f034d7 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Sat, 1 Apr 2023 15:19:43 +0200 Subject: [PATCH 35/43] PHP 8.1: fixes for strlen() not accepting NULL anymore, part 2 Summary: This is a fix for PHP 8.1 deprecation of strlen(NULL), for these Phorge components: - scripts - aphront - project The strlen() was used in Phabricator to check if a generic value was a non-empty string. For this reason, Phorge adopts phutil_nonempty_string() that checks that. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If your phutil_nonempty_string() throws an exception, just report it to Phorge to evaluate and fix together that specific corner case. Closes T15223 Ref T15190 Ref T15064 Test Plan: - check with your big eyes that there are no obvious typos Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15223, T15190, T15064 Differential Revision: https://we.phorge.it/D25105 --- scripts/repository/commit_hook.php | 8 ++++---- scripts/ssh/ssh-auth.php | 8 ++++---- scripts/ssh/ssh-exec.php | 4 ++-- scripts/symbols/generate_ctags_symbols.php | 2 +- scripts/symbols/generate_php_symbols.php | 2 +- src/aphront/AphrontRequest.php | 8 ++++---- .../configuration/AphrontApplicationConfiguration.php | 2 +- src/aphront/response/AphrontAjaxResponse.php | 2 +- src/aphront/response/AphrontFileResponse.php | 2 +- src/aphront/response/AphrontWebpageResponse.php | 2 +- src/aphront/sink/AphrontPHPHTTPSink.php | 2 +- src/aphront/site/AphrontSite.php | 2 +- src/aphront/site/PhabricatorPlatformSite.php | 2 +- src/aphront/site/PhabricatorResourceSite.php | 2 +- src/aphront/site/PhabricatorShortSite.php | 2 +- .../project/phid/PhabricatorProjectProjectPHIDType.php | 2 +- support/startup/preamble-utils.php | 2 +- 17 files changed, 27 insertions(+), 27 deletions(-) diff --git a/scripts/repository/commit_hook.php b/scripts/repository/commit_hook.php index ccbfde08ff..28829edeb2 100755 --- a/scripts/repository/commit_hook.php +++ b/scripts/repository/commit_hook.php @@ -119,7 +119,7 @@ if ($is_svnrevprop) { exit($err); } else if ($repository->isGit() || $repository->isHg()) { $username = getenv(DiffusionCommitHookEngine::ENV_USER); - if (!strlen($username)) { + if (!phutil_nonempty_string($username)) { throw new Exception( pht( 'No Direct Pushes: You are pushing directly to a hosted repository. '. @@ -181,17 +181,17 @@ $engine->setStdin($stdin); $engine->setOriginalArgv(array_slice($argv, 2)); $remote_address = getenv(DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS); -if (strlen($remote_address)) { +if (phutil_nonempty_string($remote_address)) { $engine->setRemoteAddress($remote_address); } $remote_protocol = getenv(DiffusionCommitHookEngine::ENV_REMOTE_PROTOCOL); -if (strlen($remote_protocol)) { +if (phutil_nonempty_string($remote_protocol)) { $engine->setRemoteProtocol($remote_protocol); } $request_identifier = getenv(DiffusionCommitHookEngine::ENV_REQUEST); -if (strlen($request_identifier)) { +if (phutil_nonempty_string($request_identifier)) { $engine->setRequestIdentifier($request_identifier); } diff --git a/scripts/ssh/ssh-auth.php b/scripts/ssh/ssh-auth.php index 45e9a823db..2b3b52cf4b 100755 --- a/scripts/ssh/ssh-auth.php +++ b/scripts/ssh/ssh-auth.php @@ -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); } diff --git a/scripts/ssh/ssh-exec.php b/scripts/ssh/ssh-exec.php index 70c95d28da..7b5b6adb8f 100755 --- a/scripts/ssh/ssh-exec.php +++ b/scripts/ssh/ssh-exec.php @@ -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( diff --git a/scripts/symbols/generate_ctags_symbols.php b/scripts/symbols/generate_ctags_symbols.php index e93b0c5cbc..13972385ab 100755 --- a/scripts/symbols/generate_ctags_symbols.php +++ b/scripts/symbols/generate_ctags_symbols.php @@ -39,7 +39,7 @@ $data = array(); $futures = array(); foreach (explode("\n", trim($input)) as $file) { - if (!strlen($file)) { + if (!phutil_nonempty_string($file)) { continue; } diff --git a/scripts/symbols/generate_php_symbols.php b/scripts/symbols/generate_php_symbols.php index af87d580d8..cc8cab3817 100755 --- a/scripts/symbols/generate_php_symbols.php +++ b/scripts/symbols/generate_php_symbols.php @@ -27,7 +27,7 @@ $data = array(); $futures = array(); foreach (explode("\n", trim($input)) as $file) { - if (!strlen($file)) { + if (!phutil_nonempty_string($file)) { continue; } diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php index f35f3f1279..58fc6f9346 100644 --- a/src/aphront/AphrontRequest.php +++ b/src/aphront/AphrontRequest.php @@ -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; } diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index 550a5a0316..3bdd2c00bc 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -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) { diff --git a/src/aphront/response/AphrontAjaxResponse.php b/src/aphront/response/AphrontAjaxResponse.php index 2187defc8f..b9e60809fc 100644 --- a/src/aphront/response/AphrontAjaxResponse.php +++ b/src/aphront/response/AphrontAjaxResponse.php @@ -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); } } diff --git a/src/aphront/response/AphrontFileResponse.php b/src/aphront/response/AphrontFileResponse.php index 6bae4c808f..980d1ee5dd 100644 --- a/src/aphront/response/AphrontFileResponse.php +++ b/src/aphront/response/AphrontFileResponse.php @@ -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; diff --git a/src/aphront/response/AphrontWebpageResponse.php b/src/aphront/response/AphrontWebpageResponse.php index de642f9b19..00a953ece8 100644 --- a/src/aphront/response/AphrontWebpageResponse.php +++ b/src/aphront/response/AphrontWebpageResponse.php @@ -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;', diff --git a/src/aphront/sink/AphrontPHPHTTPSink.php b/src/aphront/sink/AphrontPHPHTTPSink.php index 4953b6e610..1aaa1810ff 100644 --- a/src/aphront/sink/AphrontPHPHTTPSink.php +++ b/src/aphront/sink/AphrontPHPHTTPSink.php @@ -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); diff --git a/src/aphront/site/AphrontSite.php b/src/aphront/site/AphrontSite.php index 85bf42fe6c..08813462c6 100644 --- a/src/aphront/site/AphrontSite.php +++ b/src/aphront/site/AphrontSite.php @@ -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; } diff --git a/src/aphront/site/PhabricatorPlatformSite.php b/src/aphront/site/PhabricatorPlatformSite.php index a3193e65d1..e6c733684b 100644 --- a/src/aphront/site/PhabricatorPlatformSite.php +++ b/src/aphront/site/PhabricatorPlatformSite.php @@ -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(); } diff --git a/src/aphront/site/PhabricatorResourceSite.php b/src/aphront/site/PhabricatorResourceSite.php index 88f7777607..3fca474a17 100644 --- a/src/aphront/site/PhabricatorResourceSite.php +++ b/src/aphront/site/PhabricatorResourceSite.php @@ -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; } diff --git a/src/aphront/site/PhabricatorShortSite.php b/src/aphront/site/PhabricatorShortSite.php index d4c36aecbe..05cf2c3584 100644 --- a/src/aphront/site/PhabricatorShortSite.php +++ b/src/aphront/site/PhabricatorShortSite.php @@ -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; } diff --git a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php index 9247966d75..4a9cfbb8a6 100644 --- a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php +++ b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php @@ -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}/"); diff --git a/support/startup/preamble-utils.php b/support/startup/preamble-utils.php index 8dd3b502d6..dfb5619a43 100644 --- a/support/startup/preamble-utils.php +++ b/support/startup/preamble-utils.php @@ -21,7 +21,7 @@ function preamble_trust_x_forwarded_for_header($layers = 1) { } $forwarded_for = $_SERVER['HTTP_X_FORWARDED_FOR']; - if (!strlen($forwarded_for)) { + if (!phutil_nonempty_string($forwarded_for)) { return; } From 306ce1c0b71ad81c76ce695244024b9514e18c56 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Wed, 5 Apr 2023 15:36:20 +0200 Subject: [PATCH 36/43] Fix "Search" Application description Summary: This description was somehow broken since 2019: {F276845} Probably this was caused by a Remarkup breaking change introduced by this specific XSS security fix: {54bcbdaba94a3573e128c6498816dbfa41d3a9cb} Maybe in the future the backtick operator will be supported again in Remarkup. Or, maybe not. So, let's just workaround to make that specific page "less exploded" again. Closes T15230 Test Plan: - visit the page /applications/view/PhabricatorSearchApplication/ - check that now the Remarkup is rendered perfectly Reviewers: O1 Blessed Committers, Cigaryno, avivey Reviewed By: O1 Blessed Committers, Cigaryno, avivey Subscribers: avivey, speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15230 Differential Revision: https://we.phorge.it/D25110 --- .../PhabricatorSearchApplicationStorageEnginePanel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php b/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php index 6dd0786c0c..e8bc9854b7 100644 --- a/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php +++ b/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php @@ -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', From 787a84969fa04c519a9291eaac3f7ebbbb3585fa Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Wed, 5 Apr 2023 15:39:25 +0200 Subject: [PATCH 37/43] Phriction: clarify its search results as "Wiki page" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: This patch changes a bit how your search results from Phriction are described in the autocomplete component (the "Typehead"): {F276843} After this change, Phriction search results will also contain the word "Wiki page" so that users can better understand what the result actually is. Just like a Diffusion repository mentions that it's a "Repository", and just like a Project mentions that it's a "Project" and so on. Before this change, the Typehead entries were only mentioning the slug of that wiki page. | Before | After | | {F274820} | {F272278} | It's unclear if the previous behavior was a mistake (since the internal parameter of the Typehead is called "type", and so, it is supposed to mention the application type, not the slug. Anyway, as a compromise, the slug is still mentioned. To be honest this is just an excuse to put the middle dot / aka interpunct character in this project again. Yeah, here the middle point was used as separator. The apparent reason is: because the middle point was already in use elsewhere in Phorge. The real reason is: I'm a lobbyist in a corporation that sells interpuncts worldwide as our core business, and so, I designed thisvil plan to put the following middle point - again - in Phorge (evil laugh). Closes T15213 Test Plan: - Search "Change Log" in the up-right bar (or similar) - You see "Wiki Page ยท change_log/" (or similar) Reviewers: O1 Blessed Committers, Cigaryno, avivey Reviewed By: O1 Blessed Committers, Cigaryno, avivey Subscribers: avivey, speck, tobiaswiese, Matthew, Cigaryno Tags: #phriction Maniphest Tasks: T15213 Differential Revision: https://we.phorge.it/D25102 --- .../typeahead/PhrictionDocumentDatasource.php | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php b/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php index 09056fe761..8e16051840 100644 --- a/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php +++ b/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php @@ -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) From 524579fe64a62adfed8964233be8a32a376b80c5 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Thu, 6 Apr 2023 10:58:05 +0200 Subject: [PATCH 38/43] Diffusion: fix missing mobile "Actions" from Manage Repo Summary: After this change, the page "/manage/" related to Diffusion repositories has an "Actions" button for mobile users: {F276113} Before this change, that button was missing only from this specific page, and so it was really difficult for them (if not impossible) to edit repository's basic information: {F276115} Do not confuse the hamburger menu on the sidebar with this "Actions". The hamburger contains queries like Active Repo, All Repo, etc. Instead the Actions contains Edit Basic Info, Allow Dangerous Changes, etc. It seems the cause of this problem is originated from an apparently incomplete rollback occurred in Sep 6 2017 from Phabricator: e1fd74ddb545a2de3963fd60a6c5f4c1d5075814 As you can see, the DiffusionRepositoryBasicsManagementPanel was probably not completely restored from the previous version, at least for the part related to the method newActionList(): bb369c7b711f6eb5f6b3ca9a0c8b85a088b05dc1 The method newActionList() was restored in every other panel but no the one related to the Basics Management. So, a new PhabricatorActionListView object was manually created, but without generating an ID from the Celerity utilities. In short, this change assigns an ID to this Curtain, in order to correctly generates a related "Actions" menu again. Otherwise, on mobile the curtain (with all the actions) is automatically hidden, without any alternative. Closes T15067 Test Plan: - Repository > Actions > Manage Repository - Reduce the page until "Actions" appears (yeeh!) Reviewers: O1 Blessed Committers, Cigaryno, avivey Reviewed By: O1 Blessed Committers, Cigaryno, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Tags: #diffusion, #mobile Maniphest Tasks: T15067 Differential Revision: https://we.phorge.it/D25109 --- .../management/DiffusionRepositoryBasicsManagementPanel.php | 3 +-- .../management/DiffusionRepositoryManagementPanel.php | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php index 02df82c7c0..fdd477f874 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php @@ -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, diff --git a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php index 1a58ef6a49..d938ed834c 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php @@ -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()) From 3a8ee271d9df1797c8a0926f18a97b8ba9970c9e Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Thu, 6 Apr 2023 14:44:02 +0200 Subject: [PATCH 39/43] Render all Removed Comments a little more docile Summary: This small restyle makes any Removed Comment a little less prominent than normal ones, with the goal of decreasing a bit your in-page distractions and increase your individual productivity in your business by at least 250 milliseconds every 48 hours of hard work in front of your monitor. | Before | After | |---------------------|---------------------| | {F274834,size=full} | {F274835,size=full} | This implementation (which is called "Kasper on Diet") contains these specific changes for Removed Comments: - user icon visibility: reduced by ~50% (-> Kasper) - black "trash" icon: reduced by ~50% (-> Diet) - texts: visibility reduced by ~50% - vertical padding: reduced from 16px down to 4px Note that if your Phorge is under the Serious Business Mode, it seems it is still technically possible to manually activate the "Decaying Curse" proposal mentioned in the Task. Closes T15192 Test Plan: - Add a Comment "I love Phorge" - Add a Comment "I love Phabricator" - Mark the second Comment as Removed - Call a person at your desk - Plug that person to an eyeball tracker If the general attention focuses first on a normal Comment and then on the Removed Comment, this change works perfectly. Reviewers: O1 Blessed Committers, Cigaryno, avivey Reviewed By: O1 Blessed Committers, Cigaryno, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Tags: #comments Maniphest Tasks: T15192 Differential Revision: https://we.phorge.it/D25096 --- resources/celerity/map.php | 6 +++--- .../PhabricatorApplicationTransaction.php | 2 +- .../PhabricatorApplicationTransactionView.php | 5 +++++ webroot/rsrc/css/phui/phui-timeline-view.css | 20 +++++++++++++++++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d11174a1d2..89425d6cc4 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => 'bb377509', + 'core.pkg.css' => '6590cb9f', 'core.pkg.js' => '66c49ca1', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '609e63d4', @@ -178,7 +178,7 @@ 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' => 'cc554a79', 'rsrc/css/phui/phui-two-column-view.css' => 'f96d319f', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '3a1c21ff', 'rsrc/css/phui/workboards/phui-workboard.css' => '74fc9d98', @@ -880,7 +880,7 @@ 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' => 'cc554a79', 'phui-two-column-view-css' => 'f96d319f', 'phui-workboard-color-css' => '3a1c21ff', 'phui-workboard-view-css' => '74fc9d98', diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index e32fe92983..56079efaf2 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -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: diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php index ee2020f890..180ba071a9 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php @@ -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); } diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index e08373d589..59caec7fea 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -189,9 +189,29 @@ 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 { + 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; From dad1b15a9c5a75e56b8e19a6d91fa74be55fd98a Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Fri, 7 Apr 2023 00:21:28 +0200 Subject: [PATCH 40/43] User Badges: better integrate with Removed Comments Summary: The left part of a Removed Comment can contains User Badges that, after this change, are more consistent with the new general softness. | Before | Proposed | |------------|-----------| | {F277371} | {F277373} | Probably five people in a million will notice this change, BUT, these people will have a huge, deep, breath of relief, knowing that Phorge takes care about their obsessive-compulsive impulses, to have everything nice and consistent and neat. You are welcome! Closes T15235 Test Plan: - do something to deserve a Badge - share a Comment somewhere (Maniphest?) - delete that comment - check that the badge is slightly softer Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15235 Differential Revision: https://we.phorge.it/D25112 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-timeline-view.css | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 89425d6cc4..c0805e16a6 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0e3cf785', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => '6590cb9f', + 'core.pkg.css' => 'b239afaa', 'core.pkg.js' => '66c49ca1', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '609e63d4', @@ -178,7 +178,7 @@ 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' => 'cc554a79', + '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' => '3a1c21ff', 'rsrc/css/phui/workboards/phui-workboard.css' => '74fc9d98', @@ -880,7 +880,7 @@ return array( 'phui-status-list-view-css' => '293b5dad', 'phui-tag-view-css' => 'fb811341', 'phui-theme-css' => '35883b37', - 'phui-timeline-view-css' => 'cc554a79', + 'phui-timeline-view-css' => '7f8659ec', 'phui-two-column-view-css' => 'f96d319f', 'phui-workboard-color-css' => '3a1c21ff', 'phui-workboard-view-css' => '74fc9d98', diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index 59caec7fea..b9d95ccb9e 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -197,7 +197,8 @@ .phui-timeline-core-content .comment-deleted { font-style: italic; } -.phui-timeline-shell-removed .phui-timeline-image { +.phui-timeline-shell-removed .phui-timeline-image, +.phui-timeline-shell-removed .phui-timeline-badges { opacity: 0.5; } .phui-timeline-shell-removed, From d897413d0517bf2439f1cb254469f8329caa2653 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Sun, 9 Apr 2023 11:10:49 -0700 Subject: [PATCH 41/43] Fix preamble-support Summary: Phutil is not yet loaded during preamble, so we can't use it. This change fixes a regression introduced here, fixing PHP 8.1 support: {96ae4ba13acbf0e2f8932e950a92af0495f034d7} Ref T15064 Test Plan: Included test script. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15064 Differential Revision: https://we.phorge.it/D25114 --- .../__tests__/PreambleUtilsTestCase.php | 123 ++++++++++++++++++ support/startup/preamble-utils.php | 2 +- 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100755 support/startup/__tests__/PreambleUtilsTestCase.php diff --git a/support/startup/__tests__/PreambleUtilsTestCase.php b/support/startup/__tests__/PreambleUtilsTestCase.php new file mode 100755 index 0000000000..25062e3701 --- /dev/null +++ b/support/startup/__tests__/PreambleUtilsTestCase.php @@ -0,0 +1,123 @@ +#!/usr/bin/env php + '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); +} diff --git a/support/startup/preamble-utils.php b/support/startup/preamble-utils.php index dfb5619a43..8dd3b502d6 100644 --- a/support/startup/preamble-utils.php +++ b/support/startup/preamble-utils.php @@ -21,7 +21,7 @@ function preamble_trust_x_forwarded_for_header($layers = 1) { } $forwarded_for = $_SERVER['HTTP_X_FORWARDED_FOR']; - if (!phutil_nonempty_string($forwarded_for)) { + if (!strlen($forwarded_for)) { return; } From 187da02ecd236be1e658403669efcb5c495cd416 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Sun, 9 Apr 2023 05:27:42 -0700 Subject: [PATCH 42/43] Add GitHub mirror to list of known mirrors Summary: Ref T15046 Test Plan: Clone phorge from github mirror, visit `/config/`, see correct branch-point in `Version Information` Reviewers: O1 Blessed Committers, Cigaryno, valerio.bozzolan Reviewed By: O1 Blessed Committers, Cigaryno, valerio.bozzolan Subscribers: speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15046 Differential Revision: https://we.phorge.it/D25115 --- .../config/controller/PhabricatorConfigConsoleController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/config/controller/PhabricatorConfigConsoleController.php b/src/applications/config/controller/PhabricatorConfigConsoleController.php index 8d60459837..4773a8ab60 100644 --- a/src/applications/config/controller/PhabricatorConfigConsoleController.php +++ b/src/applications/config/controller/PhabricatorConfigConsoleController.php @@ -178,6 +178,7 @@ final class PhabricatorConfigConsoleController '('. implode('|', array( 'we\.phorge\.it/', + 'github\.com/phorgeit/', 'github\.com/phacility/', 'secure\.phabricator\.com/', )). From 9a3a0354b2daf6668310d56866c2000cca0b7824 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Fri, 14 Apr 2023 22:08:15 +0200 Subject: [PATCH 43/43] Fix InvalidArgumentException on commit hook Summary: Fix a regression introduced here: 96ae4ba13acbf0e2f8932e950a92af0495f034d7 I reproduced this exception executing "svn commit" on a hosted repository. That crash happened because the PHP getenv() function can return false. But, that is a very terrible value that blasts the non-string-empty check. So, now the default getenv() value is skipped, without causing problems. Closes T15253 Ref T15190 Test Plan: - I've run `svn commit` and I have not encountered any issue now Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15253, T15190 Differential Revision: https://we.phorge.it/D25122 --- scripts/repository/commit_hook.php | 32 +++++++++++++++++++----------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/scripts/repository/commit_hook.php b/scripts/repository/commit_hook.php index 28829edeb2..08f4600b9b 100755 --- a/scripts/repository/commit_hook.php +++ b/scripts/repository/commit_hook.php @@ -119,12 +119,14 @@ if ($is_svnrevprop) { exit($err); } else if ($repository->isGit() || $repository->isHg()) { $username = getenv(DiffusionCommitHookEngine::ENV_USER); - 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 ($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 (phutil_nonempty_string($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 (phutil_nonempty_string($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 (phutil_nonempty_string($request_identifier)) { - $engine->setRequestIdentifier($request_identifier); +if ($request_identifier !== false) { + if (phutil_nonempty_string($request_identifier)) { + $engine->setRequestIdentifier($request_identifier); + } } try {