From 29d34e8f62f0352fcb5e5257aa968f17f2f44ef0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 May 2015 11:09:14 -0700 Subject: [PATCH 01/70] Don't require memes to be at the end of a line Summary: Fixes T7893. Test Plan: {F395543} Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7893 Differential Revision: https://secure.phabricator.com/D12752 --- src/applications/macro/markup/PhabricatorMemeRemarkupRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/macro/markup/PhabricatorMemeRemarkupRule.php b/src/applications/macro/markup/PhabricatorMemeRemarkupRule.php index 0c949e130c..004e415450 100644 --- a/src/applications/macro/markup/PhabricatorMemeRemarkupRule.php +++ b/src/applications/macro/markup/PhabricatorMemeRemarkupRule.php @@ -10,7 +10,7 @@ final class PhabricatorMemeRemarkupRule extends PhutilRemarkupRule { public function apply($text) { return preg_replace_callback( - '@{meme,((?:[^}\\\\]+|\\\\.)+)}$@m', + '@{meme,((?:[^}\\\\]+|\\\\.)+)}@m', array($this, 'markupMeme'), $text); } From 9d132f177b23b5a42e36447a63b890ded17232c8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 May 2015 11:09:29 -0700 Subject: [PATCH 02/70] Fix "reply" to left-side inline appearing on right side Summary: Fixes T8098. This parameter was being misinterpreted over the wire. Test Plan: Replied to left-side inline, got left-side reply. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T8098 Differential Revision: https://secure.phabricator.com/D12747 --- resources/celerity/map.php | 22 +++++++++---------- .../DifferentialInlineCommentEditor.js | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d6dd3971fd..30dbefb7bc 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -11,7 +11,7 @@ return array( 'core.pkg.js' => 'ff529dc7', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', - 'differential.pkg.js' => '3cfa26f9', + 'differential.pkg.js' => '895b8d62', 'diffusion.pkg.css' => '591664fa', 'diffusion.pkg.js' => '0115b37c', 'maniphest.pkg.css' => '68d4dd3d', @@ -359,7 +359,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/differential/ChangesetViewManager.js' => '58562350', - 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '2529c82d', + 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'd4c87bf4', 'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => 'e10f8e18', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => '8e1389b5', @@ -519,7 +519,7 @@ return array( 'conpherence-widget-pane-css' => '2af42ebe', 'differential-changeset-view-css' => 'e19cfd6e', 'differential-core-view-css' => '7ac3cabc', - 'differential-inline-comment-editor' => '2529c82d', + 'differential-inline-comment-editor' => 'd4c87bf4', 'differential-results-table-css' => '181aa9d9', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -982,14 +982,6 @@ return array( 'phabricator-drag-and-drop-file-upload', 'phabricator-draggable-list', ), - '2529c82d' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-request', - 'javelin-workflow', - ), '2818f5ce' => array( 'javelin-install', 'javelin-util', @@ -1815,6 +1807,14 @@ return array( 'javelin-dom', 'javelin-view', ), + 'd4c87bf4' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-request', + 'javelin-workflow', + ), 'd4eecc63' => array( 'javelin-behavior', 'javelin-dom', diff --git a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js b/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js index 049e3f1db1..9842da11f5 100644 --- a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js +++ b/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js @@ -33,7 +33,7 @@ JX.install('DifferentialInlineCommentEditor', { on_right : this.getOnRight(), id : this.getID(), number : this.getLineNumber(), - is_new : this.getIsNew(), + is_new : (this.getIsNew() ? 1 : 0), length : this.getLength(), changesetID : this.getChangesetID(), text : this.getText() || '', From 88b1ad7e92e5113e5dc60ca6a99829c836b91477 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 May 2015 11:10:23 -0700 Subject: [PATCH 03/70] Allow Phabricator to parse bare revision IDs from "Differential Revision:" fields Summary: Fixes T8087. Depends on D12748. Test Plan: See D12748. Reviewers: btrahan Reviewed By: btrahan Subscribers: cburroughs, epriestley Maniphest Tasks: T8087 Differential Revision: https://secure.phabricator.com/D12749 --- .../customfield/DifferentialRevisionIDField.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/applications/differential/customfield/DifferentialRevisionIDField.php b/src/applications/differential/customfield/DifferentialRevisionIDField.php index 3741c0126f..bb54951dc4 100644 --- a/src/applications/differential/customfield/DifferentialRevisionIDField.php +++ b/src/applications/differential/customfield/DifferentialRevisionIDField.php @@ -32,6 +32,14 @@ final class DifferentialRevisionIDField } public function parseValueFromCommitMessage($value) { + // If the value is just "D123" or similar, parse the ID from it directly. + $value = trim($value); + $matches = null; + if (preg_match('/^[dD]([1-9]\d*)\z/', $value, $matches)) { + return (int)$matches[1]; + } + + // Otherwise, try to extract a URI value. return self::parseRevisionIDFromURI($value); } From 791e897c0d0f7606289e486c4f2bd9d314748c3f Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Thu, 7 May 2015 11:26:48 -0700 Subject: [PATCH 04/70] Conpherence - improve reliability of message delivery Summary: Ref T7755. T7755#107290 reproduced for me reliably and now it does not. Cleaned up the logic around in flight updates as it was not correct. Not sure this is enough to close T7755, but maybe? Test Plan: T7755#107290 no longer reproduces! Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T6713, T7755 Differential Revision: https://secure.phabricator.com/D12755 --- resources/celerity/map.php | 26 +++++++++---------- .../conpherence/ConpherenceThreadManager.js | 16 ++++++++---- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 30dbefb7bc..8f2e5af1a4 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => 'ca3f6a60', - 'core.pkg.js' => 'ff529dc7', + 'core.pkg.js' => 'f6b48b53', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', 'differential.pkg.js' => '895b8d62', @@ -346,7 +346,7 @@ return array( 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '6709c934', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'f8dace3b', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', 'rsrc/js/application/conpherence/behavior-durable-column.js' => '657c2b50', 'rsrc/js/application/conpherence/behavior-menu.js' => '804b0773', @@ -513,7 +513,7 @@ return array( 'conpherence-menu-css' => 'f389e048', 'conpherence-message-pane-css' => '3150e2a2', 'conpherence-notification-css' => 'd208f806', - 'conpherence-thread-manager' => '6709c934', + 'conpherence-thread-manager' => 'f8dace3b', 'conpherence-transaction-css' => '25138b7f', 'conpherence-update-css' => '1099a660', 'conpherence-widget-pane-css' => '2af42ebe', @@ -1288,16 +1288,6 @@ return array( 'phabricator-keyboard-shortcut', 'conpherence-thread-manager', ), - '6709c934' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), '6882e80a' => array( 'javelin-dom', ), @@ -1997,6 +1987,16 @@ return array( 'javelin-util', 'phabricator-busy', ), + 'f8dace3b' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), 'f9539603' => array( 'javelin-behavior', 'javelin-dom', diff --git a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js index b1694e19ca..2f4c72bd16 100644 --- a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js +++ b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js @@ -164,15 +164,19 @@ JX.install('ConpherenceThreadManager', { // Message event for something we already know about. return; } - // If we're currently updating, wait for the update to complete. + // If this notification tells us about a message which is newer than - // the newest one we know to exist, keep track of it so we can - // update once the in-flight update finishes. + // the newest one we know to exist, update our latest knownID so we + // can properly update later. if (this._updating && this._updating.threadPHID == this._loadedThreadPHID) { if (message.messageID > this._updating.knownID) { this._updating.knownID = message.messageID; - return; + // We're currently updating, so wait for the update to complete. + // this.syncWorkflow has us covered in this case. + if (this._updating.active) { + return; + } } } @@ -226,7 +230,8 @@ JX.install('ConpherenceThreadManager', { syncWorkflow: function(workflow, stage) { this._updating = { threadPHID: this._loadedThreadPHID, - knownID: this._latestTransactionID + knownID: this._latestTransactionID, + active: true }; workflow.listen(stage, JX.bind(this, function() { // TODO - do we need to handle if we switch threads somehow? @@ -235,6 +240,7 @@ JX.install('ConpherenceThreadManager', { if (need_sync) { return this._updateThread(); } + this._updating.active = false; })); workflow.start(); }, From 524aee03dca02d974e57c170421120f2356b27f1 Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Thu, 7 May 2015 12:47:49 -0700 Subject: [PATCH 05/70] Conpherence - make sure real time updates still work if you switch threads Summary: Fixes T8118. Turns out this also was broken in the main view if e.g. you went to just /conpherence/. The fix is to make sure the threadmanager js class explicitly manages subscriptions as the loaded thread changes. Test Plan: - from /conpherence/ was able to receive messages in real time. - from /conpherence/ changed threads and still received messages in real time - from durable column switched threads and received messages in real time Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8118 Differential Revision: https://secure.phabricator.com/D12758 --- resources/celerity/map.php | 45 ++++++++++--------- .../rsrc/js/application/aphlict/Aphlict.js | 4 ++ .../conpherence/ConpherenceThreadManager.js | 17 +++++++ 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 8f2e5af1a4..456a8a2b0a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => 'ca3f6a60', - 'core.pkg.js' => 'f6b48b53', + 'core.pkg.js' => '5a18f0ab', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', 'differential.pkg.js' => '895b8d62', @@ -340,13 +340,13 @@ return array( 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', - 'rsrc/js/application/aphlict/Aphlict.js' => '30a6303c', + 'rsrc/js/application/aphlict/Aphlict.js' => '5359e785', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '0323afdd', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'f8dace3b', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '7a608e21', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', 'rsrc/js/application/conpherence/behavior-durable-column.js' => '657c2b50', 'rsrc/js/application/conpherence/behavior-menu.js' => '804b0773', @@ -513,7 +513,7 @@ return array( 'conpherence-menu-css' => 'f389e048', 'conpherence-message-pane-css' => '3150e2a2', 'conpherence-notification-css' => 'd208f806', - 'conpherence-thread-manager' => 'f8dace3b', + 'conpherence-thread-manager' => '7a608e21', 'conpherence-transaction-css' => '25138b7f', 'conpherence-update-css' => '1099a660', 'conpherence-widget-pane-css' => '2af42ebe', @@ -539,7 +539,7 @@ return array( 'herald-test-css' => '778b008e', 'homepage-panel-css' => 'e34bf140', 'inline-comment-summary-css' => 'eb5f8e8c', - 'javelin-aphlict' => '30a6303c', + 'javelin-aphlict' => '5359e785', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => '0323afdd', 'javelin-behavior-aphlict-listen' => 'b1a59974', @@ -1022,13 +1022,6 @@ return array( 'javelin-install', 'javelin-event', ), - '30a6303c' => array( - 'javelin-install', - 'javelin-util', - 'javelin-websocket', - 'javelin-leader', - 'javelin-json', - ), '316b8fa1' => array( 'javelin-install', 'javelin-typeahead-source', @@ -1155,6 +1148,13 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), + '5359e785' => array( + 'javelin-install', + 'javelin-util', + 'javelin-websocket', + 'javelin-leader', + 'javelin-json', + ), '54b612ba' => array( 'javelin-color', 'javelin-install', @@ -1380,6 +1380,17 @@ return array( 'javelin-behavior', 'javelin-quicksand', ), + '7a608e21' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-aphlict', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), '7a68dda3' => array( 'owners-path-editor', 'javelin-behavior', @@ -1987,16 +1998,6 @@ return array( 'javelin-util', 'phabricator-busy', ), - 'f8dace3b' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), 'f9539603' => array( 'javelin-behavior', 'javelin-dom', diff --git a/webroot/rsrc/js/application/aphlict/Aphlict.js b/webroot/rsrc/js/application/aphlict/Aphlict.js index d4886a12a0..78d0958c81 100644 --- a/webroot/rsrc/js/application/aphlict/Aphlict.js +++ b/webroot/rsrc/js/application/aphlict/Aphlict.js @@ -49,6 +49,10 @@ JX.install('Aphlict', { JX.Leader.call(JX.bind(this, this._begin)); }, + getSubscriptions: function() { + return this._subscriptions; + }, + setSubscriptions: function(subscriptions) { this._subscriptions = subscriptions; JX.Leader.broadcast( diff --git a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js index 2f4c72bd16..0e97321605 100644 --- a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js +++ b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js @@ -4,6 +4,7 @@ * javelin-util * javelin-stratcom * javelin-install + * javelin-aphlict * javelin-workflow * javelin-router * javelin-behavior-device @@ -277,10 +278,26 @@ JX.install('ConpherenceThreadManager', { params = this._getParams(params); var handler = JX.bind(this, function(r) { + var client = JX.Aphlict.getInstance(); + if (client) { + var old_subs = client.getSubscriptions(); + var new_subs = []; + for (var ii = 0; ii < old_subs.length; ii++) { + if (old_subs[ii] == this._loadedThreadPHID) { + continue; + } else { + new_subs.push(old_subs[ii]); + } + } + new_subs.push(r.threadPHID); + client.clearSubscriptions(client.getSubscriptions()); + client.setSubscriptions(new_subs); + } this._loadedThreadID = r.threadID; this._loadedThreadPHID = r.threadPHID; this._latestTransactionID = r.latestTransactionID; this._canEditLoadedThread = r.canEdit; + JX.Stratcom.invoke('notification-panel-update', null, {}); this._didLoadThreadCallback(r); From a238f6a7598a48d97bf72454acb41fce72c258c7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 May 2015 14:09:41 -0700 Subject: [PATCH 06/70] Implement rough content-aware inline adjustment rules for ghosts Summary: Ref T7447. Fixes T7600. This likely needs significant adjustment, but implements content-aware comment porting for line changes. Specifically, this moves lines around to adjust their position considering added and removed lines between the diffs and across rebases. It does not try to do any actual content (line against line) matching. Test Plan: - Unit tests. - Poking around in the web UI seems to generate mostly reasonable-ish results? - This may be a huge step backward in some cases that I just haven't hit. Reviewers: btrahan Reviewed By: btrahan Subscribers: yelirekim, epriestley Maniphest Tasks: T7600, T7447 Differential Revision: https://secure.phabricator.com/D12741 --- src/__phutil_library_map__.php | 4 + .../parser/DifferentialLineAdjustmentMap.php | 376 ++++++++++++++++++ .../query/DifferentialInlineCommentQuery.php | 102 +++++ .../DifferentialChangesetTwoUpRenderer.php | 1 + .../differential/storage/DifferentialHunk.php | 2 +- .../DifferentialAdjustmentMapTestCase.php | 294 ++++++++++++++ .../storage/__tests__/map/add.diff | 32 ++ .../storage/__tests__/map/chain.adjust.1.diff | 14 + .../storage/__tests__/map/chain.adjust.2.diff | 11 + .../storage/__tests__/map/chain.adjust.3.diff | 14 + .../storage/__tests__/map/change.diff | 34 ++ .../storage/__tests__/map/context.diff | 24 ++ .../storage/__tests__/map/insert.diff | 14 + .../storage/__tests__/map/remove.diff | 32 ++ 14 files changed, 953 insertions(+), 1 deletion(-) create mode 100644 src/applications/differential/parser/DifferentialLineAdjustmentMap.php create mode 100644 src/applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php create mode 100644 src/applications/differential/storage/__tests__/map/add.diff create mode 100644 src/applications/differential/storage/__tests__/map/chain.adjust.1.diff create mode 100644 src/applications/differential/storage/__tests__/map/chain.adjust.2.diff create mode 100644 src/applications/differential/storage/__tests__/map/chain.adjust.3.diff create mode 100644 src/applications/differential/storage/__tests__/map/change.diff create mode 100644 src/applications/differential/storage/__tests__/map/context.diff create mode 100644 src/applications/differential/storage/__tests__/map/insert.diff create mode 100644 src/applications/differential/storage/__tests__/map/remove.diff diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8e19b001f3..3b422cc6d3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -293,6 +293,7 @@ phutil_register_library_map(array( 'DifferentialActionEmailCommand' => 'applications/differential/command/DifferentialActionEmailCommand.php', 'DifferentialActionMenuEventListener' => 'applications/differential/event/DifferentialActionMenuEventListener.php', 'DifferentialAddCommentView' => 'applications/differential/view/DifferentialAddCommentView.php', + 'DifferentialAdjustmentMapTestCase' => 'applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php', 'DifferentialAffectedPath' => 'applications/differential/storage/DifferentialAffectedPath.php', 'DifferentialApplyPatchField' => 'applications/differential/customfield/DifferentialApplyPatchField.php', 'DifferentialArcanistProjectField' => 'applications/differential/customfield/DifferentialArcanistProjectField.php', @@ -391,6 +392,7 @@ phutil_register_library_map(array( 'DifferentialLandingActionMenuEventListener' => 'applications/differential/landing/DifferentialLandingActionMenuEventListener.php', 'DifferentialLandingStrategy' => 'applications/differential/landing/DifferentialLandingStrategy.php', 'DifferentialLegacyHunk' => 'applications/differential/storage/DifferentialLegacyHunk.php', + 'DifferentialLineAdjustmentMap' => 'applications/differential/parser/DifferentialLineAdjustmentMap.php', 'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php', 'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php', 'DifferentialLocalCommitsView' => 'applications/differential/view/DifferentialLocalCommitsView.php', @@ -3530,6 +3532,7 @@ phutil_register_library_map(array( 'DifferentialActionEmailCommand' => 'MetaMTAEmailTransactionCommand', 'DifferentialActionMenuEventListener' => 'PhabricatorEventListener', 'DifferentialAddCommentView' => 'AphrontView', + 'DifferentialAdjustmentMapTestCase' => 'ArcanistPhutilTestCase', 'DifferentialAffectedPath' => 'DifferentialDAO', 'DifferentialApplyPatchField' => 'DifferentialCustomField', 'DifferentialArcanistProjectField' => 'DifferentialCustomField', @@ -3632,6 +3635,7 @@ phutil_register_library_map(array( 'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField', 'DifferentialLandingActionMenuEventListener' => 'PhabricatorEventListener', 'DifferentialLegacyHunk' => 'DifferentialHunk', + 'DifferentialLineAdjustmentMap' => 'Phobject', 'DifferentialLintField' => 'DifferentialCustomField', 'DifferentialLocalCommitsView' => 'AphrontView', 'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField', diff --git a/src/applications/differential/parser/DifferentialLineAdjustmentMap.php b/src/applications/differential/parser/DifferentialLineAdjustmentMap.php new file mode 100644 index 0000000000..fde8f61f7d --- /dev/null +++ b/src/applications/differential/parser/DifferentialLineAdjustmentMap.php @@ -0,0 +1,376 @@ +map; + } + + public function getNearestMap() { + if ($this->nearestMap === null) { + $this->buildNearestMap(); + } + + return $this->nearestMap; + } + + public function getFinalOffset() { + // Make sure we've built this map already. + $this->getNearestMap(); + return $this->finalOffset; + } + + + /** + * Add a map to the end of the chain. + * + * When a line is mapped with @{method:mapLine}, it is mapped through all + * maps in the chain. + */ + public function addMapToChain(DifferentialLineAdjustmentMap $map) { + if ($this->nextMapInChain) { + $this->nextMapInChain->addMapToChain($map); + } else { + $this->nextMapInChain = $map; + } + return $this; + } + + + /** + * Map a line across a change, or a series of changes. + * + * @param int Line to map + * @param bool True to map it as the end of a range. + * @return wild Spooky magic. + */ + public function mapLine($line, $is_end) { + $nmap = $this->getNearestMap(); + + $deleted = false; + $offset = false; + if (isset($nmap[$line])) { + $line_range = $nmap[$line]; + if ($is_end) { + $to_line = end($line_range); + } else { + $to_line = reset($line_range); + } + if ($to_line <= 0) { + // If we're tracing the first line and this block is collapsing, + // compute the offset from the top of the block. + if (!$is_end && $this->isInverse) { + $offset = 0; + $cursor = $line - 1; + while (isset($nmap[$cursor])) { + $prev = $nmap[$cursor]; + $prev = reset($prev); + if ($prev == $to_line) { + $offset++; + } else { + break; + } + $cursor--; + } + } + + $to_line = -$to_line; + if (!$this->isInverse) { + $deleted = true; + } + } + $line = $to_line; + } else { + $line = $line + $this->finalOffset; + } + + if ($this->nextMapInChain) { + $chain = $this->nextMapInChain->mapLine($line, $is_end); + list($chain_deleted, $chain_offset, $line) = $chain; + $deleted = ($deleted || $chain_deleted); + if ($chain_offset !== false) { + if ($offset === false) { + $offset = 0; + } + $offset += $chain_offset; + } + } + + return array($deleted, $offset, $line); + } + + + /** + * Build a derived map which maps deleted lines to the nearest valid line. + * + * This computes a "nearest line" map and a final-line offset. These + * derived maps allow us to map deleted code to the previous (or next) line + * which actually exists. + */ + private function buildNearestMap() { + $map = $this->map; + $nmap = array(); + + $nearest = 0; + foreach ($map as $key => $value) { + if ($value) { + $nmap[$key] = $value; + $nearest = end($value); + } else { + $nmap[$key][0] = -$nearest; + } + } + + if (isset($key)) { + $this->finalOffset = ($nearest - $key); + } else { + $this->finalOffset = 0; + } + + foreach (array_reverse($map, true) as $key => $value) { + if ($value) { + $nearest = reset($value); + } else { + $nmap[$key][1] = -$nearest; + } + } + + $this->nearestMap = $nmap; + + return $this; + } + + public static function newFromHunks(array $hunks) { + assert_instances_of($hunks, 'DifferentialHunk'); + + $map = array(); + $o = 0; + $n = 0; + + $hunks = msort($hunks, 'getOldOffset'); + foreach ($hunks as $hunk) { + + // If the hunks are disjoint, add the implied missing lines where + // nothing changed. + $min = ($hunk->getOldOffset() - 1); + while ($o < $min) { + $o++; + $n++; + $map[$o][] = $n; + } + + $lines = $hunk->getStructuredLines(); + foreach ($lines as $line) { + switch ($line['type']) { + case '-': + $o++; + $map[$o] = array(); + break; + case '+': + $n++; + $map[$o][] = $n; + break; + case ' ': + $o++; + $n++; + $map[$o][] = $n; + break; + default: + break; + } + } + } + + $map = self::reduceMapRanges($map); + + return self::newFromMap($map); + } + + public static function newFromMap(array $map) { + $obj = new DifferentialLineAdjustmentMap(); + $obj->map = $map; + return $obj; + } + + public static function newInverseMap(DifferentialLineAdjustmentMap $map) { + $old = $map->getMap(); + $inv = array(); + $last = 0; + foreach ($old as $k => $v) { + if (count($v) > 1) { + $v = range(reset($v), end($v)); + } + if ($k == 0) { + foreach ($v as $line) { + $inv[$line] = array(); + $last = $line; + } + } else if ($v) { + $first = true; + foreach ($v as $line) { + if ($first) { + $first = false; + $inv[$line][] = $k; + $last = $line; + } else { + $inv[$line] = array(); + } + } + } else { + $inv[$last][] = $k; + } + } + + $inv = self::reduceMapRanges($inv); + + $obj = new DifferentialLineAdjustmentMap(); + $obj->map = $inv; + $obj->isInverse = !$map->isInverse; + return $obj; + } + + private static function reduceMapRanges(array $map) { + foreach ($map as $key => $values) { + if (count($values) > 2) { + $map[$key] = array(reset($values), end($values)); + } + } + return $map; + } + + + public static function loadMaps(array $maps) { + $keys = array(); + foreach ($maps as $map) { + list($u, $v) = $map; + $keys[self::getCacheKey($u, $v)] = $map; + } + + $cache = new PhabricatorKeyValueDatabaseCache(); + $cache = new PhutilKeyValueCacheProfiler($cache); + $cache->setProfiler(PhutilServiceProfiler::getInstance()); + + $results = array(); + + if ($keys) { + $caches = $cache->getKeys(array_keys($keys)); + foreach ($caches as $key => $value) { + list($u, $v) = $keys[$key]; + try { + $results[$u][$v] = self::newFromMap( + phutil_json_decode($value)); + } catch (Exception $ex) { + // Ignore, rebuild below. + } + unset($keys[$key]); + } + } + + if ($keys) { + $built = self::buildMaps($maps); + + $write = array(); + foreach ($built as $u => $list) { + foreach ($list as $v => $map) { + $write[self::getCacheKey($u, $v)] = json_encode($map->getMap()); + $results[$u][$v] = $map; + } + } + + $cache->setKeys($write); + } + + return $results; + } + + private static function buildMaps(array $maps) { + $need = array(); + foreach ($maps as $map) { + list($u, $v) = $map; + $need[$u] = $u; + $need[$v] = $v; + } + + if ($need) { + $changesets = id(new DifferentialChangesetQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIDs($need) + ->needHunks(true) + ->execute(); + $changesets = mpull($changesets, null, 'getID'); + } + + $results = array(); + foreach ($maps as $map) { + list($u, $v) = $map; + $u_set = idx($changesets, $u); + $v_set = idx($changesets, $v); + + if (!$u_set || !$v_set) { + continue; + } + + // This is the simple case. + if ($u == $v) { + $results[$u][$v] = self::newFromHunks( + $u_set->getHunks()); + continue; + } + + $u_old = $u_set->makeOldFile(); + $v_old = $v_set->makeOldFile(); + + // No difference between the two left sides. + if ($u_old == $v_old) { + $results[$u][$v] = self::newFromMap( + array()); + continue; + } + + // If we're missing context, this won't currently work. We can + // make this case work, but it's fairly rare. + $u_hunks = $u_set->getHunks(); + $v_hunks = $v_set->getHunks(); + if (count($u_hunks) != 1 || + count($v_hunks) != 1 || + head($u_hunks)->getOldOffset() != 1 || + head($u_hunks)->getNewOffset() != 1 || + head($v_hunks)->getOldOffset() != 1 || + head($v_hunks)->getNewOffset() != 1) { + continue; + } + + $changeset = id(new PhabricatorDifferenceEngine()) + ->setIgnoreWhitespace(true) + ->generateChangesetFromFileContent($u_old, $v_old); + + $results[$u][$v] = self::newFromHunks( + $changeset->getHunks()); + } + + return $results; + } + + private static function getCacheKey($u, $v) { + return 'diffadjust.v1('.$u.','.$v.')'; + } + +} diff --git a/src/applications/differential/query/DifferentialInlineCommentQuery.php b/src/applications/differential/query/DifferentialInlineCommentQuery.php index 711c638bf1..3fb26739c0 100644 --- a/src/applications/differential/query/DifferentialInlineCommentQuery.php +++ b/src/applications/differential/query/DifferentialInlineCommentQuery.php @@ -323,6 +323,7 @@ final class DifferentialInlineCommentQuery 'new' => $is_new, 'reason' => $reason, 'href' => $href, + 'originalID' => $changeset->getID(), )); $results[] = $inline; @@ -348,6 +349,107 @@ final class DifferentialInlineCommentQuery } } + // Adjust inline line numbers to account for content changes across + // updates and rebases. + $plan = array(); + $need = array(); + foreach ($results as $inline) { + $ghost = $inline->getIsGhost(); + if (!$ghost) { + // If this isn't a "ghost" inline, ignore it. + continue; + } + + $src_id = $ghost['originalID']; + $dst_id = $inline->getChangesetID(); + + $xforms = array(); + + // If the comment is on the right, transform it through the inverse map + // back to the left. + if ($inline->getIsNewFile()) { + $xforms[] = array($src_id, $src_id, true); + } + + // Transform it across rebases. + $xforms[] = array($src_id, $dst_id, false); + + // If the comment is on the right, transform it back onto the right. + if ($inline->getIsNewFile()) { + $xforms[] = array($dst_id, $dst_id, false); + } + + $key = array(); + foreach ($xforms as $xform) { + list($u, $v, $inverse) = $xform; + + $short = $u.'/'.$v; + $need[$short] = array($u, $v); + + $part = $u.($inverse ? '<' : '>').$v; + $key[] = $part; + } + $key = implode(',', $key); + + if (empty($plan[$key])) { + $plan[$key] = array( + 'xforms' => $xforms, + 'inlines' => array(), + ); + } + + $plan[$key]['inlines'][] = $inline; + } + + if ($need) { + $maps = DifferentialLineAdjustmentMap::loadMaps($need); + } else { + $maps = array(); + } + + foreach ($plan as $step) { + $xforms = $step['xforms']; + + $chain = null; + foreach ($xforms as $xform) { + list($u, $v, $inverse) = $xform; + $map = idx(idx($maps, $u, array()), $v); + if (!$map) { + continue 2; + } + + if ($inverse) { + $map = DifferentialLineAdjustmentMap::newInverseMap($map); + } else { + $map = clone $map; + } + + if ($chain) { + $chain->addMapToChain($map); + } else { + $chain = $map; + } + } + + foreach ($step['inlines'] as $inline) { + $head_line = $inline->getLineNumber(); + $tail_line = ($head_line + $inline->getLineLength()); + + $head_info = $chain->mapLine($head_line, false); + $tail_info = $chain->mapLine($tail_line, true); + + list($head_deleted, $head_offset, $head_line) = $head_info; + list($tail_deleted, $tail_offset, $tail_line) = $tail_info; + + if ($head_offset !== false) { + $inline->setLineNumber($head_line + 1 + $head_offset); + } else { + $inline->setLineNumber($head_line); + $inline->setLineLength($tail_line - $head_line); + } + } + } + return $results; } diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php index a9eef41ac8..3eb0190664 100644 --- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php @@ -278,6 +278,7 @@ final class DifferentialChangesetTwoUpRenderer $scaffold->addInlineView($companion); unset($new_comments[$n_num][$key]); + break; } } } diff --git a/src/applications/differential/storage/DifferentialHunk.php b/src/applications/differential/storage/DifferentialHunk.php index f22ba59d2b..bd13cadfec 100644 --- a/src/applications/differential/storage/DifferentialHunk.php +++ b/src/applications/differential/storage/DifferentialHunk.php @@ -117,7 +117,7 @@ abstract class DifferentialHunk extends DifferentialDAO return $this->splitLines; } - private function getStructuredLines() { + public function getStructuredLines() { if ($this->structuredLines === null) { $lines = $this->getSplitLines(); diff --git a/src/applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php b/src/applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php new file mode 100644 index 0000000000..8fc28953fc --- /dev/null +++ b/src/applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php @@ -0,0 +1,294 @@ + array(1), + 2 => array(2), + 3 => array(3), + 4 => array(), + 5 => array(), + 6 => array(), + 7 => array(4), + 8 => array(5), + 9 => array(6), + 10 => array(7), + 11 => array(8), + 12 => array(9), + 13 => array(10), + 14 => array(11), + 15 => array(12), + 16 => array(13), + 17 => array(14), + 18 => array(15), + 19 => array(16), + 20 => array(17, 20), + 21 => array(21), + 22 => array(22), + 23 => array(23), + 24 => array(24), + 25 => array(25), + 26 => array(26), + ); + + $hunks = $this->loadHunks('add.diff'); + $this->assertEqual( + array( + 0 => array(1, 26), + ), + DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap()); + + $hunks = $this->loadHunks('change.diff'); + $this->assertEqual( + $change_map, + DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap()); + + $hunks = $this->loadHunks('remove.diff'); + $this->assertEqual( + array_fill_keys(range(1, 26), array()), + DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap()); + + // With the contextless diff, we don't get the last few similar lines + // in the map. + $reduced_map = $change_map; + unset($reduced_map[24]); + unset($reduced_map[25]); + unset($reduced_map[26]); + + $hunks = $this->loadHunks('context.diff'); + $this->assertEqual( + $reduced_map, + DifferentialLineAdjustmentMap::newFromHunks($hunks)->getMap()); + } + + + public function testInverseMaps() { + $change_map = array( + 1 => array(1), + 2 => array(2), + 3 => array(3, 6), + 4 => array(7), + 5 => array(8), + 6 => array(9), + 7 => array(10), + 8 => array(11), + 9 => array(12), + 10 => array(13), + 11 => array(14), + 12 => array(15), + 13 => array(16), + 14 => array(17), + 15 => array(18), + 16 => array(19), + 17 => array(20), + 18 => array(), + 19 => array(), + 20 => array(), + 21 => array(21), + 22 => array(22), + 23 => array(23), + 24 => array(24), + 25 => array(25), + 26 => array(26), + ); + + $hunks = $this->loadHunks('add.diff'); + $this->assertEqual( + array_fill_keys(range(1, 26), array()), + DifferentialLineAdjustmentMap::newInverseMap( + DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap()); + + $hunks = $this->loadHunks('change.diff'); + $this->assertEqual( + $change_map, + DifferentialLineAdjustmentMap::newInverseMap( + DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap()); + + $hunks = $this->loadHunks('remove.diff'); + $this->assertEqual( + array( + 0 => array(1, 26), + ), + DifferentialLineAdjustmentMap::newInverseMap( + DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap()); + + // With the contextless diff, we don't get the last few similar lines + // in the map. + $reduced_map = $change_map; + unset($reduced_map[24]); + unset($reduced_map[25]); + unset($reduced_map[26]); + + $hunks = $this->loadHunks('context.diff'); + $this->assertEqual( + $reduced_map, + DifferentialLineAdjustmentMap::newInverseMap( + DifferentialLineAdjustmentMap::newFromHunks($hunks))->getMap()); + } + + + public function testNearestMaps() { + $change_map = array( + 1 => array(1), + 2 => array(2), + 3 => array(3), + 4 => array(-3, -4), + 5 => array(-3, -4), + 6 => array(-3, -4), + 7 => array(4), + 8 => array(5), + 9 => array(6), + 10 => array(7), + 11 => array(8), + 12 => array(9), + 13 => array(10), + 14 => array(11), + 15 => array(12), + 16 => array(13), + 17 => array(14), + 18 => array(15), + 19 => array(16), + 20 => array(17, 20), + 21 => array(21), + 22 => array(22), + 23 => array(23), + 24 => array(24), + 25 => array(25), + 26 => array(26), + ); + + $hunks = $this->loadHunks('add.diff'); + $map = DifferentialLineAdjustmentMap::newFromHunks($hunks); + $this->assertEqual( + array( + 0 => array(1, 26), + ), + $map->getNearestMap()); + $this->assertEqual(26, $map->getFinalOffset()); + + + $hunks = $this->loadHunks('change.diff'); + $map = DifferentialLineAdjustmentMap::newFromHunks($hunks); + $this->assertEqual( + $change_map, + $map->getNearestMap()); + $this->assertEqual(0, $map->getFinalOffset()); + + + $hunks = $this->loadHunks('remove.diff'); + $map = DifferentialLineAdjustmentMap::newFromHunks($hunks); + $this->assertEqual( + array_fill_keys( + range(1, 26), + array(0, 0)), + $map->getNearestMap()); + $this->assertEqual(-26, $map->getFinalOffset()); + + + $reduced_map = $change_map; + unset($reduced_map[24]); + unset($reduced_map[25]); + unset($reduced_map[26]); + + $hunks = $this->loadHunks('context.diff'); + $map = DifferentialLineAdjustmentMap::newFromHunks($hunks); + $this->assertEqual( + $reduced_map, + $map->getNearestMap()); + $this->assertEqual(0, $map->getFinalOffset()); + + + $hunks = $this->loadHunks('insert.diff'); + $map = DifferentialLineAdjustmentMap::newFromHunks($hunks); + $this->assertEqual( + array( + 1 => array(1), + 2 => array(2), + 3 => array(3), + 4 => array(4), + 5 => array(5), + 6 => array(6), + 7 => array(7), + 8 => array(8), + 9 => array(9), + 10 => array(10, 13), + 11 => array(14), + 12 => array(15), + 13 => array(16), + ), + $map->getNearestMap()); + $this->assertEqual(3, $map->getFinalOffset()); + } + + + public function testChainMaps() { + // This test simulates porting inlines forward across a rebase. + // Part 1 is the original diff. + // Part 2 is the rebase, which we would normally compute synthetically. + // Part 3 is the updated diff against the rebased changes. + + $diff1 = $this->loadHunks('chain.adjust.1.diff'); + $diff2 = $this->loadHunks('chain.adjust.2.diff'); + $diff3 = $this->loadHunks('chain.adjust.3.diff'); + + $map = DifferentialLineAdjustmentMap::newInverseMap( + DifferentialLineAdjustmentMap::newFromHunks($diff1)); + + $map->addMapToChain( + DifferentialLineAdjustmentMap::newFromHunks($diff2)); + + $map->addMapToChain( + DifferentialLineAdjustmentMap::newFromHunks($diff3)); + + $actual = array(); + for ($ii = 1; $ii <= 13; $ii++) { + $actual[$ii] = array( + $map->mapLine($ii, false), + $map->mapLine($ii, true), + ); + } + + $this->assertEqual( + array( + 1 => array(array(false, false, 1), array(false, false, 1)), + 2 => array(array(true, false, 1), array(true, false, 2)), + 3 => array(array(true, false, 1), array(true, false, 2)), + 4 => array(array(false, false, 2), array(false, false, 2)), + 5 => array(array(false, false, 3), array(false, false, 3)), + 6 => array(array(false, false, 4), array(false, false, 4)), + 7 => array(array(false, false, 5), array(false, false, 8)), + 8 => array(array(false, 0, 5), array(false, false, 9)), + 9 => array(array(false, 1, 5), array(false, false, 9)), + 10 => array(array(false, 2, 5), array(false, false, 9)), + 11 => array(array(false, false, 9), array(false, false, 9)), + 12 => array(array(false, false, 10), array(false, false, 10)), + 13 => array(array(false, false, 11), array(false, false, 11)), + ), + $actual); + } + + + private function loadHunks($name) { + $root = dirname(__FILE__).'/map/'; + $data = Filesystem::readFile($root.$name); + + $parser = new ArcanistDiffParser(); + $changes = $parser->parseDiff($data); + + $viewer = PhabricatorUser::getOmnipotentUser(); + $diff = DifferentialDiff::newFromRawChanges($viewer, $changes); + + $changesets = $diff->getChangesets(); + if (count($changesets) !== 1) { + throw new Exception( + pht( + 'Expected exactly one changeset from "%s".', + $name)); + } + $changeset = head($changesets); + + return $changeset->getHunks(); + } + +} diff --git a/src/applications/differential/storage/__tests__/map/add.diff b/src/applications/differential/storage/__tests__/map/add.diff new file mode 100644 index 0000000000..97e60a8b7c --- /dev/null +++ b/src/applications/differential/storage/__tests__/map/add.diff @@ -0,0 +1,32 @@ +diff --git a/alphabet b/alphabet +new file mode 100644 +index 0000000..0edb856 +--- /dev/null ++++ b/alphabet +@@ -0,0 +1,26 @@ ++a ++b ++c ++d ++e ++f ++g ++h ++i ++j ++k ++l ++m ++n ++o ++p ++q ++r ++s ++t ++u ++v ++w ++x ++y ++z diff --git a/src/applications/differential/storage/__tests__/map/chain.adjust.1.diff b/src/applications/differential/storage/__tests__/map/chain.adjust.1.diff new file mode 100644 index 0000000000..8370a66e1a --- /dev/null +++ b/src/applications/differential/storage/__tests__/map/chain.adjust.1.diff @@ -0,0 +1,14 @@ +diff --git a/alphabet b/alphabet +index 92dfa21..292798b 100644 +--- a/alphabet ++++ b/alphabet +@@ -5,6 +5,9 @@ d + e + f + g ++G1 ++G2 ++G3 + h + i + j diff --git a/src/applications/differential/storage/__tests__/map/chain.adjust.2.diff b/src/applications/differential/storage/__tests__/map/chain.adjust.2.diff new file mode 100644 index 0000000000..ac6f8c854a --- /dev/null +++ b/src/applications/differential/storage/__tests__/map/chain.adjust.2.diff @@ -0,0 +1,11 @@ +diff --git a/alphabet b/alphabet +index 92dfa21..e3344af 100644 +--- a/alphabet ++++ b/alphabet +@@ -1,6 +1,4 @@ + a +-b +-c + d + e + f diff --git a/src/applications/differential/storage/__tests__/map/chain.adjust.3.diff b/src/applications/differential/storage/__tests__/map/chain.adjust.3.diff new file mode 100644 index 0000000000..4d23f185fd --- /dev/null +++ b/src/applications/differential/storage/__tests__/map/chain.adjust.3.diff @@ -0,0 +1,14 @@ +diff --git a/alphabet b/alphabet +index e3344af..febfe3e 100644 +--- a/alphabet ++++ b/alphabet +@@ -3,6 +3,9 @@ d + e + f + g ++G1x ++G2x ++G3x + h + i + j diff --git a/src/applications/differential/storage/__tests__/map/change.diff b/src/applications/differential/storage/__tests__/map/change.diff new file mode 100644 index 0000000000..7ef945267f --- /dev/null +++ b/src/applications/differential/storage/__tests__/map/change.diff @@ -0,0 +1,34 @@ +diff --git a/alphabet b/alphabet +index 0edb856..2449de2 100644 +--- a/alphabet ++++ b/alphabet +@@ -1,26 +1,26 @@ + a + b + c +-d +-e +-f + g + h + i + j + k + l + m + n + o + p + q + r + s + t ++tx ++ty ++tz + u + v + w + x + y + z diff --git a/src/applications/differential/storage/__tests__/map/context.diff b/src/applications/differential/storage/__tests__/map/context.diff new file mode 100644 index 0000000000..ab77e4a9ba --- /dev/null +++ b/src/applications/differential/storage/__tests__/map/context.diff @@ -0,0 +1,24 @@ +diff --git a/alphabet b/alphabet +index 0edb856..2449de2 100644 +--- a/alphabet ++++ b/alphabet +@@ -1,9 +1,6 @@ + a + b + c +-d +-e +-f + g + h + i +@@ -18,6 +15,9 @@ q + r + s + t ++tx ++ty ++tz + u + v + w diff --git a/src/applications/differential/storage/__tests__/map/insert.diff b/src/applications/differential/storage/__tests__/map/insert.diff new file mode 100644 index 0000000000..9a726955e7 --- /dev/null +++ b/src/applications/differential/storage/__tests__/map/insert.diff @@ -0,0 +1,14 @@ +diff --git a/alphabet b/alphabet +index f2b41ef..755b349 100644 +--- a/alphabet ++++ b/alphabet +@@ -8,6 +8,9 @@ g + h + i + j ++j1 ++j2 ++j3 + k + l + n diff --git a/src/applications/differential/storage/__tests__/map/remove.diff b/src/applications/differential/storage/__tests__/map/remove.diff new file mode 100644 index 0000000000..0feafbbfc3 --- /dev/null +++ b/src/applications/differential/storage/__tests__/map/remove.diff @@ -0,0 +1,32 @@ +diff --git a/alphabet b/alphabet +deleted file mode 100644 +index 2449de2..0000000 +--- a/alphabet ++++ /dev/null +@@ -1,26 +0,0 @@ +-a +-b +-c +-g +-h +-i +-j +-k +-l +-m +-n +-o +-p +-q +-r +-s +-t +-tx +-ty +-tz +-u +-v +-w +-x +-y +-z From 7556a70280c8a6b681572f26ff06bb60e4ab6888 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 May 2015 15:58:35 -0700 Subject: [PATCH 07/70] Prevent Files from requiring infinite policy checks Summary: Fixes T6726. Currently, a file may be attached to itself (or to other files, ultimately forming a loop). In this case, we currently run around the loop forever trying to load all the files. Instead, decline to load objects if we're inside a query which is already loading them. This produces the right policy result //and// completes in finite time. Test Plan: - Looped two files by writing `{F123}` and `{F124}` on the other files, respectively. - Loaded `F123`. - Saw long hang; used `debug.time-limit` to see huge stack trace instead. - Wrote patch. - `F123` now loads correctly. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T6726 Differential Revision: https://secure.phabricator.com/D12756 --- .../phid/query/PhabricatorObjectQuery.php | 12 +++++++ .../policy/PhabricatorPolicyAwareQuery.php | 34 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/applications/phid/query/PhabricatorObjectQuery.php b/src/applications/phid/query/PhabricatorObjectQuery.php index 49d238c6a9..fbec78b29b 100644 --- a/src/applications/phid/query/PhabricatorObjectQuery.php +++ b/src/applications/phid/query/PhabricatorObjectQuery.php @@ -104,6 +104,16 @@ final class PhabricatorObjectQuery } private function loadObjectsByPHID(array $types, array $phids) { + // Don't try to load PHIDs which are already "in flight"; this prevents us + // from recursing indefinitely if policy checks or edges form a loop. We + // will decline to load the corresponding objects. + $in_flight = $this->getPHIDsInFlight(); + foreach ($phids as $key => $phid) { + if (isset($in_flight[$phid])) { + unset($phids[$key]); + } + } + $results = array(); $workspace = $this->getObjectsFromWorkspace($phids); @@ -119,6 +129,8 @@ final class PhabricatorObjectQuery return $results; } + $this->putPHIDsInFlight($phids); + $groups = array(); foreach ($phids as $phid) { $type = phid_get_type($phid); diff --git a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php index 1f353d7dfb..e1b2028a19 100644 --- a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php @@ -33,6 +33,7 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { private $rawResultLimit; private $capabilities; private $workspace = array(); + private $inFlightPHIDs = array(); private $policyFilteredPHIDs = array(); private $canUseApplication; @@ -468,6 +469,39 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { } + /** + * Mark PHIDs as in flight. + * + * PHIDs which are "in flight" are actively being queried for. Using this + * list can prevent infinite query loops by aborting queries which cycle. + * + * @param list List of PHIDs which are now in flight. + * @return this + */ + public function putPHIDsInFlight(array $phids) { + foreach ($phids as $phid) { + $this->inFlightPHIDs[$phid] = $phid; + } + return $this; + } + + + /** + * Get PHIDs which are currently in flight. + * + * PHIDs which are "in flight" are actively being queried for. + * + * @return map PHIDs currently in flight. + */ + public function getPHIDsInFlight() { + $results = $this->inFlightPHIDs; + if ($this->getParentQuery()) { + $results += $this->getParentQuery()->getPHIDsInFlight(); + } + return $results; + } + + /* -( Policy Query Implementation )---------------------------------------- */ From f7c14736c173a76e862c3f5846dd3cea36054558 Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Thu, 7 May 2015 16:01:10 -0700 Subject: [PATCH 08/70] Hovercards - don't let them run off the right edge of the viewport anymore Summary: Fixes T7524. Test Plan: - made a task with a comment including another task. resized window so still desktop size and task reference on edge of window. invoked hovercard by bovering over task reference and noted the hovercard was completely visible. - opened the durable column and made a task reference. invoked hovercard by hovering over task reference and noted the hovercard was completely visible. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T7524 Differential Revision: https://secure.phabricator.com/D12759 --- resources/celerity/map.php | 20 ++++++++++---------- webroot/rsrc/js/core/Hovercard.js | 7 ++++++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 456a8a2b0a..dfc8e986a9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => 'ca3f6a60', - 'core.pkg.js' => '5a18f0ab', + 'core.pkg.js' => 'dcd6c8e3', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', 'differential.pkg.js' => '895b8d62', @@ -436,7 +436,7 @@ return array( 'rsrc/js/core/DragAndDropFileUpload.js' => '07de8873', 'rsrc/js/core/DraggableList.js' => 'a16ec1c6', 'rsrc/js/core/FileUpload.js' => '477359c8', - 'rsrc/js/core/Hovercard.js' => '7e8468ae', + 'rsrc/js/core/Hovercard.js' => '14ac66f5', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', @@ -724,7 +724,7 @@ return array( 'phabricator-file-upload' => '477359c8', 'phabricator-filetree-view-css' => 'fccf9f82', 'phabricator-flag-css' => '5337623f', - 'phabricator-hovercard' => '7e8468ae', + 'phabricator-hovercard' => '14ac66f5', 'phabricator-hovercard-view-css' => '44394670', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'c1700f6f', @@ -913,6 +913,13 @@ return array( 'javelin-json', 'phabricator-draggable-list', ), + '14ac66f5' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-vector', + 'javelin-request', + 'javelin-uri', + ), '14d7a8b8' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1410,13 +1417,6 @@ return array( '7e41274a' => array( 'javelin-install', ), - '7e8468ae' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-vector', - 'javelin-request', - 'javelin-uri', - ), '7ebaeed3' => array( 'herald-rule-editor', 'javelin-behavior', diff --git a/webroot/rsrc/js/core/Hovercard.js b/webroot/rsrc/js/core/Hovercard.js index e83b0fcba0..15149eceeb 100644 --- a/webroot/rsrc/js/core/Hovercard.js +++ b/webroot/rsrc/js/core/Hovercard.js @@ -80,6 +80,7 @@ JX.install('Hovercard', { var p = JX.$V(root); var d = JX.Vector.getDim(root); var n = JX.Vector.getDim(child); + var v = JX.Vector.getViewport(); // Move the tip so it's nicely aligned. // I'm just doing north/south alignment for now @@ -89,8 +90,12 @@ JX.install('Hovercard', { var x = parseInt(p.x, 10) - margin / 2; var y = parseInt(p.y - n.y, 10) - margin; + // If running off the edge of the viewport, make it margin / 2 away + // from the far right edge of the viewport instead + if ((x + n.x) > (v.x)) { + x = x - parseInt(x + n.x - v.x + margin / 2, 10); // If more in the center, we can safely center - if (x > (n.x / 2) + margin) { + } else if (x > (n.x / 2) + margin) { x = parseInt(p.x - (n.x / 2) + d.x, 10); } From ef3b62564ed17aa29fac537ba732b28d67ada98c Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Thu, 7 May 2015 16:01:41 -0700 Subject: [PATCH 09/70] Conpherence - improve durable column performance when sending updates Summary: Ref T7708. We were generating things like the files widget when users sent a comment. This is unnecessary if we are in minimal display mode. This saves us fetching some data + rendering. Test Plan: sent messages successfully in durable column and full conpherence view Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T7708 Differential Revision: https://secure.phabricator.com/D12760 --- .../ConpherenceUpdateController.php | 81 ++++++++++--------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 269b87f0e6..7b5bc9a8b6 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -453,6 +453,7 @@ final class ConpherenceUpdateController $conpherence_id, $latest_transaction_id) { + $minimal_display = $this->getRequest()->getExists('minimal_display'); $need_widget_data = false; $need_transactions = false; $need_participant_cache = true; @@ -464,7 +465,7 @@ final class ConpherenceUpdateController case ConpherenceUpdateActions::MESSAGE: case ConpherenceUpdateActions::ADD_PERSON: $need_transactions = true; - $need_widget_data = true; + $need_widget_data = !$minimal_display; break; case ConpherenceUpdateActions::REMOVE_PERSON: case ConpherenceUpdateActions::NOTIFICATIONS: @@ -488,7 +489,7 @@ final class ConpherenceUpdateController $data = ConpherenceTransactionRenderer::renderTransactions( $user, $conpherence, - !$this->getRequest()->getExists('minimal_display')); + !$minimal_display); $participant_obj = $conpherence->getParticipant($user->getPHID()); $participant_obj->markUpToDate($conpherence, $data['latest_transaction']); } else if ($need_transactions) { @@ -505,51 +506,55 @@ final class ConpherenceUpdateController $header = null; $people_widget = null; $file_widget = null; - switch ($action) { - case ConpherenceUpdateActions::METADATA: - $policy_objects = id(new PhabricatorPolicyQuery()) - ->setViewer($user) - ->setObject($conpherence) - ->execute(); - $header = $this->buildHeaderPaneContent($conpherence, $policy_objects); - $nav_item = id(new ConpherenceThreadListView()) - ->setUser($user) - ->setBaseURI($this->getApplicationURI()) - ->renderSingleThread($conpherence); - break; - case ConpherenceUpdateActions::MESSAGE: - $file_widget = id(new ConpherenceFileWidgetView()) - ->setUser($this->getRequest()->getUser()) - ->setConpherence($conpherence) - ->setUpdateURI($widget_uri); - break; - case ConpherenceUpdateActions::ADD_PERSON: - $people_widget = id(new ConpherencePeopleWidgetView()) - ->setUser($user) - ->setConpherence($conpherence) - ->setUpdateURI($widget_uri); - break; - case ConpherenceUpdateActions::REMOVE_PERSON: - case ConpherenceUpdateActions::NOTIFICATIONS: - default: - break; + if (!$minimal_display) { + switch ($action) { + case ConpherenceUpdateActions::METADATA: + $policy_objects = id(new PhabricatorPolicyQuery()) + ->setViewer($user) + ->setObject($conpherence) + ->execute(); + $header = $this->buildHeaderPaneContent( + $conpherence, + $policy_objects); + $header = hsprintf('%s', $header); + $nav_item = id(new ConpherenceThreadListView()) + ->setUser($user) + ->setBaseURI($this->getApplicationURI()) + ->renderSingleThread($conpherence); + $nav_item = hsprintf('%s', $nav_item); + break; + case ConpherenceUpdateActions::MESSAGE: + $file_widget = id(new ConpherenceFileWidgetView()) + ->setUser($this->getRequest()->getUser()) + ->setConpherence($conpherence) + ->setUpdateURI($widget_uri); + $file_widget = $file_widget->render(); + break; + case ConpherenceUpdateActions::ADD_PERSON: + $people_widget = id(new ConpherencePeopleWidgetView()) + ->setUser($user) + ->setConpherence($conpherence) + ->setUpdateURI($widget_uri); + $people_widget = $people_widget->render(); + break; + case ConpherenceUpdateActions::REMOVE_PERSON: + case ConpherenceUpdateActions::NOTIFICATIONS: + default: + break; + } } - $people_html = null; - if ($people_widget) { - $people_html = hsprintf('%s', $people_widget->render()); - } $data = $conpherence->getDisplayData($user); $content = array( 'non_update' => $non_update, 'transactions' => hsprintf('%s', $rendered_transactions), 'conpherence_title' => (string) $data['title'], 'latest_transaction_id' => $new_latest_transaction_id, - 'nav_item' => hsprintf('%s', $nav_item), + 'nav_item' => $nav_item, 'conpherence_phid' => $conpherence->getPHID(), - 'header' => hsprintf('%s', $header), - 'file_widget' => $file_widget ? $file_widget->render() : null, - 'people_widget' => $people_html, + 'header' => $header, + 'file_widget' => $file_widget, + 'people_widget' => $people_widget, ); return $content; From 33e7038b96f107d31a4a50f6d8e6333d56fb9ef7 Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Thu, 7 May 2015 16:04:56 -0700 Subject: [PATCH 10/70] Conpherence - improve performance by handling dropdowns (notifications, threads) as standard response data Summary: Ref T7708. Rather than invoking the general client -> server dropdown refresh path, return the data with the various conpherence requests and update the dropdowns that way. Saves 2 client -> server requests per conpherence action. Test Plan: loaded up /conpherence/ and noted message count deduct correctly. clicked specific message and noted message count deduct successfully. did same two tests via durable column and again saw message counts deduct successfully. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T7708 Differential Revision: https://secure.phabricator.com/D12761 --- resources/celerity/map.php | 52 +++++++++---------- .../ConpherenceColumnViewController.php | 7 +++ .../ConpherenceUpdateController.php | 8 ++- .../controller/ConpherenceViewController.php | 7 +++ .../aphlict/behavior-aphlict-dropdown.js | 39 +++++++++----- .../conpherence/ConpherenceThreadManager.js | 10 +++- 6 files changed, 80 insertions(+), 43 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index dfc8e986a9..5c303ae781 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => 'ca3f6a60', - 'core.pkg.js' => 'dcd6c8e3', + 'core.pkg.js' => '919c56b5', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', 'differential.pkg.js' => '895b8d62', @@ -341,12 +341,12 @@ return array( 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 'rsrc/js/application/aphlict/Aphlict.js' => '5359e785', - 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '0323afdd', + 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '995ad707', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '7a608e21', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '9e507b59', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', 'rsrc/js/application/conpherence/behavior-durable-column.js' => '657c2b50', 'rsrc/js/application/conpherence/behavior-menu.js' => '804b0773', @@ -513,7 +513,7 @@ return array( 'conpherence-menu-css' => 'f389e048', 'conpherence-message-pane-css' => '3150e2a2', 'conpherence-notification-css' => 'd208f806', - 'conpherence-thread-manager' => '7a608e21', + 'conpherence-thread-manager' => '9e507b59', 'conpherence-transaction-css' => '25138b7f', 'conpherence-update-css' => '1099a660', 'conpherence-widget-pane-css' => '2af42ebe', @@ -541,7 +541,7 @@ return array( 'inline-comment-summary-css' => 'eb5f8e8c', 'javelin-aphlict' => '5359e785', 'javelin-behavior' => '61cbc29a', - 'javelin-behavior-aphlict-dropdown' => '0323afdd', + 'javelin-behavior-aphlict-dropdown' => '995ad707', 'javelin-behavior-aphlict-listen' => 'b1a59974', 'javelin-behavior-aphlict-status' => 'ea681761', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', @@ -836,16 +836,6 @@ return array( '029a133d' => array( 'aphront-dialog-view-css', ), - '0323afdd' => array( - 'javelin-behavior', - 'javelin-request', - 'javelin-stratcom', - 'javelin-vector', - 'javelin-dom', - 'javelin-uri', - 'javelin-behavior-device', - 'phabricator-title', - ), '048330fa' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', @@ -1387,17 +1377,6 @@ return array( 'javelin-behavior', 'javelin-quicksand', ), - '7a608e21' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-aphlict', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), '7a68dda3' => array( 'owners-path-editor', 'javelin-behavior', @@ -1589,6 +1568,16 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), + '995ad707' => array( + 'javelin-behavior', + 'javelin-request', + 'javelin-stratcom', + 'javelin-vector', + 'javelin-dom', + 'javelin-uri', + 'javelin-behavior-device', + 'phabricator-title', + ), '9a340b3d' => array( 'javelin-behavior', 'javelin-dom', @@ -1598,6 +1587,17 @@ return array( 'phuix-action-view', 'javelin-workflow', ), + '9e507b59' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-aphlict', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), '9f36c42d' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/conpherence/controller/ConpherenceColumnViewController.php b/src/applications/conpherence/controller/ConpherenceColumnViewController.php index 0155784015..9a146b4b23 100644 --- a/src/applications/conpherence/controller/ConpherenceColumnViewController.php +++ b/src/applications/conpherence/controller/ConpherenceColumnViewController.php @@ -88,12 +88,19 @@ final class ConpherenceColumnViewController extends PhabricatorPolicyCapability::CAN_EDIT); } + $dropdown_query = id(new AphlictDropdownDataQuery()) + ->setViewer($user); + $dropdown_query->execute(); $response = array( 'content' => hsprintf('%s', $durable_column), 'threadID' => $conpherence_id, 'threadPHID' => $conpherence_phid, 'latestTransactionID' => $latest_transaction_id, 'canEdit' => $can_edit, + 'aphlictDropdownData' => array( + $dropdown_query->getNotificationData(), + $dropdown_query->getConpherenceData(), + ), ); return id(new AphrontAjaxResponse())->setContent($response); diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 7b5bc9a8b6..3e86accf7c 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -543,8 +543,10 @@ final class ConpherenceUpdateController break; } } - $data = $conpherence->getDisplayData($user); + $dropdown_query = id(new AphlictDropdownDataQuery()) + ->setViewer($user); + $dropdown_query->execute(); $content = array( 'non_update' => $non_update, 'transactions' => hsprintf('%s', $rendered_transactions), @@ -555,6 +557,10 @@ final class ConpherenceUpdateController 'header' => $header, 'file_widget' => $file_widget, 'people_widget' => $people_widget, + 'aphlictDropdownData' => array( + $dropdown_query->getNotificationData(), + $dropdown_query->getConpherenceData(), + ), ); return $content; diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 7a3027dc53..e0a39030c3 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -94,6 +94,9 @@ final class ConpherenceViewController extends $content['title'] = $title = $d_data['title']; if ($request->isAjax()) { + $dropdown_query = id(new AphlictDropdownDataQuery()) + ->setViewer($user); + $dropdown_query->execute(); $content['threadID'] = $conpherence->getID(); $content['threadPHID'] = $conpherence->getPHID(); $content['latestTransactionID'] = $data['latest_transaction_id']; @@ -101,6 +104,10 @@ final class ConpherenceViewController extends $user, $conpherence, PhabricatorPolicyCapability::CAN_EDIT); + $content['aphlictDropdownData'] = array( + $dropdown_query->getNotificationData(), + $dropdown_query->getConpherenceData(), + ); return id(new AphrontAjaxResponse())->setContent($content); } diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js index b1446593f3..110ab8a175 100644 --- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js +++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js @@ -93,23 +93,34 @@ JX.behavior('aphlict-dropdown', function(config, statics) { if (!data.fromServer) { return; } - var updated = false; var new_data = data.newResponse.aphlictDropdownData; - for (var ii = 0; ii < new_data.length; ii++) { - if (new_data[ii].countType != config.countType) { - continue; - } - if (!new_data[ii].isInstalled) { - continue; - } - updated = true; - _updateCount(parseInt(new_data[ii].count)); - } - if (updated) { - dirty = true; - } + update_counts(new_data); }); + JX.Stratcom.listen( + 'conpherence-redraw-aphlict', + null, + function (e) { + update_counts(e.getData()); + }); + + function update_counts(new_data) { + var updated = false; + for (var ii = 0; ii < new_data.length; ii++) { + if (new_data[ii].countType != config.countType) { + continue; + } + if (!new_data[ii].isInstalled) { + continue; + } + updated = true; + _updateCount(parseInt(new_data[ii].count)); + } + if (updated) { + dirty = true; + } + } + function set_visible(menu, icon) { if (menu) { statics.visible = {menu: menu, icon: icon}; diff --git a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js index 0e97321605..37dad6f2ba 100644 --- a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js +++ b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js @@ -205,7 +205,10 @@ JX.install('ConpherenceThreadManager', { _markUpdated: function(r) { this._updating.knownID = r.latest_transaction_id; this._latestTransactionID = r.latest_transaction_id; - JX.Stratcom.invoke('notification-panel-update', null, {}); + JX.Stratcom.invoke( + 'conpherence-redraw-aphlict', + null, + r.aphlictDropdownData); }, _updateThread: function() { @@ -298,7 +301,10 @@ JX.install('ConpherenceThreadManager', { this._latestTransactionID = r.latestTransactionID; this._canEditLoadedThread = r.canEdit; - JX.Stratcom.invoke('notification-panel-update', null, {}); + JX.Stratcom.invoke( + 'conpherence-redraw-aphlict', + null, + r.aphlictDropdownData); this._didLoadThreadCallback(r); From 57e22e3ce591d37170a9f8a58ee6d871b30a5353 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Thu, 7 May 2015 16:57:49 -0700 Subject: [PATCH 11/70] Just a sql patch for all day events. Summary: Ref T8021, sql patch for all day events. Test Plan: check schema by running `bin/storage upgrade` Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8021 Differential Revision: https://secure.phabricator.com/D12763 --- .../sql/autopatches/20150507.calendar.1.isallday.sql | 2 ++ .../calendar/editor/PhabricatorCalendarEventEditor.php | 8 ++++++++ .../calendar/storage/PhabricatorCalendarEvent.php | 2 ++ .../storage/PhabricatorCalendarEventTransaction.php | 4 ++++ 4 files changed, 16 insertions(+) create mode 100644 resources/sql/autopatches/20150507.calendar.1.isallday.sql diff --git a/resources/sql/autopatches/20150507.calendar.1.isallday.sql b/resources/sql/autopatches/20150507.calendar.1.isallday.sql new file mode 100644 index 0000000000..172015b1be --- /dev/null +++ b/resources/sql/autopatches/20150507.calendar.1.isallday.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD isAllDay BOOL NOT NULL; diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index 09ab1309eb..f00ba4349f 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -21,6 +21,7 @@ final class PhabricatorCalendarEventEditor $types[] = PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION; $types[] = PhabricatorCalendarEventTransaction::TYPE_CANCEL; $types[] = PhabricatorCalendarEventTransaction::TYPE_INVITE; + $types[] = PhabricatorCalendarEventTransaction::TYPE_ALL_DAY; $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; @@ -49,6 +50,8 @@ final class PhabricatorCalendarEventEditor return $object->getDescription(); case PhabricatorCalendarEventTransaction::TYPE_CANCEL: return $object->getIsCancelled(); + case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: + return $object->getIsAllDay(); case PhabricatorCalendarEventTransaction::TYPE_INVITE: $map = $xaction->getNewValue(); $phids = array_keys($map); @@ -86,6 +89,7 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: case PhabricatorCalendarEventTransaction::TYPE_CANCEL: case PhabricatorCalendarEventTransaction::TYPE_INVITE: + case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: return $xaction->getNewValue(); case PhabricatorCalendarEventTransaction::TYPE_STATUS: return (int)$xaction->getNewValue(); @@ -120,6 +124,9 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_CANCEL: $object->setIsCancelled((int)$xaction->getNewValue()); return; + case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: + $object->setIsAllDay((int)$xaction->getNewValue()); + return; case PhabricatorCalendarEventTransaction::TYPE_INVITE: case PhabricatorTransactions::TYPE_COMMENT: case PhabricatorTransactions::TYPE_VIEW_POLICY: @@ -143,6 +150,7 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_STATUS: case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: case PhabricatorCalendarEventTransaction::TYPE_CANCEL: + case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: return; case PhabricatorCalendarEventTransaction::TYPE_INVITE: $map = $xaction->getNewValue(); diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 03da6851a5..a63c869808 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -17,6 +17,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $status; protected $description; protected $isCancelled; + protected $isAllDay; protected $mailKey; protected $viewPolicy; @@ -84,6 +85,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO 'status' => 'uint32', 'description' => 'text', 'isCancelled' => 'bool', + 'isAllDay' => 'bool', 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php index 7e4e7ff512..ef61e5b1e8 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -9,6 +9,7 @@ final class PhabricatorCalendarEventTransaction const TYPE_STATUS = 'calendar.status'; const TYPE_DESCRIPTION = 'calendar.description'; const TYPE_CANCEL = 'calendar.cancel'; + const TYPE_ALL_DAY = 'calendar.allday'; const TYPE_INVITE = 'calendar.invite'; const MAILTAG_RESCHEDULE = 'calendar-reschedule'; @@ -37,6 +38,7 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_STATUS: case self::TYPE_DESCRIPTION: case self::TYPE_CANCEL: + case self::TYPE_ALL_DAY: $phids[] = $this->getObjectPHID(); break; case self::TYPE_INVITE: @@ -58,6 +60,7 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_STATUS: case self::TYPE_DESCRIPTION: case self::TYPE_CANCEL: + case self::TYPE_ALL_DAY: case self::TYPE_INVITE: return ($old === null); } @@ -71,6 +74,7 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_END_DATE: case self::TYPE_STATUS: case self::TYPE_DESCRIPTION: + case self::TYPE_ALL_DAY: case self::TYPE_CANCEL: return 'fa-pencil'; break; From f3d76a90f0e322c114933668595d93555531a030 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 May 2015 18:57:28 -0700 Subject: [PATCH 12/70] Translate "All Day" events into the viewer's time Summary: Ref T8021. - When "All Day" events are loaded, convert them into the viewer's time. - When "All Day" events are saved, convert them into a +24 hour range. Test Plan: - Created and updated "All Day" events. - Created and updated normal events. - Changed timezones, edited and viewed "All Day" events and normal events. - In all cases, "All Day" events appeared to be 12:00AM - 11:59:59PM to the viewer, on the correct day. - Normal events shifted around properly according to timezones. Reviewers: lpriestley Reviewed By: lpriestley Subscribers: epriestley Maniphest Tasks: T8021 Differential Revision: https://secure.phabricator.com/D12765 --- ...PhabricatorCalendarEventEditController.php | 15 +++ .../editor/PhabricatorCalendarEventEditor.php | 10 ++ .../query/PhabricatorCalendarEventQuery.php | 8 +- .../storage/PhabricatorCalendarEvent.php | 100 +++++++++++++++++- .../people/storage/PhabricatorUser.php | 4 + ...habricatorApplicationTransactionEditor.php | 7 ++ 6 files changed, 142 insertions(+), 2 deletions(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 38d12b099b..967c7deb19 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -74,6 +74,7 @@ final class PhabricatorCalendarEventEditController $name = $event->getName(); $description = $event->getDescription(); $type = $event->getStatus(); + $is_all_day = $event->getIsAllDay(); $current_policies = id(new PhabricatorPolicyQuery()) ->setViewer($user) @@ -95,6 +96,7 @@ final class PhabricatorCalendarEventEditController $subscribers = $request->getArr('subscribers'); $edit_policy = $request->getStr('editPolicy'); $view_policy = $request->getStr('viewPolicy'); + $is_all_day = $request->getStr('isAllDay'); $invitees = $request->getArr('invitees'); $new_invitees = $this->getNewInviteeList($invitees, $event); @@ -111,6 +113,11 @@ final class PhabricatorCalendarEventEditController PhabricatorCalendarEventTransaction::TYPE_NAME) ->setNewValue($name); + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_ALL_DAY) + ->setNewValue($is_all_day); + $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_START_DATE) @@ -184,6 +191,13 @@ final class PhabricatorCalendarEventEditController ->setValue($type) ->setOptions($event->getStatusOptions()); + $all_day_select = id(new AphrontFormCheckboxControl()) + ->addCheckbox( + 'isAllDay', + 1, + pht('All Day Event'), + $is_all_day); + $start_control = id(new AphrontFormDateControl()) ->setUser($user) ->setName('start') @@ -234,6 +248,7 @@ final class PhabricatorCalendarEventEditController ->setUser($user) ->appendChild($name) ->appendChild($status_select) + ->appendChild($all_day_select) ->appendChild($start_control) ->appendChild($end_control) ->appendControl($view_policies) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index f00ba4349f..8c992ce16f 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -183,6 +183,16 @@ final class PhabricatorCalendarEventEditor return parent::applyCustomExternalTransaction($object, $xaction); } + protected function didApplyInternalEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + $object->removeViewerTimezone($this->requireActor()); + + return $xactions; + } + + protected function validateAllTransactions( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 1fd294781f..c66e367793 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -56,7 +56,13 @@ final class PhabricatorCalendarEventQuery $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); - return $table->loadAllFromArray($data); + $events = $table->loadAllFromArray($data); + + foreach ($events as $event) { + $event->applyViewerTimezone($this->getViewer()); + } + + return $events; } protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) { diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index a63c869808..5d4d3cdd5a 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -24,6 +24,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $editPolicy; private $invitees = self::ATTACHABLE; + private $appliedViewer; const STATUS_AWAY = 1; const STATUS_SPORADIC = 2; @@ -37,15 +38,112 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return id(new PhabricatorCalendarEvent()) ->setUserPHID($actor->getPHID()) ->setIsCancelled(0) + ->setIsAllDay(0) ->setViewPolicy($actor->getPHID()) ->setEditPolicy($actor->getPHID()) - ->attachInvitees(array()); + ->attachInvitees(array()) + ->applyViewerTimezone($actor); + } + + public function applyViewerTimezone(PhabricatorUser $viewer) { + if ($this->appliedViewer) { + throw new Exception(pht('Viewer timezone is already applied!')); + } + + $this->appliedViewer = $viewer; + + if (!$this->getIsAllDay()) { + return $this; + } + + $zone = $viewer->getTimeZone(); + + + $this->setDateFrom( + $this->getDateEpochForTimeZone( + $this->getDateFrom(), + new DateTimeZone('GMT+12'), + 'Y-m-d', + null, + $zone)); + + $this->setDateTo( + $this->getDateEpochForTimeZone( + $this->getDateTo(), + new DateTimeZone('GMT-12'), + 'Y-m-d 23:59:59', + '-1 day', + $zone)); + + return $this; + } + + + public function removeViewerTimezone(PhabricatorUser $viewer) { + if (!$this->appliedViewer) { + throw new Exception(pht('Viewer timezone is not applied!')); + } + + if ($viewer->getPHID() != $this->appliedViewer->getPHID()) { + throw new Exception(pht('Removed viewer must match applied viewer!')); + } + + $this->appliedViewer = null; + + if (!$this->getIsAllDay()) { + return $this; + } + + $zone = $viewer->getTimeZone(); + + $this->setDateFrom( + $this->getDateEpochForTimeZone( + $this->getDateFrom(), + $zone, + 'Y-m-d', + null, + new DateTimeZone('GMT+12'))); + + $this->setDateTo( + $this->getDateEpochForTimeZone( + $this->getDateTo(), + $zone, + 'Y-m-d', + '+1 day', + new DateTimeZone('GMT-12'))); + + return $this; + } + + private function getDateEpochForTimeZone( + $epoch, + $src_zone, + $format, + $adjust, + $dst_zone) { + + $src = new DateTime('@'.$epoch); + $src->setTimeZone($src_zone); + + if (strlen($adjust)) { + $adjust = ' '.$adjust; + } + + $dst = new DateTime($src->format($format).$adjust, $dst_zone); + return $dst->format('U'); } public function save() { + if ($this->appliedViewer) { + throw new Exception( + pht( + 'Can not save event with viewer timezone still applied!')); + } + if (!$this->mailKey) { $this->mailKey = Filesystem::readRandomCharacters(20); } + return parent::save(); } diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index ea138042b2..a6c9f6f349 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -682,6 +682,10 @@ EOBODY; } } + public function getTimeZone() { + return new DateTimeZone($this->getTimezoneIdentifier()); + } + public function __toString() { return $this->getUsername(); } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index c28fb135ee..1e75bd0432 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -569,6 +569,11 @@ abstract class PhabricatorApplicationTransactionEditor return $xaction; } + protected function didApplyInternalEffects( + PhabricatorLiskDAO $object, + array $xactions) { + return $xactions; + } protected function applyFinalEffects( PhabricatorLiskDAO $object, @@ -731,6 +736,8 @@ abstract class PhabricatorApplicationTransactionEditor $this->applyInternalEffects($object, $xaction); } + $xactions = $this->didApplyInternalEffects($object, $xactions); + $object->save(); foreach ($xactions as $xaction) { From 528f1a97449e726689c364a554481c4b2a51dc91 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 May 2015 21:10:43 -0700 Subject: [PATCH 13/70] Scope "in-flight" PHIDs more carefully Fixes T8125. In Feed, we query for a bunch of objects and also a bunch of transactions. The transactions require the objects. Normally, whichever executes last will fill out of the Workspace cheaply, so this query strategy is fine overall. The new "in-flight" code would mark everything in flight before the transactions loaded, though, so they'd fail to load even though the query plan is not cyclic. Instead, be more surgical and mark things in flight only immediately before we put them in flight. Test Plan: Feed now shows more stories again; files with cycles still load in finite time. Auditors: btrahan --- .../phid/query/PhabricatorObjectQuery.php | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/applications/phid/query/PhabricatorObjectQuery.php b/src/applications/phid/query/PhabricatorObjectQuery.php index fbec78b29b..cf8f8da4b2 100644 --- a/src/applications/phid/query/PhabricatorObjectQuery.php +++ b/src/applications/phid/query/PhabricatorObjectQuery.php @@ -104,16 +104,6 @@ final class PhabricatorObjectQuery } private function loadObjectsByPHID(array $types, array $phids) { - // Don't try to load PHIDs which are already "in flight"; this prevents us - // from recursing indefinitely if policy checks or edges form a loop. We - // will decline to load the corresponding objects. - $in_flight = $this->getPHIDsInFlight(); - foreach ($phids as $key => $phid) { - if (isset($in_flight[$phid])) { - unset($phids[$key]); - } - } - $results = array(); $workspace = $this->getObjectsFromWorkspace($phids); @@ -129,16 +119,25 @@ final class PhabricatorObjectQuery return $results; } - $this->putPHIDsInFlight($phids); - $groups = array(); foreach ($phids as $phid) { $type = phid_get_type($phid); $groups[$type][] = $phid; } + $in_flight = $this->getPHIDsInFlight(); foreach ($groups as $type => $group) { - if (isset($types[$type])) { + // Don't try to load PHIDs which are already "in flight"; this prevents + // us from recursing indefinitely if policy checks or edges form a loop. + // We will decline to load the corresponding objects. + foreach ($group as $key => $phid) { + if (isset($in_flight[$phid])) { + unset($group[$key]); + } + } + + if ($group && isset($types[$type])) { + $this->putPHIDsInFlight($group); $objects = $types[$type]->loadObjects($this, $group); $results += mpull($objects, null, 'getPHID'); } From 8b05487dd0b80967fd99f3027d3041c3c4aaeed6 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Thu, 7 May 2015 21:16:30 -0700 Subject: [PATCH 14/70] Adding basic transaction messages for Calendar event updates re:all-day Summary: Closes T8021, Adding basic transaction messages for Calendar event updates re:all-day Test Plan: Toggling "all-day" checkbox on event edit and saving should result in feed stories for event and Feed. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8021 Differential Revision: https://secure.phabricator.com/D12766 --- .../PhabricatorCalendarEventTransaction.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php index ef61e5b1e8..d3e7a25efd 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -132,6 +132,16 @@ final class PhabricatorCalendarEventTransaction return pht( "%s updated the event's description.", $this->renderHandleLink($author_phid)); + case self::TYPE_ALL_DAY: + if ($new) { + return pht( + '%s made this an all day event.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s converted this from an all day event.', + $this->renderHandleLink($author_phid)); + } case self::TYPE_CANCEL: if ($new) { return pht( @@ -291,6 +301,18 @@ final class PhabricatorCalendarEventTransaction '%s updated the description of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); + case self::TYPE_ALL_DAY: + if ($new) { + return pht( + '%s made %s an all day event.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } else { + return pht( + '%s converted %s from an all day event.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } case self::TYPE_CANCEL: if ($new) { return pht( From 314dc5b9576bb9153edf8a400a649e4c687b1c6f Mon Sep 17 00:00:00 2001 From: lkassianik Date: Thu, 7 May 2015 21:18:48 -0700 Subject: [PATCH 15/70] Calendar builtin queries should include a Day View Summary: Closes T8108, Calendar sidebar should include a Day View builtin query Test Plan: Open Calendar, navigate to Day View in left sidebar, Day View with event preview should work as expected Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8108 Differential Revision: https://secure.phabricator.com/D12767 --- .../calendar/query/PhabricatorCalendarEventSearchEngine.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index b525420ca3..6e79d44940 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -220,6 +220,7 @@ final class PhabricatorCalendarEventSearchEngine protected function getBuiltinQueryNames() { $names = array( 'month' => pht('Month View'), + 'day' => pht('Day View'), 'upcoming' => pht('Upcoming Events'), 'all' => pht('All Events'), ); @@ -242,6 +243,8 @@ final class PhabricatorCalendarEventSearchEngine switch ($query_key) { case 'month': return $query->setParameter('display', 'month'); + case 'day': + return $query->setParameter('display', 'day'); case 'upcoming': return $query->setParameter('upcoming', true); case 'all': From 7c96ba4ced4825b659286f1ecd55e954408b685c Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 May 2015 07:06:14 -0700 Subject: [PATCH 16/70] Fix another "reply" issue with inlines showing up on the wrong side We can just trust the "reply" value on the server, since inlines never port from old to new or vice versa. Auditors: btrahan --- src/infrastructure/diff/PhabricatorInlineCommentController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php index bc4358ba55..4608ebeffa 100644 --- a/src/infrastructure/diff/PhabricatorInlineCommentController.php +++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php @@ -266,6 +266,8 @@ abstract class PhabricatorInlineCommentController // comment appears on. This is expected in the case of ghost comments. // We currently put the new comment on the visible changeset, not the // original comment's changeset. + + $this->isNewFile = $reply_comment->getIsNewFile(); } } From 559ba4e94435141cdcf6177e0eef748950bb559e Mon Sep 17 00:00:00 2001 From: lkassianik Date: Fri, 8 May 2015 08:07:52 -0700 Subject: [PATCH 17/70] Change latest and earliest saved timezones on all day events to php official timezones instead of guessing GMT offsets Summary: Ref T8021, Change latest and earliest saved timezones on all day events to php official timezones instead of guessing GMT offsets Test Plan: On different versions of php, create and save all day event in various timezones without errors. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8021 Differential Revision: https://secure.phabricator.com/D12772 --- .../calendar/storage/PhabricatorCalendarEvent.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 5d4d3cdd5a..6f13d99112 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -62,7 +62,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $this->setDateFrom( $this->getDateEpochForTimeZone( $this->getDateFrom(), - new DateTimeZone('GMT+12'), + new DateTimeZone('Pacific/Kiritimati'), 'Y-m-d', null, $zone)); @@ -70,7 +70,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $this->setDateTo( $this->getDateEpochForTimeZone( $this->getDateTo(), - new DateTimeZone('GMT-12'), + new DateTimeZone('Pacific/Midway'), 'Y-m-d 23:59:59', '-1 day', $zone)); @@ -102,7 +102,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $zone, 'Y-m-d', null, - new DateTimeZone('GMT+12'))); + new DateTimeZone('Pacific/Kiritimati'))); $this->setDateTo( $this->getDateEpochForTimeZone( @@ -110,7 +110,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $zone, 'Y-m-d', '+1 day', - new DateTimeZone('GMT-12'))); + new DateTimeZone('Pacific/Midway'))); return $this; } From da52a27b42c2ecf53ee0fe67615ff4d194d57cf5 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Fri, 8 May 2015 08:08:26 -0700 Subject: [PATCH 18/70] Calendar event detail view should show no time for all day events, and should show only one time field for one day events. Summary: Ref T8021, Calendar event detail view should show no time for all day events, and should show only one time field for one day events Test Plan: Open all-day event, event should show "Time" field with not start/end dates. Two day events should show start and end days, not times. Normal events should show old way of displaying start and end times. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8021 Differential Revision: https://secure.phabricator.com/D12768 --- ...PhabricatorCalendarEventViewController.php | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 6f6fa66e6e..742ec95e3f 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -179,13 +179,31 @@ final class PhabricatorCalendarEventViewController ->setUser($viewer) ->setObject($event); - $properties->addProperty( - pht('Starts'), - phabricator_datetime($event->getDateFrom(), $viewer)); + if ($event->getIsAllDay()) { + $date_start = phabricator_date($event->getDateFrom(), $viewer); + $date_end = phabricator_date($event->getDateTo(), $viewer); - $properties->addProperty( - pht('Ends'), - phabricator_datetime($event->getDateTo(), $viewer)); + if ($date_start == $date_end) { + $properties->addProperty( + pht('Time'), + phabricator_date($event->getDateFrom(), $viewer)); + } else { + $properties->addProperty( + pht('Starts'), + phabricator_date($event->getDateFrom(), $viewer)); + $properties->addProperty( + pht('Ends'), + phabricator_date($event->getDateTo(), $viewer)); + } + } else { + $properties->addProperty( + pht('Starts'), + phabricator_datetime($event->getDateFrom(), $viewer)); + + $properties->addProperty( + pht('Ends'), + phabricator_datetime($event->getDateTo(), $viewer)); + } $properties->addProperty( pht('Host'), From d249456a0414667e2958406d6eea31adc6e8b267 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Fri, 8 May 2015 10:01:13 -0700 Subject: [PATCH 19/70] All day events should disable time editing in edit view Summary: Closes T8021, All day events should disable time editing in edit view Test Plan: Edit all day event, time text fields should be disabled. Unchecking all-day should show time fields. Reviewers: chad, #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8021 Differential Revision: https://secure.phabricator.com/D12774 --- resources/celerity/map.php | 2 ++ ...PhabricatorCalendarEventEditController.php | 25 +++++++++++++++---- .../control/AphrontFormCheckboxControl.php | 13 ++++++++-- .../form/control/AphrontFormDateControl.php | 10 ++++++++ webroot/rsrc/css/phui/phui-form-view.css | 4 +++ .../js/application/calendar/event-all-day.js | 16 ++++++++++++ 6 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 webroot/rsrc/js/application/calendar/event-all-day.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 5c303ae781..90122925b4 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -345,6 +345,7 @@ return array( 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', + 'rsrc/js/application/calendar/event-all-day.js' => '712540b4', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '9e507b59', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', @@ -583,6 +584,7 @@ return array( 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-durable-column' => '657c2b50', 'javelin-behavior-error-log' => '6882e80a', + 'javelin-behavior-event-all-day' => '712540b4', 'javelin-behavior-fancy-datepicker' => '5c0f680f', 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 967c7deb19..e31b9bdf0e 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -179,6 +179,16 @@ final class PhabricatorCalendarEventEditController } } + $all_day_id = celerity_generate_unique_node_id(); + $start_date_id = celerity_generate_unique_node_id(); + $end_date_id = celerity_generate_unique_node_id(); + + Javelin::initBehavior('event-all-day', array( + 'allDayID' => $all_day_id, + 'startDateID' => $start_date_id, + 'endDateID' => $end_date_id, + )); + $name = id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') @@ -191,26 +201,31 @@ final class PhabricatorCalendarEventEditController ->setValue($type) ->setOptions($event->getStatusOptions()); - $all_day_select = id(new AphrontFormCheckboxControl()) + $all_day_checkbox = id(new AphrontFormCheckboxControl()) ->addCheckbox( 'isAllDay', 1, pht('All Day Event'), - $is_all_day); + $is_all_day, + $all_day_id); $start_control = id(new AphrontFormDateControl()) ->setUser($user) ->setName('start') ->setLabel(pht('Start')) ->setError($error_start_date) - ->setValue($start_value); + ->setValue($start_value) + ->setID($start_date_id) + ->setIsTimeDisabled($is_all_day); $end_control = id(new AphrontFormDateControl()) ->setUser($user) ->setName('end') ->setLabel(pht('End')) ->setError($error_end_date) - ->setValue($end_value); + ->setValue($end_value) + ->setID($end_date_id) + ->setIsTimeDisabled($is_all_day); $description = id(new AphrontFormTextAreaControl()) ->setLabel(pht('Description')) @@ -248,7 +263,7 @@ final class PhabricatorCalendarEventEditController ->setUser($user) ->appendChild($name) ->appendChild($status_select) - ->appendChild($all_day_select) + ->appendChild($all_day_checkbox) ->appendChild($start_control) ->appendChild($end_control) ->appendControl($view_policies) diff --git a/src/view/form/control/AphrontFormCheckboxControl.php b/src/view/form/control/AphrontFormCheckboxControl.php index 673c1c6d82..0d89e0a6ad 100644 --- a/src/view/form/control/AphrontFormCheckboxControl.php +++ b/src/view/form/control/AphrontFormCheckboxControl.php @@ -4,12 +4,18 @@ final class AphrontFormCheckboxControl extends AphrontFormControl { private $boxes = array(); - public function addCheckbox($name, $value, $label, $checked = false) { + public function addCheckbox( + $name, + $value, + $label, + $checked = false, + $id = null) { $this->boxes[] = array( 'name' => $name, 'value' => $value, 'label' => $label, 'checked' => $checked, + 'id' => $id, ); return $this; } @@ -21,7 +27,10 @@ final class AphrontFormCheckboxControl extends AphrontFormControl { protected function renderInput() { $rows = array(); foreach ($this->boxes as $box) { - $id = celerity_generate_unique_node_id(); + $id = idx($box, 'id'); + if ($id === null) { + $id = celerity_generate_unique_node_id(); + } $checkbox = phutil_tag( 'input', array( diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php index ed79392fbb..84de3b3215 100644 --- a/src/view/form/control/AphrontFormDateControl.php +++ b/src/view/form/control/AphrontFormDateControl.php @@ -11,6 +11,7 @@ final class AphrontFormDateControl extends AphrontFormControl { private $valueTime; private $allowNull; private $continueOnInvalidDate = false; + private $isTimeDisabled; private $isDisabled; public function setAllowNull($allow_null) { @@ -18,6 +19,11 @@ final class AphrontFormDateControl extends AphrontFormControl { return $this; } + public function setIsTimeDisabled($is_disabled) { + $this->isTimeDisabled = $is_disabled; + return $this; + } + const TIME_START_OF_DAY = 'start-of-day'; const TIME_END_OF_DAY = 'end-of-day'; const TIME_START_OF_BUSINESS = 'start-of-business'; @@ -282,6 +288,9 @@ final class AphrontFormDateControl extends AphrontFormControl { if ($disabled) { $classes[] = 'datepicker-disabled'; } + if ($this->isTimeDisabled) { + $classes[] = 'no-time'; + } return javelin_tag( 'div', @@ -291,6 +300,7 @@ final class AphrontFormDateControl extends AphrontFormControl { 'meta' => array( 'disabled' => (bool)$disabled, ), + 'id' => $this->getID(), ), array( $checkbox, diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css index 02c7e55bb8..5a3180d8b0 100644 --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -457,6 +457,10 @@ properly, and submit values. */ opacity: 0.5; } +.aphront-form-date-container.no-time .aphront-form-date-time-input{ + display: none; +} + .login-to-comment { margin: 12px; diff --git a/webroot/rsrc/js/application/calendar/event-all-day.js b/webroot/rsrc/js/application/calendar/event-all-day.js new file mode 100644 index 0000000000..a8bd7da7a8 --- /dev/null +++ b/webroot/rsrc/js/application/calendar/event-all-day.js @@ -0,0 +1,16 @@ +/** + * @provides javelin-behavior-event-all-day + */ + + +JX.behavior('event-all-day', function(config) { + var checkbox = JX.$(config.allDayID); + JX.DOM.listen(checkbox, 'change', null, function() { + var start = JX.$(config.startDateID); + var end = JX.$(config.endDateID); + + JX.DOM.alterClass(start, 'no-time', checkbox.checked); + JX.DOM.alterClass(end, 'no-time', checkbox.checked); + }); + +}); From 3a34d948b9d3db1db89bb224efcf408668bffa13 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 May 2015 12:19:52 -0700 Subject: [PATCH 20/70] Show how to call Conduit API methods from clients Summary: Fixes T3628. Ref T5955. Test Plan: On the method page, you see a generic example: {F396471} After making a call, you see a specific example with your parameters: {F396472} {F396474} {F396475} Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T3628, T5955 Differential Revision: https://secure.phabricator.com/D12770 --- resources/celerity/map.php | 4 +- src/applications/conduit/call/ConduitCall.php | 4 + .../PhabricatorConduitAPIController.php | 17 +- .../PhabricatorConduitConsoleController.php | 106 ++++---- .../PhabricatorConduitController.php | 246 ++++++++++++++++++ .../css/application/conduit/conduit-api.css | 16 ++ 6 files changed, 343 insertions(+), 50 deletions(-) create mode 100644 webroot/rsrc/css/application/conduit/conduit-api.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 90122925b4..c732e061f0 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -39,6 +39,7 @@ return array( 'rsrc/css/application/base/phabricator-application-launch-view.css' => '16ca323f', 'rsrc/css/application/base/standard-page-view.css' => 'd3e1abe9', 'rsrc/css/application/chatlog/chatlog.css' => '852140ff', + 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '7fedf08b', 'rsrc/css/application/config/config-template.css' => '8e6c6fcd', 'rsrc/css/application/config/config-welcome.css' => '6abd79be', @@ -345,7 +346,6 @@ return array( 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', - 'rsrc/js/application/calendar/event-all-day.js' => '712540b4', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '9e507b59', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', @@ -508,6 +508,7 @@ return array( 'aphront-typeahead-control-css' => '0e403212', 'auth-css' => '1e655982', 'changeset-view-manager' => '58562350', + 'conduit-api-css' => '7bc725c4', 'config-options-css' => '7fedf08b', 'config-welcome-css' => '6abd79be', 'conpherence-durable-column-view' => '2e68a92f', @@ -584,7 +585,6 @@ return array( 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-durable-column' => '657c2b50', 'javelin-behavior-error-log' => '6882e80a', - 'javelin-behavior-event-all-day' => '712540b4', 'javelin-behavior-fancy-datepicker' => '5c0f680f', 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', diff --git a/src/applications/conduit/call/ConduitCall.php b/src/applications/conduit/call/ConduitCall.php index aa55d1b895..7d767cd8c1 100644 --- a/src/applications/conduit/call/ConduitCall.php +++ b/src/applications/conduit/call/ConduitCall.php @@ -150,5 +150,9 @@ final class ConduitCall { return $method; } + public function getMethodImplementation() { + return $this->handler; + } + } diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php index e8e69f18e3..cdc14da27d 100644 --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -21,6 +21,7 @@ final class PhabricatorConduitAPIController $method = $this->method; $api_request = null; + $method_implementation = null; $log = new PhabricatorConduitMethodCallLog(); $log->setMethod($method); @@ -36,6 +37,7 @@ final class PhabricatorConduitAPIController list($metadata, $params) = $this->decodeConduitParams($request, $method); $call = new ConduitCall($method, $params); + $method_implementation = $call->getMethodImplementation(); $result = null; @@ -151,7 +153,8 @@ final class PhabricatorConduitAPIController return $this->buildHumanReadableResponse( $method, $api_request, - $response->toDictionary()); + $response->toDictionary(), + $method_implementation); case 'json': default: return id(new AphrontJSONResponse()) @@ -525,7 +528,8 @@ final class PhabricatorConduitAPIController private function buildHumanReadableResponse( $method, ConduitAPIRequest $request = null, - $result = null) { + $result = null, + ConduitAPIMethod $method_implementation = null) { $param_rows = array(); $param_rows[] = array('Method', $this->renderAPIValue($method)); @@ -574,11 +578,20 @@ final class PhabricatorConduitAPIController ->addTextCrumb($method, $method_uri) ->addTextCrumb(pht('Call')); + $example_panel = null; + if ($request && $method_implementation) { + $params = $request->getAllParameters(); + $example_panel = $this->renderExampleBox( + $method_implementation, + $params); + } + return $this->buildApplicationPage( array( $crumbs, $param_panel, $result_panel, + $example_panel, ), array( 'title' => pht('Method Call Result'), diff --git a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php index 62cdb7ba77..5c0ccfe6fb 100644 --- a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php +++ b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php @@ -3,31 +3,23 @@ final class PhabricatorConduitConsoleController extends PhabricatorConduitController { - private $method; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->method = $data['method']; - } - - public function processRequest() { - - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $method_name = $request->getURIData('method'); $method = id(new PhabricatorConduitMethodQuery()) ->setViewer($viewer) - ->withMethods(array($this->method)) + ->withMethods(array($method_name)) ->executeOne(); - if (!$method) { return new Aphront404Response(); } - $can_call_method = false; + $call_uri = '/api/'.$method->getAPIMethodName(); $status = $method->getMethodStatus(); $reason = $method->getMethodStatusDescription(); @@ -48,37 +40,13 @@ final class PhabricatorConduitConsoleController break; } - $error_types = $method->getErrorTypes(); - $error_types['ERR-CONDUIT-CORE'] = pht('See error message for details.'); - $error_description = array(); - foreach ($error_types as $error => $meaning) { - $error_description[] = hsprintf( - '
  • %s: %s
  • ', - $error, - $meaning); - } - $error_description = phutil_tag('ul', array(), $error_description); - - $form = new AphrontFormView(); - $form + $form = id(new AphrontFormView()) + ->setAction($call_uri) ->setUser($request->getUser()) - ->setAction('/api/'.$this->method) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel('Description') - ->setValue($method->getMethodDescription())) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel('Returns') - ->setValue($method->getReturnType())) - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel('Errors') - ->setValue($error_description)) - ->appendChild(hsprintf( - '

    Enter parameters using '. - 'JSON. For instance, to enter a list, type: '. - '["apple", "banana", "cherry"]')); + ->appendRemarkupInstructions( + pht( + 'Enter parameters using **JSON**. For instance, to enter a '. + 'list, type: `["apple", "banana", "cherry"]`')); $params = $method->getParamTypes(); foreach ($params as $param => $desc) { @@ -117,12 +85,22 @@ final class PhabricatorConduitConsoleController ->setHeader($method->getAPIMethodName()); $form_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setFormErrors($errors) + ->setHeaderText(pht('Call Method')) ->appendChild($form); $content = array(); + $properties = $this->buildMethodProperties($method); + + $info_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('API Method: %s', $method->getAPIMethodName())) + ->setFormErrors($errors) + ->appendChild($properties); + + $content[] = $info_box; + $content[] = $form_box; + $content[] = $this->renderExampleBox($method, null); + $query = $method->newQueryObject(); if ($query) { $orders = $query->getBuiltinOrders(); @@ -185,7 +163,6 @@ final class PhabricatorConduitConsoleController return $this->buildApplicationPage( array( $crumbs, - $form_box, $content, ), array( @@ -193,4 +170,41 @@ final class PhabricatorConduitConsoleController )); } + private function buildMethodProperties(ConduitAPIMethod $method) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()); + + $view->addProperty( + pht('Returns'), + $method->getReturnType()); + + $error_types = $method->getErrorTypes(); + $error_types['ERR-CONDUIT-CORE'] = pht('See error message for details.'); + $error_description = array(); + foreach ($error_types as $error => $meaning) { + $error_description[] = hsprintf( + '

  • %s: %s
  • ', + $error, + $meaning); + } + $error_description = phutil_tag('ul', array(), $error_description); + + $view->addProperty( + pht('Errors'), + $error_description); + + + $description = $method->getMethodDescription(); + $description = PhabricatorMarkupEngine::renderOneObject( + id(new PhabricatorMarkupOneOff())->setContent($description), + 'default', + $viewer); + $view->addSectionHeader(pht('Description')); + $view->addTextContent($description); + + return $view; + } + + } diff --git a/src/applications/conduit/controller/PhabricatorConduitController.php b/src/applications/conduit/controller/PhabricatorConduitController.php index 4643511952..4fa11dbfad 100644 --- a/src/applications/conduit/controller/PhabricatorConduitController.php +++ b/src/applications/conduit/controller/PhabricatorConduitController.php @@ -24,4 +24,250 @@ abstract class PhabricatorConduitController extends PhabricatorController { return $this->buildSideNavView()->getMenu(); } + protected function renderExampleBox(ConduitAPIMethod $method, $params) { + $arc_example = id(new PHUIPropertyListView()) + ->addRawContent($this->renderExample($method, 'arc', $params)); + + $curl_example = id(new PHUIPropertyListView()) + ->addRawContent($this->renderExample($method, 'curl', $params)); + + $php_example = id(new PHUIPropertyListView()) + ->addRawContent($this->renderExample($method, 'php', $params)); + + $panel_link = phutil_tag( + 'a', + array( + 'href' => '/settings/panel/apitokens/', + ), + pht('Conduit API Tokens')); + + $panel_link = phutil_tag('strong', array(), $panel_link); + + $messages = array( + pht( + 'Use the %s panel in Settings to generate or manage API tokens.', + $panel_link), + ); + + $info_view = id(new PHUIInfoView()) + ->setErrors($messages) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Examples')) + ->setInfoView($info_view) + ->addPropertyList($arc_example, pht('arc call-conduit')) + ->addPropertyList($curl_example, pht('cURL')) + ->addPropertyList($php_example, pht('PHP')); + } + + private function renderExample( + ConduitAPIMethod $method, + $kind, + $params) { + + switch ($kind) { + case 'arc': + $example = $this->buildArcanistExample($method, $params); + break; + case 'php': + $example = $this->buildPHPExample($method, $params); + break; + case 'curl': + $example = $this->buildCURLExample($method, $params); + break; + default: + throw new Exception(pht('Conduit client "%s" is not known.', $kind)); + } + + return $example; + } + + private function buildArcanistExample( + ConduitAPIMethod $method, + $params) { + + $parts = array(); + + $parts[] = '$ echo '; + if ($params === null) { + $parts[] = phutil_tag('strong', array(), ''); + } else { + $params = $this->simplifyParams($params); + $params = id(new PhutilJSON())->encodeFormatted($params); + $params = trim($params); + $params = csprintf('%s', $params); + $parts[] = phutil_tag('strong', array('class' => 'real'), $params); + } + + $parts[] = ' | '; + $parts[] = 'arc call-conduit '; + + $parts[] = '--conduit-uri '; + $parts[] = phutil_tag( + 'strong', + array('class' => 'real'), + PhabricatorEnv::getURI('/')); + $parts[] = ' '; + + $parts[] = '--conduit-token '; + $parts[] = phutil_tag('strong', array(), ''); + $parts[] = ' '; + + $parts[] = $method->getAPIMethodName(); + + return $this->renderExampleCode($parts); + } + + private function buildPHPExample( + ConduitAPIMethod $method, + $params) { + + $parts = array(); + + $libphutil_path = 'path/to/libphutil/src/__phutil_library_init__.php'; + + $parts[] = '')); + $parts[] = "\";\n"; + + $parts[] = '$api_parameters = '; + if ($params === null) { + $parts[] = 'array('; + $parts[] = phutil_tag('strong', array(), pht('')); + $parts[] = ');'; + } else { + $params = $this->simplifyParams($params); + $params = phutil_var_export($params, true); + $parts[] = phutil_tag('strong', array('class' => 'real'), $params); + $parts[] = ';'; + } + $parts[] = "\n\n"; + + $parts[] = '$client = new ConduitClient('; + $parts[] = phutil_tag( + 'strong', + array('class' => 'real'), + phutil_var_export(PhabricatorEnv::getURI('/'), true)); + $parts[] = ");\n"; + + $parts[] = '$client->setConduitToken($api_token);'; + $parts[] = "\n\n"; + + $parts[] = '$result = $client->callMethodSynchronous('; + $parts[] = phutil_tag( + 'strong', + array('class' => 'real'), + phutil_var_export($method->getAPIMethodName(), true)); + $parts[] = ', '; + $parts[] = '$api_parameters'; + $parts[] = ");\n"; + + $parts[] = 'print_r($result);'; + + return $this->renderExampleCode($parts); + } + + private function buildCURLExample( + ConduitAPIMethod $method, + $params) { + + $call_uri = '/api/'.$method->getAPIMethodName(); + + $parts = array(); + + $linebreak = array('\\', phutil_tag('br'), ' '); + + $parts[] = '$ curl '; + $parts[] = phutil_tag( + 'strong', + array('class' => 'real'), + csprintf('%R', PhabricatorEnv::getURI($call_uri))); + $parts[] = ' '; + $parts[] = $linebreak; + + $parts[] = '-d api.token='; + $parts[] = phutil_tag('strong', array(), 'api-token'); + $parts[] = ' '; + $parts[] = $linebreak; + + if ($params === null) { + $parts[] = '-d '; + $parts[] = phutil_tag('strong', array(), 'param'); + $parts[] = '='; + $parts[] = phutil_tag('strong', array(), 'value'); + $parts[] = ' '; + $parts[] = $linebreak; + $parts[] = phutil_tag('strong', array(), '...'); + } else { + $lines = array(); + $params = $this->simplifyParams($params); + + foreach ($params as $key => $value) { + $pieces = $this->getQueryStringParts(null, $key, $value); + foreach ($pieces as $piece) { + $lines[] = array( + '-d ', + phutil_tag('strong', array('class' => 'real'), $piece), + ); + } + } + + $parts[] = phutil_implode_html(array(' ', $linebreak), $lines); + } + + return $this->renderExampleCode($parts); + } + + private function renderExampleCode($example) { + require_celerity_resource('conduit-api-css'); + + return phutil_tag( + 'div', + array( + 'class' => 'PhabricatorMonospaced conduit-api-example-code', + ), + $example); + } + + private function simplifyParams(array $params) { + foreach ($params as $key => $value) { + if ($value === null) { + unset($params[$key]); + } + } + return $params; + } + + private function getQueryStringParts($prefix, $key, $value) { + if ($prefix === null) { + $head = phutil_escape_uri($key); + } else { + $head = $prefix.'['.phutil_escape_uri($key).']'; + } + + if (!is_array($value)) { + return array( + $head.'='.phutil_escape_uri($value), + ); + } + + $results = array(); + foreach ($value as $subkey => $subvalue) { + $subparts = $this->getQueryStringParts($head, $subkey, $subvalue); + foreach ($subparts as $subpart) { + $results[] = $subpart; + } + } + + return $results; + } + } diff --git a/webroot/rsrc/css/application/conduit/conduit-api.css b/webroot/rsrc/css/application/conduit/conduit-api.css new file mode 100644 index 0000000000..fbe577e908 --- /dev/null +++ b/webroot/rsrc/css/application/conduit/conduit-api.css @@ -0,0 +1,16 @@ +/** + * @provides conduit-api-css + */ +.conduit-api-example-code { + margin: 16px; + white-space: pre; + color: {$darkgreytext}; +} + +.conduit-api-example-code strong { + color: {$red}; +} + +.conduit-api-example-code strong.real { + color: {$blue}; +} From 7beae445e9eefca724f9f5278ff33151a0747945 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 May 2015 12:20:16 -0700 Subject: [PATCH 21/70] Clear project notifications when viewing workboard or project detail Summary: Fixes T8112. Test Plan: - Sent notifications. - Notied them un-cleared by clicking them (profile or workboard). - Made changes. - Verified profile and workboard both clear them. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T8112 Differential Revision: https://secure.phabricator.com/D12771 --- .../project/controller/PhabricatorProjectBoardViewController.php | 1 + .../project/controller/PhabricatorProjectProfileController.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 031427d058..b9c366fecc 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -383,6 +383,7 @@ final class PhabricatorProjectBoardViewController array( 'title' => pht('%s Board', $project->getName()), 'showFooter' => false, + 'pageObjects' => array($project->getPHID()), )); } diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 9679ddab5c..cb8fe9e87f 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -67,6 +67,7 @@ final class PhabricatorProjectProfileController $nav, array( 'title' => $project->getName(), + 'pageObjects' => array($project->getPHID()), )); } From dd88a0e9e9ea567c1bc395cb37c69b65fdb4a356 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 May 2015 12:20:44 -0700 Subject: [PATCH 22/70] Clean up miscellaneous bot issues Summary: Fixes T6073. Fixes T7775. Ref T4377. This fixes a few easy things without any compatibility-breaking changes. - Stop matching things like `:D123` as a revision (particularly: IPv6 addresses). - Use "diffusion.querycommits". - Support "conduit.token". Test Plan: Used a token: ``` [07:52am] epriestley: -D22 :D22 #D22 /D22 [07:52am] epriestley: D22 [07:52am] phabot-local: D22: asdbb - http://local.phacility.com/D22 [07:54am] epriestley: rHGTESTX6518697472d6395f5d1cec557148957734aa76c2 [07:54am] phabot-local: http://local.phacility.com/rHGTESTX6518697472d6395f5d1cec557148957734aa76c2 ``` Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7775, T6073, T4377 Differential Revision: https://secure.phabricator.com/D12773 --- .../daemon/bot/PhabricatorBot.php | 30 +++++++++++-------- .../PhabricatorBotObjectNameHandler.php | 17 +++++------ 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/infrastructure/daemon/bot/PhabricatorBot.php b/src/infrastructure/daemon/bot/PhabricatorBot.php index 12ab9e663a..18a0c74648 100644 --- a/src/infrastructure/daemon/bot/PhabricatorBot.php +++ b/src/infrastructure/daemon/bot/PhabricatorBot.php @@ -53,8 +53,7 @@ final class PhabricatorBot extends PhabricatorDaemon { $conduit_uri = idx($config, 'conduit.uri'); if ($conduit_uri) { - $conduit_user = idx($config, 'conduit.user'); - $conduit_cert = idx($config, 'conduit.cert'); + $conduit_token = idx($config, 'conduit.token'); // Normalize the path component of the URI so users can enter the // domain without the "/api/" part. @@ -64,16 +63,23 @@ final class PhabricatorBot extends PhabricatorDaemon { $conduit_uri = (string)$conduit_uri->setPath('/api/'); $conduit = new ConduitClient($conduit_uri); - $response = $conduit->callMethodSynchronous( - 'conduit.connect', - array( - 'client' => 'PhabricatorBot', - 'clientVersion' => '1.0', - 'clientDescription' => php_uname('n').':'.$nick, - 'host' => $conduit_host, - 'user' => $conduit_user, - 'certificate' => $conduit_cert, - )); + if ($conduit_token) { + $conduit->setConduitToken($conduit_token); + } else { + $conduit_user = idx($config, 'conduit.user'); + $conduit_cert = idx($config, 'conduit.cert'); + + $response = $conduit->callMethodSynchronous( + 'conduit.connect', + array( + 'client' => 'PhabricatorBot', + 'clientVersion' => '1.0', + 'clientDescription' => php_uname('n').':'.$nick, + 'host' => $conduit_host, + 'user' => $conduit_user, + 'certificate' => $conduit_cert, + )); + } $this->conduit = $conduit; } diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php index cffa6faf35..f226d0ad76 100644 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php +++ b/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php @@ -26,7 +26,7 @@ final class PhabricatorBotObjectNameHandler extends PhabricatorBotHandler { $pattern = '@'. - '(?getConduit()->callMethodSynchronous( - 'diffusion.getcommits', + 'diffusion.querycommits', array( - 'commits' => $commit_names, + 'names' => $commit_names, )); - foreach ($commits as $commit) { - if (isset($commit['error'])) { - continue; - } - $output[$commit['commitPHID']] = $commit['uri']; + foreach ($commits['data'] as $commit) { + $output[$commit['phid']] = $commit['uri']; } } From 22ac7d7c7607b2e52d22b9d530df16300217c6b9 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Fri, 8 May 2015 13:27:48 -0700 Subject: [PATCH 23/70] Calendar day view and corresponding sidebar should correctly display all all-day events returned from query Summary: Closes T8085, Calendar day view and corresponding sidebar should correctly display all all-day events returned from query Test Plan: Open day view with all-day and multi-day events, all events should correctly be drawn in day view in correct order, and sidebar preview should correctly mark future day boxes with all day events. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8085 Differential Revision: https://secure.phabricator.com/D12776 --- .../PhabricatorCalendarEventSearchEngine.php | 5 + .../view/AphrontCalendarEventView.php | 15 +-- .../phui/calendar/PHUICalendarDayView.php | 93 +++++++++++++------ .../phui/calendar/PHUICalendarListView.php | 2 +- 4 files changed, 82 insertions(+), 33 deletions(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 6e79d44940..d84f81cac4 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -392,9 +392,14 @@ final class PhabricatorCalendarEventSearchEngine $phids = mpull($statuses, 'getUserPHID'); foreach ($statuses as $status) { + if ($status->getIsCancelled()) { + continue; + } + $event = new AphrontCalendarEventView(); $event->setEventID($status->getID()); $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); + $event->setIsAllDay($status->getIsAllDay()); $event->setName($status->getName()); $event->setURI('/'.$status->getMonogram()); diff --git a/src/applications/calendar/view/AphrontCalendarEventView.php b/src/applications/calendar/view/AphrontCalendarEventView.php index aebfaa9a50..11dbff3847 100644 --- a/src/applications/calendar/view/AphrontCalendarEventView.php +++ b/src/applications/calendar/view/AphrontCalendarEventView.php @@ -10,6 +10,7 @@ final class AphrontCalendarEventView extends AphrontView { private $eventID; private $color; private $uri; + private $isAllDay; public function setURI($uri) { $this->uri = $uri; @@ -81,14 +82,16 @@ final class AphrontCalendarEventView extends AphrontView { } } - public function getAllDay() { - $time = (60 * 60 * 22); - if (($this->getEpochEnd() - $this->getEpochStart()) >= $time) { - return true; - } - return false; + public function setIsAllDay($is_all_day) { + $this->isAllDay = $is_all_day; + return $this; } + public function getIsAllDay() { + return $this->isAllDay; + } + + public function getMultiDay() { $nextday = strtotime('12:00 AM Tomorrow', $this->getEpochStart()); if ($this->getEpochEnd() > $nextday) { diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 6d42fdcec3..c3f0d6b61d 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -7,6 +7,9 @@ final class PHUICalendarDayView extends AphrontView { private $year; private $browseURI; private $events = array(); + private $todayEvents = array(); + + private $allDayEvents = array(); public function addEvent(AphrontCalendarEventView $event) { $this->events[] = $event; @@ -34,43 +37,62 @@ final class PHUICalendarDayView extends AphrontView { $hourly_events = array(); $rows = array(); + $all_day_events = $this->getAllDayEvents(); + // sort events into buckets by their start time // pretend no events overlap foreach ($hours as $hour) { - $events = array(); + $current_hour_events = array(); $hour_start = $hour->format('U'); $hour_end = id(clone $hour)->modify('+1 hour')->format('U'); - foreach ($this->events as $event) { - if ($event->getEpochStart() >= $hour_start - && $event->getEpochStart() < $hour_end) { - $events[] = $event; + + if ($hour == $this->getDateTime()) { + foreach ($all_day_events as $all_day_event) { + $all_day_start = $all_day_event->getEpochStart(); + $all_day_end = $all_day_event->getEpochEnd(); + $day_end = id(clone $hour)->modify('+1 day')->format('U') - 1; + + if ($all_day_start < $day_end && $all_day_end > $hour_start) { + + $current_hour_events[] = $all_day_event; + $this->todayEvents[] = $all_day_event; + } } } - $count_events = count($events); - $n = 0; - foreach ($events as $event) { + foreach ($this->events as $event) { + if ($event->getIsAllDay()) { + continue; + } + if ($event->getEpochStart() >= $hour_start + && $event->getEpochStart() < $hour_end) { + $current_hour_events[] = $event; + $this->todayEvents[] = $event; + } + } + foreach ($current_hour_events as $event) { $event_start = $event->getEpochStart(); $event_end = $event->getEpochEnd(); - $top = ((($event_start - $hour_start) / ($hour_end - $hour_start)) - * 100).'%'; - $height = ((($event_end - $event_start) / ($hour_end - $hour_start)) - * 100).'%'; + $top = (($event_start - $hour_start) / ($hour_end - $hour_start)) + * 100; + $top = max(0, $top); + + $height = (($event_end - $event_start) / ($hour_end - $hour_start)) + * 100; + $height = min(2400, $height); $hourly_events[$event->getEventID()] = array( 'hour' => $hour, 'event' => $event, 'offset' => '0', 'width' => '100%', - 'top' => $top, - 'height' => $height, + 'top' => $top.'%', + 'height' => $height.'%', ); - - $n++; } } - $clusters = $this->findClusters(); + $clusters = $this->findTodayClusters(); foreach ($clusters as $cluster) { $hourly_events = $this->updateEventsFromCluster( $cluster, @@ -138,26 +160,46 @@ final class PHUICalendarDayView extends AphrontView { $layout); } + private function getAllDayEvents() { + $all_day_events = array(); + + foreach ($this->events as $event) { + if ($event->getIsAllDay()) { + $all_day_events[] = $event; + } + } + + $all_day_events = array_values(msort($all_day_events, 'getEpochStart')); + + return $all_day_events; + } + private function renderSidebar() { $this->events = msort($this->events, 'getEpochStart'); $week_of_boxes = $this->getWeekOfBoxes(); $filled_boxes = array(); - foreach ($week_of_boxes as $weekly_box) { - $start = $weekly_box['start']; - $end = id(clone $start)->modify('+1 day'); + foreach ($week_of_boxes as $day_box) { + $box_start = $day_box['start']; + $box_end = id(clone $box_start)->modify('+1 day'); + + $box_start = $box_start->format('U'); + $box_end = $box_end->format('U'); $box_events = array(); foreach ($this->events as $event) { - if ($event->getEpochStart() >= $start->format('U') && - $event->getEpochStart() < $end->format('U')) { + $event_start = $event->getEpochStart(); + $event_end = $event->getEpochEnd(); + + if ($event_start < $box_end && $event_end > $box_start) { $box_events[] = $event; } } + $filled_boxes[] = $this->renderSidebarBox( $box_events, - $weekly_box['title']); + $day_box['title']); } return $filled_boxes; @@ -267,7 +309,6 @@ final class PHUICalendarDayView extends AphrontView { private function updateEventsFromCluster($cluster, $hourly_events) { $cluster_size = count($cluster); - $n = 0; foreach ($cluster as $cluster_member) { $event_id = $cluster_member->getEventID(); @@ -375,8 +416,8 @@ final class PHUICalendarDayView extends AphrontView { return $date; } - private function findClusters() { - $events = msort($this->events, 'getEpochStart'); + private function findTodayClusters() { + $events = msort($this->todayEvents, 'getEpochStart'); $clusters = array(); foreach ($events as $event) { diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php index fd6a7a0e73..88c78cda9f 100644 --- a/src/view/phui/calendar/PHUICalendarListView.php +++ b/src/view/phui/calendar/PHUICalendarListView.php @@ -37,7 +37,7 @@ final class PHUICalendarListView extends AphrontTagView { foreach ($events as $event) { $color = $event->getColor(); - if ($event->getAllDay()) { + if ($event->getIsAllDay()) { $timelabel = pht('All Day'); } else { $timelabel = phabricator_time( From 188c21ae0887326fcc29b8b037c88f58aa7f5b6e Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 May 2015 14:53:46 -0700 Subject: [PATCH 24/70] Improve rendering of Conpherence fulltext search results Summary: Ref T3165. This: - Fixes a bug with overlapping matches. - Makes the UI a little less hideous (and more standard). - Links comments into the chat history view. Test Plan: {F396749} Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T3165 Differential Revision: https://secure.phabricator.com/D12777 --- resources/celerity/map.php | 12 +-- .../query/ConpherenceThreadSearchEngine.php | 81 +++++++++++-------- .../application/conpherence/transaction.css | 19 +++++ 3 files changed, 75 insertions(+), 37 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c732e061f0..eec3ad47fa 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'ca3f6a60', + 'core.pkg.css' => 'a821cfc9', 'core.pkg.js' => '919c56b5', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', @@ -49,7 +49,7 @@ return array( 'rsrc/css/application/conpherence/menu.css' => 'f389e048', 'rsrc/css/application/conpherence/message-pane.css' => '3150e2a2', 'rsrc/css/application/conpherence/notification.css' => 'd208f806', - 'rsrc/css/application/conpherence/transaction.css' => '25138b7f', + 'rsrc/css/application/conpherence/transaction.css' => '885433f0', 'rsrc/css/application/conpherence/update.css' => '1099a660', 'rsrc/css/application/conpherence/widget-pane.css' => '2af42ebe', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', @@ -133,7 +133,7 @@ return array( 'rsrc/css/phui/phui-document.css' => '94d5dcd8', 'rsrc/css/phui/phui-feed-story.css' => 'c9f3a0b5', 'rsrc/css/phui/phui-fontkit.css' => 'dd8ddf27', - 'rsrc/css/phui/phui-form-view.css' => '17eace76', + 'rsrc/css/phui/phui-form-view.css' => '94ae3032', 'rsrc/css/phui/phui-form.css' => 'f535f938', 'rsrc/css/phui/phui-header-view.css' => 'da4586b1', 'rsrc/css/phui/phui-icon.css' => 'bc766998', @@ -346,6 +346,7 @@ return array( 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', + 'rsrc/js/application/calendar/event-all-day.js' => 'ca5fa62a', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '9e507b59', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', @@ -516,7 +517,7 @@ return array( 'conpherence-message-pane-css' => '3150e2a2', 'conpherence-notification-css' => 'd208f806', 'conpherence-thread-manager' => '9e507b59', - 'conpherence-transaction-css' => '25138b7f', + 'conpherence-transaction-css' => '885433f0', 'conpherence-update-css' => '1099a660', 'conpherence-widget-pane-css' => '2af42ebe', 'differential-changeset-view-css' => 'e19cfd6e', @@ -585,6 +586,7 @@ return array( 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-durable-column' => '657c2b50', 'javelin-behavior-error-log' => '6882e80a', + 'javelin-behavior-event-all-day' => 'ca5fa62a', 'javelin-behavior-fancy-datepicker' => '5c0f680f', 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', @@ -784,7 +786,7 @@ return array( 'phui-font-icon-base-css' => '3dad2ae3', 'phui-fontkit-css' => 'dd8ddf27', 'phui-form-css' => 'f535f938', - 'phui-form-view-css' => '17eace76', + 'phui-form-view-css' => '94ae3032', 'phui-header-view-css' => 'da4586b1', 'phui-icon-view-css' => 'bc766998', 'phui-image-mask-css' => '5a8b09c8', diff --git a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php index 2a23d6c073..f2a2d920bd 100644 --- a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php +++ b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php @@ -149,32 +149,48 @@ final class ConpherenceThreadSearchEngine $viewer, $conpherences); + $engines = array(); + $fulltext = $query->getParameter('fulltext'); if (strlen($fulltext) && $conpherences) { $context = $this->loadContextMessages($conpherences, $fulltext); $author_phids = array(); - foreach ($context as $messages) { + foreach ($context as $phid => $messages) { + $conpherence = $conpherences[$phid]; + + $engine = id(new PhabricatorMarkupEngine()) + ->setViewer($viewer) + ->setContextObject($conpherence); + foreach ($messages as $group) { foreach ($group as $message) { $xaction = $message['xaction']; if ($xaction) { $author_phids[] = $xaction->getAuthorPHID(); + $engine->addObject( + $xaction->getComment(), + PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } } } + $engine->process(); + + $engines[$phid] = $engine; } $handles = $viewer->loadHandles($author_phids); + $handles = iterator_to_array($handles); } else { $context = array(); } $list = new PHUIObjectItemListView(); $list->setUser($viewer); - foreach ($conpherences as $conpherence) { + foreach ($conpherences as $conpherence_phid => $conpherence) { $created = phabricator_date($conpherence->getDateCreated(), $viewer); $title = $conpherence->getDisplayTitle($viewer); + $monogram = $conpherence->getMonogram(); if ($conpherence->getIsRoom()) { $icon_name = $conpherence->getPolicyIconName($policy_objects); @@ -186,7 +202,7 @@ final class ConpherenceThreadSearchEngine $item = id(new PHUIObjectItemView()) ->setObjectName($conpherence->getMonogram()) ->setHeader($title) - ->setHref('/conpherence/'.$conpherence->getID().'/') + ->setHref('/'.$conpherence->getMonogram()) ->setObject($conpherence) ->addIcon('none', $created) ->addIcon( @@ -201,43 +217,37 @@ final class ConpherenceThreadSearchEngine phabricator_datetime($conpherence->getDateModified(), $viewer)), )); - $messages = idx($context, $conpherence->getPHID()); + $messages = idx($context, $conpherence_phid); if ($messages) { - - // TODO: This is egregiously under-designed. - foreach ($messages as $group) { $rows = array(); - $rowc = array(); foreach ($group as $message) { $xaction = $message['xaction']; if (!$xaction) { continue; } - $rowc[] = ($message['match'] ? 'highlighted' : null); - $rows[] = array( - $handles->renderHandle($xaction->getAuthorPHID()), - $xaction->getComment()->getContent(), - phabricator_datetime($xaction->getDateCreated(), $viewer), - ); + $history_href = '/'.$monogram.'#'.$xaction->getID(); + + $view = id(new ConpherenceTransactionView()) + ->setUser($viewer) + ->setHandles($handles) + ->setMarkupEngine($engines[$conpherence_phid]) + ->setConpherenceThread($conpherence) + ->setConpherenceTransaction($xaction) + ->setEpoch($xaction->getDateCreated(), $history_href) + ->addClass('conpherence-fulltext-result'); + + if ($message['match']) { + $view->addClass('conpherence-fulltext-match'); + } + + $rows[] = $view; } - $table = id(new AphrontTableView($rows)) - ->setHeaders( - array( - pht('User'), - pht('Message'), - pht('At'), - )) - ->setRowClasses($rowc) - ->setColumnClasses( - array( - '', - 'wide', - )); + $box = id(new PHUIBoxView()) - ->appendChild($table) - ->addMargin(PHUI::MARGIN_SMALL); + ->appendChild($rows) + ->addClass('conpherence-fulltext-results'); $item->appendChild($box); } } @@ -362,8 +372,12 @@ final class ConpherenceThreadSearchEngine $groups = array(); foreach ($hits as $thread_phid => $rows) { $rows = ipull($rows, null, 'transactionPHID'); + $done = array(); foreach ($rows as $phid => $row) { - unset($rows[$phid]); + if (isset($done[$phid])) { + continue; + } + $done[$phid] = true; $group = array(); @@ -381,7 +395,7 @@ final class ConpherenceThreadSearchEngine if (isset($rows[$prev])) { $match = true; - unset($rows[$prev]); + $done[$prev] = true; } else { $match = false; } @@ -411,7 +425,7 @@ final class ConpherenceThreadSearchEngine if (isset($rows[$next])) { $match = true; - unset($rows[$next]); + $done[$next] = true; } else { $match = false; } @@ -451,6 +465,9 @@ final class ConpherenceThreadSearchEngine foreach ($group as $key => $list) { foreach ($list as $lkey => $item) { $xaction = idx($xactions, $item['phid']); + if ($xaction->shouldHide()) { + continue; + } $groups[$thread_phid][$key][$lkey]['xaction'] = $xaction; } } diff --git a/webroot/rsrc/css/application/conpherence/transaction.css b/webroot/rsrc/css/application/conpherence/transaction.css index d67d31692e..6ebd53b2ee 100644 --- a/webroot/rsrc/css/application/conpherence/transaction.css +++ b/webroot/rsrc/css/application/conpherence/transaction.css @@ -25,3 +25,22 @@ color: {$darkbluetext}; font-weight: bold; } + +.conpherence-fulltext-results { + margin: 0 8px 8px; + background: {$lightgreybackground}; + border: 1px solid {$lightgreyborder}; +} + +.conpherence-fulltext-result { + margin: 0 0 1px; + padding: 8px; +} + +.conpherence-fulltext-match { + background: {$lightyellow}; +} + +.conpherence-fulltext-results .epoch-link { + float: right; +} From f309960a2d784511f7c3ec9580b7cd9ea78fc20d Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Fri, 8 May 2015 14:59:11 -0700 Subject: [PATCH 25/70] Conpherence - allow for public rooms to really work Summary: Fixes T8102. This makes public rooms actually work. Also lets users see the search listings page so they can wander into all public rooms without logging in. Test Plan: As logged out user, visited ZXX and ZYY. ZXX was public, so I could see it and had a little "Login to Participate" button in the bottom. ZYY was not public so I was prompted to login. Back on ZXX I clicked the Conpherence crumb and got a sensible UI where most links prompted me to login. CLicked "search" and saw listings for all public rooms. Reviewers: epriestley, chad Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8102 Differential Revision: https://secure.phabricator.com/D12778 --- resources/celerity/map.php | 4 ++-- .../controller/ConpherenceListController.php | 4 ++++ .../controller/ConpherenceNewController.php | 3 +-- .../ConpherenceNewRoomController.php | 3 +-- .../ConpherenceRoomListController.php | 4 ++++ .../ConpherenceUpdateController.php | 2 +- .../controller/ConpherenceViewController.php | 21 ++++++++++++++++--- .../ConpherenceWidgetController.php | 12 ++++++++++- src/view/form/PHUIFormLayoutView.php | 9 +++++++- .../application/conpherence/message-pane.css | 8 +++++++ 10 files changed, 58 insertions(+), 12 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index eec3ad47fa..ad7a2c1b62 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -47,7 +47,7 @@ return array( 'rsrc/css/application/config/unhandled-exception.css' => '37d4f9a2', 'rsrc/css/application/conpherence/durable-column.css' => '2e68a92f', 'rsrc/css/application/conpherence/menu.css' => 'f389e048', - 'rsrc/css/application/conpherence/message-pane.css' => '3150e2a2', + 'rsrc/css/application/conpherence/message-pane.css' => '0e75feef', 'rsrc/css/application/conpherence/notification.css' => 'd208f806', 'rsrc/css/application/conpherence/transaction.css' => '885433f0', 'rsrc/css/application/conpherence/update.css' => '1099a660', @@ -514,7 +514,7 @@ return array( 'config-welcome-css' => '6abd79be', 'conpherence-durable-column-view' => '2e68a92f', 'conpherence-menu-css' => 'f389e048', - 'conpherence-message-pane-css' => '3150e2a2', + 'conpherence-message-pane-css' => '0e75feef', 'conpherence-notification-css' => 'd208f806', 'conpherence-thread-manager' => '9e507b59', 'conpherence-transaction-css' => '885433f0', diff --git a/src/applications/conpherence/controller/ConpherenceListController.php b/src/applications/conpherence/controller/ConpherenceListController.php index f8be8f7de6..ad672ff6d8 100644 --- a/src/applications/conpherence/controller/ConpherenceListController.php +++ b/src/applications/conpherence/controller/ConpherenceListController.php @@ -25,6 +25,10 @@ final class ConpherenceListController extends ConpherenceController { return $mode; } + public function shouldAllowPublic() { + return true; + } + public function handleRequest(AphrontRequest $request) { $user = $request->getUser(); $title = pht('Conpherence'); diff --git a/src/applications/conpherence/controller/ConpherenceNewController.php b/src/applications/conpherence/controller/ConpherenceNewController.php index a887404c7d..102cf0e5ea 100644 --- a/src/applications/conpherence/controller/ConpherenceNewController.php +++ b/src/applications/conpherence/controller/ConpherenceNewController.php @@ -42,9 +42,8 @@ final class ConpherenceNewController extends ConpherenceController { } } } else { - $uri = $this->getApplicationURI($conpherence->getID()); return id(new AphrontRedirectResponse()) - ->setURI($uri); + ->setURI('/'.$conpherence->getMonogram()); } } else { $participant_prefill = $request->getStr('participant'); diff --git a/src/applications/conpherence/controller/ConpherenceNewRoomController.php b/src/applications/conpherence/controller/ConpherenceNewRoomController.php index ce9afcb195..a401da0a19 100644 --- a/src/applications/conpherence/controller/ConpherenceNewRoomController.php +++ b/src/applications/conpherence/controller/ConpherenceNewRoomController.php @@ -36,9 +36,8 @@ final class ConpherenceNewRoomController extends ConpherenceController { ->setActor($user) ->applyTransactions($conpherence, $xactions); - $uri = $this->getApplicationURI($conpherence->getID()); return id(new AphrontRedirectResponse()) - ->setURI($uri); + ->setURI('/'.$conpherence->getMonogram()); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; diff --git a/src/applications/conpherence/controller/ConpherenceRoomListController.php b/src/applications/conpherence/controller/ConpherenceRoomListController.php index 299ca6b8d0..43c36d20d1 100644 --- a/src/applications/conpherence/controller/ConpherenceRoomListController.php +++ b/src/applications/conpherence/controller/ConpherenceRoomListController.php @@ -2,6 +2,10 @@ final class ConpherenceRoomListController extends ConpherenceController { + public function shouldAllowPublic() { + return true; + } + public function handleRequest(AphrontRequest $request) { $user = $request->getUser(); diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 3e86accf7c..eea1d1dc8a 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -251,7 +251,7 @@ final class ConpherenceUpdateController case 'redirect': default: return id(new AphrontRedirectResponse()) - ->setURI($this->getApplicationURI($conpherence->getID().'/')); + ->setURI('/'.$conpherence->getMonogram()); break; } } diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index e0a39030c3..39138a40b4 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -5,6 +5,10 @@ final class ConpherenceViewController extends const OLDER_FETCH_LIMIT = 5; + public function shouldAllowPublic() { + return true; + } + public function handleRequest(AphrontRequest $request) { $user = $request->getUser(); @@ -138,7 +142,7 @@ final class ConpherenceViewController extends $conpherence, PhabricatorPolicyCapability::CAN_JOIN); $participating = $conpherence->getParticipantIfExists($user->getPHID()); - if (!$can_join && !$participating) { + if (!$can_join && !$participating && $user->isLoggedIn()) { return null; } $draft = PhabricatorDraft::newFromUserAndKey( @@ -147,9 +151,20 @@ final class ConpherenceViewController extends if ($participating) { $action = ConpherenceUpdateActions::MESSAGE; $button_text = pht('Send'); - } else { + } else if ($user->isLoggedIn()) { $action = ConpherenceUpdateActions::JOIN_ROOM; $button_text = pht('Join'); + } else { + // user not logged in so give them a login button. + $login_href = id(new PhutilURI('/auth/start/')) + ->setQueryParam('next', '/'.$conpherence->getMonogram()); + return id(new PHUIFormLayoutView()) + ->addClass('login-to-participate') + ->appendChild( + id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Login to Participate')) + ->setHref((string)$login_href)); } $update_uri = $this->getApplicationURI('update/'.$conpherence->getID().'/'); @@ -157,10 +172,10 @@ final class ConpherenceViewController extends $form = id(new AphrontFormView()) + ->setUser($user) ->setAction($update_uri) ->addSigil('conpherence-pontificate') ->setWorkflow(true) - ->setUser($user) ->addHiddenInput('action', $action) ->appendChild( id(new PhabricatorRemarkupControl()) diff --git a/src/applications/conpherence/controller/ConpherenceWidgetController.php b/src/applications/conpherence/controller/ConpherenceWidgetController.php index f7775dcc32..9a06f0dcf9 100644 --- a/src/applications/conpherence/controller/ConpherenceWidgetController.php +++ b/src/applications/conpherence/controller/ConpherenceWidgetController.php @@ -13,6 +13,10 @@ final class ConpherenceWidgetController extends ConpherenceController { return $this->userPreferences; } + public function shouldAllowPublic() { + return true; + } + public function handleRequest(AphrontRequest $request) { $request = $this->getRequest(); $user = $request->getUser(); @@ -26,6 +30,9 @@ final class ConpherenceWidgetController extends ConpherenceController { ->withIDs(array($conpherence_id)) ->needWidgetData(true) ->executeOne(); + if (!$conpherence) { + return new Aphront404Response(); + } $this->setConpherence($conpherence); $this->setUserPreferences($user->loadPreferences()); @@ -138,8 +145,11 @@ final class ConpherenceWidgetController extends ConpherenceController { PhabricatorPolicyCapability::CAN_JOIN); if ($can_join) { $text = pht('Settings are available after joining the room.'); - } else { + } else if ($viewer->isLoggedIn()) { $text = pht('Settings not applicable to rooms you can not join.'); + } else { + $text = pht( + 'Settings are available after logging in and joining the room.'); } return phutil_tag( 'div', diff --git a/src/view/form/PHUIFormLayoutView.php b/src/view/form/PHUIFormLayoutView.php index fa05a72a59..87e35e03c1 100644 --- a/src/view/form/PHUIFormLayoutView.php +++ b/src/view/form/PHUIFormLayoutView.php @@ -7,6 +7,7 @@ */ final class PHUIFormLayoutView extends AphrontView { + private $classes = array(); private $fullWidth; public function setFullWidth($width) { @@ -14,6 +15,11 @@ final class PHUIFormLayoutView extends AphrontView { return $this; } + public function addClass($class) { + $this->classes[] = $class; + return $this; + } + public function appendInstructions($text) { return $this->appendChild( phutil_tag( @@ -38,7 +44,8 @@ final class PHUIFormLayoutView extends AphrontView { } public function render() { - $classes = array('phui-form-view'); + $classes = $this->classes; + $classes[] = 'phui-form-view'; if ($this->fullWidth) { $classes[] = 'phui-form-full-width'; diff --git a/webroot/rsrc/css/application/conpherence/message-pane.css b/webroot/rsrc/css/application/conpherence/message-pane.css index 5226480031..e702b34083 100644 --- a/webroot/rsrc/css/application/conpherence/message-pane.css +++ b/webroot/rsrc/css/application/conpherence/message-pane.css @@ -113,6 +113,14 @@ right: 241px; } +.conpherence-message-pane .phui-form-view.login-to-participate { + height: 28px; +} + +.conpherence-message-pane .login-to-participate a.button { + float: right; +} + .conpherence-message-pane .aphront-form-control-submit button, .conpherence-message-pane .aphront-form-control-submit a.button { margin-top: 6px; From ddb62d1ec3766f2baa47b582c9a96543d090782e Mon Sep 17 00:00:00 2001 From: lkassianik Date: Fri, 8 May 2015 16:19:51 -0700 Subject: [PATCH 26/70] Calendar day view should start at 8am at the latest and hour of first event at the earliest. Summary: Closes T8114, Calendar day view should start at 8am at the latest and hour of first event at the earliest. Test Plan: Open day view on day with all day event and event at 5am, all day events should all be stacked at the top of the day view table, and day should start at 5am. Reviewers: chad, #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8114 Differential Revision: https://secure.phabricator.com/D12779 --- resources/celerity/map.php | 4 +- .../phui/calendar/PHUICalendarDayView.php | 92 ++++++++++++++----- .../css/phui/calendar/phui-calendar-day.css | 22 +++++ 3 files changed, 94 insertions(+), 24 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index ad7a2c1b62..4d3db0e092 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -120,7 +120,7 @@ return array( 'rsrc/css/layout/phabricator-hovercard-view.css' => '44394670', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'c1db9e9c', 'rsrc/css/layout/phabricator-source-code-view.css' => '2ceee894', - 'rsrc/css/phui/calendar/phui-calendar-day.css' => '75b8cc4a', + 'rsrc/css/phui/calendar/phui-calendar-day.css' => '49037167', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59', 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'a92e47d2', 'rsrc/css/phui/calendar/phui-calendar.css' => '8675968e', @@ -777,7 +777,7 @@ return array( 'phui-box-css' => '7b3a2eed', 'phui-button-css' => 'de610129', 'phui-calendar-css' => '8675968e', - 'phui-calendar-day-css' => '75b8cc4a', + 'phui-calendar-day-css' => '49037167', 'phui-calendar-list-css' => 'c1d0ca59', 'phui-calendar-month-css' => 'a92e47d2', 'phui-crumbs-view-css' => '594d719e', diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index c3f0d6b61d..1f708912fa 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -35,30 +35,32 @@ final class PHUICalendarDayView extends AphrontView { $hours = $this->getHoursOfDay(); $hourly_events = array(); - $rows = array(); + + $first_event_hour = null; $all_day_events = $this->getAllDayEvents(); + $today_all_day_events = array(); + + $day_start = $this->getDateTime(); + $day_end = id(clone $day_start)->modify('+1 day'); + + $day_start = $day_start->format('U'); + $day_end = $day_end->format('U'); + + foreach ($all_day_events as $all_day_event) { + $all_day_start = $all_day_event->getEpochStart(); + $all_day_end = $all_day_event->getEpochEnd(); + + if ($all_day_start < $day_end && $all_day_end > $day_start) { + $today_all_day_events[] = $all_day_event; + } + } - // sort events into buckets by their start time - // pretend no events overlap foreach ($hours as $hour) { $current_hour_events = array(); $hour_start = $hour->format('U'); $hour_end = id(clone $hour)->modify('+1 hour')->format('U'); - if ($hour == $this->getDateTime()) { - foreach ($all_day_events as $all_day_event) { - $all_day_start = $all_day_event->getEpochStart(); - $all_day_end = $all_day_event->getEpochEnd(); - $day_end = id(clone $hour)->modify('+1 day')->format('U') - 1; - - if ($all_day_start < $day_end && $all_day_end > $hour_start) { - - $current_hour_events[] = $all_day_event; - $this->todayEvents[] = $all_day_event; - } - } - } foreach ($this->events as $event) { if ($event->getIsAllDay()) { continue; @@ -81,6 +83,10 @@ final class PHUICalendarDayView extends AphrontView { * 100; $height = min(2400, $height); + if ($first_event_hour === null) { + $first_event_hour = $hour; + } + $hourly_events[$event->getEventID()] = array( 'hour' => $hour, 'event' => $event, @@ -99,8 +105,17 @@ final class PHUICalendarDayView extends AphrontView { $hourly_events); } - // actually construct table + $rows = array(); + foreach ($hours as $hour) { + $early_hours = array(8); + if ($first_event_hour) { + $early_hours[] = $first_event_hour->format('G'); + } + if ($hour->format('G') < min($early_hours)) { + continue; + } + $drawn_hourly_events = array(); $cell_time = phutil_tag( 'td', @@ -109,6 +124,7 @@ final class PHUICalendarDayView extends AphrontView { foreach ($hourly_events as $hourly_event) { if ($hourly_event['hour'] == $hour) { + $drawn_hourly_events[] = $this->drawEvent( $hourly_event['event'], $hourly_event['offset'], @@ -133,16 +149,20 @@ final class PHUICalendarDayView extends AphrontView { $table = phutil_tag( 'table', array('class' => 'phui-calendar-day-view'), - array( - '', - $rows, - )); + $rows); + + $all_day_event_box = new PHUIBoxView(); + foreach ($today_all_day_events as $all_day_event) { + $all_day_event_box->appendChild( + $this->drawAllDayEvent($all_day_event)); + } $header = $this->renderDayViewHeader(); $sidebar = $this->renderSidebar(); $table_box = id(new PHUIObjectBoxView()) ->setHeader($header) + ->appendChild($all_day_event_box) ->appendChild($table) ->setFlush(true); @@ -170,7 +190,6 @@ final class PHUICalendarDayView extends AphrontView { } $all_day_events = array_values(msort($all_day_events, 'getEpochStart')); - return $all_day_events; } @@ -325,6 +344,35 @@ final class PHUICalendarDayView extends AphrontView { return $hourly_events; } + private function drawAllDayEvent(AphrontCalendarEventView $event) { + $name = phutil_tag( + 'a', + array( + 'class' => 'all-day', + 'href' => $event->getURI(), + ), + $event->getName()); + + $all_day_label = phutil_tag( + 'span', + array( + 'class' => 'phui-calendar-all-day-label', + ), + pht('All Day')); + + $div = phutil_tag( + 'div', + array( + 'class' => 'phui-calendar-day-event', + ), + array( + $all_day_label, + $name, + )); + + return $div; + } + private function drawEvent( AphrontCalendarEventView $event, $offset, diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css index bb07f8b8f8..1b8b887199 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css @@ -48,3 +48,25 @@ text-decoration: none; color: {$greytext}; } + +.all-day { + border: 1px solid {$blueborder}; + height: 12px; + margin: 0; + display: block; + padding: 8px; + background-color: {$bluebackground}; + text-decoration: none; + color: {$greytext}; +} + +.phui-calendar-day-event + .phui-calendar-day-event .all-day { + border-top-style: none; + border-top-width: 0; +} + +.phui-calendar-all-day-label { + color: {$greytext}; + float: right; + margin: 8px 8px 0 0; +} From dd8e4ff056af198aef0d027620c62c5e05f9aec4 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Fri, 8 May 2015 17:04:02 -0700 Subject: [PATCH 27/70] Trim transaction overload when converting events from all-day and back Summary: Closes T8136, Trim transaction overload when converting events from all-day and back Test Plan: Create new event, save, edit, change to all-day, save, remove all-day flag, save. Feed should not show "end date changed" transaction Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8136 Differential Revision: https://secure.phabricator.com/D12781 --- .../calendar/editor/PhabricatorCalendarEventEditor.php | 5 +++-- .../calendar/storage/PhabricatorCalendarEvent.php | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index 8c992ce16f..6e80d19236 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -51,7 +51,7 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_CANCEL: return $object->getIsCancelled(); case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: - return $object->getIsAllDay(); + return (int)$object->getIsAllDay(); case PhabricatorCalendarEventTransaction::TYPE_INVITE: $map = $xaction->getNewValue(); $phids = array_keys($map); @@ -89,8 +89,9 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: case PhabricatorCalendarEventTransaction::TYPE_CANCEL: case PhabricatorCalendarEventTransaction::TYPE_INVITE: - case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: return $xaction->getNewValue(); + case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: + return (int)$xaction->getNewValue(); case PhabricatorCalendarEventTransaction::TYPE_STATUS: return (int)$xaction->getNewValue(); case PhabricatorCalendarEventTransaction::TYPE_START_DATE: diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 6f13d99112..133b130614 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -71,7 +71,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $this->getDateEpochForTimeZone( $this->getDateTo(), new DateTimeZone('Pacific/Midway'), - 'Y-m-d 23:59:59', + 'Y-m-d 23:59:00', '-1 day', $zone)); From 99b4941c9ad307ccb75dcabee3b454573d0146f8 Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Fri, 8 May 2015 18:14:04 -0700 Subject: [PATCH 28/70] Conpherence - use some handle pools for Durable column perf Summary: Ref T7708. This changes things to $viewer->loadHandles where applicable in the durable column render stack. I saw some big wins on my test data like 34 queries => 24 queries on a newly created room as my default thread. For my test data, the next big perf win would be to change how remarkup rendering works and try to multiload all objects of a certain type in one shot. e.g. `PhabricatorEmbedFileRemarkupRule` implements `loadObjects` as do all classes which inherit from `PhabricatorObjectRemarkupRule`. This is because `PhabricatorObjectRemarkupRule` implements its `didMarkupText` method using `loadObjects`, and `didMarkupText` gets called per transaction over in `PhabricatorMarkupEngine->process()`. Instead, the `loadObjects` in `didMarkupText` should be hitting some cache, and we should do a bulk load for all `PhabricatorEmbedFileRemarkupRule` that had matches earlier in the rendering stack. ...I think. Test Plan: carefully looked at "Services" tab in dark console and noted fewer queries with changes post changes versus pre changes Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T7708 Differential Revision: https://secure.phabricator.com/D12780 --- .../conpherence/query/ConpherenceThreadQuery.php | 7 +++---- .../query/PhabricatorApplicationTransactionQuery.php | 6 ++---- .../markup/rule/PhabricatorObjectRemarkupRule.php | 7 +++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index 03c341f090..b17044ead0 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -285,10 +285,9 @@ final class ConpherenceThreadQuery $conpherence->$method(); } $flat_phids = array_mergev($handle_phids); - $handles = id(new PhabricatorHandleQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs($flat_phids) - ->execute(); + $viewer = $this->getViewer(); + $handles = $viewer->loadHandles($flat_phids); + $handles = iterator_to_array($handles); foreach ($handle_phids as $conpherence_phid => $phids) { $conpherence = $conpherences[$conpherence_phid]; $conpherence->attachHandles( diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php index ac6f713eeb..4c1e3ccd17 100644 --- a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php +++ b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php @@ -126,10 +126,8 @@ abstract class PhabricatorApplicationTransactionQuery $handles = array(); $merged = array_mergev($phids); if ($merged) { - $handles = id(new PhabricatorHandleQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs($merged) - ->execute(); + $handles = $this->getViewer()->loadHandles($merged); + $handles = iterator_to_array($handles); } foreach ($xactions as $xaction) { $xaction->setHandles( diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php index 0b4471d811..28377c19d5 100644 --- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php @@ -28,10 +28,9 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule { protected function loadHandles(array $objects) { $phids = mpull($objects, 'getPHID'); - $handles = id(new PhabricatorHandleQuery($phids)) - ->withPHIDs($phids) - ->setViewer($this->getEngine()->getConfig('viewer')) - ->execute(); + $viewer = $this->getEngine()->getConfig('viewer'); + $handles = $viewer->loadHandles($phids); + $handles = iterator_to_array($handles); $result = array(); foreach ($objects as $id => $object) { From 7668d47e6da14878777fa586fc8c2072814c0f67 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 9 May 2015 10:05:21 -0700 Subject: [PATCH 29/70] Fix conduit result tables on mobile views Summary: Fixes T8139. These tables don't `setHeaders()`, so we don't correctly default columns to be visible on devices. Test Plan: Conduit results now visible on devices. Reviewers: btrahan, chad Reviewed By: chad Subscribers: epriestley Maniphest Tasks: T8139 Differential Revision: https://secure.phabricator.com/D12784 --- src/view/control/AphrontTableView.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/view/control/AphrontTableView.php b/src/view/control/AphrontTableView.php index 9edd851c19..a6814a030e 100644 --- a/src/view/control/AphrontTableView.php +++ b/src/view/control/AphrontTableView.php @@ -124,6 +124,7 @@ final class AphrontTableView extends AphrontView { $visibility = array_values($this->columnVisibility); $device_visibility = array_values($this->deviceVisibility); + $headers = $this->headers; $short_headers = $this->shortHeaders; $sort_values = $this->sortValues; @@ -235,12 +236,16 @@ final class AphrontTableView extends AphrontView { if ($data) { $row_num = 0; foreach ($data as $row) { + $row_size = count($row); while (count($row) > count($col_classes)) { $col_classes[] = null; } while (count($row) > count($visibility)) { $visibility[] = true; } + while (count($row) > count($device_visibility)) { + $device_visibility[] = true; + } $tr = array(); // NOTE: Use of a separate column counter is to allow this to work // correctly if the row data has string or non-sequential keys. From 8b7a4670f8eaa3a10180913bd7718877be384f68 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 9 May 2015 13:23:37 -0700 Subject: [PATCH 30/70] Fix an issue where PhabricatorHandleList could fail to completely iterate Fixes T8140. If a HandleList contained a null (because some caller sloppily added `null` as a handle), iteration (e.g., via `iterator_to_array()`) would abort prematurely. In T8140, some of the project transactions add a `null` for an old file PHID when there's no old profile image. Auditors: btrahan --- src/applications/phid/handle/pool/PhabricatorHandleList.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/applications/phid/handle/pool/PhabricatorHandleList.php b/src/applications/phid/handle/pool/PhabricatorHandleList.php index d21e461871..21a695b258 100644 --- a/src/applications/phid/handle/pool/PhabricatorHandleList.php +++ b/src/applications/phid/handle/pool/PhabricatorHandleList.php @@ -24,6 +24,7 @@ final class PhabricatorHandleList private $handlePool; private $phids; + private $count; private $handles; private $cursor; private $map; @@ -35,6 +36,7 @@ final class PhabricatorHandleList public function setPHIDs(array $phids) { $this->phids = $phids; + $this->count = count($phids); return $this; } @@ -119,7 +121,7 @@ final class PhabricatorHandleList } public function valid() { - return isset($this->phids[$this->cursor]); + return ($this->cursor < $this->count); } @@ -165,7 +167,7 @@ final class PhabricatorHandleList public function count() { - return count($this->phids); + return $this->count; } } From 347dabbbd68f7cb83078d2cb65989ecf082897d6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 9 May 2015 13:46:42 -0700 Subject: [PATCH 31/70] Make HandlePool resistant to reentrant calls Conpherence thread handles need to load other handles in order to load. Currently, HandlePool can loop when reentered. Instead, clear the on-deck list before querying so that reentering it will query for only new handles, not reissue queries for in-flight handles. Auditors: btrahan --- .../phid/handle/pool/PhabricatorHandlePool.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/applications/phid/handle/pool/PhabricatorHandlePool.php b/src/applications/phid/handle/pool/PhabricatorHandlePool.php index a1195225bb..eaa48828a4 100644 --- a/src/applications/phid/handle/pool/PhabricatorHandlePool.php +++ b/src/applications/phid/handle/pool/PhabricatorHandlePool.php @@ -61,12 +61,17 @@ final class PhabricatorHandlePool extends Phobject { // If we need any handles, bulk load everything in the queue. if ($need) { + // Clear the list of PHIDs that need to be loaded before performing the + // actual fetch. This prevents us from looping if we need to reenter the + // HandlePool while loading handles. + $fetch_phids = array_keys($this->unloadedPHIDs); + $this->unloadedPHIDs = array(); + $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->getViewer()) - ->withPHIDs(array_keys($this->unloadedPHIDs)) + ->withPHIDs($fetch_phids) ->execute(); $this->handles += $handles; - $this->unloadedPHIDs = array(); } return array_select_keys($this->handles, $phids); From b67ad3c4e06a2af668c98c6109c6ead94f5e1b50 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Sat, 9 May 2015 14:19:48 -0700 Subject: [PATCH 32/70] Calendar day view should notify user if they have navigated away from the query range dates. Summary: Ref T8104, Calendar day view should notify user if they have navigated away from the query range dates. Test Plan: Open Calendar day view, choose May 12-13 range, execute query, page through to May 11, day view should show error that day is out of range. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8104 Differential Revision: https://secure.phabricator.com/D12786 --- .../PhabricatorCalendarEventSearchEngine.php | 2 + .../phui/calendar/PHUICalendarDayView.php | 50 ++++++++++++++----- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index d84f81cac4..d9be394049 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -383,6 +383,8 @@ final class PhabricatorCalendarEventSearchEngine $this->getDisplayYearAndMonthAndDay($query); $day_view = new PHUICalendarDayView( + $this->getDateFrom($query), + $this->getDateTo($query), $start_year, $start_month, $start_day); diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 1f708912fa..3df6d26d3d 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -1,6 +1,8 @@ browseURI; } - public function __construct($year, $month, $day = null) { + public function __construct( + $range_start, + $range_end, + $year, + $month, + $day = null) { + + $this->rangeStart = $range_start; + $this->rangeEnd = $range_end; + $this->day = $day; $this->month = $month; $this->year = $year; @@ -45,7 +56,7 @@ final class PHUICalendarDayView extends AphrontView { $day_end = id(clone $day_start)->modify('+1 day'); $day_start = $day_start->format('U'); - $day_end = $day_end->format('U'); + $day_end = $day_end->format('U') - 1; foreach ($all_day_events as $all_day_event) { $all_day_start = $all_day_event->getEpochStart(); @@ -160,10 +171,32 @@ final class PHUICalendarDayView extends AphrontView { $header = $this->renderDayViewHeader(); $sidebar = $this->renderSidebar(); + $errors = array(); + + $range_start_epoch = $this->rangeStart->getEpoch(); + $range_end_epoch = $this->rangeEnd->getEpoch(); + + if (($range_start_epoch != null && + $range_start_epoch < $day_end && + $range_start_epoch > $day_start) || + ($range_end_epoch != null && + $range_end_epoch < $day_end && + $range_end_epoch > $day_start)) { + $errors[] = pht('Part of the day is out of range'); + } + + if (($this->rangeEnd->getEpoch() != null && + $this->rangeEnd->getEpoch() < $day_start) || + ($this->rangeStart->getEpoch() != null && + $this->rangeStart->getEpoch() > $day_end)) { + $errors[] = pht('Day is out of query range'); + } + $table_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($all_day_event_box) ->appendChild($table) + ->setFormErrors($errors) ->setFlush(true); $layout = id(new AphrontMultiColumnView()) @@ -277,8 +310,6 @@ final class PHUICalendarDayView extends AphrontView { private function renderDayViewHeader() { $button_bar = null; - - // check for a browseURI, which means we need "fancy" prev / next UI $uri = $this->getBrowseURI(); if ($uri) { list($prev_year, $prev_month, $prev_day) = $this->getPrevDay(); @@ -312,9 +343,8 @@ final class PHUICalendarDayView extends AphrontView { } - $day_of_week = $this->getDayOfWeek(); - $header_text = $this->getDateTime()->format('F j, Y'); - $header_text = $day_of_week.', '.$header_text; + $display_day = $this->getDateTime(); + $header_text = $display_day->format('l, F j, Y'); $header = id(new PHUIHeaderView()) ->setHeader($header_text); @@ -402,12 +432,6 @@ final class PHUICalendarDayView extends AphrontView { return $div; } - private function getDayOfWeek() { - $date = $this->getDateTime(); - $day_of_week = $date->format('l'); - return $day_of_week; - } - // returns DateTime of each hour in the day private function getHoursOfDay() { $included_datetimes = array(); From f7662d61ac6b82e4e3b62727aebc1cf01c740a0b Mon Sep 17 00:00:00 2001 From: lkassianik Date: Sat, 9 May 2015 19:11:30 -0700 Subject: [PATCH 33/70] Calendar month view should notify user if all or part of the month was not included in the query search Summary: Closes T8104, Calendar month view should notify user if all or part of the month was not included in the query search Test Plan: In Calendar month view, search May 1-13, get "part of month is out of range", search May 1-31, 12am - 11:59:59, get no errors. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8104 Differential Revision: https://secure.phabricator.com/D12787 --- .../PhabricatorCalendarEventSearchEngine.php | 4 ++ .../phui/calendar/PHUICalendarDayView.php | 53 +++++++++++-------- .../phui/calendar/PHUICalendarMonthView.php | 49 ++++++++++++++++- 3 files changed, 82 insertions(+), 24 deletions(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index d9be394049..5d7b908604 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -323,11 +323,15 @@ final class PhabricatorCalendarEventSearchEngine if ($start_month == $now_month && $start_year == $now_year) { $month_view = new PHUICalendarMonthView( + $this->getDateFrom($query), + $this->getDateTo($query), $start_month, $start_year, $now_day); } else { $month_view = new PHUICalendarMonthView( + $this->getDateFrom($query), + $this->getDateTo($query), $start_month, $start_year); } diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 3df6d26d3d..b1dbd8c6af 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -170,33 +170,13 @@ final class PHUICalendarDayView extends AphrontView { $header = $this->renderDayViewHeader(); $sidebar = $this->renderSidebar(); - - $errors = array(); - - $range_start_epoch = $this->rangeStart->getEpoch(); - $range_end_epoch = $this->rangeEnd->getEpoch(); - - if (($range_start_epoch != null && - $range_start_epoch < $day_end && - $range_start_epoch > $day_start) || - ($range_end_epoch != null && - $range_end_epoch < $day_end && - $range_end_epoch > $day_start)) { - $errors[] = pht('Part of the day is out of range'); - } - - if (($this->rangeEnd->getEpoch() != null && - $this->rangeEnd->getEpoch() < $day_start) || - ($this->rangeStart->getEpoch() != null && - $this->rangeStart->getEpoch() > $day_end)) { - $errors[] = pht('Day is out of query range'); - } + $warnings = $this->getQueryRangeWarning(); $table_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($all_day_event_box) ->appendChild($table) - ->setFormErrors($errors) + ->setFormErrors($warnings) ->setFlush(true); $layout = id(new AphrontMultiColumnView()) @@ -226,6 +206,35 @@ final class PHUICalendarDayView extends AphrontView { return $all_day_events; } + private function getQueryRangeWarning() { + $errors = array(); + + $range_start_epoch = $this->rangeStart->getEpoch(); + $range_end_epoch = $this->rangeEnd->getEpoch(); + + $day_start = $this->getDateTime(); + $day_end = id(clone $day_start)->modify('+1 day'); + + $day_start = $day_start->format('U'); + $day_end = $day_end->format('U') - 1; + + if (($range_start_epoch != null && + $range_start_epoch < $day_end && + $range_start_epoch > $day_start) || + ($range_end_epoch != null && + $range_end_epoch < $day_end && + $range_end_epoch > $day_start)) { + $errors[] = pht('Part of the day is out of range'); + } + + if (($this->rangeEnd->getEpoch() != null && + $this->rangeEnd->getEpoch() < $day_start) || + ($this->rangeStart->getEpoch() != null && + $this->rangeStart->getEpoch() > $day_end)) { + $errors[] = pht('Day is out of query range'); + } + } + private function renderSidebar() { $this->events = msort($this->events, 'getEpochStart'); $week_of_boxes = $this->getWeekOfBoxes(); diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index 089d98144c..10039672d6 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -1,6 +1,8 @@ rangeStart = $range_start; + $this->rangeEnd = $range_end; + $this->day = $day; $this->month = $month; $this->year = $year; @@ -188,9 +199,12 @@ final class PHUICalendarMonthView extends AphrontView { phutil_implode_html("\n", $table), )); + $warnings = $this->getQueryRangeWarning(); + $box = id(new PHUIObjectBoxView()) ->setHeader($this->renderCalendarHeader($first)) - ->appendChild($table); + ->appendChild($table) + ->setFormErrors($warnings); if ($this->error) { $box->setInfoView($this->error); @@ -250,6 +264,37 @@ final class PHUICalendarMonthView extends AphrontView { return $header; } + private function getQueryRangeWarning() { + $errors = array(); + + $range_start_epoch = $this->rangeStart->getEpoch(); + $range_end_epoch = $this->rangeEnd->getEpoch(); + + $month_start = $this->getDateTime(); + $month_end = id(clone $month_start)->modify('+1 month'); + + $month_start = $month_start->format('U'); + $month_end = $month_end->format('U') - 1; + + if (($range_start_epoch != null && + $range_start_epoch < $month_end && + $range_start_epoch > $month_start) || + ($range_end_epoch != null && + $range_end_epoch < $month_end && + $range_end_epoch > $month_start)) { + $errors[] = pht('Part of the month is out of range'); + } + + if (($this->rangeEnd->getEpoch() != null && + $this->rangeEnd->getEpoch() < $month_start) || + ($this->rangeStart->getEpoch() != null && + $this->rangeStart->getEpoch() > $month_end)) { + $errors[] = pht('Month is out of query range'); + } + + return $errors; + } + private function getNextYearAndMonth() { $next = $this->getDateTime(); $next->modify('+1 month'); From e074be0d3fc5509a500123013563adc6765b1ea0 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Sun, 10 May 2015 10:40:55 -0700 Subject: [PATCH 34/70] Calendar events in month view should display event names instead of "Away" summary Summary: Closes T8137, Calendar events in month view should display event names instead of "Away" summary Test Plan: Open month view in Calendar, all events should display as their actual names. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8137 Differential Revision: https://secure.phabricator.com/D12790 --- .../calendar/query/PhabricatorCalendarEventSearchEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 5d7b908604..46f6419ba4 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -363,7 +363,7 @@ final class PhabricatorCalendarEventSearchEngine $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); $name_text = $handles[$status->getUserPHID()]->getName(); - $status_text = $status->getHumanStatus(); + $status_text = $status->getName(); $event->setUserPHID($status->getUserPHID()); $event->setDescription(pht('%s (%s)', $name_text, $status_text)); $event->setName($status_text); From a03a488ba61b3af400fd3bcdcac9891b33d8faeb Mon Sep 17 00:00:00 2001 From: Alex Monk Date: Sun, 10 May 2015 10:53:14 -0700 Subject: [PATCH 35/70] Don't claim logged out users are automatically subscribed to un-owned objects Summary: The PHID for logged out users is NULL, but so is the PHID for un-owned objects. Test Plan: Browse a non-owned object (i.e. unassigned task) while logged out, notice "Automatically subscribed" before this commit. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D12788 --- .../events/PhabricatorSubscriptionsUIEventListener.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php index 380c8380ce..fb7c32892f 100644 --- a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php +++ b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php @@ -21,6 +21,7 @@ final class PhabricatorSubscriptionsUIEventListener private function handleActionEvent($event) { $user = $event->getUser(); + $user_phid = $user->getPHID(); $object = $event->getValue('object'); if (!$object || !$object->getPHID()) { @@ -33,12 +34,12 @@ final class PhabricatorSubscriptionsUIEventListener return; } - if (!$object->shouldAllowSubscription($user->getPHID())) { + if (!$object->shouldAllowSubscription($user_phid)) { // This object doesn't allow the viewer to subscribe. return; } - if ($object->isAutomaticallySubscribed($user->getPHID())) { + if ($user_phid && $object->isAutomaticallySubscribed($user_phid)) { $sub_action = id(new PhabricatorActionView()) ->setWorkflow(true) ->setDisabled(true) @@ -50,15 +51,14 @@ final class PhabricatorSubscriptionsUIEventListener $subscribed = false; if ($user->isLoggedIn()) { $src_phid = $object->getPHID(); - $dst_phid = $user->getPHID(); $edge_type = PhabricatorObjectHasSubscriberEdgeType::EDGECONST; $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($src_phid)) ->withEdgeTypes(array($edge_type)) - ->withDestinationPHIDs(array($user->getPHID())) + ->withDestinationPHIDs(array($user_phid)) ->execute(); - $subscribed = isset($edges[$src_phid][$edge_type][$dst_phid]); + $subscribed = isset($edges[$src_phid][$edge_type][$user_phid]); } if ($subscribed) { From 2e54b3ff57a5019225987bf751b1e753bb9c1939 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 10 May 2015 10:55:01 -0700 Subject: [PATCH 36/70] Fix excessively-conservative feed story policy checks Summary: When checking if a user can see a feed story about an object, we currently use the object's primary policy but ignore automatic capabilities. Instead, proxy both primary policies and automatic capabilities. Test Plan: - Before this patch, users could not see stories about events they were invited to but not permitted to see by the primary policy (this is currently the default for newly created events). - After this patch, these invited users can now see the stories. Reviewers: btrahan Reviewed By: btrahan Subscribers: lpriestley, epriestley Differential Revision: https://secure.phabricator.com/D12785 --- .../feed/story/PhabricatorFeedStory.php | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/applications/feed/story/PhabricatorFeedStory.php b/src/applications/feed/story/PhabricatorFeedStory.php index a115985704..0c989d4948 100644 --- a/src/applications/feed/story/PhabricatorFeedStory.php +++ b/src/applications/feed/story/PhabricatorFeedStory.php @@ -453,15 +453,9 @@ abstract class PhabricatorFeedStory * @task policy */ public function getPolicy($capability) { - // If this story's primary object is a policy-aware object, use its policy - // to control story visiblity. - - $primary_phid = $this->getPrimaryObjectPHID(); - if (isset($this->objects[$primary_phid])) { - $object = $this->objects[$primary_phid]; - if ($object instanceof PhabricatorPolicyInterface) { - return $object->getPolicy($capability); - } + $policy_object = $this->getPrimaryPolicyObject(); + if ($policy_object) { + return $policy_object->getPolicy($capability); } // TODO: Remove this once all objects are policy-aware. For now, keep @@ -476,6 +470,11 @@ abstract class PhabricatorFeedStory * @task policy */ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + $policy_object = $this->getPrimaryPolicyObject(); + if ($policy_object) { + return $policy_object->hasAutomaticCapability($capability, $viewer); + } + return false; } @@ -484,6 +483,26 @@ abstract class PhabricatorFeedStory } + /** + * Get the policy object this story is about, if such a policy object + * exists. + * + * @return PhabricatorPolicyInterface|null Policy object, if available. + * @task policy + */ + private function getPrimaryPolicyObject() { + $primary_phid = $this->getPrimaryObjectPHID(); + if (empty($this->objects[$primary_phid])) { + $object = $this->objects[$primary_phid]; + if ($object instanceof PhabricatorPolicyInterface) { + return $object; + } + } + + return null; + } + + /* -( PhabricatorMarkupInterface Implementation )--------------------------- */ From 51facbabba91dd3f22f78ad67aec812a9a1b8372 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Sun, 10 May 2015 11:14:24 -0700 Subject: [PATCH 37/70] Deprecating BrowseController and getting rid of unneeded calls to `getTerseSummary()` and `getHumanStatus()` Summary: Ref T8050, Deprecating BrowseController and getting rid of unneeded calls to `getTerseSummary()` and `getHumanStatus()` Test Plan: Use calendar, make sure nothing explodes Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8050 Differential Revision: https://secure.phabricator.com/D12791 --- .../PhabricatorCalendarBrowseController.php | 97 ------------------- .../PhabricatorCalendarEventSearchEngine.php | 5 +- .../storage/PhabricatorCalendarEvent.php | 5 - 3 files changed, 1 insertion(+), 106 deletions(-) delete mode 100644 src/applications/calendar/controller/PhabricatorCalendarBrowseController.php diff --git a/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php b/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php deleted file mode 100644 index ba2017cba9..0000000000 --- a/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php +++ /dev/null @@ -1,97 +0,0 @@ -getViewer(); - - $now = time(); - $year_d = phabricator_format_local_time($now, $viewer, 'Y'); - $year = $request->getInt('year', $year_d); - $month_d = phabricator_format_local_time($now, $viewer, 'm'); - $month = $request->getInt('month', $month_d); - $day = phabricator_format_local_time($now, $viewer, 'j'); - - $holidays = id(new PhabricatorCalendarHoliday())->loadAllWhere( - 'day BETWEEN %s AND %s', - "{$year}-{$month}-01", - "{$year}-{$month}-31"); - - $statuses = id(new PhabricatorCalendarEventQuery()) - ->setViewer($viewer) - ->withDateRange( - strtotime("{$year}-{$month}-01"), - strtotime("{$year}-{$month}-01 next month")) - ->execute(); - - if ($month == $month_d && $year == $year_d) { - $month_view = new PHUICalendarMonthView($month, $year, $day); - } else { - $month_view = new PHUICalendarMonthView($month, $year); - } - - $month_view->setBrowseURI($request->getRequestURI()); - $month_view->setUser($viewer); - $month_view->setHolidays($holidays); - - $phids = mpull($statuses, 'getUserPHID'); - $handles = $viewer->loadHandles($phids); - - /* Assign Colors */ - $unique = array_unique($phids); - $allblue = false; - $calcolors = CalendarColors::getColors(); - if (count($unique) > count($calcolors)) { - $allblue = true; - } - $i = 0; - $eventcolor = array(); - foreach ($unique as $phid) { - if ($allblue) { - $eventcolor[$phid] = CalendarColors::COLOR_SKY; - } else { - $eventcolor[$phid] = $calcolors[$i]; - } - $i++; - } - - foreach ($statuses as $status) { - $event = new AphrontCalendarEventView(); - $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); - - $name_text = $handles[$status->getUserPHID()]->getName(); - $status_text = $status->getHumanStatus(); - $event->setUserPHID($status->getUserPHID()); - $event->setDescription(pht('%s (%s)', $name_text, $status_text)); - $event->setName($status_text); - $event->setEventID($status->getID()); - $event->setColor($eventcolor[$status->getUserPHID()]); - $month_view->addEvent($event); - } - - $date = new DateTime("{$year}-{$month}-01"); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('All Events')); - $crumbs->addTextCrumb($date->format('F Y')); - - $nav = $this->buildSideNavView(); - $nav->selectFilter('all/'); - $nav->appendChild( - array( - $crumbs, - $month_view, - )); - - return $this->buildApplicationPage( - $nav, - array( - 'title' => pht('Calendar'), - )); - } - -} diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 46f6419ba4..e3aecc89ba 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -284,15 +284,12 @@ final class PhabricatorCalendarEventSearchEngine $to = phabricator_datetime($event->getDateTo(), $viewer); $creator_handle = $handles[$event->getUserPHID()]; - $name = (strlen($event->getName())) ? - $event->getName() : $event->getTerseSummary($viewer); - $color = ($event->getStatus() == PhabricatorCalendarEvent::STATUS_AWAY) ? 'red' : 'yellow'; $item = id(new PHUIObjectItemView()) - ->setHeader($name) + ->setHeader($event->getName()) ->setHref($href) ->setBarColor($color) ->addByline(pht('Creator: %s', $creator_handle->renderLink())) diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 133b130614..af756b5273 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -168,11 +168,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ); } - public function getHumanStatus() { - $options = $this->getStatusOptions(); - return $options[$this->status]; - } - protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, From 75fc4e763ad60e02ae2de4be8112c8f1eeb9bb5a Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 May 2015 11:10:42 -0700 Subject: [PATCH 38/70] Don't trim remarkup corpora before summarizing Summary: Currently, lists like this: ``` - a - b - c ``` ...get trimmed before summarization and end up looking like this after summarization: ``` - a - b - c ``` This produces the summary artifacts (first item at wrong indent level): {F399841} Instead, don't trim. This produces better summaries. Test Plan: Saw a better summary of a list. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D12794 --- src/infrastructure/markup/PhabricatorMarkupEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php index 491940d23f..ccfe1e44fd 100644 --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -586,7 +586,7 @@ final class PhabricatorMarkupEngine { // - Hopefully don't return too much text. We don't explicitly limit // this right now. - $blocks = preg_split("/\n *\n\s*/", trim($corpus)); + $blocks = preg_split("/\n *\n\s*/", $corpus); $best = null; foreach ($blocks as $block) { From 3df64104de203fe4c8e9fecf67bceb87da8e82a9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 May 2015 11:11:25 -0700 Subject: [PATCH 39/70] Use full URIs to link to objects from Phame blogs Summary: Fixes T6299. Test Plan: Viewed live blog, saw full URIs. Reviewers: btrahan Reviewed By: btrahan Subscribers: johnny-bit, epriestley Maniphest Tasks: T6299 Differential Revision: https://secure.phabricator.com/D12793 --- src/applications/pholio/remarkup/PholioRemarkupRule.php | 4 ++++ src/infrastructure/markup/PhabricatorMarkupEngine.php | 7 +++++-- .../markup/rule/PhabricatorObjectRemarkupRule.php | 8 +++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/applications/pholio/remarkup/PholioRemarkupRule.php b/src/applications/pholio/remarkup/PholioRemarkupRule.php index e8c8f00a17..2b1ca0f876 100644 --- a/src/applications/pholio/remarkup/PholioRemarkupRule.php +++ b/src/applications/pholio/remarkup/PholioRemarkupRule.php @@ -25,6 +25,10 @@ final class PholioRemarkupRule extends PhabricatorObjectRemarkupRule { $href = $href.'/'.$id[1].'/'; } + if ($this->getEngine()->getConfig('uri.full')) { + $href = PhabricatorEnv::getURI($href); + } + return $href; } diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php index ccfe1e44fd..c29dddd821 100644 --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -42,7 +42,7 @@ final class PhabricatorMarkupEngine { private $objects = array(); private $viewer; private $contextObject; - private $version = 14; + private $version = 15; /* -( Markup Pipeline )---------------------------------------------------- */ @@ -337,6 +337,7 @@ final class PhabricatorMarkupEngine { public static function newPhameMarkupEngine() { return self::newMarkupEngine(array( 'macros' => false, + 'uri.full' => true, )); } @@ -349,7 +350,6 @@ final class PhabricatorMarkupEngine { array( 'macros' => false, 'youtube' => false, - )); } @@ -437,6 +437,7 @@ final class PhabricatorMarkupEngine { 'macros' => true, 'uri.allowed-protocols' => PhabricatorEnv::getEnvConfig( 'uri.allowed-protocols'), + 'uri.full' => false, 'syntax-highlighter.engine' => PhabricatorEnv::getEnvConfig( 'syntax-highlighter.engine'), 'preserve-linebreaks' => true, @@ -464,6 +465,8 @@ final class PhabricatorMarkupEngine { 'syntax-highlighter.engine', $options['syntax-highlighter.engine']); + $engine->setConfig('uri.full', $options['uri.full']); + $rules = array(); $rules[] = new PhutilRemarkupEscapeRemarkupRule(); $rules[] = new PhutilRemarkupMonospaceRule(); diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php index 28377c19d5..8f776716ea 100644 --- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php @@ -44,7 +44,13 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule { PhabricatorObjectHandle $handle, $id) { - return $handle->getURI(); + $uri = $handle->getURI(); + + if ($this->getEngine()->getConfig('uri.full')) { + $uri = PhabricatorEnv::getURI($uri); + } + + return $uri; } protected function renderObjectRefForAnyMedia ( From c94bd8e4f2f801a57bc0640e52e93c2d3c46d6ff Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 May 2015 12:02:00 -0700 Subject: [PATCH 40/70] Stop using JX.Scrollbar for main page content Summary: Ref T8151. This is option (5). It needs a few adjustments but feels pretty good. Major issues are: - Without a mouse, the scrollbars overlap by default, so we //must// move the column off the right margin. - Scrolling sometimes "bleeds" between the chat vs the main frame in a way that's not as discrete as the old framed content, but feels generally reasonable to me. If we pursue this, I'd plan to make these additional changes: - Move the panel away from the right margin only if the page scrollbars are zero-width (i.e., in OSX trackpad mode). - Fix the notch in the upper right corner when the chat is moved away from the right margin. - Probably remove the body "overflow-y: scroll" on Conpherence and Workboards. - Update the resizing code to deal with 300px vs 315px widths. - We can probably clean up some JX.Scrollbar "main panel" code. Here's the "bad" case, where I've visually separated the column to provide room for a scrollbar. This isn't ideal, but looks and feels OK to me: {F398375} Test Plan: - Tried Firefox, Chrome, Safari, with and without a mouse. - Tried normal Conpherence. Reviewers: btrahan, chad Reviewed By: btrahan Subscribers: avivey, epriestley Maniphest Tasks: T8151 Differential Revision: https://secure.phabricator.com/D12789 --- resources/celerity/map.php | 64 +++++++++---------- src/view/page/PhabricatorStandardPageView.php | 9 --- .../application/base/standard-page-view.css | 21 ------ .../conpherence/durable-column.css | 29 ++++++++- webroot/rsrc/css/core/core.css | 4 ++ webroot/rsrc/css/core/z-index.css | 7 +- .../rsrc/externals/javelin/lib/Scrollbar.js | 28 ++++++++ .../conpherence/behavior-durable-column.js | 8 ++- 8 files changed, 102 insertions(+), 68 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4d3db0e092..e67c107921 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,8 +7,8 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'a821cfc9', - 'core.pkg.js' => '919c56b5', + 'core.pkg.css' => 'ed3d6355', + 'core.pkg.js' => '31eaf90a', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', 'differential.pkg.js' => '895b8d62', @@ -37,7 +37,7 @@ return array( 'rsrc/css/application/base/main-menu-view.css' => '663e3810', 'rsrc/css/application/base/notification-menu.css' => '3c9d8aa1', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '16ca323f', - 'rsrc/css/application/base/standard-page-view.css' => 'd3e1abe9', + 'rsrc/css/application/base/standard-page-view.css' => '2acd4611', 'rsrc/css/application/chatlog/chatlog.css' => '852140ff', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '7fedf08b', @@ -45,11 +45,11 @@ return array( 'rsrc/css/application/config/config-welcome.css' => '6abd79be', 'rsrc/css/application/config/setup-issue.css' => '22270af2', 'rsrc/css/application/config/unhandled-exception.css' => '37d4f9a2', - 'rsrc/css/application/conpherence/durable-column.css' => '2e68a92f', + 'rsrc/css/application/conpherence/durable-column.css' => '8c43d6ac', 'rsrc/css/application/conpherence/menu.css' => 'f389e048', 'rsrc/css/application/conpherence/message-pane.css' => '0e75feef', 'rsrc/css/application/conpherence/notification.css' => 'd208f806', - 'rsrc/css/application/conpherence/transaction.css' => '885433f0', + 'rsrc/css/application/conpherence/transaction.css' => '42a457f6', 'rsrc/css/application/conpherence/update.css' => '1099a660', 'rsrc/css/application/conpherence/widget-pane.css' => '2af42ebe', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', @@ -108,10 +108,10 @@ return array( 'rsrc/css/application/slowvote/slowvote.css' => '266df6a1', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => '76e8ee93', + 'rsrc/css/core/core.css' => 'aaea7a7a', 'rsrc/css/core/remarkup.css' => '0037bdbf', 'rsrc/css/core/syntax.css' => '6b7b24d9', - 'rsrc/css/core/z-index.css' => '3b612549', + 'rsrc/css/core/z-index.css' => '8414a09b', 'rsrc/css/diviner/diviner-shared.css' => '38813222', 'rsrc/css/font/font-awesome.css' => 'e2e712fe', 'rsrc/css/font/font-source-sans-pro.css' => '8906c07b', @@ -211,7 +211,7 @@ return array( 'rsrc/externals/javelin/lib/Resource.js' => '44959b73', 'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692', 'rsrc/externals/javelin/lib/Router.js' => '29274e2b', - 'rsrc/externals/javelin/lib/Scrollbar.js' => 'eaa5b321', + 'rsrc/externals/javelin/lib/Scrollbar.js' => '4f812f8a', 'rsrc/externals/javelin/lib/Sound.js' => '949c0fe5', 'rsrc/externals/javelin/lib/URI.js' => '6eff08aa', 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', @@ -350,7 +350,7 @@ return array( 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '9e507b59', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', - 'rsrc/js/application/conpherence/behavior-durable-column.js' => '657c2b50', + 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'ac11eb8a', 'rsrc/js/application/conpherence/behavior-menu.js' => '804b0773', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', @@ -512,12 +512,12 @@ return array( 'conduit-api-css' => '7bc725c4', 'config-options-css' => '7fedf08b', 'config-welcome-css' => '6abd79be', - 'conpherence-durable-column-view' => '2e68a92f', + 'conpherence-durable-column-view' => '8c43d6ac', 'conpherence-menu-css' => 'f389e048', 'conpherence-message-pane-css' => '0e75feef', 'conpherence-notification-css' => 'd208f806', 'conpherence-thread-manager' => '9e507b59', - 'conpherence-transaction-css' => '885433f0', + 'conpherence-transaction-css' => '42a457f6', 'conpherence-update-css' => '1099a660', 'conpherence-widget-pane-css' => '2af42ebe', 'differential-changeset-view-css' => 'e19cfd6e', @@ -584,7 +584,7 @@ return array( 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => '2b228192', 'javelin-behavior-doorkeeper-tag' => 'e5822781', - 'javelin-behavior-durable-column' => '657c2b50', + 'javelin-behavior-durable-column' => 'ac11eb8a', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-event-all-day' => 'ca5fa62a', 'javelin-behavior-fancy-datepicker' => '5c0f680f', @@ -681,7 +681,7 @@ return array( 'javelin-resource' => '44959b73', 'javelin-routable' => 'b3e7d692', 'javelin-router' => '29274e2b', - 'javelin-scrollbar' => 'eaa5b321', + 'javelin-scrollbar' => '4f812f8a', 'javelin-sound' => '949c0fe5', 'javelin-stratcom' => '6c53634d', 'javelin-tokenizer' => 'ab5f468d', @@ -718,7 +718,7 @@ return array( 'phabricator-busy' => '59a7976a', 'phabricator-chatlog-css' => '852140ff', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => '76e8ee93', + 'phabricator-core-css' => 'aaea7a7a', 'phabricator-countdown-css' => '86b7b0a0', 'phabricator-dashboard-css' => '17937d22', 'phabricator-drag-and-drop-file-upload' => '07de8873', @@ -747,7 +747,7 @@ return array( 'phabricator-side-menu-view-css' => 'c1db9e9c', 'phabricator-slowvote-css' => '266df6a1', 'phabricator-source-code-view-css' => '2ceee894', - 'phabricator-standard-page-view' => 'd3e1abe9', + 'phabricator-standard-page-view' => '2acd4611', 'phabricator-textareautils' => '5c93c52c', 'phabricator-title' => 'df5e11d2', 'phabricator-tooltip' => '1d298e3a', @@ -762,7 +762,7 @@ return array( 'phabricator-uiexample-reactor-select' => 'a155550f', 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', - 'phabricator-zindex-css' => '3b612549', + 'phabricator-zindex-css' => '8414a09b', 'phame-css' => '88bd4705', 'pholio-css' => '95174bdd', 'pholio-edit-css' => '3ad9d1ee', @@ -1134,6 +1134,12 @@ return array( 'javelin-stratcom', 'javelin-request', ), + '4f812f8a' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-vector', + ), '4fdb476d' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1279,16 +1285,6 @@ return array( 'javelin-workflow', 'javelin-dom', ), - '657c2b50' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-behavior-device', - 'javelin-scrollbar', - 'javelin-quicksand', - 'phabricator-keyboard-shortcut', - 'conpherence-thread-manager', - ), '6882e80a' => array( 'javelin-dom', ), @@ -1685,6 +1681,16 @@ return array( 'javelin-stratcom', 'javelin-install', ), + 'ac11eb8a' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-behavior-device', + 'javelin-scrollbar', + 'javelin-quicksand', + 'phabricator-keyboard-shortcut', + 'conpherence-thread-manager', + ), 'b1a59974' => array( 'javelin-behavior', 'javelin-aphlict', @@ -1928,12 +1934,6 @@ return array( 'phabricator-phtize', 'javelin-dom', ), - 'eaa5b321' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-vector', - ), 'efe49472' => array( 'javelin-install', 'javelin-util', diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index b0f8b5d0be..3c8891ba80 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -377,15 +377,6 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView { } } - if (!$this->isQuicksandBlacklistURI()) { - Javelin::initBehavior( - 'scrollbar', - array( - 'nodeID' => 'phabricator-standard-page', - 'isMainContent' => true, - )); - } - $main_page = phutil_tag( 'div', array( diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css index b2d7fff276..69725e14e6 100644 --- a/webroot/rsrc/css/application/base/standard-page-view.css +++ b/webroot/rsrc/css/application/base/standard-page-view.css @@ -126,27 +126,6 @@ a.handle-disabled { margin: 2px 2px -2px 0; } -.main-page-frame { - position: absolute; - top: 0; - bottom: 0; - right: 0; - left: 0; - overflow: hidden; -} - -.phabricator-standard-page { - /* If we don't activate JX.Scrollbar because the default scrollbars are - satisfactory, make sure the page still has sensible behavior. These - settings will be overwritten by .jx-scrollbar-frame if JX.Scrollbar - activates. */ - position: relative; - height: 100%; - overflow-y: scroll; - - -webkit-overflow-scrolling: touch; -} - .jx-scrollbar-frame { position: relative; overflow: hidden; diff --git a/webroot/rsrc/css/application/conpherence/durable-column.css b/webroot/rsrc/css/application/conpherence/durable-column.css index 324d76c27c..1dbb829565 100644 --- a/webroot/rsrc/css/application/conpherence/durable-column.css +++ b/webroot/rsrc/css/application/conpherence/durable-column.css @@ -2,10 +2,22 @@ * @provides conpherence-durable-column-view */ -.with-durable-column .phabricator-standard-page { +.with-durable-column .phabricator-standard-page-body { margin-right: 300px; } +.with-durable-margin .phabricator-standard-page-body { + margin-right: 312px; +} + +.with-durable-column .phabricator-main-menu { + padding-right: 304px; +} + +.with-durable-margin .phabricator-main-menu { + padding-right: 316px; +} + .with-durable-column .phabricator-global-upload-instructions { font-size: 28px; @@ -20,8 +32,12 @@ right: 300px; } +.with-durable-margin .global-upload-mask { + right: 312px; +} + .conpherence-durable-column { - position: absolute; + position: fixed; top: 0; bottom: 0; right: 0; @@ -29,6 +45,11 @@ background: #fff; } +.with-durable-margin .conpherence-durable-column { + right: 12px; + box-shadow: 1px 0px 2px rgba(0, 0, 0, 0.10); +} + .conpherence-durable-column .loading-mask { position: absolute; top: 90px; @@ -126,6 +147,10 @@ border-left: 1px solid {$lightblueborder}; } +.with-durable-margin .conpherence-durable-column-body { + border-right: 1px solid {$lightblueborder}; +} + .conpherence-durable-column-main { position: absolute; top: 46px; diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css index e06abed794..4c465de6d3 100644 --- a/webroot/rsrc/css/core/core.css +++ b/webroot/rsrc/css/core/core.css @@ -54,6 +54,10 @@ body { breaks lots of things and prevents you from using landscape to see more columns in source code views. */ -webkit-text-size-adjust: none; + + /* Prevent content from resizing abruptly when shifting between scrollable + and unscrollable pages. */ + overflow-y: scroll; } textarea { diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css index 30fc5368c2..11cb76de27 100644 --- a/webroot/rsrc/css/core/z-index.css +++ b/webroot/rsrc/css/core/z-index.css @@ -85,7 +85,6 @@ z-index: 5; } -.conpherence-durable-column-header, .phabricator-main-menu { z-index: 6; } @@ -94,10 +93,14 @@ z-index: 6; } -.jx-scrollbar-bar { +.conpherence-durable-column { z-index: 7; } +.jx-scrollbar-bar { + z-index: 8; +} + .differential-haunt-mode-1 .differential-add-comment-panel, .differential-haunt-mode-2 .differential-add-comment-panel { z-index: 8; diff --git a/webroot/rsrc/externals/javelin/lib/Scrollbar.js b/webroot/rsrc/externals/javelin/lib/Scrollbar.js index 684fb4ce08..7596939581 100644 --- a/webroot/rsrc/externals/javelin/lib/Scrollbar.js +++ b/webroot/rsrc/externals/javelin/lib/Scrollbar.js @@ -100,6 +100,7 @@ JX.install('Scrollbar', { statics: { _controlWidth: null, + /** * Compute the width of the browser's scrollbar control, in pixels. */ @@ -118,8 +119,35 @@ JX.install('Scrollbar', { } return self._controlWidth; + }, + + + /** + * Get the margin width required to avoid double scrollbars. + * + * For most browsers which render a real scrollbar control, this is 0. + * Adjacent elements may touch the edge of the content directly without + * overlapping. + * + * On OSX with a trackpad, scrollbars are only drawn when content is + * scrolled. Content panes with internal scrollbars may overlap adjacent + * scrollbars if they are not laid out with a margin. + * + * @return int Control margin width in pixels. + */ + getScrollbarControlMargin: function() { + var self = JX.Scrollbar; + + // If this browser and OS don't render a real scrollbar control, we + // need to leave a margin. Generally, this is OSX with no mouse attached. + if (self._getScrollbarControlWidth() === 0) { + return 12; + } + + return 0; } + }, members: { diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js index adcaa382a3..c9157c41c9 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js +++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js @@ -29,9 +29,11 @@ JX.behavior('durable-column', function(config, statics) { var loadThreadID = null; var scrollbar = null; - var columnWidth = 300; + var margin = JX.Scrollbar.getScrollbarControlMargin(); + + var columnWidth = (300 + margin); // This is the smallest window size where we'll enable the column. - var minimumViewportWidth = 768; + var minimumViewportWidth = (768 - margin); var quick = JX.$('phabricator-standard-page-body'); @@ -72,6 +74,8 @@ JX.behavior('durable-column', function(config, statics) { function _drawColumn(visible) { JX.DOM.alterClass(document.body, 'with-durable-column', visible); + JX.DOM.alterClass(document.body, 'with-durable-margin', !!margin); + var column = _getColumnNode(); if (visible) { JX.DOM.show(column); From 8056719eee5bd935e0cdc5c2982b621eacd83a4f Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Mon, 11 May 2015 13:50:44 -0700 Subject: [PATCH 41/70] Refine durable column class toggling Summary: don't bother with margin class if column isn't visible. Ref T8151. Test Plan: using chrome with trackpad - loaded home with toggled off column - noted no margin class erroneously applied - toggled column off and on - correct - loaded home with toggled on column - correct - toggled column off and on - correct Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8151 Differential Revision: https://secure.phabricator.com/D12795 --- resources/celerity/map.php | 42 +++++++++---------- .../conpherence/behavior-durable-column.js | 10 ++++- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e67c107921..ba88e0d757 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => 'ed3d6355', - 'core.pkg.js' => '31eaf90a', + 'core.pkg.js' => '3ed82e2f', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', 'differential.pkg.js' => '895b8d62', @@ -211,7 +211,7 @@ return array( 'rsrc/externals/javelin/lib/Resource.js' => '44959b73', 'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692', 'rsrc/externals/javelin/lib/Router.js' => '29274e2b', - 'rsrc/externals/javelin/lib/Scrollbar.js' => '4f812f8a', + 'rsrc/externals/javelin/lib/Scrollbar.js' => '087e919c', 'rsrc/externals/javelin/lib/Sound.js' => '949c0fe5', 'rsrc/externals/javelin/lib/URI.js' => '6eff08aa', 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', @@ -350,7 +350,7 @@ return array( 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '9e507b59', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', - 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'ac11eb8a', + 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'bc5107aa', 'rsrc/js/application/conpherence/behavior-menu.js' => '804b0773', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', @@ -584,7 +584,7 @@ return array( 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => '2b228192', 'javelin-behavior-doorkeeper-tag' => 'e5822781', - 'javelin-behavior-durable-column' => 'ac11eb8a', + 'javelin-behavior-durable-column' => 'bc5107aa', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-event-all-day' => 'ca5fa62a', 'javelin-behavior-fancy-datepicker' => '5c0f680f', @@ -681,7 +681,7 @@ return array( 'javelin-resource' => '44959b73', 'javelin-routable' => 'b3e7d692', 'javelin-router' => '29274e2b', - 'javelin-scrollbar' => '4f812f8a', + 'javelin-scrollbar' => '087e919c', 'javelin-sound' => '949c0fe5', 'javelin-stratcom' => '6c53634d', 'javelin-tokenizer' => 'ab5f468d', @@ -868,6 +868,12 @@ return array( 'javelin-uri', 'phabricator-file-upload', ), + '087e919c' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-vector', + ), '0a3f3021' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1134,12 +1140,6 @@ return array( 'javelin-stratcom', 'javelin-request', ), - '4f812f8a' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-vector', - ), '4fdb476d' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1681,16 +1681,6 @@ return array( 'javelin-stratcom', 'javelin-install', ), - 'ac11eb8a' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-behavior-device', - 'javelin-scrollbar', - 'javelin-quicksand', - 'phabricator-keyboard-shortcut', - 'conpherence-thread-manager', - ), 'b1a59974' => array( 'javelin-behavior', 'javelin-aphlict', @@ -1744,6 +1734,16 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + 'bc5107aa' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-behavior-device', + 'javelin-scrollbar', + 'javelin-quicksand', + 'phabricator-keyboard-shortcut', + 'conpherence-thread-manager', + ), 'bd4c8dca' => array( 'javelin-install', 'javelin-util', diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js index c9157c41c9..edaed9d2da 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js +++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js @@ -73,8 +73,14 @@ JX.behavior('durable-column', function(config, statics) { } function _drawColumn(visible) { - JX.DOM.alterClass(document.body, 'with-durable-column', visible); - JX.DOM.alterClass(document.body, 'with-durable-margin', !!margin); + JX.DOM.alterClass( + document.body, + 'with-durable-column', + visible); + JX.DOM.alterClass( + document.body, + 'with-durable-margin', + visible && !!margin); var column = _getColumnNode(); if (visible) { From b53ecd0d423fe36094921bc001a8e59f6f895acb Mon Sep 17 00:00:00 2001 From: lkassianik Date: Mon, 11 May 2015 18:13:10 -0700 Subject: [PATCH 42/70] Cleaning up day view clusters for events shorter than about 30 minutes Summary: Cleaning up day view clusters for events shorter than about 30 minutes Test Plan: Create two event within 30 minutes, 1 minute long, each. Day view should properly cluster the events. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D12799 --- src/view/phui/calendar/PHUICalendarDayView.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index b1dbd8c6af..7fdf44d297 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -84,7 +84,7 @@ final class PHUICalendarDayView extends AphrontView { } foreach ($current_hour_events as $event) { $event_start = $event->getEpochStart(); - $event_end = $event->getEpochEnd(); + $event_end = min($event->getEpochEnd(), $day_end); $top = (($event_start - $hour_start) / ($hour_end - $hour_start)) * 100; @@ -233,6 +233,7 @@ final class PHUICalendarDayView extends AphrontView { $this->rangeStart->getEpoch() > $day_end)) { $errors[] = pht('Day is out of query range'); } + return $errors; } private function renderSidebar() { @@ -503,8 +504,8 @@ final class PHUICalendarDayView extends AphrontView { foreach ($events as $event) { $destination_cluster_key = null; - $event_start = $event->getEpochStart(); - $event_end = $event->getEpochEnd(); + $event_start = $event->getEpochStart() - (30 * 60); + $event_end = $event->getEpochEnd() + (30 * 60); foreach ($clusters as $key => $cluster) { foreach ($cluster as $clustered_event) { From c4dfc097c8c979b557c6f16a641c7544e43525b8 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Mon, 11 May 2015 18:15:27 -0700 Subject: [PATCH 43/70] First pass at Month View Summary: Ref T4392, First pass at Month View Test Plan: Open month view, month view days should correctly grow with number of events, day numbers should now live at the bottom of day cells, day numbers should be links to day views of those days. Reviewers: chad, epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T4392 Differential Revision: https://secure.phabricator.com/D12800 --- resources/celerity/map.php | 4 +- .../PhabricatorCalendarEventSearchEngine.php | 39 +++++-- .../phui/calendar/PHUICalendarListView.php | 38 ++++--- .../phui/calendar/PHUICalendarMonthView.php | 100 +++++++++++------- .../css/phui/calendar/phui-calendar-month.css | 71 ++++++++----- 5 files changed, 167 insertions(+), 85 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index ba88e0d757..9e18b8b5cc 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -122,7 +122,7 @@ return array( 'rsrc/css/layout/phabricator-source-code-view.css' => '2ceee894', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '49037167', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59', - 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'a92e47d2', + 'rsrc/css/phui/calendar/phui-calendar-month.css' => '873e00da', 'rsrc/css/phui/calendar/phui-calendar.css' => '8675968e', 'rsrc/css/phui/phui-action-header-view.css' => '89c497e7', 'rsrc/css/phui/phui-action-list.css' => '4f4d09f2', @@ -779,7 +779,7 @@ return array( 'phui-calendar-css' => '8675968e', 'phui-calendar-day-css' => '49037167', 'phui-calendar-list-css' => 'c1d0ca59', - 'phui-calendar-month-css' => 'a92e47d2', + 'phui-calendar-month-css' => '873e00da', 'phui-crumbs-view-css' => '594d719e', 'phui-document-view-css' => '94d5dcd8', 'phui-feed-story-css' => 'c9f3a0b5', diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index e3aecc89ba..0c9e13b03f 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -57,8 +57,8 @@ final class PhabricatorCalendarEventSearchEngine $min_range = $this->getDateFrom($saved)->getEpoch(); $max_range = $this->getDateTo($saved)->getEpoch(); - if ($saved->getParameter('display') == 'month' || - $saved->getParameter('display') == 'day') { + if ($this->isMonthView($saved) || + $this->isDayView($saved)) { list($start_year, $start_month, $start_day) = $this->getDisplayYearAndMonthAndDay($saved); @@ -67,9 +67,9 @@ final class PhabricatorCalendarEventSearchEngine $timezone); $next = clone $start_day; - if ($saved->getParameter('display') == 'month') { + if ($this->isMonthView($saved)) { $next->modify('+1 month'); - } else if ($saved->getParameter('display') == 'day') { + } else if ($this->isDayView($saved)) { $next->modify('+6 day'); } @@ -269,9 +269,9 @@ final class PhabricatorCalendarEventSearchEngine PhabricatorSavedQuery $query, array $handles) { - if ($query->getParameter('display') == 'month') { + if ($this->isMonthView($query)) { return $this->buildCalendarView($events, $query, $handles); - } else if ($query->getParameter('display') == 'day') { + } else if ($this->isDayView($query)) { return $this->buildCalendarDayView($events, $query, $handles); } @@ -358,6 +358,7 @@ final class PhabricatorCalendarEventSearchEngine foreach ($statuses as $status) { $event = new AphrontCalendarEventView(); $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); + $event->setIsAllDay($status->getIsAllDay()); $name_text = $handles[$status->getUserPHID()]->getName(); $status_text = $status->getName(); @@ -430,9 +431,14 @@ final class PhabricatorCalendarEventSearchEngine $epoch = time(); } } + if ($this->isMonthView($query)) { + $day = 1; + } else { + $day = phabricator_format_local_time($epoch, $viewer, 'd'); + } $start_year = phabricator_format_local_time($epoch, $viewer, 'Y'); $start_month = phabricator_format_local_time($epoch, $viewer, 'm'); - $start_day = phabricator_format_local_time($epoch, $viewer, 'd'); + $start_day = $day; } return array($start_year, $start_month, $start_day); } @@ -467,4 +473,23 @@ final class PhabricatorCalendarEventSearchEngine return $value; } + private function isMonthView(PhabricatorSavedQuery $query) { + if ($this->isDayView($query)) { + return false; + } + if ($query->getParameter('display') == 'month') { + return true; + } + } + + private function isDayView(PhabricatorSavedQuery $query) { + if ($query->getParameter('display') == 'day') { + return true; + } + if ($this->calendarDay) { + return true; + } + + return false; + } } diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php index 88c78cda9f..7e8aa956e0 100644 --- a/src/view/phui/calendar/PHUICalendarListView.php +++ b/src/view/phui/calendar/PHUICalendarListView.php @@ -4,12 +4,18 @@ final class PHUICalendarListView extends AphrontTagView { private $events = array(); private $blankState; + private $isDayView = false; public function addEvent(AphrontCalendarEventView $event) { $this->events[] = $event; return $this; } + public function setIsDayView($is_day_view) { + $this->isDayView = $is_day_view; + return $this; + } + public function showBlankState($state) { $this->blankState = $state; return $this; @@ -22,7 +28,7 @@ final class PHUICalendarListView extends AphrontTagView { protected function getTagAttributes() { require_celerity_resource('phui-calendar-css'); require_celerity_resource('phui-calendar-list-css'); - return array('class' => 'phui-calendar-day-list'); + return array('class' => 'phui-calendar-event-list'); } protected function getTagContent() { @@ -30,27 +36,28 @@ final class PHUICalendarListView extends AphrontTagView { return ''; } - $events = msort($this->events, 'getEpochStart'); - $singletons = array(); $allday = false; - foreach ($events as $event) { + foreach ($this->events as $event) { $color = $event->getColor(); + $start_epoch = $event->getEpochStart(); if ($event->getIsAllDay()) { $timelabel = pht('All Day'); + $dot = null; } else { $timelabel = phabricator_time( $event->getEpochStart(), $this->getUser()); + + $dot = phutil_tag( + 'span', + array( + 'class' => 'phui-calendar-list-dot', + ), + ''); } - $dot = phutil_tag( - 'span', - array( - 'class' => 'phui-calendar-list-dot', - ), - ''); $title = phutil_tag( 'span', array( @@ -64,10 +71,15 @@ final class PHUICalendarListView extends AphrontTagView { ), $timelabel); + $class = 'phui-calendar-list-item phui-calendar-'.$color; + if ($event->getIsAllDay()) { + $class = $class.' all-day'; + } + $singletons[] = phutil_tag( 'li', array( - 'class' => 'phui-calendar-list-item phui-calendar-'.$color, + 'class' => $class, ), array( $dot, @@ -112,11 +124,13 @@ final class PHUICalendarListView extends AphrontTagView { $description = pht('(%s)', $event->getName()); } + $class = 'phui-calendar-item-link'; + $anchor = javelin_tag( 'a', array( 'sigil' => 'has-tooltip', - 'class' => 'phui-calendar-item-link', + 'class' => $class, 'href' => '/E'.$event->getEventID(), 'meta' => array( 'tip' => $tip, diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index 10039672d6..6752637b09 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -72,14 +72,14 @@ final class PHUICalendarMonthView extends AphrontView { $empty = $first->format('w'); $markup = array(); - - $empty_box = phutil_tag( - 'div', - array('class' => 'phui-calendar-day phui-calendar-empty'), - ''); + $empty_cell = array( + 'list' => null, + 'date' => null, + 'class' => 'phui-calendar-empty', + ); for ($ii = 0; $ii < $empty; $ii++) { - $markup[] = $empty_box; + $markup[] = $empty_cell; } $show_events = array(); @@ -88,7 +88,7 @@ final class PHUICalendarMonthView extends AphrontView { $day_number = $day->format('j'); $holiday = idx($this->holidays, $day->format('Y-m-d')); - $class = 'phui-calendar-day'; + $class = 'phui-calendar-month-day'; $weekday = $day->format('w'); if ($day_number == $this->day) { @@ -102,8 +102,8 @@ final class PHUICalendarMonthView extends AphrontView { $day->setTime(0, 0, 0); $epoch_start = $day->format('U'); - $day->modify('+1 day'); - $epoch_end = $day->format('U'); + + $epoch_end = id(clone $day)->modify('+1 day')->format('U'); if ($weekday == 0) { $show_events = array(); @@ -116,6 +116,7 @@ final class PHUICalendarMonthView extends AphrontView { } $list_events = array(); + $all_day_events = array(); foreach ($events as $event) { if ($event->getEpochStart() >= $epoch_end) { // This list is sorted, so we can stop looking. @@ -123,57 +124,80 @@ final class PHUICalendarMonthView extends AphrontView { } if ($event->getEpochStart() < $epoch_end && $event->getEpochEnd() > $epoch_start) { - $list_events[] = $event; + if ($event->getIsAllDay()) { + $all_day_events[] = $event; + } else { + $list_events[] = $event; + } } } $list = new PHUICalendarListView(); $list->setUser($this->user); + foreach ($all_day_events as $item) { + $list->addEvent($item); + } foreach ($list_events as $item) { $list->addEvent($item); } - $holiday_markup = null; - if ($holiday) { - $name = $holiday->getName(); - $holiday_markup = phutil_tag( - 'div', - array( - 'class' => 'phui-calendar-holiday', - 'title' => $name, - ), - $name); - } - - $markup[] = phutil_tag_div( - $class, - array( - phutil_tag_div('phui-calendar-date-number', $day_number), - $holiday_markup, - $list, - )); + $markup[] = array( + 'list' => $list, + 'date' => $day, + 'class' => $class, + ); } $table = array(); $rows = array_chunk($markup, 7); + foreach ($rows as $row) { $cells = array(); while (count($row) < 7) { - $row[] = $empty_box; + $row[] = $empty_cell; } - $j = 0; foreach ($row as $cell) { - if ($j == 0) { - $cells[] = phutil_tag( - 'td', + $cell_list = $cell['list']; + $class = $cell['class']; + $cells[] = phutil_tag( + 'td', + array( + 'class' => 'phui-calendar-month-event-list '.$class, + ), + $cell_list); + } + $table[] = phutil_tag('tr', array(), $cells); + + $cells = array(); + foreach ($row as $cell) { + $class = $cell['class']; + + if ($cell['date']) { + $cell_day = $cell['date']; + + $uri = $this->getBrowseURI(); + $date = $cell['date']; + $uri = $uri.$date->format('Y').'/'. + $date->format('m').'/'. + $date->format('d').'/'; + + $cell_day = phutil_tag( + 'a', array( - 'class' => 'phui-calendar-month-weekstart', + 'class' => 'phui-calendar-date-number', + 'href' => $uri, ), - $cell); + $cell_day->format('j')); } else { - $cells[] = phutil_tag('td', array(), $cell); + $cell_day = null; } - $j++; + + $cells[] = phutil_tag( + 'td', + array( + 'class' => 'phui-calendar-date-number-container '.$class, + ), + $cell_day); } $table[] = phutil_tag('tr', array(), $cells); } diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css index c3abbb131c..bd99ebf925 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css @@ -17,40 +17,36 @@ tr.phui-calendar-day-of-week-header th { } table.phui-calendar-view td { - border: 1px solid #dfdfdf; + border: solid #dfdfdf; + border-width: 1px 1px 0 1px; width: 14.2857%; /* This is one seventh, approximately. */ } -table.phui-calendar-view td div.phui-calendar-day { +table.phui-calendar-view tr td:first-child { + border-left-width: 0px; +} + +table.phui-calendar-view .phui-calendar-event-list { min-height: 125px; - position: relative; } -.phui-calendar-holiday { - color: {$greytext}; - padding: .5em; - max-height: 1em; - overflow: hidden; -} - -table.phui-calendar-view td.phui-calendar-month-weekstart { - border-left: none; -} - -.phui-calendar-date-number { - font-weight: normal; +table.phui-calendar-view a.phui-calendar-date-number { color: {$lightgreytext}; - padding: 4px; border-color: {$thinblueborder}; border-style: solid; - border-width: 0 0 1px 1px; - position: absolute; - background: #ffffff; - width: 16px; - height: 16px; + border-width: 1px 0 0 1px; + padding: 4px; + display: inline-block; + min-width: 16px; text-align: center; - top: 0; - right: 0; + background-color: #ffffff; +} + +table.phui-calendar-view td.phui-calendar-date-number-container { + font-weight: normal; + color: {$lightgreytext}; + border-width: 0 1px 0 1px; + text-align: right; } .phui-calendar-not-work-day { @@ -71,11 +67,30 @@ table.phui-calendar-view td.phui-calendar-month-weekstart { } .phui-calendar-view .phui-calendar-list { - padding: 8px; + padding: 1px; +} + +.phui-calendar-list-item.all-day span { + padding: 0; + margin: 0; +} + +.phui-calendar-view .phui-calendar-list li.phui-calendar-list-item.all-day { + margin: 0; + padding: 4px 8px; + background-color: {$lightpink}; +} + +li.phui-calendar-list-item.all-day:first-child { + margin-top: 0; +} + +.phui-calendar-view .phui-calendar-list li { + margin: 0 8px; } .phui-calendar-view .phui-calendar-list li:first-child { - margin-right: 16px; + margin-top: 8px; } .phui-calendar-view .phui-calendar-list-dot { @@ -95,6 +110,10 @@ table.phui-calendar-view td.phui-calendar-month-weekstart { word-break: break-word; } +li.phui-calendar-list-item.all-day .phui-calendar-list-title a{ + color: {$pink}; +} + .phui-calendar-view .phui-calendar-list-time { display: none; } From 8465ef779e3be671192c76b83327dba1b0186b00 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Mon, 11 May 2015 18:35:41 -0700 Subject: [PATCH 44/70] Cleaning up day view sidebar css Summary: Cleaning up day view sidebar css Test Plan: All day events in day view sidebar should look like links, not boxes Reviewers: chad, #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D12801 --- resources/celerity/map.php | 4 ++-- src/view/phui/calendar/PHUICalendarDayView.php | 8 +++++--- src/view/phui/calendar/PHUICalendarListView.php | 6 ------ webroot/rsrc/css/phui/calendar/phui-calendar-day.css | 4 ++-- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 9e18b8b5cc..642c17dea8 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -120,7 +120,7 @@ return array( 'rsrc/css/layout/phabricator-hovercard-view.css' => '44394670', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'c1db9e9c', 'rsrc/css/layout/phabricator-source-code-view.css' => '2ceee894', - 'rsrc/css/phui/calendar/phui-calendar-day.css' => '49037167', + 'rsrc/css/phui/calendar/phui-calendar-day.css' => '38891735', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '873e00da', 'rsrc/css/phui/calendar/phui-calendar.css' => '8675968e', @@ -777,7 +777,7 @@ return array( 'phui-box-css' => '7b3a2eed', 'phui-button-css' => 'de610129', 'phui-calendar-css' => '8675968e', - 'phui-calendar-day-css' => '49037167', + 'phui-calendar-day-css' => '38891735', 'phui-calendar-list-css' => 'c1d0ca59', 'phui-calendar-month-css' => '873e00da', 'phui-crumbs-view-css' => '594d719e', diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 7fdf44d297..35b41ffa52 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -268,7 +268,8 @@ final class PHUICalendarDayView extends AphrontView { } private function renderSidebarBox($events, $title) { - $widget = new PHUICalendarWidgetView(); + $widget = id(new PHUICalendarWidgetView()) + ->addClass('calendar-day-view-sidebar'); $list = id(new PHUICalendarListView()) ->setUser($this->user); @@ -276,7 +277,8 @@ final class PHUICalendarDayView extends AphrontView { if (count($events) == 0) { $list->showBlankState(true); } else { - foreach ($events as $event) { + $sorted_events = msort($events, 'getEpochStart'); + foreach ($sorted_events as $event) { $list->addEvent($event); } } @@ -388,7 +390,7 @@ final class PHUICalendarDayView extends AphrontView { $name = phutil_tag( 'a', array( - 'class' => 'all-day', + 'class' => 'day-view-all-day', 'href' => $event->getURI(), ), $event->getName()); diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php index 7e8aa956e0..fc40d87745 100644 --- a/src/view/phui/calendar/PHUICalendarListView.php +++ b/src/view/phui/calendar/PHUICalendarListView.php @@ -4,18 +4,12 @@ final class PHUICalendarListView extends AphrontTagView { private $events = array(); private $blankState; - private $isDayView = false; public function addEvent(AphrontCalendarEventView $event) { $this->events[] = $event; return $this; } - public function setIsDayView($is_day_view) { - $this->isDayView = $is_day_view; - return $this; - } - public function showBlankState($state) { $this->blankState = $state; return $this; diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css index 1b8b887199..11f4045fe1 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css @@ -49,7 +49,7 @@ color: {$greytext}; } -.all-day { +.day-view-all-day { border: 1px solid {$blueborder}; height: 12px; margin: 0; @@ -60,7 +60,7 @@ color: {$greytext}; } -.phui-calendar-day-event + .phui-calendar-day-event .all-day { +.phui-calendar-day-event + .phui-calendar-day-event .day-view-all-day { border-top-style: none; border-top-width: 0; } From 97fbc54d0bc788c938ca13d22fc9c51131381f44 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 12 May 2015 06:55:46 -0700 Subject: [PATCH 45/70] Implement PHUIHeaderView from AphrontTagView Summary: Looking at implementing ManiphestTaskListView as standard components, need to add this functionality for Headers. Test Plan: Browsed various pages, couldn't spot any regressions offhand. Reviewers: btrahan, epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D12802 --- src/__phutil_library_map__.php | 4 +-- src/view/phui/PHUIHeaderView.php | 42 +++++++++++++++++++------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3b422cc6d3..7056c0a3a3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1495,7 +1495,6 @@ phutil_register_library_map(array( 'PhabricatorCacheTTLGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php', 'PhabricatorCaches' => 'applications/cache/PhabricatorCaches.php', 'PhabricatorCalendarApplication' => 'applications/calendar/application/PhabricatorCalendarApplication.php', - 'PhabricatorCalendarBrowseController' => 'applications/calendar/controller/PhabricatorCalendarBrowseController.php', 'PhabricatorCalendarController' => 'applications/calendar/controller/PhabricatorCalendarController.php', 'PhabricatorCalendarDAO' => 'applications/calendar/storage/PhabricatorCalendarDAO.php', 'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php', @@ -4520,7 +4519,7 @@ phutil_register_library_map(array( 'PHUIHandleListView' => 'AphrontTagView', 'PHUIHandleTagListView' => 'AphrontTagView', 'PHUIHandleView' => 'AphrontView', - 'PHUIHeaderView' => 'AphrontView', + 'PHUIHeaderView' => 'AphrontTagView', 'PHUIIconExample' => 'PhabricatorUIExample', 'PHUIIconView' => 'AphrontTagView', 'PHUIImageMaskExample' => 'PhabricatorUIExample', @@ -4839,7 +4838,6 @@ phutil_register_library_map(array( 'PhabricatorCacheSpec' => 'Phobject', 'PhabricatorCacheTTLGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorCalendarApplication' => 'PhabricatorApplication', - 'PhabricatorCalendarBrowseController' => 'PhabricatorCalendarController', 'PhabricatorCalendarController' => 'PhabricatorController', 'PhabricatorCalendarDAO' => 'PhabricatorLiskDAO', 'PhabricatorCalendarEvent' => array( diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index ad3561d930..14159e500c 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -1,6 +1,6 @@ image) { + $classes[] = 'phui-header-has-image'; + } + + return array( + 'class' => $classes, + ); + } + + protected function getTagContent() { $image = null; if ($this->image) { $image = phutil_tag( @@ -156,7 +170,6 @@ final class PHUIHeaderView extends AphrontView { 'style' => 'background-image: url('.$this->image.')', ), ' '); - $classes[] = 'phui-header-has-image'; } $header = array(); @@ -243,20 +256,15 @@ final class PHUIHeaderView extends AphrontView { $property_list); } - return phutil_tag( - 'div', - array( - 'class' => implode(' ', $classes), - ), - array( - $image, - phutil_tag( - 'h1', - array( - 'class' => 'phui-header-view grouped', - ), - $header), - )); + return array( + $image, + phutil_tag( + 'h1', + array( + 'class' => 'phui-header-view grouped', + ), + $header), + ); } private function renderPolicyProperty(PhabricatorPolicyInterface $object) { From 7ef8da82590acecca1933800c0f5e469869dcd7c Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Tue, 12 May 2015 11:37:55 -0700 Subject: [PATCH 46/70] Conpherence + workboards - fix dragging behavior breaking on subsequent page loads Summary: Fixes T8155. The former "init_board()" function was aptly named as it needs to be called each time a new board dom piece is downloaded. Ergo, break out a setup() function and call that in the once-only setup place, and use init_board() there as well as when we have a quicksand redraw event with data from the server. Test Plan: paged about a project and was able to keep dragging and dropping tasks on various loads of the board. verified drops saved correctly from load to load. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8155 Differential Revision: https://secure.phabricator.com/D12798 --- resources/celerity/map.php | 22 +++++++++---------- .../projects/behavior-project-boards.js | 9 +++++++- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 642c17dea8..c20eeda15f 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -407,7 +407,7 @@ return array( 'rsrc/js/application/policy/behavior-policy-control.js' => '9a340b3d', 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c', 'rsrc/js/application/ponder/behavior-votebox.js' => '4e9b766b', - 'rsrc/js/application/projects/behavior-project-boards.js' => '60292820', + 'rsrc/js/application/projects/behavior-project-boards.js' => 'ba4fa35c', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', 'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb', 'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf', @@ -637,7 +637,7 @@ return array( 'javelin-behavior-policy-control' => '9a340b3d', 'javelin-behavior-policy-rule-editor' => '5e9f347c', 'javelin-behavior-ponder-votebox' => '4e9b766b', - 'javelin-behavior-project-boards' => '60292820', + 'javelin-behavior-project-boards' => 'ba4fa35c', 'javelin-behavior-project-create' => '065227cc', 'javelin-behavior-quicksand-blacklist' => '7927a7d3', 'javelin-behavior-refresh-csrf' => '7814b593', @@ -1247,15 +1247,6 @@ return array( 'javelin-workflow', 'javelin-stratcom', ), - 60292820 => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'javelin-stratcom', - 'javelin-workflow', - 'phabricator-draggable-list', - ), 60479091 => array( 'phabricator-busy', 'javelin-behavior', @@ -1729,6 +1720,15 @@ return array( 'javelin-dom', 'javelin-util', ), + 'ba4fa35c' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'javelin-stratcom', + 'javelin-workflow', + 'phabricator-draggable-list', + ), 'bba9eedf' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/application/projects/behavior-project-boards.js b/webroot/rsrc/js/application/projects/behavior-project-boards.js index 20a78d0963..04e99fde28 100644 --- a/webroot/rsrc/js/application/projects/behavior-project-boards.js +++ b/webroot/rsrc/js/application/projects/behavior-project-boards.js @@ -249,6 +249,9 @@ JX.behavior('project-boards', function(config, statics) { for (ii = 0; ii < lists.length; ii++) { lists[ii].setGroup(lists); } + } + + function setup() { JX.Stratcom.listen( 'click', @@ -336,6 +339,9 @@ JX.behavior('project-boards', function(config, statics) { statics.boardID = new_config.boardID; } update_statics(new_config); + if (data.fromServer) { + init_board(); + } }); return true; } @@ -345,7 +351,8 @@ JX.behavior('project-boards', function(config, statics) { var current_page_id = JX.Quicksand.getCurrentPageID(); statics.boardConfigCache = {}; statics.boardConfigCache[current_page_id] = config; - statics.setup = init_board(); + init_board(); + statics.setup = setup(); } }); From 8e9ee86357926b9f8378ad931f8d71014f125f01 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Tue, 12 May 2015 13:21:57 -0700 Subject: [PATCH 47/70] Calendar month view "today" indicator should be a blue bar across the bottom of the day cell Summary: Ref T4392, Calendar month view "today" indicator should be a blue bar across the bottom of the day cell Test Plan: Open month view of Calendar, today should have a blue bar across the bottom of the day cell Reviewers: chad, epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T4392 Differential Revision: https://secure.phabricator.com/D12815 --- resources/celerity/map.php | 4 +-- .../phui/calendar/PHUICalendarMonthView.php | 25 ++++++++++++++----- .../css/phui/calendar/phui-calendar-month.css | 18 ++++++------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c20eeda15f..9c0d2186ad 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -122,7 +122,7 @@ return array( 'rsrc/css/layout/phabricator-source-code-view.css' => '2ceee894', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '38891735', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59', - 'rsrc/css/phui/calendar/phui-calendar-month.css' => '873e00da', + 'rsrc/css/phui/calendar/phui-calendar-month.css' => '4c39f6d9', 'rsrc/css/phui/calendar/phui-calendar.css' => '8675968e', 'rsrc/css/phui/phui-action-header-view.css' => '89c497e7', 'rsrc/css/phui/phui-action-list.css' => '4f4d09f2', @@ -779,7 +779,7 @@ return array( 'phui-calendar-css' => '8675968e', 'phui-calendar-day-css' => '38891735', 'phui-calendar-list-css' => 'c1d0ca59', - 'phui-calendar-month-css' => '873e00da', + 'phui-calendar-month-css' => '4c39f6d9', 'phui-crumbs-view-css' => '594d719e', 'phui-document-view-css' => '94d5dcd8', 'phui-feed-story-css' => 'c9f3a0b5', diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index 6752637b09..407df512fb 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -75,7 +75,7 @@ final class PHUICalendarMonthView extends AphrontView { $empty_cell = array( 'list' => null, 'date' => null, - 'class' => 'phui-calendar-empty', + 'class' => null, ); for ($ii = 0; $ii < $empty; $ii++) { @@ -91,10 +91,6 @@ final class PHUICalendarMonthView extends AphrontView { $class = 'phui-calendar-month-day'; $weekday = $day->format('w'); - if ($day_number == $this->day) { - $class .= ' phui-calendar-today'; - } - if ($holiday || $weekday == 0 || $weekday == 6) { $class .= ' phui-calendar-not-work-day'; } @@ -188,16 +184,33 @@ final class PHUICalendarMonthView extends AphrontView { 'href' => $uri, ), $cell_day->format('j')); + } else { $cell_day = null; } + if ($cell['date'] && $cell['date']->format('j') == $this->day) { + $today_class = 'phui-calendar-today-slot phui-calendar-today'; + } else { + $today_class = 'phui-calendar-today-slot'; + } + + $today_slot = phutil_tag ( + 'div', + array( + 'class' => $today_class, + ), + null); + $cells[] = phutil_tag( 'td', array( 'class' => 'phui-calendar-date-number-container '.$class, ), - $cell_day); + array( + $cell_day, + $today_slot, + )); } $table[] = phutil_tag('tr', array(), $cells); } diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css index bd99ebf925..b4d1faa8fa 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css @@ -32,14 +32,10 @@ table.phui-calendar-view .phui-calendar-event-list { table.phui-calendar-view a.phui-calendar-date-number { color: {$lightgreytext}; - border-color: {$thinblueborder}; - border-style: solid; - border-width: 1px 0 0 1px; - padding: 4px; + padding: 0 4px; display: inline-block; min-width: 16px; text-align: center; - background-color: #ffffff; } table.phui-calendar-view td.phui-calendar-date-number-container { @@ -53,12 +49,16 @@ table.phui-calendar-view td.phui-calendar-date-number-container { background-color: {$lightgreybackground}; } -.phui-calendar-today { - background-color: {$lightgreen}; +.phui-calendar-today-slot { + display: block; + width: 100%; + height: 4px; + padding: 0; + margin: 0; } -.phui-calendar-empty { - background-color: {$greybackground}; +.phui-calendar-today-slot.phui-calendar-today { + background-color: {$lightblueborder}; } .phui-calendar-event-empty { From 048eb6cf61f6ac6e018f7f5dda289665cb86b5fb Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Tue, 12 May 2015 14:44:25 -0700 Subject: [PATCH 48/70] Conpherence - make show older messages work correctly in durable column Summary: Fixes T8009. This was basically moving the behavior from conpherence full behavior menu into the ConpherenceThreadManager so it could be re used in the durable column. The durable column bit has no special styles - its just a link - but it seems to work well enough. I think it could be removed altogether if / when we add some automagical scrolling back into history stuff. Test Plan: loaded up a conpherence in a durable column and used 'show older messages' successfully. loaded up a conpherence in regular view mid transaction and used show older and show newer successfully Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8009 Differential Revision: https://secure.phabricator.com/D12816 --- resources/celerity/map.php | 84 +++++++++---------- .../conpherence/ConpherenceThreadManager.js | 67 +++++++++++++-- .../conpherence/behavior-durable-column.js | 3 + .../application/conpherence/behavior-menu.js | 53 +----------- 4 files changed, 110 insertions(+), 97 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 9c0d2186ad..09912fe086 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => 'ed3d6355', - 'core.pkg.js' => '3ed82e2f', + 'core.pkg.js' => '616511ac', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', 'differential.pkg.js' => '895b8d62', @@ -348,10 +348,10 @@ return array( 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/calendar/event-all-day.js' => 'ca5fa62a', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '9e507b59', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'de397217', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', - 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'bc5107aa', - 'rsrc/js/application/conpherence/behavior-menu.js' => '804b0773', + 'rsrc/js/application/conpherence/behavior-durable-column.js' => '61252a27', + 'rsrc/js/application/conpherence/behavior-menu.js' => 'eb61cb03', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '93568464', @@ -516,7 +516,7 @@ return array( 'conpherence-menu-css' => 'f389e048', 'conpherence-message-pane-css' => '0e75feef', 'conpherence-notification-css' => 'd208f806', - 'conpherence-thread-manager' => '9e507b59', + 'conpherence-thread-manager' => 'de397217', 'conpherence-transaction-css' => '42a457f6', 'conpherence-update-css' => '1099a660', 'conpherence-widget-pane-css' => '2af42ebe', @@ -557,7 +557,7 @@ return array( 'javelin-behavior-choose-control' => '6153c708', 'javelin-behavior-config-reorder-fields' => '14a827de', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', - 'javelin-behavior-conpherence-menu' => '804b0773', + 'javelin-behavior-conpherence-menu' => 'eb61cb03', 'javelin-behavior-conpherence-pontificate' => '21ba5861', 'javelin-behavior-conpherence-widget-pane' => '93568464', 'javelin-behavior-countdown-timer' => 'e4cc26b3', @@ -584,7 +584,7 @@ return array( 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => '2b228192', 'javelin-behavior-doorkeeper-tag' => 'e5822781', - 'javelin-behavior-durable-column' => 'bc5107aa', + 'javelin-behavior-durable-column' => '61252a27', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-event-all-day' => 'ca5fa62a', 'javelin-behavior-fancy-datepicker' => '5c0f680f', @@ -1256,6 +1256,16 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + '61252a27' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-behavior-device', + 'javelin-scrollbar', + 'javelin-quicksand', + 'phabricator-keyboard-shortcut', + 'conpherence-thread-manager', + ), '6153c708' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1395,20 +1405,6 @@ return array( 'javelin-behavior', 'javelin-history', ), - '804b0773' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-behavior-device', - 'javelin-history', - 'javelin-vector', - 'javelin-scrollbar', - 'phabricator-title', - 'phabricator-shaped-request', - 'conpherence-thread-manager', - ), 82439934 => array( 'javelin-behavior', 'javelin-dom', @@ -1578,17 +1574,6 @@ return array( 'phuix-action-view', 'javelin-workflow', ), - '9e507b59' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-aphlict', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), '9f36c42d' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1734,16 +1719,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - 'bc5107aa' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-behavior-device', - 'javelin-scrollbar', - 'javelin-quicksand', - 'phabricator-keyboard-shortcut', - 'conpherence-thread-manager', - ), 'bd4c8dca' => array( 'javelin-install', 'javelin-util', @@ -1857,6 +1832,17 @@ return array( 'javelin-typeahead-ondemand-source', 'javelin-dom', ), + 'de397217' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-aphlict', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), 'df5e11d2' => array( 'javelin-install', ), @@ -1934,6 +1920,20 @@ return array( 'phabricator-phtize', 'javelin-dom', ), + 'eb61cb03' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-behavior-device', + 'javelin-history', + 'javelin-vector', + 'javelin-scrollbar', + 'phabricator-title', + 'phabricator-shaped-request', + 'conpherence-thread-manager', + ), 'efe49472' => array( 'javelin-install', 'javelin-util', diff --git a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js index 37dad6f2ba..8a534c9c01 100644 --- a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js +++ b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js @@ -30,6 +30,7 @@ JX.install('ConpherenceThreadManager', { _canEditLoadedThread: null, _updating: null, _minimalDisplay: false, + _messagesRootCallback: JX.bag, _willLoadThreadCallback: JX.bag, _didLoadThreadCallback: JX.bag, _didUpdateThreadCallback: JX.bag, @@ -99,6 +100,11 @@ JX.install('ConpherenceThreadManager', { return this; }, + setMessagesRootCallback: function(callback) { + this._messagesRootCallback = callback; + return this; + }, + setWillLoadThreadCallback: function(callback) { this._willLoadThreadCallback = callback; return this; @@ -183,6 +189,52 @@ JX.install('ConpherenceThreadManager', { this._updateThread(); })); + + JX.Stratcom.listen( + 'click', + 'show-older-messages', + JX.bind(this, function(e) { + e.kill(); + var data = e.getNodeData('show-older-messages'); + + var node = e.getNode('show-older-messages'); + JX.DOM.setContent(node, 'Loading...'); + JX.DOM.alterClass( + node, + 'conpherence-show-more-messages-loading', + true); + + new JX.Workflow(this._getMoreMessagesURI(), data) + .setHandler(JX.bind(this, function(r) { + JX.DOM.remove(node); + var messages = JX.$H(r.messages); + JX.DOM.prependContent( + this._messagesRootCallback(), + messages); + })).start(); + })); + JX.Stratcom.listen( + 'click', + 'show-newer-messages', + JX.bind(this, function(e) { + e.kill(); + var data = e.getNodeData('show-newer-messages'); + var node = e.getNode('show-newer-messages'); + JX.DOM.setContent(node, 'Loading...'); + JX.DOM.alterClass( + node, + 'conpherence-show-more-messages-loading', + true); + + new JX.Workflow(this._getMoreMessagesURI(), data) + .setHandler(JX.bind(this, function(r) { + JX.DOM.remove(node); + var messages = JX.$H(r.messages); + JX.DOM.appendContent( + this._messagesRootCallback(), + JX.$H(messages)); + })).start(); + })); }, _shouldUpdateDOM: function(r) { @@ -216,9 +268,7 @@ JX.install('ConpherenceThreadManager', { action: 'load', }); - var uri = '/conpherence/update/' + this._loadedThreadID + '/'; - - var workflow = new JX.Workflow(uri) + var workflow = new JX.Workflow(this._getUpdateURI()) .setData(params) .setHandler(JX.bind(this, function(r) { if (this._shouldUpdateDOM(r)) { @@ -350,9 +400,8 @@ JX.install('ConpherenceThreadManager', { var data = e.getNodeData('tag:form'); if (!data.preview) { - var uri = '/conpherence/update/' + this._loadedThreadID + '/'; data.preview = new JX.PhabricatorShapedRequest( - uri, + this._getUpdateURI(), JX.bag, JX.bind(this, function () { var data = JX.DOM.convertFormToDictionary(form); @@ -362,6 +411,14 @@ JX.install('ConpherenceThreadManager', { })); } data.preview.trigger(); + }, + + _getUpdateURI: function() { + return '/conpherence/update/' + this._loadedThreadID + '/'; + }, + + _getMoreMessagesURI: function() { + return '/conpherence/' + this._loadedThreadID + '/'; } }, diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js index edaed9d2da..78ed506da9 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js +++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js @@ -119,6 +119,9 @@ JX.behavior('durable-column', function(config, statics) { var threadManager = new JX.ConpherenceThreadManager(); threadManager.setMinimalDisplay(true); + threadManager.setMessagesRootCallback(function() { + return _getColumnMessagesNode(); + }); threadManager.setLoadThreadURI('/conpherence/columnview/'); threadManager.setWillLoadThreadCallback(function() { _markLoading(true); diff --git a/webroot/rsrc/js/application/conpherence/behavior-menu.js b/webroot/rsrc/js/application/conpherence/behavior-menu.js index 26e184813e..e8621cc990 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-menu.js +++ b/webroot/rsrc/js/application/conpherence/behavior-menu.js @@ -28,6 +28,9 @@ JX.behavior('conpherence-menu', function(config) { // TODO - move more logic into the ThreadManager var threadManager = new JX.ConpherenceThreadManager(); + threadManager.setMessagesRootCallback(function() { + return scrollbar.getContentNode(); + }); threadManager.setWillLoadThreadCallback(function() { markThreadLoading(true); }); @@ -415,56 +418,6 @@ JX.behavior('conpherence-menu', function(config) { .start(); }); - var _oldLoadingTransactionID = null; - JX.Stratcom.listen('click', 'show-older-messages', function(e) { - e.kill(); - var data = e.getNodeData('show-older-messages'); - if (data.oldest_transaction_id == _oldLoadingTransactionID) { - return; - } - _oldLoadingTransactionID = data.oldest_transaction_id; - - var node = e.getNode('show-older-messages'); - JX.DOM.setContent(node, 'Loading...'); - JX.DOM.alterClass(node, 'conpherence-show-more-messages-loading', true); - - var conf_id = _thread.selected; - var messages_root = scrollbar.getContentNode(); - new JX.Workflow(config.baseURI + conf_id + '/', data) - .setHandler(function(r) { - JX.DOM.remove(node); - var messages = JX.$H(r.messages); - JX.DOM.prependContent( - messages_root, - JX.$H(messages)); - }).start(); - }); - - var _newLoadingTransactionID = null; - JX.Stratcom.listen('click', 'show-newer-messages', function(e) { - e.kill(); - var data = e.getNodeData('show-newer-messages'); - if (data.newest_transaction_id == _newLoadingTransactionID) { - return; - } - _newLoadingTransactionID = data.newest_transaction_id; - - var node = e.getNode('show-newer-messages'); - JX.DOM.setContent(node, 'Loading...'); - JX.DOM.alterClass(node, 'conpherence-show-more-messages-loading', true); - - var conf_id = _thread.selected; - var messages_root = scrollbar.getContentNode(); - new JX.Workflow(config.baseURI + conf_id + '/', data) - .setHandler(function(r) { - JX.DOM.remove(node); - var messages = JX.$H(r.messages); - JX.DOM.appendContent( - messages_root, - JX.$H(messages)); - }).start(); - }); - /** * On devices, we just show a thread list, so we don't want to automatically * select or load any threads. On desktop, we automatically select the first From 7bfbc46a635ca0872464c4aefd6093bebe89aac1 Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Tue, 12 May 2015 14:44:47 -0700 Subject: [PATCH 49/70] Quicksand - add phame live URLs to quicksand blacklist Summary: Fixes T8160. AFAIK this is the only route pattern that needs blacklisting. Double checked that the resource controller is good to go; it is because its a celerity resource controller descendant and returns data differently than normal controllers Test Plan: Clicked "view live" on a block. Read a few posts. Clicked into a post and read it. Clicked an image and it linked to the image. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8160 Differential Revision: https://secure.phabricator.com/D12817 --- .../phame/application/PhabricatorPhameApplication.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/phame/application/PhabricatorPhameApplication.php b/src/applications/phame/application/PhabricatorPhameApplication.php index b466ad5191..4d8cebffa0 100644 --- a/src/applications/phame/application/PhabricatorPhameApplication.php +++ b/src/applications/phame/application/PhabricatorPhameApplication.php @@ -69,4 +69,10 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { ); } + public function getQuicksandURIPatternBlacklist() { + return array( + '/phame/live/.*', + ); + } + } From 20b7308dfb9fa927de1526a41bda7ca3e881a371 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Tue, 12 May 2015 17:56:52 -0700 Subject: [PATCH 50/70] Month view should adjust to display badges with event count instead of event list on mobile and tablets. Summary: Ref T4392, Month view should adjust to display badges with event count instead of event list on mobile and tablets. Test Plan: Open month view, adjust browser as mobile size, event lists should turn into badges with event counts and calendar days should be links to day views of those days. Reviewers: epriestley, #blessed_reviewers, chad Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T4392 Differential Revision: https://secure.phabricator.com/D12820 --- resources/celerity/map.php | 4 +- .../phui/calendar/PHUICalendarMonthView.php | 82 ++++++++++++++++--- .../css/phui/calendar/phui-calendar-month.css | 52 +++++++++++- 3 files changed, 124 insertions(+), 14 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 09912fe086..0d1ec02ee0 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -122,7 +122,7 @@ return array( 'rsrc/css/layout/phabricator-source-code-view.css' => '2ceee894', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '38891735', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59', - 'rsrc/css/phui/calendar/phui-calendar-month.css' => '4c39f6d9', + 'rsrc/css/phui/calendar/phui-calendar-month.css' => '75e6a2ee', 'rsrc/css/phui/calendar/phui-calendar.css' => '8675968e', 'rsrc/css/phui/phui-action-header-view.css' => '89c497e7', 'rsrc/css/phui/phui-action-list.css' => '4f4d09f2', @@ -779,7 +779,7 @@ return array( 'phui-calendar-css' => '8675968e', 'phui-calendar-day-css' => '38891735', 'phui-calendar-list-css' => 'c1d0ca59', - 'phui-calendar-month-css' => '4c39f6d9', + 'phui-calendar-month-css' => '75e6a2ee', 'phui-crumbs-view-css' => '594d719e', 'phui-document-view-css' => '94d5dcd8', 'phui-feed-story-css' => 'c9f3a0b5', diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index 407df512fb..573ee3ba8b 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -75,6 +75,8 @@ final class PHUICalendarMonthView extends AphrontView { $empty_cell = array( 'list' => null, 'date' => null, + 'uri' => null, + 'count' => 0, 'class' => null, ); @@ -137,9 +139,16 @@ final class PHUICalendarMonthView extends AphrontView { $list->addEvent($item); } + $uri = $this->getBrowseURI(); + $uri = $uri.$day->format('Y').'/'. + $day->format('m').'/'. + $day->format('d').'/'; + $markup[] = array( 'list' => $list, 'date' => $day, + 'uri' => $uri, + 'count' => count($all_day_events) + count($list_events), 'class' => $class, ); } @@ -149,33 +158,78 @@ final class PHUICalendarMonthView extends AphrontView { foreach ($rows as $row) { $cells = array(); + $event_count_badge = null; + while (count($row) < 7) { $row[] = $empty_cell; } foreach ($row as $cell) { $cell_list = $cell['list']; $class = $cell['class']; + $uri = $cell['uri']; + $count = $cell['count']; + + $event_count = null; + if ($count > 0) { + $event_count = phutil_tag( + 'div', + array( + 'class' => 'phui-calendar-month-count-badge', + ), + $count); + } + + $event_count_badge = phutil_tag( + 'div', + array( + 'class' => 'phui-calendar-month-event-count', + ), + $event_count); + + $cell_day_secret_link = phutil_tag( + 'a', + array( + 'class' => 'phui-calendar-month-secret-link', + 'href' => $uri, + ), + null); + + $cell_div = phutil_tag( + 'div', + array( + 'class' => 'phui-calendar-month-cell-div', + ), + array( + $cell_day_secret_link, + $event_count_badge, + $cell_list, + )); + $cells[] = phutil_tag( 'td', array( 'class' => 'phui-calendar-month-event-list '.$class, ), - $cell_list); + $cell_div); } $table[] = phutil_tag('tr', array(), $cells); $cells = array(); foreach ($row as $cell) { $class = $cell['class']; + $cell_day_secret_link = null; if ($cell['date']) { $cell_day = $cell['date']; + $uri = $cell['uri']; - $uri = $this->getBrowseURI(); - $date = $cell['date']; - $uri = $uri.$date->format('Y').'/'. - $date->format('m').'/'. - $date->format('d').'/'; + $cell_day_secret_link = phutil_tag( + 'a', + array( + 'class' => 'phui-calendar-month-secret-link', + 'href' => $uri, + ), + null); $cell_day = phutil_tag( 'a', @@ -202,15 +256,23 @@ final class PHUICalendarMonthView extends AphrontView { ), null); + $cell_div = phutil_tag( + 'div', + array( + 'class' => 'phui-calendar-month-cell-div', + ), + array( + $cell_day_secret_link, + $cell_day, + $today_slot, + )); + $cells[] = phutil_tag( 'td', array( 'class' => 'phui-calendar-date-number-container '.$class, ), - array( - $cell_day, - $today_slot, - )); + $cell_div); } $table[] = phutil_tag('tr', array(), $cells); } diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css index b4d1faa8fa..3f53e89b25 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css @@ -22,12 +22,55 @@ table.phui-calendar-view td { width: 14.2857%; /* This is one seventh, approximately. */ } +.phui-calendar-month-cell-div { + position: relative; +} + +.phui-calendar-month-event-list .phui-calendar-month-cell-div { + min-height: 125px; +} + +.device .phui-calendar-month-event-list .phui-calendar-month-cell-div { + min-height: 60px; +} + +a.phui-calendar-month-secret-link { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + outline: 0; +} + table.phui-calendar-view tr td:first-child { border-left-width: 0px; } -table.phui-calendar-view .phui-calendar-event-list { - min-height: 125px; +.device table.phui-calendar-view .phui-calendar-event-list { + display: none; +} + +.phui-calendar-month-event-count { + display: none; +} + +.device .phui-calendar-month-event-count { + display: block; + text-align: center; + padding-top: 12px; +} + +.phui-calendar-month-event-count .phui-calendar-month-count-badge { + border: 1px solid {$lightgreyborder}; + color: {$lightgreytext}; + width: 20px; + height: 20px; + border-radius: 50%; + text-align: center; + vertical-align: middle; + padding: 5px 3px 0px 3px; + margin: 0 auto; } table.phui-calendar-view a.phui-calendar-date-number { @@ -79,6 +122,8 @@ table.phui-calendar-view td.phui-calendar-date-number-container { margin: 0; padding: 4px 8px; background-color: {$lightpink}; + display: block; + float: none; } li.phui-calendar-list-item.all-day:first-child { @@ -87,6 +132,9 @@ li.phui-calendar-list-item.all-day:first-child { .phui-calendar-view .phui-calendar-list li { margin: 0 8px; + display: inline-block; + float: left; + clear: both; } .phui-calendar-view .phui-calendar-list li:first-child { From 6c049d06ce4d9d1cf4701c4a66fbc53e09954d05 Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Wed, 13 May 2015 11:06:54 -0700 Subject: [PATCH 51/70] Conpherence - change message rendering logic to eradicate possibility of duplicates Summary: Fixes T6713. Before this diff, we would update the DOM when various requests came back, but the logic to erase race conditions proved too tricky for me to get right. Instead, change the algorithm up and keep a set of transaction ids around per thread. When its time to update the transactions, sort the list of ids and just render the whole darn set again. To make this work, this ends up adding transacton ids to fake transactons like "show older" and date markers. This is able to work by using a float sort and giving these transactions ids that are .5 from being an integer and in the right place numerically. Test Plan: for durable column, clicked show older and it worked. sent a message and it worked. for main view, clicked show older and it worked. sent a message and it worked. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T6713 Differential Revision: https://secure.phabricator.com/D12819 --- resources/celerity/map.php | 84 +++++++-------- .../ConpherenceTransactionRenderer.php | 19 ++++ .../controller/ConpherenceViewController.php | 4 +- .../view/ConpherenceTransactionView.php | 12 ++- .../conpherence/ConpherenceThreadManager.js | 101 +++++++++++++++--- .../conpherence/behavior-durable-column.js | 2 - .../application/conpherence/behavior-menu.js | 8 +- 7 files changed, 162 insertions(+), 68 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0d1ec02ee0..3afab6cdb9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => 'ed3d6355', - 'core.pkg.js' => '616511ac', + 'core.pkg.js' => 'ac41c400', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', 'differential.pkg.js' => '895b8d62', @@ -348,10 +348,10 @@ return array( 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/calendar/event-all-day.js' => 'ca5fa62a', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'de397217', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'b7342ddb', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', - 'rsrc/js/application/conpherence/behavior-durable-column.js' => '61252a27', - 'rsrc/js/application/conpherence/behavior-menu.js' => 'eb61cb03', + 'rsrc/js/application/conpherence/behavior-durable-column.js' => '16c695bf', + 'rsrc/js/application/conpherence/behavior-menu.js' => '4351c4a0', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '93568464', @@ -516,7 +516,7 @@ return array( 'conpherence-menu-css' => 'f389e048', 'conpherence-message-pane-css' => '0e75feef', 'conpherence-notification-css' => 'd208f806', - 'conpherence-thread-manager' => 'de397217', + 'conpherence-thread-manager' => 'b7342ddb', 'conpherence-transaction-css' => '42a457f6', 'conpherence-update-css' => '1099a660', 'conpherence-widget-pane-css' => '2af42ebe', @@ -557,7 +557,7 @@ return array( 'javelin-behavior-choose-control' => '6153c708', 'javelin-behavior-config-reorder-fields' => '14a827de', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', - 'javelin-behavior-conpherence-menu' => 'eb61cb03', + 'javelin-behavior-conpherence-menu' => '4351c4a0', 'javelin-behavior-conpherence-pontificate' => '21ba5861', 'javelin-behavior-conpherence-widget-pane' => '93568464', 'javelin-behavior-countdown-timer' => 'e4cc26b3', @@ -584,7 +584,7 @@ return array( 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => '2b228192', 'javelin-behavior-doorkeeper-tag' => 'e5822781', - 'javelin-behavior-durable-column' => '61252a27', + 'javelin-behavior-durable-column' => '16c695bf', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-event-all-day' => 'ca5fa62a', 'javelin-behavior-fancy-datepicker' => '5c0f680f', @@ -930,6 +930,16 @@ return array( 'javelin-request', 'javelin-util', ), + '16c695bf' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-behavior-device', + 'javelin-scrollbar', + 'javelin-quicksand', + 'phabricator-keyboard-shortcut', + 'conpherence-thread-manager', + ), '1ad0a787' => array( 'javelin-install', 'javelin-reactor', @@ -1073,6 +1083,20 @@ return array( 'javelin-dom', 'javelin-request', ), + '4351c4a0' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-behavior-device', + 'javelin-history', + 'javelin-vector', + 'javelin-scrollbar', + 'phabricator-title', + 'phabricator-shaped-request', + 'conpherence-thread-manager', + ), '44168bad' => array( 'javelin-behavior', 'javelin-dom', @@ -1256,16 +1280,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - '61252a27' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-behavior-device', - 'javelin-scrollbar', - 'javelin-quicksand', - 'phabricator-keyboard-shortcut', - 'conpherence-thread-manager', - ), '6153c708' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1705,6 +1719,17 @@ return array( 'javelin-dom', 'javelin-util', ), + 'b7342ddb' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-aphlict', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), 'ba4fa35c' => array( 'javelin-behavior', 'javelin-dom', @@ -1832,17 +1857,6 @@ return array( 'javelin-typeahead-ondemand-source', 'javelin-dom', ), - 'de397217' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-aphlict', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), 'df5e11d2' => array( 'javelin-install', ), @@ -1920,20 +1934,6 @@ return array( 'phabricator-phtize', 'javelin-dom', ), - 'eb61cb03' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-behavior-device', - 'javelin-history', - 'javelin-vector', - 'javelin-scrollbar', - 'phabricator-title', - 'phabricator-shaped-request', - 'conpherence-thread-manager', - ), 'efe49472' => array( 'javelin-install', 'javelin-util', diff --git a/src/applications/conpherence/ConpherenceTransactionRenderer.php b/src/applications/conpherence/ConpherenceTransactionRenderer.php index d6be83fb65..1b14f5f0d6 100644 --- a/src/applications/conpherence/ConpherenceTransactionRenderer.php +++ b/src/applications/conpherence/ConpherenceTransactionRenderer.php @@ -90,6 +90,7 @@ final class ConpherenceTransactionRenderer { if ($previous_day != $current_day) { $date_marker_transaction->setDateCreated( $transaction->getDateCreated()); + $date_marker_transaction->setID($previous_transaction->getID()); $rendered_transactions[] = $date_marker_transaction_view->render(); } } @@ -144,6 +145,15 @@ final class ConpherenceTransactionRenderer { ), ), pht('Show Older Messages')); + $oldscrollbutton = javelin_tag( + 'div', + array( + 'sigil' => 'conpherence-transaction-view', + 'meta' => array( + 'id' => $oldest_transaction_id - 0.5, + ), + ), + $oldscrollbutton); } $newscrollbutton = ''; @@ -160,6 +170,15 @@ final class ConpherenceTransactionRenderer { ), ), pht('Show Newer Messages')); + $newscrollbutton = javelin_tag( + 'div', + array( + 'sigil' => 'conpherence-transaction-view', + 'meta' => array( + 'id' => $newest_transaction_id + 0.5, + ), + ), + $newscrollbutton); } return hsprintf( diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 39138a40b4..6293c1bbdd 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -79,7 +79,7 @@ final class ConpherenceViewController extends if ($before_transaction_id || $after_transaction_id) { $header = null; $form = null; - $content = array('messages' => $messages); + $content = array('transactions' => $messages); } else { $policy_objects = id(new PhabricatorPolicyQuery()) ->setViewer($user) @@ -89,7 +89,7 @@ final class ConpherenceViewController extends $form = $this->renderFormContent(); $content = array( 'header' => $header, - 'messages' => $messages, + 'transactions' => $messages, 'form' => $form, ); } diff --git a/src/applications/conpherence/view/ConpherenceTransactionView.php b/src/applications/conpherence/view/ConpherenceTransactionView.php index 3ed5fa14eb..2dba770e54 100644 --- a/src/applications/conpherence/view/ConpherenceTransactionView.php +++ b/src/applications/conpherence/view/ConpherenceTransactionView.php @@ -103,10 +103,14 @@ final class ConpherenceTransactionView extends AphrontView { $transaction = $this->getConpherenceTransaction(); switch ($transaction->getTransactionType()) { case ConpherenceTransactionType::TYPE_DATE_MARKER: - return phutil_tag( + return javelin_tag( 'div', array( 'class' => 'conpherence-transaction-view date-marker', + 'sigil' => 'conpherence-transaction-view', + 'meta' => array( + 'id' => $transaction->getID() + 0.5, + ), ), array( phutil_tag( @@ -134,11 +138,15 @@ final class ConpherenceTransactionView extends AphrontView { 'conpherence-transaction-header grouped', array($actions, $info)); - return phutil_tag( + return javelin_tag( 'div', array( 'class' => 'conpherence-transaction-view '.$classes, 'id' => $transaction_id, + 'sigil' => 'conpherence-transaction-view', + 'meta' => array( + 'id' => $transaction->getID(), + ), ), array( $image, diff --git a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js index 8a534c9c01..c1c47c5c78 100644 --- a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js +++ b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js @@ -27,6 +27,8 @@ JX.install('ConpherenceThreadManager', { _loadedThreadID: null, _loadedThreadPHID: null, _latestTransactionID: null, + _transactionIDMap: null, + _transactionCache: null, _canEditLoadedThread: null, _updating: null, _minimalDisplay: false, @@ -83,6 +85,59 @@ JX.install('ConpherenceThreadManager', { return this; }, + _updateTransactionIDMap: function(transactions) { + var loaded_id = this.getLoadedThreadID(); + if (!this._transactionIDMap[loaded_id]) { + this._transactionIDMap[this._loadedThreadID] = {}; + } + var loaded_transaction_ids = this._transactionIDMap[loaded_id]; + var transaction; + for (var ii = 0; ii < transactions.length; ii++) { + transaction = transactions[ii]; + loaded_transaction_ids[JX.Stratcom.getData(transaction).id] = 1; + } + this._transactionIDMap[this._loadedThreadID] = loaded_transaction_ids; + return this; + }, + + _updateTransactionCache: function(transactions) { + var transaction; + for (var ii = 0; ii < transactions.length; ii++) { + transaction = transactions[ii]; + this._transactionCache[JX.Stratcom.getData(transaction).id] = + transaction; + } + return this; + }, + + _getLoadedTransactions: function() { + var loaded_id = this.getLoadedThreadID(); + var loaded_tx_ids = JX.keys(this._transactionIDMap[loaded_id]); + loaded_tx_ids.sort(function (a, b) { + var x = parseFloat(a); + var y = parseFloat(b); + if (x > y) { + return 1; + } + if (x < y) { + return -1; + } + return 0; + }); + var transactions = []; + for (var ii = 0; ii < loaded_tx_ids.length; ii++) { + transactions.push(this._transactionCache[loaded_tx_ids[ii]]); + } + return transactions; + }, + + _deleteTransactionCaches: function(id) { + delete this._transactionCache[id]; + delete this._transactionIDMap[this._loadedThreadID][id]; + + return this; + }, + setCanEditLoadedThread: function(bool) { this._canEditLoadedThread = bool; return this; @@ -151,6 +206,10 @@ JX.install('ConpherenceThreadManager', { }, start: function() { + + this._transactionIDMap = {}; + this._transactionCache = {}; + JX.Stratcom.listen( 'aphlict-server-message', null, @@ -206,11 +265,9 @@ JX.install('ConpherenceThreadManager', { new JX.Workflow(this._getMoreMessagesURI(), data) .setHandler(JX.bind(this, function(r) { + this._deleteTransactionCaches(JX.Stratcom.getData(node).id); JX.DOM.remove(node); - var messages = JX.$H(r.messages); - JX.DOM.prependContent( - this._messagesRootCallback(), - messages); + this._updateTransactions(r); })).start(); })); JX.Stratcom.listen( @@ -228,11 +285,9 @@ JX.install('ConpherenceThreadManager', { new JX.Workflow(this._getMoreMessagesURI(), data) .setHandler(JX.bind(this, function(r) { + this._deleteTransactionCaches(JX.Stratcom.getData(node).id); JX.DOM.remove(node); - var messages = JX.$H(r.messages); - JX.DOM.appendContent( - this._messagesRootCallback(), - JX.$H(messages)); + this._updateTransactions(r); })).start(); })); }, @@ -254,7 +309,9 @@ JX.install('ConpherenceThreadManager', { return true; }, - _markUpdated: function(r) { + _updateDOM: function(r) { + this._updateTransactions(r); + this._updating.knownID = r.latest_transaction_id; this._latestTransactionID = r.latest_transaction_id; JX.Stratcom.invoke( @@ -263,6 +320,16 @@ JX.install('ConpherenceThreadManager', { r.aphlictDropdownData); }, + _updateTransactions: function(r) { + var new_transactions = JX.$H(r.transactions).getFragment().childNodes; + this._updateTransactionIDMap(new_transactions); + this._updateTransactionCache(new_transactions); + + var transactions = this._getLoadedTransactions(); + + JX.DOM.setContent(this._messagesRootCallback(), transactions); + }, + _updateThread: function() { var params = this._getParams({ action: 'load', @@ -272,8 +339,7 @@ JX.install('ConpherenceThreadManager', { .setData(params) .setHandler(JX.bind(this, function(r) { if (this._shouldUpdateDOM(r)) { - this._markUpdated(r); - + this._updateDOM(r); this._didUpdateThreadCallback(r); } })); @@ -306,8 +372,7 @@ JX.install('ConpherenceThreadManager', { .setData(params) .setHandler(JX.bind(this, function(r) { if (this._shouldUpdateDOM(r)) { - this._markUpdated(r); - + this._updateDOM(r); this._didUpdateWorkflowCallback(r); } })); @@ -357,6 +422,13 @@ JX.install('ConpherenceThreadManager', { r.aphlictDropdownData); this._didLoadThreadCallback(r); + var messages_root = this._messagesRootCallback(); + var messages = JX.DOM.scry( + messages_root, + 'div', + 'conpherence-transaction-view'); + this._updateTransactionIDMap(messages); + this._updateTransactionCache(messages); if (force_reload) { JX.Stratcom.invoke('hashchange'); @@ -383,8 +455,7 @@ JX.install('ConpherenceThreadManager', { var workflow = JX.Workflow.newFromForm(form, params, keep_enabled) .setHandler(JX.bind(this, function(r) { if (this._shouldUpdateDOM(r)) { - this._markUpdated(r); - + this._updateDOM(r); this._didSendMessageCallback(r); } else if (r.non_update) { this._didSendMessageCallback(r, true); diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js index 78ed506da9..4a9eac1eaf 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js +++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js @@ -159,7 +159,6 @@ JX.behavior('durable-column', function(config, statics) { return; } var messages = _getColumnMessagesNode(); - JX.DOM.appendContent(messages, JX.$H(r.transactions)); scrollbar.scrollTo(messages.scrollHeight); }); @@ -168,7 +167,6 @@ JX.behavior('durable-column', function(config, statics) { }); threadManager.setDidUpdateWorkflowCallback(function(r) { var messages = _getColumnMessagesNode(); - JX.DOM.appendContent(messages, JX.$H(r.transactions)); scrollbar.scrollTo(messages.scrollHeight); JX.DOM.setContent(_getColumnTitleNode(), r.conpherence_title); }); diff --git a/webroot/rsrc/js/application/conpherence/behavior-menu.js b/webroot/rsrc/js/application/conpherence/behavior-menu.js index e8621cc990..2233b3ff28 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-menu.js +++ b/webroot/rsrc/js/application/conpherence/behavior-menu.js @@ -36,7 +36,7 @@ JX.behavior('conpherence-menu', function(config) { }); threadManager.setDidLoadThreadCallback(function(r) { var header = JX.$H(r.header); - var messages = JX.$H(r.messages); + var messages = JX.$H(r.transactions); var form = JX.$H(r.form); var root = JX.DOM.find(document, 'div', 'conpherence-layout'); var header_root = JX.DOM.find(root, 'div', 'conpherence-header-pane'); @@ -51,7 +51,6 @@ JX.behavior('conpherence-menu', function(config) { }); threadManager.setDidUpdateThreadCallback(function(r) { - JX.DOM.appendContent(scrollbar.getContentNode(), JX.$H(r.transactions)); _scrollMessageWindow(); }); @@ -73,14 +72,13 @@ JX.behavior('conpherence-menu', function(config) { } catch (ex) { // Ignore; maybe no files widget } - JX.DOM.appendContent(scrollbar.getContentNode(), JX.$H(r.transactions)); - _scrollMessageWindow(); - if (fileWidget) { JX.DOM.setContent( fileWidget, JX.$H(r.file_widget)); } + + _scrollMessageWindow(); textarea.value = ''; } markThreadLoading(false); From ae32d1afb87aafd420a8495b50dcc567164ac098 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 May 2015 06:16:08 -0700 Subject: [PATCH 52/70] Remove unused file transforms Summary: Ref T7707. - Modernize the file transform endpoint a bit. - Delete transforms which are no longer used in the product. Test Plan: - Used Pholio (navigation, inline thumbs). - Uploaded images (embed thumb). - Changed profile picture (profile thumb). Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7707 Differential Revision: https://secure.phabricator.com/D12807 --- src/__phutil_library_map__.php | 2 - .../PhabricatorFileTransformController.php | 60 ++++++------------ .../files/storage/PhabricatorFile.php | 12 ---- .../PhabricatorPholioApplication.php | 1 - .../PholioInlineThumbController.php | 46 -------------- .../icon/fatcow/thumbnails/default160x120.png | Bin 1006 -> 0 bytes .../icon/fatcow/thumbnails/default60x45.png | Bin 762 -> 0 bytes .../icon/fatcow/thumbnails/image160x120.png | Bin 1459 -> 0 bytes .../icon/fatcow/thumbnails/image60x45.png | Bin 1179 -> 0 bytes .../icon/fatcow/thumbnails/pdf160x120.png | Bin 1654 -> 0 bytes .../image/icon/fatcow/thumbnails/pdf60x45.png | Bin 1378 -> 0 bytes .../icon/fatcow/thumbnails/zip160x120.png | Bin 1383 -> 0 bytes .../image/icon/fatcow/thumbnails/zip60x45.png | Bin 1078 -> 0 bytes 13 files changed, 20 insertions(+), 101 deletions(-) delete mode 100644 src/applications/pholio/controller/PholioInlineThumbController.php delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/default160x120.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/default60x45.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/image160x120.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/image60x45.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/pdf160x120.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/pdf60x45.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/zip160x120.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/zip60x45.png diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7056c0a3a3..2db8ddee4d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2824,7 +2824,6 @@ phutil_register_library_map(array( 'PholioImageUploadController' => 'applications/pholio/controller/PholioImageUploadController.php', 'PholioInlineController' => 'applications/pholio/controller/PholioInlineController.php', 'PholioInlineListController' => 'applications/pholio/controller/PholioInlineListController.php', - 'PholioInlineThumbController' => 'applications/pholio/controller/PholioInlineThumbController.php', 'PholioMock' => 'applications/pholio/storage/PholioMock.php', 'PholioMockCommentController' => 'applications/pholio/controller/PholioMockCommentController.php', 'PholioMockEditController' => 'applications/pholio/controller/PholioMockEditController.php', @@ -6311,7 +6310,6 @@ phutil_register_library_map(array( 'PholioImageUploadController' => 'PholioController', 'PholioInlineController' => 'PholioController', 'PholioInlineListController' => 'PholioController', - 'PholioInlineThumbController' => 'PholioController', 'PholioMock' => array( 'PholioDAO', 'PhabricatorMarkupInterface', diff --git a/src/applications/files/controller/PhabricatorFileTransformController.php b/src/applications/files/controller/PhabricatorFileTransformController.php index 248a6dddd2..e3a939d45c 100644 --- a/src/applications/files/controller/PhabricatorFileTransformController.php +++ b/src/applications/files/controller/PhabricatorFileTransformController.php @@ -3,43 +3,36 @@ final class PhabricatorFileTransformController extends PhabricatorFileController { - private $transform; - private $phid; - private $key; - public function shouldRequireLogin() { return false; } - public function willProcessRequest(array $data) { - $this->transform = $data['transform']; - $this->phid = $data['phid']; - $this->key = $data['key']; - } - - public function processRequest() { - $viewer = $this->getRequest()->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); // NOTE: This is a public/CDN endpoint, and permission to see files is // controlled by knowing the secret key, not by authentication. + $source_phid = $request->getURIData('phid'); $file = id(new PhabricatorFileQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs(array($this->phid)) + ->withPHIDs(array($source_phid)) ->executeOne(); if (!$file) { return new Aphront404Response(); } - if (!$file->validateSecretKey($this->key)) { + $secret_key = $request->getURIData('key'); + if (!$file->validateSecretKey($secret_key)) { return new Aphront403Response(); } + $transform = $request->getURIData('transform'); $xform = id(new PhabricatorTransformedFile()) ->loadOneWhere( 'originalPHID = %s AND transform = %s', - $this->phid, - $this->transform); + $source_phid, + $transform); if ($xform) { return $this->buildTransformedFileResponse($xform); @@ -48,35 +41,26 @@ final class PhabricatorFileTransformController $type = $file->getMimeType(); if (!$file->isViewableInBrowser() || !$file->isTransformableImage()) { - return $this->buildDefaultTransformation($file); + return $this->buildDefaultTransformation($file, $transform); } // We're essentially just building a cache here and don't need CSRF // protection. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - switch ($this->transform) { + switch ($transform) { case 'thumb-profile': $xformed_file = $this->executeThumbTransform($file, 50, 50); break; case 'thumb-280x210': $xformed_file = $this->executeThumbTransform($file, 280, 210); break; - case 'thumb-220x165': - $xformed_file = $this->executeThumbTransform($file, 220, 165); - break; case 'preview-100': $xformed_file = $this->executePreviewTransform($file, 100); break; case 'preview-220': $xformed_file = $this->executePreviewTransform($file, 220); break; - case 'thumb-160x120': - $xformed_file = $this->executeThumbTransform($file, 160, 120); - break; - case 'thumb-60x45': - $xformed_file = $this->executeThumbTransform($file, 60, 45); - break; default: return new Aphront400Response(); } @@ -85,16 +69,18 @@ final class PhabricatorFileTransformController return new Aphront400Response(); } - $xform = new PhabricatorTransformedFile(); - $xform->setOriginalPHID($this->phid); - $xform->setTransform($this->transform); - $xform->setTransformedPHID($xformed_file->getPHID()); - $xform->save(); + $xform = id(new PhabricatorTransformedFile()) + ->setOriginalPHID($source_phid) + ->setTransform($transform) + ->setTransformedPHID($xformed_file->getPHID()) + ->save(); return $this->buildTransformedFileResponse($xform); } - private function buildDefaultTransformation(PhabricatorFile $file) { + private function buildDefaultTransformation( + PhabricatorFile $file, + $transform) { static $regexps = array( '@application/zip@' => 'zip', '@image/@' => 'image', @@ -111,16 +97,10 @@ final class PhabricatorFileTransformController } } - switch ($this->transform) { + switch ($transform) { case 'thumb-280x210': $suffix = '280x210'; break; - case 'thumb-160x120': - $suffix = '160x120'; - break; - case 'thumb-60x45': - $suffix = '60x45'; - break; case 'preview-100': $suffix = '.p100'; break; diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 5bc0eef0e6..0d352f20f0 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -784,14 +784,6 @@ final class PhabricatorFile extends PhabricatorFileDAO return $this->getTransformedURI('thumb-profile'); } - public function getThumb60x45URI() { - return $this->getTransformedURI('thumb-60x45'); - } - - public function getThumb160x120URI() { - return $this->getTransformedURI('thumb-160x120'); - } - public function getPreview100URI() { return $this->getTransformedURI('preview-100'); } @@ -800,10 +792,6 @@ final class PhabricatorFile extends PhabricatorFileDAO return $this->getTransformedURI('preview-220'); } - public function getThumb220x165URI() { - return $this->getTransfomredURI('thumb-220x165'); - } - public function getThumb280x210URI() { return $this->getTransformedURI('thumb-280x210'); } diff --git a/src/applications/pholio/application/PhabricatorPholioApplication.php b/src/applications/pholio/application/PhabricatorPholioApplication.php index e56f989630..801a2de4e8 100644 --- a/src/applications/pholio/application/PhabricatorPholioApplication.php +++ b/src/applications/pholio/application/PhabricatorPholioApplication.php @@ -49,7 +49,6 @@ final class PhabricatorPholioApplication extends PhabricatorApplication { 'inline/' => array( '(?:(?P\d+)/)?' => 'PholioInlineController', 'list/(?P\d+)/' => 'PholioInlineListController', - 'thumb/(?P\d+)/' => 'PholioInlineThumbController', ), 'image/' => array( 'upload/' => 'PholioImageUploadController', diff --git a/src/applications/pholio/controller/PholioInlineThumbController.php b/src/applications/pholio/controller/PholioInlineThumbController.php deleted file mode 100644 index 624ce3d3ea..0000000000 --- a/src/applications/pholio/controller/PholioInlineThumbController.php +++ /dev/null @@ -1,46 +0,0 @@ -imageid = idx($data, 'imageid'); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $image = id(new PholioImage())->load($this->imageid); - - if ($image == null) { - return new Aphront404Response(); - } - - $mock = id(new PholioMockQuery()) - ->setViewer($user) - ->withIDs(array($image->getMockID())) - ->executeOne(); - - if (!$mock) { - return new Aphront404Response(); - } - - $file = id(new PhabricatorFileQuery()) - ->setViewer($user) - ->witHPHIDs(array($image->getFilePHID())) - ->executeOne(); - - if (!$file) { - return new Aphront404Response(); - } - - return id(new AphrontRedirectResponse())->setURI($file->getThumb60x45URI()); - } - -} diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/default160x120.png b/webroot/rsrc/image/icon/fatcow/thumbnails/default160x120.png deleted file mode 100644 index 16d6fd4f907913415c3387189dd1129385865575..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1006 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$SgAGWQF8Y}dq$EpRBT9nv(@M${i&7aJQ}UBi z6+Ckj(^G>|6H_V+Po~;1FfdQ>ba4!+xb^0)ZN7**L&L+Fd--z&1!DvpMYu8)R$f}l zP$l5f_w$AD6sbT*$3_9sDSsNjY)s-2TGY5-AWEfa!li})A%zjxPd`XfkS|?iBw9-K|q0tg%O!*IkwgJptj+$ zZ`X>iKb!yc(ZQv0AJzK4-aoN%>d_0lf3@r9{{NQo>(l(HeIMB-GSy5!skBf1Ip3OB zD(3aSldFC`y>q@>oKN06{+rKghSjqq*3>A*?ER~7{~u{+r3@MDOsE5 zx|#p6>M3~lO?|F+1+ z|17+J=E}^UmDlg)Ij!>biMrS&Jt4yI)wI)> znUw$84Gj19v!-8V*7PQucyC$-NL{c@ltAoUqhtTw}Xee3;w_C zF8UfB$IxE=%l!6ox7*bwyP4K(U`l(sEo%L=AJ?+gl@P}*%tBj{OnxDbs`DNBUmoKSa)%K#mr+#vp%bC)^+&D z{wLXr$)!QTfrW!qs)NC?fkOZoY)I6s@9P2w-fDjS&y06{XpDhTGIYQW*a16W2ke0VGhjs26OUeYw?mxmhdf{L zIX#6?gJ}8d<15$ca&NyeBI58Q!#dfa=7U#eSX){BY{u>d0rQs?s}{9}vStJTp zzx3eJ=_3R^-EQ0sI)75y2H@O$3S1L~@8MrP9;a0P$j0k6c)Ru;W~Yvp1}zTkQ7RYp z%vudiUV}F&bg($nrkWOo76Z=ur$@?h$1UP%K~s7wIMF-+lg$RD_ZD2F^j^Moq20RQ z?a~O5exs!o)}$CWEFAD5Wdh%rJ_UH^Ipja&n^6=uvcB>!(^Z3f(>h>QY*1jfhz5S# z*oJ2d=VIT_!YIz4x}CsS3|P_F_1-S5ZSKbE<^rcRa(=Rvl?z&#(jL_zV$taEx3yVW zOJ4aJ_E1C)BO~a)Nog5isEEQUNlyu2tct>;0c;>&ON*PSKn>e!rPZ*|7OXloJTkzV zia1_02y`thp*+NaYYaRRT{?|!<^894eJo)SWy;*yk@xfEn!8iLAZz+HWb+FB*5}7C^29^-4!M?A5wWJ^xx7rolcUsN_c&8fZxbJP^=ra0Q)R5uRBUw^Zz8@ sqFDP@09VNzumg6$4%h+PY`+8;0Iwg8LNtyxvH$=807*qoM6N<$g3|6-kpKVy diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/image160x120.png b/webroot/rsrc/image/icon/fatcow/thumbnails/image160x120.png deleted file mode 100644 index 90cc11c02d86c9654463b2148399cc4faa998ed6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1459 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$SgAGWQF8Y}dq$EpRBT9nv(@M${i&7aJQ}UBi z6+Ckj(^G>|6H_V+Po~;1FtCPtx;TbZ+>mA-6Y;H2|ZxX-lEqbt-_3=?Tk(P$`0PQZ;g;F`py+1E5xjA?4 z85^J>vs08k%bPk`f7U$Mn4eyD|Ip*f@4wgHf0NQ%?8hJ{GEI|#1qd8~fIP>RRpBqqp_5zT?_brwGI?ewX zztObaqDp4{+>Sk;{=MP;wPtayEaSpuZxrb?9K*`iV0f; zT)p-dR|Y;!6b^6n`yF-k7t_+vgqL5M2Z{hDk(6T9esa6b=#@M zi={U1t9i3%jhcVwmv=4Ce*c?cKi%!;rI022|I8Fwr|f!j;+|?}pZ{(h3%0iC^+g*U zUGhN9wIlqP`+=C>;mh;wW9N__tbEIPG7p;aQ)wpS+y_X7pi`` zd-U|A+&?<%ezkhr)*So%_z}>tx7#LaEO>KbyLX7~#1##%>uc3Le+N!I^G2)ww9(X@ zm$wVvhIfF1jN`@8B4*daaZJC&f0@QA%4&X|HzUmCQn1LZs%k5@<&)OMF{`;abWKp) z@s=aQS#V>1_icmobN+=+nwa@AE!(KH)Y#nU^on&#Y7BuK=PTnGRYgSAB(kj+Pby#i^3Tg(JyY(+-Ppjl@%*ok&r0uzs);kUKVE-d z>i32(n+3lf=j)GLwu|rj{^+$CIpOou-)5}iT)IVGKasJc!FP3nsoSO;ZLS*)djyIf zro2d#uGMv6Tf#g2lTN4sliB)~xl=Cfy?*sU+HBoh-+mi8{YdkbJa*WgB}XS=TKQ#z23#-Zpdc)GSYkrNfDm0w z7P9V->||%Ud%DW^s;W1$(M?vSmT5>AJf^05`b~Z6{is(pL&g|9NloDiYYlJ%+yFPg z4R8ZoqZW^ALwUFt@mx9ky412`S$Dcub<>%X)rcmZj%|qe(~N3Q#h~0b?sZQi z_8R^TMxSoOKRy{csr1%zS&B*Aj;z+V zi!eQsOfxxP1G5(XNrzqfSTefdT6F; z$j2WFaizjrI4E$HofBa!O6S+)qu6ktv#cbvxNou^znu^zT}czFd=giHA)RAYc6wrD^jT+eXp53CeF;52>Jy*Q;w8!{mU)!Ugv) z&s;?5-7zw5SyINuxX9YAkJZdt}Nn)S+XnlSx4JJ zRxuofvv9DaWwtG>Y|`0P{Ic}Y@!t@&X3@(wFmq+giuC20+*3ZeiN4BnYGdJQM`#NL znnaqo;$pWmesSwo(!#4JzDG0(T)i@5ug{%cLV2Ivdf=XoygpThNbO>nwN2f69=}c> zlNw`JVAv!?c7{}Yf|nyVjO*9tpnH8=pjg`~(YMHY*XmhmN&QReY2;ap$EzN16LNKB ze$Pdg>hb$5^)p2_)r-EUwzae!ze1J`Jn&vNyd~(uQQOVBIVY~6NwTG>s_*8(#hFI$ zevgf(_7-%zZ655-OIv**PmF>CXGIrl8+RP=$R{b<>Z=`am7L`K>65do!O!qYUu}i> zWjCmWr44lWQ%kB_3~wLB@T;c4Ge{4rqY&UhfMcchXM_ES?-?s_8lyY+!Tgk*8}G+I zDC~xVVNHMir%?+o8udROM09{(W?7~KNAJJV1Ir4_pFHNxaRPJYj7fV+x4%sW%5^7t tqx`l+&A)gW;0Cw>Zh#x$`m}!p7y#Z%ch+0IL<|4`002ovPDHLkV1o2xKfV9} diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf160x120.png b/webroot/rsrc/image/icon/fatcow/thumbnails/pdf160x120.png deleted file mode 100644 index 20f08f955bf72d402ad77cde599dec462218ba4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1654 zcmbuAYd8}M7{^CjvYt6DcHB#9>23ujhHrw|>v_zW?8+_rv?;pXTA_1Oe-S0RRBR#TkWB zWQk%ZTQ)0pbm?-qBDAld@mIXUFJ8GyiX;OZ{KNgo1}>qb05XP5@~1`!$j1PH%~~!f z2P|dq+x_5FELrQ#&w#EEG2&s|KZCjL9~w+Iv(tnJs#ABS8ye-kawU0cq%wBWQy1u{ z^T=&xst?RL(x7KS*BqQRXE0&uMn2Bik%OX6p)ad%z~;+*k)@?{N^Wb(+%B3{>F&Dx znKvhqG+8px_tf{|~e#-aBZVWhMkSh73RJTuJGoQdn6+ba4T2F z%0^f(DbK~q*FZQdHZYbj+|liG)-C@f6mvW2W{t|G@-^|fY=pVim%MlQQvriNF=R43 zsIt=3I&36fLls&_RN9(hR*y%aXDP+WnSG)=hjO>6LJuZ}3>BZLUVF)1i6srGRX#mh z1BJtBy%B^^nO5%@3x(v8BBRy4aVLVHFDbxN!i3*Isw=b2g(1jTn26GAF4zRnMXP?geC_VdQKa+ zxbAJT#s!==5t47Wi-E&0`B=1}!QLm>?X8XEH>Ho)Ls_Q)u$i(m_Ptc+QF+eiZhDy* z2=m-nyz^A#2-3ToR94ABQg2P>-t>9PP9s`QX@+F%+$TF> zr1-;rV$6dv?NF7aj@) z9n`hr-9#lq-FK?VE6qNvEvHc!I{c`;NcJLu=s&=mYI^S2mU3129e((!XIL@`%zx)q zci(rFkU%T<6XpT)#^XOun`IO1&}nJC$&FUW#dhVManDQ<~$aoutQtYRpeFC zgogXZeI7V!DT>gD`-Fr0;!ETeD&rj#q*rrubdV@H{2Qg=RlbfZMP5;#$gN(yy<52z z*Y9}1JTIQB-yRb&i)kAT&RSiC!o{>QQRXT+gO6LJsbkj!Qza+Qj>`!s# z(P5WlvgPW(s{HD;`R`xBfyqiN){QBk9g z@1hYbJ*%VLt@s$<*`+&_0ik})6Zc8GhtO>kD>y55ak}nhP1i(YFJrbEce1B+bFNil zRe#kZmGx)ea`do$^5pk>?9{3*P4~0sGs^iCW4fDqPwms2x57{s7JoWrn$CW^-P83q zn0aK(<9m5={PAr6I{jYqM^;*l`j^vt1h%l5^n*oW4ta>%Gq$+A0eidUtD~Wb4fW)2 z2}|W_N6at_trIJf8?ciJoZ&!`^u*dBe;lMp-%Ti%n(GSYlXy|R56dHWce?hmnxcZ~ zf^nRxi_P7kfqR#^@|A@L7Czc-qL)ioM z8s<0_-ywU6O=*V;y83GU0#5I#nLJXnucXo;MpJ-L{yY*Zn1`-cT*yolK2u`0kMB^r z7OG`$4*)8#SMUo0+yhEd0NA47zhZskIYs`C diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf60x45.png b/webroot/rsrc/image/icon/fatcow/thumbnails/pdf60x45.png deleted file mode 100644 index 8a16eaf488e00f922e2ce0d77c0f6ec43ba311da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1378 zcmV-o1)chdP)OP7(~*v7V$+2foPG6FPdZ%n_GnlQoKMf z+L#hrq7+e+wp5a?O`6*#o!y!7KeMxI+-{m(XVab7{o!MVGv}Y3@BG(u5~k}qZY2?L zi%FJnmT;Ev(iVpmWyYAJqpPc%ZZ_$fhSc&spUogJ231v$JYQ8+MFZiaG_6z?uKZs? zl1qejt1>b&qBr~fo%B9&vA@}2(L?0uY7Y*b=N30oqoOrpP)0ZAbJBdFemWZ&i#=!dCoH5=gUu|>N>%}< z&wx!%MhHh66Sf2hGbWmuI!SPyRYgE=KXBWMIDOInF`T+p%*JE=qyJbn3cK!ylZQvYo$d#EaU$64mBlz042EFB*nqJNKKDklOdu3cx`^*!N3=%ep@`JhS zb~$qwAsj~d%m8M0)gjma30zyANqm43>%qt&rh;fx#`_xsF+B*iFClY~)1fG|c-GMcoQu1|J$cjp?%F>@_Ugf&Tm z=h849{^&EDt=omtoqO@ahx?#>)kB{h;)hrBdW~^*9U&XyVV}0pv$EMHD=w!?adl0N zPf?VAy(p}z+Qdvy?K=MJ$MtvR)xWLh>_0ko`QYL5wLd#`-RPQc^lqoq$y%D5Ptffj z@rQVpzS$ySo;=MCR0{4)3Q-3+k$X3?1Xs; z=MxIe%*w5Fg%FI(O*bN}C2HTSd~-U=6907-iT?mLVUF{~nBfhUm8K!%F}xHd`!5e! k!db#u!db$0mcIoU0Lp_RQgVwEW&i*H07*qoM6N<$f~#YcIRF3v diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/zip160x120.png b/webroot/rsrc/image/icon/fatcow/thumbnails/zip160x120.png deleted file mode 100644 index fbe19e59f601befc61f4eb320229d6748236e536..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1383 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$SgAGWQF8Y}dq$EpRBT9nv(@M${i&7aJQ}UBi z6+Ckj(^G>|6H_V+Po~;1FtBoZx;TbZ+GHcZz2HBP z;G<%v?Dmp-VdokV;irx2$w?Ix#pN%|S6KMcNt&bcW&8y8dIlz531(Hd1n=#knnGgb zw$IPKx%BQ_TKP$V+8_I#JV|?hX4}cL(~IBjH1^$_wpo+mzo?xz0|yW^00D&zoCZv= zlAQK{bKdjT`)74^a^-{wC1=t;Z$*S&>VS*JWJ9z1ZoxxM`Tvh1r_Tfe-x@-yDQ zrs{x7&5=z(P3);_rq0c~6EkVH_x4>$FR$enmH(VqQ}m=G#YFgAs8-STU2oP&rayna zG(zW8`uq1YfBgFW*}O#S^d_YT2VP&)>I;k0ZBlr_G57M%Q)han=4h_&SX&+2d2Qpi zU202%JXLGNIR!tg{NC)RB|UkOjqTfatLND5jdbWWm_B(`+R2X}3sqbWEEhMhRMJcj zwRwEu;o-8)v$H3j?)9+axh+(;FaP|J&R~y@2IqvO*S_yvcR}mTu}z)J&tz%xJ<48p zxb*&6kA(uKiWe__TU7I<`McV_nL87tk50I{l>43Uot6sO$hDhIW>@^Ix!1Yl*EZUHnH9>e{xY;vY>*IS%12JZs(5)8L$}FQH<0F!Z97 z?X%j`CHE3;iU<_E5V~`&#ZzCrVlRJ!fB<8=q5i9k()#JwqIGf^1qB&@Kg_Qv*MEEF z>E}2Frf-Ya?LETJ%{{NW)^6?hACJtFUR_&y;MvlLsV9AxehQVncX31U)Ge=<>VA1I zW&d)P{>EDzGgJ~B+rv%V=3iVNaXsRiY5jpLWraWT-|pxzDgXfk6A)0yz-55Lt3TQY a7@{7Uo=|XDd>vSFGI+ZBxvXK@`B>%r5P=fTAX~ z$jJop3t;fZAHC_NhL}Ji6*zg|WK8@D`XL}Ke+nT&y%?_Y5fEs^1Ak(o1zKt-npoQP zz1eM3+O3i}tGi(*c^UT2>~?>_JEz-R2dIpjHz#y zmy`I%1HDuNtIW<&6jcSE&j*}yI4u;QfKoE&bH<=pEWUj_I5>!xr&hlsrDmXq{HghC-0=#SV+psD5^A4Gib3ItJS(Xfd&Er0bV_~tFsf(noz!zo<=F_ z$?!0xvWIvXKsj~}a0QH80vpc6g)1*27=icn+{L*H!GsuFh}lTs+g=q0i%zO8GD$(71A>!U8$xhHR8z9 z^1K$Nd^{72frgQo(i125M?EW9?|C~uKgmx^3Ns*Tgk$tpeVBFvK~ZVVO;6*sD)d(K zg(%GVz}zASbJM!(Yaigt*8@1r6PC3_=9{?Ja zmgKsrR0=M&UWVr{UW%TsN#D2}WiE))+&V(j+6gX`%|H&HuNDO&z79qg)RBnqTyET^VPtwXkBA{%7lJFu!SKvk2_dM6Z~@ zMnr}NU&37M($}uGVWeIYu1rz6qvOUOT-pu5R2|h+ggVoqh=8wNy{?;2dK$M1cHo96 zO$P$Y3K+pk4fLEW$a&RLJK@X*~YczoC4g0T{DW$rUL0F w$_cKu;0RzhHSZtd9OV07*qoM6N<$g2S-(b^rhX From c998e44b5a35bdf684d9e0c1ed697f0123804eb9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 May 2015 06:16:18 -0700 Subject: [PATCH 53/70] Modularize file transforms and provide a "transforms" UI Summary: Ref T7707. Available transforms are currently relatively hard-coded and don't really have any support UI. Modularize them so we can build some support UI. This doesn't actually //use// any of the new stuff yet: I want to make a clean cutover once I fix the aspect ratio stuff so I can pick up a cachekey/URI change as a side effect. Test Plan: {F400524} Reviewers: btrahan Reviewed By: btrahan Subscribers: chad, epriestley Maniphest Tasks: T7707 Differential Revision: https://secure.phabricator.com/D12808 --- src/__phutil_library_map__.php | 8 + .../PhabricatorFilesApplication.php | 2 + .../PhabricatorFileInfoController.php | 7 +- .../PhabricatorFileTransformController.php | 42 ++++-- ...PhabricatorFileTransformListController.php | 137 ++++++++++++++++++ .../files/storage/PhabricatorFile.php | 4 + .../PhabricatorFileImageTransform.php | 17 +++ .../PhabricatorFileThumbnailTransform.php | 74 ++++++++++ .../transform/PhabricatorFileTransform.php | 44 ++++++ 9 files changed, 319 insertions(+), 16 deletions(-) create mode 100644 src/applications/files/controller/PhabricatorFileTransformListController.php create mode 100644 src/applications/files/transform/PhabricatorFileImageTransform.php create mode 100644 src/applications/files/transform/PhabricatorFileThumbnailTransform.php create mode 100644 src/applications/files/transform/PhabricatorFileTransform.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2db8ddee4d..0fa277df27 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1860,6 +1860,7 @@ phutil_register_library_map(array( 'PhabricatorFileFilePHIDType' => 'applications/files/phid/PhabricatorFileFilePHIDType.php', 'PhabricatorFileHasObjectEdgeType' => 'applications/files/edge/PhabricatorFileHasObjectEdgeType.php', 'PhabricatorFileImageMacro' => 'applications/macro/storage/PhabricatorFileImageMacro.php', + 'PhabricatorFileImageTransform' => 'applications/files/transform/PhabricatorFileImageTransform.php', 'PhabricatorFileInfoController' => 'applications/files/controller/PhabricatorFileInfoController.php', 'PhabricatorFileLinkListView' => 'view/layout/PhabricatorFileLinkListView.php', 'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php', @@ -1873,10 +1874,13 @@ phutil_register_library_map(array( 'PhabricatorFileTemporaryGarbageCollector' => 'applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php', 'PhabricatorFileTestCase' => 'applications/files/storage/__tests__/PhabricatorFileTestCase.php', 'PhabricatorFileTestDataGenerator' => 'applications/files/lipsum/PhabricatorFileTestDataGenerator.php', + 'PhabricatorFileThumbnailTransform' => 'applications/files/transform/PhabricatorFileThumbnailTransform.php', 'PhabricatorFileTransaction' => 'applications/files/storage/PhabricatorFileTransaction.php', 'PhabricatorFileTransactionComment' => 'applications/files/storage/PhabricatorFileTransactionComment.php', 'PhabricatorFileTransactionQuery' => 'applications/files/query/PhabricatorFileTransactionQuery.php', + 'PhabricatorFileTransform' => 'applications/files/transform/PhabricatorFileTransform.php', 'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php', + 'PhabricatorFileTransformListController' => 'applications/files/controller/PhabricatorFileTransformListController.php', 'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php', 'PhabricatorFileUploadDialogController' => 'applications/files/controller/PhabricatorFileUploadDialogController.php', 'PhabricatorFileUploadException' => 'applications/files/exception/PhabricatorFileUploadException.php', @@ -5262,6 +5266,7 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface', 'PhabricatorPolicyInterface', ), + 'PhabricatorFileImageTransform' => 'PhabricatorFileTransform', 'PhabricatorFileInfoController' => 'PhabricatorFileController', 'PhabricatorFileLinkListView' => 'AphrontView', 'PhabricatorFileLinkView' => 'AphrontView', @@ -5274,10 +5279,13 @@ phutil_register_library_map(array( 'PhabricatorFileTemporaryGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorFileTestCase' => 'PhabricatorTestCase', 'PhabricatorFileTestDataGenerator' => 'PhabricatorTestDataGenerator', + 'PhabricatorFileThumbnailTransform' => 'PhabricatorFileImageTransform', 'PhabricatorFileTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorFileTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorFileTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorFileTransform' => 'Phobject', 'PhabricatorFileTransformController' => 'PhabricatorFileController', + 'PhabricatorFileTransformListController' => 'PhabricatorFileController', 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileUploadDialogController' => 'PhabricatorFileController', 'PhabricatorFileUploadException' => 'Exception', diff --git a/src/applications/files/application/PhabricatorFilesApplication.php b/src/applications/files/application/PhabricatorFilesApplication.php index 1187b0545b..ed4801ace6 100644 --- a/src/applications/files/application/PhabricatorFilesApplication.php +++ b/src/applications/files/application/PhabricatorFilesApplication.php @@ -91,6 +91,8 @@ final class PhabricatorFilesApplication extends PhabricatorApplication { '(?P[^/]+)/'. '(?P[^/]+)/' => 'PhabricatorFileTransformController', + 'transforms/(?P[1-9]\d*)/' => + 'PhabricatorFileTransformListController', 'uploaddialog/' => 'PhabricatorFileUploadDialogController', 'download/(?P[^/]+)/' => 'PhabricatorFileDialogController', ), diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index 3d9731f6b4..fdc5a7773c 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -170,6 +170,12 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { ->setWorkflow(true) ->setDisabled(!$can_edit)); + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View Transforms')) + ->setIcon('fa-crop') + ->setHref($this->getApplicationURI("/transforms/{$id}/"))); + return $view; } @@ -267,7 +273,6 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $user->renderHandleList($phids)); } - if ($file->isViewableImage()) { $image = phutil_tag( 'img', diff --git a/src/applications/files/controller/PhabricatorFileTransformController.php b/src/applications/files/controller/PhabricatorFileTransformController.php index e3a939d45c..9b893b9528 100644 --- a/src/applications/files/controller/PhabricatorFileTransformController.php +++ b/src/applications/files/controller/PhabricatorFileTransformController.php @@ -48,21 +48,33 @@ final class PhabricatorFileTransformController // protection. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - switch ($transform) { - case 'thumb-profile': - $xformed_file = $this->executeThumbTransform($file, 50, 50); - break; - case 'thumb-280x210': - $xformed_file = $this->executeThumbTransform($file, 280, 210); - break; - case 'preview-100': - $xformed_file = $this->executePreviewTransform($file, 100); - break; - case 'preview-220': - $xformed_file = $this->executePreviewTransform($file, 220); - break; - default: - return new Aphront400Response(); + $xformed_file = null; + + $xforms = PhabricatorFileTransform::getAllTransforms(); + if (isset($xforms[$transform])) { + $xform = $xforms[$transform]; + if ($xform->canApplyTransform($file)) { + $xformed_file = $xforms[$transform]->applyTransform($file); + } + } + + if (!$xformed_file) { + switch ($transform) { + case 'thumb-profile': + $xformed_file = $this->executeThumbTransform($file, 50, 50); + break; + case 'thumb-280x210': + $xformed_file = $this->executeThumbTransform($file, 280, 210); + break; + case 'preview-100': + $xformed_file = $this->executePreviewTransform($file, 100); + break; + case 'preview-220': + $xformed_file = $this->executePreviewTransform($file, 220); + break; + default: + return new Aphront400Response(); + } } if (!$xformed_file) { diff --git a/src/applications/files/controller/PhabricatorFileTransformListController.php b/src/applications/files/controller/PhabricatorFileTransformListController.php new file mode 100644 index 0000000000..ace9abff4c --- /dev/null +++ b/src/applications/files/controller/PhabricatorFileTransformListController.php @@ -0,0 +1,137 @@ +getViewer(); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); + if (!$file) { + return new Aphront404Response(); + } + + $monogram = $file->getMonogram(); + + $xdst = id(new PhabricatorTransformedFile())->loadAllWhere( + 'transformedPHID = %s', + $file->getPHID()); + + $dst_rows = array(); + foreach ($xdst as $source) { + $dst_rows[] = array( + $source->getTransform(), + $viewer->renderHandle($source->getOriginalPHID()), + ); + } + $dst_table = id(new AphrontTableView($dst_rows)) + ->setHeaders( + array( + pht('Key'), + pht('Source'), + )) + ->setColumnClasses( + array( + '', + 'wide', + )) + ->setNoDataString( + pht( + 'This file was not created by transforming another file.')); + + $xsrc = id(new PhabricatorTransformedFile())->loadAllWhere( + 'originalPHID = %s', + $file->getPHID()); + $xsrc = mpull($xsrc, 'getTransformedPHID', 'getTransform'); + + $src_rows = array(); + $xforms = PhabricatorFileTransform::getAllTransforms(); + foreach ($xforms as $xform) { + $dst_phid = idx($xsrc, $xform->getTransformKey()); + + if ($xform->canApplyTransform($file)) { + $can_apply = pht('Yes'); + $view_href = $file->getURIForTransform($xform); + if ($dst_phid) { + $view_text = pht('View Transform'); + } else { + $view_text = pht('Generate Transform'); + } + $view_link = phutil_tag( + 'a', + array( + 'class' => 'small grey button', + 'href' => $view_href, + ), + $view_text); + } else { + $can_apply = phutil_tag('em', array(), pht('No')); + $view_link = phutil_tag('em', array(), pht('None')); + } + + if ($dst_phid) { + $dst_link = $viewer->renderHandle($dst_phid); + } else { + $dst_link = phutil_tag('em', array(), pht('None')); + } + + $src_rows[] = array( + $xform->getTransformName(), + $xform->getTransformKey(), + $can_apply, + $dst_link, + $view_link, + ); + } + + $src_table = id(new AphrontTableView($src_rows)) + ->setHeaders( + array( + pht('Name'), + pht('Key'), + pht('Supported'), + pht('Transform'), + pht('View'), + )) + ->setColumnClasses( + array( + 'wide', + '', + '', + '', + 'action', + )); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($monogram, '/'.$monogram); + $crumbs->addTextCrumb(pht('Transforms')); + + $dst_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('File Sources')) + ->appendChild($dst_table); + + $src_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Available Transforms')) + ->appendChild($src_table); + + return $this->buildApplicationPage( + array( + $crumbs, + $dst_box, + $src_box, + ), + array( + 'title' => array( + pht('%s %s', $monogram, $file->getName()), + pht('Tranforms'), + ), + )); + } +} diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 0d352f20f0..c4aa7dc42c 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -760,6 +760,10 @@ final class PhabricatorFile extends PhabricatorFileDAO return (string) $uri; } + public function getURIForTransform(PhabricatorFileTransform $transform) { + return $this->getTransformedURI($transform->getTransformKey()); + } + private function getTransformedURI($transform) { $parts = array(); $parts[] = 'file'; diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php new file mode 100644 index 0000000000..498685f356 --- /dev/null +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -0,0 +1,17 @@ +isViewableImage()) { + return false; + } + + if (!$file->isTransformableImage()) { + return false; + } + + return true; + } + +} diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php new file mode 100644 index 0000000000..d88451a0de --- /dev/null +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -0,0 +1,74 @@ +name = $name; + return $this; + } + + public function setKey($key) { + $this->key = $key; + return $this; + } + + public function setDimensions($x, $y) { + $this->dstX = $x; + $this->dstY = $y; + return $this; + } + + public function getTransformName() { + return $this->name; + } + + public function getTransformKey() { + return $this->key; + } + + public function generateTransforms() { + return array( + id(new PhabricatorFileThumbnailTransform()) + ->setName(pht("Profile (100px \xC3\x97 100px)")) + ->setKey(self::TRANSFORM_PROFILE) + ->setDimensions(100, 100), + id(new PhabricatorFileThumbnailTransform()) + ->setName(pht("Pinboard (280px \xC3\x97 210px)")) + ->setKey(self::TRANSFORM_PINBOARD) + ->setDimensions(280, 210), + id(new PhabricatorFileThumbnailTransform()) + ->setName(pht('Thumbgrid (100px)')) + ->setKey(self::TRANSFORM_THUMBGRID) + ->setDimensions(100, null), + id(new PhabricatorFileThumbnailTransform()) + ->setName(pht('Preview (220px)')) + ->setKey(self::TRANSFORM_PREVIEW) + ->setDimensions(220, null), + ); + } + + public function applyTransform(PhabricatorFile $file) { + $x = $this->dstX; + $y = $this->dstY; + + $xformer = new PhabricatorImageTransformer(); + + if ($y === null) { + return $xformer->executePreviewTransform($file, $x); + } else { + return $xformer->executeThumbTransform($file, $x, $y); + } + } + +} diff --git a/src/applications/files/transform/PhabricatorFileTransform.php b/src/applications/files/transform/PhabricatorFileTransform.php new file mode 100644 index 0000000000..7a36641140 --- /dev/null +++ b/src/applications/files/transform/PhabricatorFileTransform.php @@ -0,0 +1,44 @@ +setAncestorClass(__CLASS__) + ->loadObjects(); + + $result = array(); + foreach ($xforms as $xform_template) { + foreach ($xform_template->generateTransforms() as $xform) { + $key = $xform->getTransformKey(); + if (isset($result[$key])) { + throw new Exception( + pht( + 'Two %s objects define the same transform key ("%s"), but '. + 'each transform must have a unique key.', + __CLASS__, + $key)); + } + $result[$key] = $xform; + } + } + + $map = $result; + } + + return $map; + } + +} From 65ff40844ba8a30f69cd12ed9b83a2124046dc58 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 May 2015 08:16:37 -0700 Subject: [PATCH 54/70] Make modular transforms handle exceptions gracefully Summary: Ref T7707. Ref T2479. Ref T5258. The thumbnailing code is some of the only code in the codebase which doesn't use exceptions to handle errors. I'm going to convert it to use exceptions; make sure they do something reasonable at top level. Strategy here is: - By default, we just fall back to a placeholder image if anything goes wrong. - Later, I'll likely add a "debug" workflow from the new "Transforms" UI which will surface the specific exception instead (the code can't really raise any interesting exceptions right now). Test Plan: Faked an exception and saw some reasonable default images. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T5258, T2479, T7707 Differential Revision: https://secure.phabricator.com/D12809 --- resources/builtin/image-100x100.png | Bin 0 -> 993 bytes resources/builtin/image-220x220.png | Bin 0 -> 1322 bytes resources/builtin/image-280x210.png | Bin 0 -> 1349 bytes .../PhabricatorFileTransformController.php | 13 ++++++++++++- .../PhabricatorFileThumbnailTransform.php | 16 ++++++++++++++++ .../transform/PhabricatorFileTransform.php | 4 ++++ 6 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 resources/builtin/image-100x100.png create mode 100644 resources/builtin/image-220x220.png create mode 100644 resources/builtin/image-280x210.png diff --git a/resources/builtin/image-100x100.png b/resources/builtin/image-100x100.png new file mode 100644 index 0000000000000000000000000000000000000000..c56a9aa0835ccbe7ab631b372d07a2b406d33872 GIT binary patch literal 993 zcmeAS@N?(olHy`uVBq!ia0vp^DImzW1*#POj@;_H{wbPqwyZUxwrsH8~R|PQhM9MnTbEQ?}2|>G{JsVeYQqHk^}Z z-Yjo_qkn#n@#Y7ffB%>|@~Sb<=YLqvaxwh5`1QCTiI^7G2nZ5M6cmFXm`IB&Oe8T7 zRaxfr#OKp(?d<;j__sE0y}iACzum1TPf{#o{{4BnI#GhJh1F+kRBm;3bXKR$*{Ph7E6?^0Qj~m~ycv~j@*hNWj3O_$ztLk~pWfoReRa2+4d=Zma zyLPST2ZfLMqSx-HE?vBI>C!OwPX`#(|JLU3-?d9h<&5P9k-0%DORB58(-Z_EVq!wV zcYB^$!8$EmS%>4~>ZrATBo;7FIL3pCAaN65m}Ze^<{nAfySF6y>5xxFTX^TR8@5` z%$%K}^qsxgaqsGd3k}mbbzUcX+n;h#Dy*#RR1K2t30fJlF(M{DKHTtK6z^4~6-%m$ zcFwus^|bDSrM2~BiP&g%=kB#t{;DxYe{Q^))Ac*xP<+Os;Jb_13SFDT1+Hy9zv}GS zv#qVHDSzF-ZekFs$@Bb>#I^svzUAwyGiJ`5+1tw-bNALQr{etl^?&^jv{Y}k@V=e3 zWB>m7=b!K1=`3U~w8w;>kMDv0)IFD8me|OZUv!Le_%g%yMSWG(q*zuhA+?V?uE*xf zCJ5x*tZr`%)Dd%j`tM)dipbjMVi8Gms`lP9fA7(7wbpF^hu`{UOHVjX^-Aqf;SJgm zetKER`Rvpw?d|Q0_cTxU$=`f*i?4GwpVY4dPYTi_@2B+I^_@NOrcL$t`$F60QfFsw z*x-8W=FPzUFM1y7+_-GwYkf9VTW9|H{~s^Ex_kF-gwWhn)L=G*2iEPk-r~#5-Wm{gKxBq`m he;sF%z+bThhMcIW_g7`zUk}W)44$rjF6*2UngDUs!>s@S literal 0 HcmV?d00001 diff --git a/resources/builtin/image-220x220.png b/resources/builtin/image-220x220.png new file mode 100644 index 0000000000000000000000000000000000000000..928b5b05eb16f1d0d6565191ab41342d6d8fd3ce GIT binary patch literal 1322 zcmeAS@N?(olHy`uVBq!ia0vp^cR-kf2}n+{h`0u%BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztu$=L9aSW-r_2#y`wLDfYpfl6KiFsef+ut`Hu z6;8QyYB)~m5CT&QUM`%AT&BP&xF(+|d9 z%$*w>A8-Hvu9TEiRdsd!-?!hL4$c%$U8{8b^5x5&ot=9Y&6zc8*Nz<-nVE%ou92UY z?Afz(r>0ojHd`f%>NdGlslYp&rid0YSGskODWza>v#UHp{D*5AK>ySuw@ym{~5 zy}Nhk{{3c`xufgT%H_8-f2`ARz7sQV&K#4wo$7{6tKFZ~CT;2pn)1_KGSRi>X_4hS z{ot&RPdlYRIRCA zT}Jbcq?uV9e)a0ruU}Pn3f-owE!Bt*kZS)Z5%}%<_wC!Z&AZpBzERRTc>2d3d-lZa zC@v@v2>-VJoo1~>%GbI7m-M`Oq-A4ZAdqbIcFpQd8#fBFFUhXTIVQDY^XAL1qNnIr z`_Ejo=A8E1{^LJ?{(SrPZN#MXcn>{YT_Cf!rzhpOMX%odpX>K+b}d)Cy?N)(na5JL zv2}6T*Zi2!_i^F`&zb*c)cg>V*{>_sJ$?H0%vr~h&z!!|Gxw(VCi8QDKZ++SDD)SY8wd>EI?72JKb*7&-)d^d4qGD}mpT=jI2`>xt^4?kgUpzU? z#%-C#=aAa|h}&yQ`CMn-ZvE{mCtv%UEoRD2&-=%pTn+w~*RiS0*VlLQeaSXWB$^JoIgLOe?NZw_)eVwu2!e&>X&bCtEq=g*_xe~SNHwh@pbFg?aA|B zeB#Y&50}W>OLVqv-D=7IUp>A?5s^b_oY~0E-8(gooImbAe=bK{e?6-N!`VI4&gZ`1 RwFVY`44$rjF6*2UngIF4TSou@ literal 0 HcmV?d00001 diff --git a/resources/builtin/image-280x210.png b/resources/builtin/image-280x210.png new file mode 100644 index 0000000000000000000000000000000000000000..48237b045e722ee3ca84586aeb70139f3be57741 GIT binary patch literal 1349 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|FL5vdNx2sudx4Z>iEBhjaDG}zd16s2gJVj5 zQmTSyZen_BP-AFu^F4jn&i(o8&-=k(=Z{}2KeMYdy?gt&y}$Gvlg*q74ju|B0zx3n!P&wB z!cI(zj6mE7lxn~#4OY@PfdQ;UK?Pz02Peb?Cnl`YBw3(n$qG@AV&uW%X~kUgymrR? zdwt%XkDveixpQ*=^1gokI(P2e{r|pI>pf4`T9PHiIn`_F!i5Wub7iKaJo)is$I6u_ zcZvpu`YxIL`Sa%*yZswC28M@+M@Luh|Gsm_j*jmyU%l!&?mxdpWyvdH-X)1fwzhw- zPP@doy}Rz-zS`M*|E4__y!1JweCguFkNOVy?IO2gu9wc1A{XY6BDztUPZ;;v{q1=US_&8 zW?h8Nw9`*Tf^0g^<~X$e{Q2|f(W5(>yQcZ5D1Nlhd~+nZ<;vZ=ckkSZ*(KzkWL&mv z+M~jT2tCzt-JVj{H#<74~-D^iGkLM?|Ci{r!6uZ`r)<(4|X3G85K)Pb#jh z-Me<}+GDHcPS{b_KgDU4kJ8)z<0ns^+`4tE)4~b0GcwZB(u#{W@8554eJryiGhOLw z>T9mAqMK~BwY4*4QeO)Mp8NEv$m-aq?VT>ZyNY%{{iL&R_W9@jetws(bRD1Jy;0J7 z^YoPbrp}P7-MpHUPMPlVVrFL6kKd;=)$4fpBhQ>;=Q`AvrfxjwVl+|D)Wl>-^^)~+ zmv7wo@$T;KAMYMKNXY5y>x=uNF4T2>mWR`$qIo^Ny=&L4yJr%5Ny%klfXD2|j~^RO z-!yya&Ye5=?Xz1R=;8EB=A>?T#>W3&zL>1-IX<~0!pbf#d!wUA&A+LEJ7f0!-SA@h z_U-2LWMpNRF4B5F^|FSF9{ah64u5==N(-U_|<`A+^a%XX(#uex{5I{u?{mH!>im8+JXSG5Ua zzPVd*$*Uj@k+$~s&HrV-Oz0FzUK^n!<>j=>>SxX2^ZWPQynnxbifdDh-t}8iH6Ir} zIKs7Zmx-C#zPi7USBI~cdtIijb;{1IWx}d-rKizRQFGd#A9VK~Qu(%f{dajgE5Q!} d|G5kplA5ROcH1z`99S+gc)I$ztaD0e0suc+Lz(~p literal 0 HcmV?d00001 diff --git a/src/applications/files/controller/PhabricatorFileTransformController.php b/src/applications/files/controller/PhabricatorFileTransformController.php index 9b893b9528..a7fcbc3c0a 100644 --- a/src/applications/files/controller/PhabricatorFileTransformController.php +++ b/src/applications/files/controller/PhabricatorFileTransformController.php @@ -54,7 +54,18 @@ final class PhabricatorFileTransformController if (isset($xforms[$transform])) { $xform = $xforms[$transform]; if ($xform->canApplyTransform($file)) { - $xformed_file = $xforms[$transform]->applyTransform($file); + try { + $xformed_file = $xforms[$transform]->applyTransform($file); + } catch (Exception $ex) { + // TODO: Provide a diagnostic mode to surface these to the viewer. + + // In normal transform mode, we ignore failures and generate a + // default transform instead. + } + } + + if (!$xformed_file) { + $xformed_file = $xform->getDefaultTransform($file); } } diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php index d88451a0de..75c16e2ddf 100644 --- a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -71,4 +71,20 @@ final class PhabricatorFileThumbnailTransform } } + public function getDefaultTransform(PhabricatorFile $file) { + $x = (int)$this->dstX; + $y = (int)$this->dstY; + $name = 'image-'.$x.'x'.nonempty($y, $x).'.png'; + + $params = array( + 'name' => $name, + 'canCDN' => true, + ); + + $root = dirname(phutil_get_library_root('phabricator')); + $data = Filesystem::readFile($root.'/resources/builtin/'.$name); + + return PhabricatorFile::newFromFileData($data, $params); + } + } diff --git a/src/applications/files/transform/PhabricatorFileTransform.php b/src/applications/files/transform/PhabricatorFileTransform.php index 7a36641140..f12aefd3ed 100644 --- a/src/applications/files/transform/PhabricatorFileTransform.php +++ b/src/applications/files/transform/PhabricatorFileTransform.php @@ -7,6 +7,10 @@ abstract class PhabricatorFileTransform extends Phobject { abstract public function canApplyTransform(PhabricatorFile $file); abstract public function applyTransform(PhabricatorFile $file); + public function getDefaultTransform(PhabricatorFile $file) { + return null; + } + public function generateTransforms() { return array($this); } From a19b0029e223e79d4158e1e2734866fc7418e1f1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 May 2015 08:30:52 -0700 Subject: [PATCH 55/70] Fix bad aspect ratio on some file previews Summary: Ref T7707. Fixes T4724. I misread the report on T4724; this is trivial. We're just reading the wrong properties in setting "width" and "height" attributes, the actual thumbnailing logic is fine. Test Plan: Uploaded image from T4724, saw it have a proper aspect ratio. Reviewers: btrahan, chad Reviewed By: chad Subscribers: spicyj, epriestley Maniphest Tasks: T4724, T7707 Differential Revision: https://secure.phabricator.com/D12810 --- .../files/markup/PhabricatorEmbedFileRemarkupRule.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php index fe0bb1da7a..87d611c0ef 100644 --- a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php +++ b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php @@ -110,8 +110,8 @@ final class PhabricatorEmbedFileRemarkupRule $attrs['src'] = $file->getPreview220URI(); $dimensions = PhabricatorImageTransformer::getPreviewDimensions($file, 220); - $attrs['width'] = $dimensions['sdx']; - $attrs['height'] = $dimensions['sdy']; + $attrs['width'] = $dimensions['dx']; + $attrs['height'] = $dimensions['dy']; $image_class = 'phabricator-remarkup-embed-image'; break; } From 2bdb5404c7e909f6aa7d10a951f516d3e7265e96 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 May 2015 10:50:02 -0700 Subject: [PATCH 56/70] Implement new profile transform with amazing "error handling" feature Summary: Ref T7707. Ref T4406. Ref T2479. This implements the profile-style (fixed width and height) transforms in a modern way. - Added a "regnerate" feature to the support UI to make testing easier and surface errors. - Laboriously check errors from everything. - Fix the profile thumbnailing so it crops properly instead of leaving margins. - Also defuses the "gigantic white PNG" attack. This doesn't handle the imagemagick case (for animated GIFs) yet. Test Plan: - Uploaded a variety of wide/narrow/small/large files and converted them into sensible profile pictures. - Tried to thumbnail some text files. - Set the pixel-size and file-size limits artificially small and hit them. - Used "regenerate" a bunch while testing the rest of this stuff. - Verified that non-regenerate flows still produce a default/placeholder image. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T4406, T2479, T7707 Differential Revision: https://secure.phabricator.com/D12811 --- .../PhabricatorFileTransformController.php | 34 ++- ...PhabricatorFileTransformListController.php | 11 +- .../PhabricatorFileImageTransform.php | 284 ++++++++++++++++++ .../PhabricatorFileThumbnailTransform.php | 48 ++- 4 files changed, 354 insertions(+), 23 deletions(-) diff --git a/src/applications/files/controller/PhabricatorFileTransformController.php b/src/applications/files/controller/PhabricatorFileTransformController.php index a7fcbc3c0a..dd7c355061 100644 --- a/src/applications/files/controller/PhabricatorFileTransformController.php +++ b/src/applications/files/controller/PhabricatorFileTransformController.php @@ -13,6 +13,8 @@ final class PhabricatorFileTransformController // NOTE: This is a public/CDN endpoint, and permission to see files is // controlled by knowing the secret key, not by authentication. + $is_regenerate = $request->getBool('regenerate'); + $source_phid = $request->getURIData('phid'); $file = id(new PhabricatorFileQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) @@ -35,7 +37,11 @@ final class PhabricatorFileTransformController $transform); if ($xform) { - return $this->buildTransformedFileResponse($xform); + if ($is_regenerate) { + $this->destroyTransform($xform); + } else { + return $this->buildTransformedFileResponse($xform); + } } $type = $file->getMimeType(); @@ -57,10 +63,12 @@ final class PhabricatorFileTransformController try { $xformed_file = $xforms[$transform]->applyTransform($file); } catch (Exception $ex) { - // TODO: Provide a diagnostic mode to surface these to the viewer. - // In normal transform mode, we ignore failures and generate a - // default transform instead. + // default transform below. If we're explicitly regenerating the + // thumbnail, rethrow the exception. + if ($is_regenerate) { + throw $ex; + } } } @@ -165,4 +173,22 @@ final class PhabricatorFileTransformController return $xformer->executeThumbTransform($file, $x, $y); } + private function destroyTransform(PhabricatorTransformedFile $xform) { + $file = id(new PhabricatorFileQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($xform->getTransformedPHID())) + ->executeOne(); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + + if (!$file) { + $xform->delete(); + } else { + $engine = new PhabricatorDestructionEngine(); + $engine->destroyObject($file); + } + + unset($unguarded); + } + } diff --git a/src/applications/files/controller/PhabricatorFileTransformListController.php b/src/applications/files/controller/PhabricatorFileTransformListController.php index ace9abff4c..6ef1818962 100644 --- a/src/applications/files/controller/PhabricatorFileTransformListController.php +++ b/src/applications/files/controller/PhabricatorFileTransformListController.php @@ -58,12 +58,13 @@ final class PhabricatorFileTransformListController if ($xform->canApplyTransform($file)) { $can_apply = pht('Yes'); + $view_href = $file->getURIForTransform($xform); - if ($dst_phid) { - $view_text = pht('View Transform'); - } else { - $view_text = pht('Generate Transform'); - } + $view_href = new PhutilURI($view_href); + $view_href->setQueryParam('regenerate', 'true'); + + $view_text = pht('Regenerate'); + $view_link = phutil_tag( 'a', array( diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php index 498685f356..ae5cbad305 100644 --- a/src/applications/files/transform/PhabricatorFileImageTransform.php +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -2,6 +2,12 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { + private $file; + private $data; + private $image; + private $imageX; + private $imageY; + public function canApplyTransform(PhabricatorFile $file) { if (!$file->isViewableImage()) { return false; @@ -14,4 +20,282 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { return true; } + protected function willTransformFile(PhabricatorFile $file) { + $this->file = $file; + $this->data = null; + $this->image = null; + $this->imageX = null; + $this->imageY = null; + } + + protected function applyCropAndScale( + $dst_w, $dst_h, + $src_x, $src_y, + $src_w, $src_h) { + + // Figure out the effective destination width, height, and offsets. We + // never want to scale images up, so if we're copying a very small source + // image we're just going to center it in the destination image. + $cpy_w = min($dst_w, $src_w); + $cpy_h = min($dst_h, $src_h); + $off_x = ($dst_w - $cpy_w) / 2; + $off_y = ($dst_h - $cpy_h) / 2; + + // TODO: Support imagemagick for animated GIFs. + + $src = $this->getImage(); + $dst = $this->newEmptyImage($dst_w, $dst_h); + + $trap = new PhutilErrorTrap(); + $ok = @imagecopyresampled( + $dst, + $src, + $off_x, $off_y, + $src_x, $src_y, + $cpy_w, $cpy_h, + $src_w, $src_h); + $errors = $trap->getErrorsAsString(); + $trap->destroy(); + + if ($ok === false) { + throw new Exception( + pht( + 'Failed to imagecopyresampled() image: %s', + $errors)); + } + + $data = PhabricatorImageTransformer::saveImageDataInAnyFormat( + $dst, + $this->file->getMimeType()); + + return $this->newFileFromData($data); + } + + + /** + * Create a new @{class:PhabricatorFile} from raw data. + * + * @param string Raw file data. + */ + protected function newFileFromData($data) { + $name = $this->getTransformKey().'-'.$this->file->getName(); + + return PhabricatorFile::newFromFileData( + $data, + array( + 'name' => $name, + 'canCDN' => true, + )); + } + + + /** + * Create a new image filled with transparent pixels. + * + * @param int Desired image width. + * @param int Desired image height. + * @return resource New image resource. + */ + protected function newEmptyImage($w, $h) { + $w = (int)$w; + $h = (int)$h; + + if (($w <= 0) || ($h <= 0)) { + throw new Exception( + pht('Can not create an image with nonpositive dimensions.')); + } + + $trap = new PhutilErrorTrap(); + $img = @imagecreatetruecolor($w, $h); + $errors = $trap->getErrorsAsString(); + $trap->destroy(); + if ($img === false) { + throw new Exception( + pht( + 'Unable to imagecreatetruecolor() a new empty image: %s', + $errors)); + } + + $trap = new PhutilErrorTrap(); + $ok = @imagesavealpha($img, true); + $errors = $trap->getErrorsAsString(); + $trap->destroy(); + if ($ok === false) { + throw new Exception( + pht( + 'Unable to imagesavealpha() a new empty image: %s', + $errors)); + } + + $trap = new PhutilErrorTrap(); + $color = @imagecolorallocatealpha($img, 255, 255, 255, 127); + $errors = $trap->getErrorsAsString(); + $trap->destroy(); + if ($color === false) { + throw new Exception( + pht( + 'Unable to imagecolorallocatealpha() a new empty image: %s', + $errors)); + } + + $trap = new PhutilErrorTrap(); + $ok = @imagefill($img, 0, 0, $color); + $errors = $trap->getErrorsAsString(); + $trap->destroy(); + if ($ok === false) { + throw new Exception( + pht( + 'Unable to imagefill() a new empty image: %s', + $errors)); + } + + return $img; + } + + + /** + * Get the pixel dimensions of the image being transformed. + * + * @return list Width and height of the image. + */ + protected function getImageDimensions() { + if ($this->imageX === null) { + $image = $this->getImage(); + + $trap = new PhutilErrorTrap(); + $x = @imagesx($image); + $y = @imagesy($image); + $errors = $trap->getErrorsAsString(); + $trap->destroy(); + + if (($x === false) || ($y === false) || ($x <= 0) || ($y <= 0)) { + throw new Exception( + pht( + 'Unable to determine image dimensions with '. + 'imagesx()/imagesy(): %s', + $errors)); + } + + $this->imageX = $x; + $this->imageY = $y; + } + + return array($this->imageX, $this->imageY); + } + + + /** + * Get the raw file data for the image being transformed. + * + * @return string Raw file data. + */ + protected function getData() { + if ($this->data !== null) { + return $this->data; + } + + $file = $this->file; + + $max_size = (1024 * 1024 * 4); + $img_size = $file->getByteSize(); + if ($img_size > $max_size) { + throw new Exception( + pht( + 'This image is too large to transform. The transform limit is %s '. + 'bytes, but the image size is %s bytes.', + new PhutilNumber($max_size), + new PhutilNumber($img_size))); + } + + $data = $file->loadFileData(); + $this->data = $data; + return $this->data; + } + + + /** + * Get the GD image resource for the image being transformed. + * + * @return resource GD image resource. + */ + protected function getImage() { + if ($this->image !== null) { + return $this->image; + } + + if (!function_exists('imagecreatefromstring')) { + throw new Exception( + pht( + 'Unable to transform image: the imagecreatefromstring() function '. + 'is not available. Install or enable the "gd" extension for PHP.')); + } + + $data = $this->getData(); + $data = (string)$data; + + // First, we're going to write the file to disk and use getimagesize() + // to determine its dimensions without actually loading the pixel data + // into memory. For very large images, we'll bail out. + + // In particular, this defuses a resource exhaustion attack where the + // attacker uploads a 40,000 x 40,000 pixel PNGs of solid white. These + // kinds of files compress extremely well, but require a huge amount + // of memory and CPU to process. + + $tmp = new TempFile(); + Filesystem::writeFile($tmp, $data); + $tmp_path = (string)$tmp; + + $trap = new PhutilErrorTrap(); + $info = @getimagesize($tmp_path); + $errors = $trap->getErrorsAsString(); + $trap->destroy(); + + unset($tmp); + + if ($info === false) { + throw new Exception( + pht( + 'Unable to get image information with getimagesize(): %s', + $errors)); + } + + list($width, $height) = $info; + if (($width <= 0) || ($height <= 0)) { + throw new Exception( + pht( + 'Unable to determine image width and height with getimagesize().')); + } + + $max_pixels = (4096 * 4096); + $img_pixels = ($width * $height); + + if ($img_pixels > $max_pixels) { + throw new Exception( + pht( + 'This image (with dimensions %spx x %spx) is too large to '. + 'transform. The image has %s pixels, but transforms are limited '. + 'to images with %s or fewer pixels.', + new PhutilNumber($width), + new PhutilNumber($height), + new PhutilNumber($img_pixels), + new PhutilNumber($max_pixels))); + } + + $trap = new PhutilErrorTrap(); + $image = @imagecreatefromstring($data); + $errors = $trap->getErrorsAsString(); + $trap->destroy(); + + if ($image === false) { + throw new Exception( + pht( + 'Unable to load image data with imagecreatefromstring(): %s', + $errors)); + } + + $this->image = $image; + return $this->image; + } + } diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php index 75c16e2ddf..2921869c2c 100644 --- a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -59,16 +59,41 @@ final class PhabricatorFileThumbnailTransform } public function applyTransform(PhabricatorFile $file) { - $x = $this->dstX; - $y = $this->dstY; - $xformer = new PhabricatorImageTransformer(); - - if ($y === null) { - return $xformer->executePreviewTransform($file, $x); - } else { - return $xformer->executeThumbTransform($file, $x, $y); + if ($this->dstY === null) { + return $xformer->executePreviewTransform($file, $this->dstX); } + + $this->willTransformFile($file); + + list($src_x, $src_y) = $this->getImageDimensions(); + $dst_x = $this->dstX; + $dst_y = $this->dstY; + + // Figure out how much we'd have to scale the image down along each + // dimension to get the entire thing to fit. + $scale_x = min(($dst_x / $src_x), 1); + $scale_y = min(($dst_y / $src_y), 1); + + if ($scale_x > $scale_y) { + // This image is relatively tall and narrow. We're going to crop off the + // top and bottom. + $copy_x = $src_x; + $copy_y = min($src_y, $dst_y / $scale_x); + } else { + // This image is relatively short and wide. We're going to crop off the + // left and right. + $copy_x = min($src_x, $dst_x / $scale_y); + $copy_y = $src_y; + } + + return $this->applyCropAndScale( + $dst_x, + $dst_y, + ($src_x - $copy_x) / 2, + ($src_y - $copy_y) / 2, + $copy_x, + $copy_y); } public function getDefaultTransform(PhabricatorFile $file) { @@ -76,15 +101,10 @@ final class PhabricatorFileThumbnailTransform $y = (int)$this->dstY; $name = 'image-'.$x.'x'.nonempty($y, $x).'.png'; - $params = array( - 'name' => $name, - 'canCDN' => true, - ); - $root = dirname(phutil_get_library_root('phabricator')); $data = Filesystem::readFile($root.'/resources/builtin/'.$name); - return PhabricatorFile::newFromFileData($data, $params); + return $this->newFileFromData($data); } } From fd1e0bc4d3020d64ba32c97fc86f4ccbf2d243b9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 May 2015 11:20:24 -0700 Subject: [PATCH 57/70] Implement error-checked "preview" transforms Summary: Ref T7707. These transforms have a single maximum dimension instead of fixed X and Y dimensions. Test Plan: Transformed a bunch of files with different sizes/aspect ratios, got sensible results. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7707 Differential Revision: https://secure.phabricator.com/D12812 --- .../PhabricatorFileImageTransform.php | 7 +- .../PhabricatorFileThumbnailTransform.php | 66 ++++++++++++++----- 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php index ae5cbad305..4878b9e2c7 100644 --- a/src/applications/files/transform/PhabricatorFileImageTransform.php +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -31,13 +31,14 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { protected function applyCropAndScale( $dst_w, $dst_h, $src_x, $src_y, - $src_w, $src_h) { + $src_w, $src_h, + $use_w, $use_h) { // Figure out the effective destination width, height, and offsets. We // never want to scale images up, so if we're copying a very small source // image we're just going to center it in the destination image. - $cpy_w = min($dst_w, $src_w); - $cpy_h = min($dst_h, $src_h); + $cpy_w = min($dst_w, $src_w, $use_w); + $cpy_h = min($dst_h, $src_h, $use_h); $off_x = ($dst_w - $cpy_w) / 2; $off_y = ($dst_h - $cpy_h) / 2; diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php index 2921869c2c..45d7ad670a 100644 --- a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -59,32 +59,60 @@ final class PhabricatorFileThumbnailTransform } public function applyTransform(PhabricatorFile $file) { - $xformer = new PhabricatorImageTransformer(); - if ($this->dstY === null) { - return $xformer->executePreviewTransform($file, $this->dstX); - } - $this->willTransformFile($file); list($src_x, $src_y) = $this->getImageDimensions(); $dst_x = $this->dstX; $dst_y = $this->dstY; - // Figure out how much we'd have to scale the image down along each - // dimension to get the entire thing to fit. - $scale_x = min(($dst_x / $src_x), 1); - $scale_y = min(($dst_y / $src_y), 1); + if ($dst_y === null) { + // If we only have one dimension, it represents a maximum dimension. + // The other dimension of the transform is scaled appropriately, except + // that we never generate images with crazily extreme aspect ratios. + if ($src_x < $src_y) { + // This is a tall, narrow image. Use the maximum dimension for the + // height and scale the width. + $use_y = $dst_x; + $dst_y = $dst_x; - if ($scale_x > $scale_y) { - // This image is relatively tall and narrow. We're going to crop off the - // top and bottom. + $use_x = $dst_y * ($src_x / $src_y); + $dst_x = max($dst_y / 4, $use_x); + } else { + // This is a short, wide image. Use the maximum dimension for the width + // and scale the height. + $use_x = $dst_x; + + $use_y = $dst_x * ($src_y / $src_x); + $dst_y = max($dst_x / 4, $use_y); + } + + // In this mode, we always copy the entire source image. We may generate + // margins in the output. $copy_x = $src_x; - $copy_y = min($src_y, $dst_y / $scale_x); - } else { - // This image is relatively short and wide. We're going to crop off the - // left and right. - $copy_x = min($src_x, $dst_x / $scale_y); $copy_y = $src_y; + } else { + // Otherwise, both dimensions are fixed. Figure out how much we'd have to + // scale the image down along each dimension to get the entire thing to + // fit. + $scale_x = min(($dst_x / $src_x), 1); + $scale_y = min(($dst_y / $src_y), 1); + + if ($scale_x > $scale_y) { + // This image is relatively tall and narrow. We're going to crop off the + // top and bottom. + $copy_x = $src_x; + $copy_y = min($src_y, $dst_y / $scale_x); + } else { + // This image is relatively short and wide. We're going to crop off the + // left and right. + $copy_x = min($src_x, $dst_x / $scale_y); + $copy_y = $src_y; + } + + // In this mode, we always use the entire destination image. We may + // crop the source input. + $use_x = $dst_x; + $use_y = $dst_y; } return $this->applyCropAndScale( @@ -93,7 +121,9 @@ final class PhabricatorFileThumbnailTransform ($src_x - $copy_x) / 2, ($src_y - $copy_y) / 2, $copy_x, - $copy_y); + $copy_y, + $use_x, + $use_y); } public function getDefaultTransform(PhabricatorFile $file) { From 200e525df1228c411d7ece5a56f05366f15bae8b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 May 2015 11:51:55 -0700 Subject: [PATCH 58/70] Support imagemagick on new image transform pathway Summary: Ref T7707. For animated GIFs, use imagemagick if it is available. Test Plan: Generated small versions of a bunch of different GIFs. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7707 Differential Revision: https://secure.phabricator.com/D12813 --- .../PhabricatorFileImageTransform.php | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php index 4878b9e2c7..3c44825dd5 100644 --- a/src/applications/files/transform/PhabricatorFileImageTransform.php +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -42,7 +42,19 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { $off_x = ($dst_w - $cpy_w) / 2; $off_y = ($dst_h - $cpy_h) / 2; - // TODO: Support imagemagick for animated GIFs. + if ($this->shouldUseImagemagick()) { + $argv = array(); + $argv[] = '-coalesce'; + $argv[] = '-shave'; + $argv[] = $src_x.'x'.$src_y; + $argv[] = '-resize'; + $argv[] = $dst_w.'x'.$dst_h.'>'; + $argv[] = '-bordercolor'; + $argv[] = 'rgba(255, 255, 255, 0)'; + $argv[] = '-border'; + $argv[] = $off_x.'x'.$off_y; + return $this->applyImagemagick($argv); + } $src = $this->getImage(); $dst = $this->newEmptyImage($dst_w, $dst_h); @@ -72,6 +84,22 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { return $this->newFileFromData($data); } + protected function applyImagemagick(array $argv) { + $tmp = new TempFile(); + Filesystem::writeFile($tmp, $this->getData()); + + $out = new TempFile(); + + $future = new ExecFuture('convert %s %Ls %s', $tmp, $argv, $out); + // Don't spend more than 10 seconds resizing; just fail if it takes longer + // than that. + $future->setTimeout(10)->resolvex(); + + $data = Filesystem::readFile($out); + + return $this->newFileFromData($data); + } + /** * Create a new @{class:PhabricatorFile} from raw data. @@ -299,4 +327,22 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { return $this->image; } + private function shouldUseImagemagick() { + if (!PhabricatorEnv::getEnvConfig('files.enable-imagemagick')) { + return false; + } + + if ($this->file->getMimeType() != 'image/gif') { + return false; + } + + // Don't try to preserve the animation in huge GIFs. + list($x, $y) = $this->getImageDimensions(); + if (($x * $y) > (512 * 512)) { + return false; + } + + return true; + } + } From 75f6211233019702fb39a11dea5cf56783977db9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 May 2015 12:24:07 -0700 Subject: [PATCH 59/70] Convert "preview" image transforms to new pathway Summary: Ref T7707. Move the 220px (file uploads) and 100px (Pholio thumbgrid) previews over to the new stuff. Test Plan: Uploaded a bunch of images to remarkup and Pholio; they generated reasonable results in the web UI. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7707 Differential Revision: https://secure.phabricator.com/D12814 --- .../files/PhabricatorImageTransformer.php | 70 ----------------- .../PhabricatorFileTransformController.php | 14 ---- .../PhabricatorEmbedFileRemarkupRule.php | 16 ++-- .../files/storage/PhabricatorFile.php | 8 -- .../PhabricatorFileImageTransform.php | 10 +++ .../PhabricatorFileThumbnailTransform.php | 71 +++++++++++++++--- .../transform/PhabricatorFileTransform.php | 14 ++++ .../pholio/view/PholioMockThumbGridView.php | 36 +++++---- .../icon/fatcow/thumbnails/default.p100.png | Bin 1720 -> 0 bytes .../icon/fatcow/thumbnails/image.p100.png | Bin 2167 -> 0 bytes .../image/icon/fatcow/thumbnails/pdf.p100.png | Bin 2363 -> 0 bytes .../image/icon/fatcow/thumbnails/zip.p100.png | Bin 2067 -> 0 bytes 12 files changed, 118 insertions(+), 121 deletions(-) delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/default.p100.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/image.p100.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/pdf.p100.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/zip.p100.png diff --git a/src/applications/files/PhabricatorImageTransformer.php b/src/applications/files/PhabricatorImageTransformer.php index 95cf1fac7a..0bbcdb9b31 100644 --- a/src/applications/files/PhabricatorImageTransformer.php +++ b/src/applications/files/PhabricatorImageTransformer.php @@ -51,20 +51,6 @@ final class PhabricatorImageTransformer { )); } - public function executePreviewTransform( - PhabricatorFile $file, - $size) { - - $image = $this->generatePreview($file, $size); - - return PhabricatorFile::newFromFileData( - $image, - array( - 'name' => 'preview-'.$file->getName(), - 'canCDN' => true, - )); - } - public function executeConpherenceTransform( PhabricatorFile $file, $top, @@ -188,37 +174,6 @@ final class PhabricatorImageTransformer { } - public static function getPreviewDimensions(PhabricatorFile $file, $size) { - $metadata = $file->getMetadata(); - $x = idx($metadata, PhabricatorFile::METADATA_IMAGE_WIDTH); - $y = idx($metadata, PhabricatorFile::METADATA_IMAGE_HEIGHT); - - if (!$x || !$y) { - $data = $file->loadFileData(); - $src = imagecreatefromstring($data); - - $x = imagesx($src); - $y = imagesy($src); - } - - $scale = min($size / $x, $size / $y, 1); - - $dx = max($size / 4, $scale * $x); - $dy = max($size / 4, $scale * $y); - - $sdx = $scale * $x; - $sdy = $scale * $y; - - return array( - 'x' => $x, - 'y' => $y, - 'dx' => $dx, - 'dy' => $dy, - 'sdx' => $sdx, - 'sdy' => $sdy, - ); - } - public static function getScaleForCrop( PhabricatorFile $file, $des_width, @@ -241,31 +196,6 @@ final class PhabricatorImageTransformer { return $scale; } - private function generatePreview(PhabricatorFile $file, $size) { - $data = $file->loadFileData(); - $src = imagecreatefromstring($data); - - $dimensions = self::getPreviewDimensions($file, $size); - $x = $dimensions['x']; - $y = $dimensions['y']; - $dx = $dimensions['dx']; - $dy = $dimensions['dy']; - $sdx = $dimensions['sdx']; - $sdy = $dimensions['sdy']; - - $dst = $this->getBlankDestinationFile($dx, $dy); - - imagecopyresampled( - $dst, - $src, - ($dx - $sdx) / 2, ($dy - $sdy) / 2, - 0, 0, - $sdx, $sdy, - $x, $y); - - return self::saveImageDataInAnyFormat($dst, $file->getMimeType()); - } - private function applyMemeToFile( PhabricatorFile $file, $upper_text, diff --git a/src/applications/files/controller/PhabricatorFileTransformController.php b/src/applications/files/controller/PhabricatorFileTransformController.php index dd7c355061..ae31990c06 100644 --- a/src/applications/files/controller/PhabricatorFileTransformController.php +++ b/src/applications/files/controller/PhabricatorFileTransformController.php @@ -85,12 +85,6 @@ final class PhabricatorFileTransformController case 'thumb-280x210': $xformed_file = $this->executeThumbTransform($file, 280, 210); break; - case 'preview-100': - $xformed_file = $this->executePreviewTransform($file, 100); - break; - case 'preview-220': - $xformed_file = $this->executePreviewTransform($file, 220); - break; default: return new Aphront400Response(); } @@ -132,9 +126,6 @@ final class PhabricatorFileTransformController case 'thumb-280x210': $suffix = '280x210'; break; - case 'preview-100': - $suffix = '.p100'; - break; default: throw new Exception('Unsupported transformation type!'); } @@ -163,11 +154,6 @@ final class PhabricatorFileTransformController return $file->getRedirectResponse(); } - private function executePreviewTransform(PhabricatorFile $file, $size) { - $xformer = new PhabricatorImageTransformer(); - return $xformer->executePreviewTransform($file, $size); - } - private function executeThumbTransform(PhabricatorFile $file, $x, $y) { $xformer = new PhabricatorImageTransformer(); return $xformer->executeThumbTransform($file, $x, $y); diff --git a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php index 87d611c0ef..5ffb70be7f 100644 --- a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php +++ b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php @@ -107,11 +107,17 @@ final class PhabricatorEmbedFileRemarkupRule break; case 'thumb': default: - $attrs['src'] = $file->getPreview220URI(); - $dimensions = - PhabricatorImageTransformer::getPreviewDimensions($file, 220); - $attrs['width'] = $dimensions['dx']; - $attrs['height'] = $dimensions['dy']; + $preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW; + $xform = PhabricatorFileTransform::getTransformByKey($preview_key); + $attrs['src'] = $file->getURIForTransform($xform); + + $dimensions = $xform->getTransformedDimensions($file); + if ($dimensions) { + list($x, $y) = $dimensions; + $attrs['width'] = $x; + $attrs['height'] = $y; + } + $image_class = 'phabricator-remarkup-embed-image'; break; } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index c4aa7dc42c..9ff75cce17 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -788,14 +788,6 @@ final class PhabricatorFile extends PhabricatorFileDAO return $this->getTransformedURI('thumb-profile'); } - public function getPreview100URI() { - return $this->getTransformedURI('preview-100'); - } - - public function getPreview220URI() { - return $this->getTransformedURI('preview-220'); - } - public function getThumb280x210URI() { return $this->getTransformedURI('thumb-280x210'); } diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php index 3c44825dd5..250aa887dd 100644 --- a/src/applications/files/transform/PhabricatorFileImageTransform.php +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -8,6 +8,16 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { private $imageX; private $imageY; + /** + * Get an estimate of the transformed dimensions of a file. + * + * @param PhabricatorFile File to transform. + * @return list|null Width and height, if available. + */ + public function getTransformedDimensions(PhabricatorFile $file) { + return null; + } + public function canApplyTransform(PhabricatorFile $file) { if (!$file->isViewableImage()) { return false; diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php index 45d7ad670a..3a72b5cf9e 100644 --- a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -65,6 +65,59 @@ final class PhabricatorFileThumbnailTransform $dst_x = $this->dstX; $dst_y = $this->dstY; + $dimensions = $this->computeDimensions( + $src_x, + $src_y, + $dst_x, + $dst_y); + + $copy_x = $dimensions['copy_x']; + $copy_y = $dimensions['copy_y']; + $use_x = $dimensions['use_x']; + $use_y = $dimensions['use_y']; + $dst_x = $dimensions['dst_x']; + $dst_y = $dimensions['dst_y']; + + return $this->applyCropAndScale( + $dst_x, + $dst_y, + ($src_x - $copy_x) / 2, + ($src_y - $copy_y) / 2, + $copy_x, + $copy_y, + $use_x, + $use_y); + } + + + public function getTransformedDimensions(PhabricatorFile $file) { + $dst_x = $this->dstX; + $dst_y = $this->dstY; + + // If this is transform has fixed dimensions, we can trivially predict + // the dimensions of the transformed file. + if ($dst_y !== null) { + return array($dst_x, $dst_y); + } + + $src_x = $file->getImageWidth(); + $src_y = $file->getImageHeight(); + + if (!$src_x || !$src_y) { + return null; + } + + $dimensions = $this->computeDimensions( + $src_x, + $src_y, + $dst_x, + $dst_y); + + return array($dimensions['dst_x'], $dimensions['dst_y']); + } + + + private function computeDimensions($src_x, $src_y, $dst_x, $dst_y) { if ($dst_y === null) { // If we only have one dimension, it represents a maximum dimension. // The other dimension of the transform is scaled appropriately, except @@ -115,17 +168,17 @@ final class PhabricatorFileThumbnailTransform $use_y = $dst_y; } - return $this->applyCropAndScale( - $dst_x, - $dst_y, - ($src_x - $copy_x) / 2, - ($src_y - $copy_y) / 2, - $copy_x, - $copy_y, - $use_x, - $use_y); + return array( + 'copy_x' => $copy_x, + 'copy_y' => $copy_y, + 'use_x' => $use_x, + 'use_y' => $use_y, + 'dst_x' => $dst_x, + 'dst_y' => $dst_y, + ); } + public function getDefaultTransform(PhabricatorFile $file) { $x = (int)$this->dstX; $y = (int)$this->dstY; diff --git a/src/applications/files/transform/PhabricatorFileTransform.php b/src/applications/files/transform/PhabricatorFileTransform.php index f12aefd3ed..633a80887a 100644 --- a/src/applications/files/transform/PhabricatorFileTransform.php +++ b/src/applications/files/transform/PhabricatorFileTransform.php @@ -45,4 +45,18 @@ abstract class PhabricatorFileTransform extends Phobject { return $map; } + public static function getTransformByKey($key) { + $all = self::getAllTransforms(); + + $xform = idx($all, $key); + if (!$xform) { + throw new Exception( + pht( + 'No file transform with key "%s" exists.', + $key)); + } + + return $xform; + } + } diff --git a/src/applications/pholio/view/PholioMockThumbGridView.php b/src/applications/pholio/view/PholioMockThumbGridView.php index df9fe1aa06..8e9d3007c5 100644 --- a/src/applications/pholio/view/PholioMockThumbGridView.php +++ b/src/applications/pholio/view/PholioMockThumbGridView.php @@ -114,28 +114,34 @@ final class PholioMockThumbGridView extends AphrontView { private function renderThumbnail(PholioImage $image) { $thumbfile = $image->getFile(); + $preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_THUMBGRID; + $xform = PhabricatorFileTransform::getTransformByKey($preview_key); + + $attributes = array( + 'class' => 'pholio-mock-thumb-grid-image', + 'src' => $thumbfile->getURIForTransform($xform), + ); + if ($image->getFile()->isViewableImage()) { - $dimensions = PhabricatorImageTransformer::getPreviewDimensions( - $thumbfile, - 100); + $dimensions = $xform->getTransformedDimensions($thumbfile); + if ($dimensions) { + list($x, $y) = $dimensions; + $attributes += array( + 'width' => $x, + 'height' => $y, + 'style' => 'top: '.floor((100 - $y) / 2).'px', + ); + } } else { // If this is a PDF or a text file or something, we'll end up using a // generic thumbnail which is always sized correctly. - $dimensions = array( - 'sdx' => 100, - 'sdy' => 100, + $attributes += array( + 'width' => 100, + 'height' => 100, ); } - $tag = phutil_tag( - 'img', - array( - 'width' => $dimensions['sdx'], - 'height' => $dimensions['sdy'], - 'src' => $thumbfile->getPreview100URI(), - 'class' => 'pholio-mock-thumb-grid-image', - 'style' => 'top: '.floor((100 - $dimensions['sdy'] ) / 2).'px', - )); + $tag = phutil_tag('img', $attributes); $classes = array('pholio-mock-thumb-grid-item'); if ($image->getIsObsolete()) { diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/default.p100.png b/webroot/rsrc/image/icon/fatcow/thumbnails/default.p100.png deleted file mode 100644 index f713c2398bafd8dad1d22b179532f0c3920c7dd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1720 zcmaJ?3s4hR6kUQKVg($r6rr%hKQ=!C!K?`o5|U61B1FNdErw(RF=RLHE=>@plR9cs zrlW|ltwoJcu+$F}jG`cZriujx3LT`t08<470b8lqDyZG?vz?Ld?CyK-zH`pK@4d6L zpU1^Y7Pu~P1pr_{v{V#NwkrGM;!M8v_YTX+#+!&rA`(zFVNhT&AW)$xFc_^GWfX&UKH3{Kb?D_8E+D!D3Zco@htLZpBWCKRAimyY0&kx!k_g~+}=%%Fl3 z5JJnRP8O9Uivxuy27@7VHciQ7u|N)o&I%6Ua9HnwK}=QtI;Xpy5;M7GW zvtcSV6fY7_FwhNbI*O$-SX?gGt`QVOBM~$_6Co5v8iFsKRuI9s z64U4j4T^wvMMVmlLGY<$rISn0>1SjSd}^D>4PzJ;dIpQmv@dBID3kp^RHvIk<3v3C zy52VmB%!r3^Gy_wT2d7?YPo;;17_1;rOpc=I)4Ld#h7u^AhU!6K0tZ|rQz$iv zJ+OL$A(KJT2u>&vB^)i{Q%McFMx%lR;^0uWK+KI2MhaN0DE8X5kwT$>$z+MxOkoHo zc$zChl^Hr1A*Q*i*IdD@T)P=`dNQ*J#xx(pDlvxYz==gen%Qd!omFp&tD3!*wX<>= zWHAi;(f;eGr$eNF?A;mTl7kuR!wBhjj5K!n_xMKgdkKsd3FOB1q4qN5mpIRw$630S z!GU|!z+%zdq*d$F-=eK62Bk5Eh&$$a&dWSfls!&Hllf<=LyXm7bD?WMnT_JG_vG34 z&GnZq9NJ60S0lLf@YU&(g5nbG*aN&|_=b3+%6vR20thexk$}S6PWJ=dHuOU#uQ56oZvdD(B^S83bj51%3G74BZnQG7GwY~W!3$<6armij!; zqD*Ba9naqGc^>Q>^iOWsWw>d93m0YE7>&NCAF&s;WZOy?;2*WO-mAw)+pG6FZHeq1 z_f9L#iD)o}=68fGta8G15BHQ+So6C4E0gyrcuf`6%~6?&2g}@kqCZ;s+-b)=`1%2D zUf;m@5#x|0_MO&l7qIH6u-s%UAKI?%ICBQQ)zY*k$2Rx)vEq!5{7r2OpCp>SD)Q4M zjW2hg$rg#|`2&ycJ`9pT^9#PFt?~D8l+u0rmk+OW_n0pYTTC$*tP^(obX7WdZ*yx~ zv-*y2t|V;>w;{k(e98TBZeFFYI^zS=)9?HWhaxW4#pgWfr&Yi547b)T&6l?i4SahF zru6#Nruq&z)_fW$VnW|o1K%!!+qyRRX$#<9y}MV@frh!IImN&R8>RmIL0{=#oyV?Q zTULP28K#!qmgZZJ5@X->uB)s7YQJo{W^)TOR$m^JGH88=Q2C;awiOR5g^ bRRE4ajngUZZ?=XDhBlbetlCwxUfb$uF=&}li)zWyX3H{%y=m$6%$YO$$KG@A@AvzD@Avcle!h=$ z&pj64?`^1Ws*ggU4B0-cAY|uh9^H?SRWx1_f@~(RXBZp|#lcA&F^F>KL9rmf7I5~1 zK_G{h{B;-TfK|mh{KHsVIUUV&lfVVDrGAc z;PV*RPzOJvpNI(_;QOSA!Qd4C5N=95*O`ZP{S0uC(Gdp%5as|fL4r_1mocz!{nC-M zMoqv1Z%yEM2KF7PFuwqR35h|#0Z*~#5=kV0M#Gb+4m28R4?rf8$OIyC)9gu9x&w_) zb_CWgED}x3i=zjzyw+kN5(9exhDCG&At@;dpG3h!;{61Yv$M0t2bpY-SlCOFg)m2E zFO*oWdtiYQu9z=^`H&FMc;v)FQka27n7&IvAoBBj?^q~VD-=>Pf{Y^~knlu;K%mL% zt+fOW0)L_Lo7R$$WD!UR0ws`C%tiVUXSohWYWMqrG=>NpIt$`T1)vaSvlv)JgXi&i zbayYRBgNgz)064pP9k|yoSZzEOm`xY#G(+H4m9dImMxUP93dB6$MS!{vVIp!XNo}% z42eS^C}F*O0S6!$k{p0U05g~d*!Xd{e4$2Rt10dpS5VAP1bJR!NC3R84xRri1{(H6 zk`vNd61}7nwJ-L4a@tD%>O5rfRG_*TKhq(*Hy^;XpZj( z7ZKi%J}5-yU5pI&-foi^6iVkgo8=xN8-97NH$GzD_Kx0WXw-^}r5!nr8$eG7WoISY z!C&P!J#X6=c*Aa0t8o)Xe=b5_*ieSsh%wGHQwE^5@g8y;7fIb5uq3 zHZR=eg_rN9lonr0Z55S%s=8mE++1-e{Yg)m-ltQ#)h7&cP;LN93$5#h($PkvGEq8+ z_-_4B`To_q@&o$|xtGKH@kdJ^Q9enRC`alGb_|#gPg(sV;C@qGYwFaM&~ga_Wpbm+ zHzsvujz44k%D44YH@Crjwtw5DP}eY7;CY8ifmUZse?P_iriHW(k*m29|Njv+`{hZFW00pT)prA{7rkg@MuC9@Zx^(W6?|fW^>*Fdurpvcy%ZOur6Cvm6nb^FlySX8zK3_#%=>`ZMPsY!B zmaDIP-j^M17kQ4^@ie8(M)^6MkQp`>|Ey}-B4>-jYXOa^mK#cfE+p*j;f}oNm=WXi zIkr25@spY{+wJWH{qEY(MAwX&aa{y9pP;9N1~tJu-MqIoOh05)jc4kA&GfL(iVEXjT(?qb^``%G&>|7;Gx! zHps^oeXt{y+177Vl5hRb^&fxivSR*x@@Nju8!6i?zr?wHqtQe4$vaOyEjxm~OpD;` zA)LD?jAt)BvS^*QtJG1}i%%*B2=L4HU1Zu1!Ab+b@^s7gOtt(+RdlqKHt&T>Wn1Nn zKk{IqICS7@u{?chp;p&iOf>pFGck?57@hI+38ufzb$6NyJ&3i~7wB_+=4igx;eg4) z$~)#kLzQ!hF71nzE;qkZEHnqV29inMvk9l->JpZ(rZKKg`TDLLtW&$lI?=>#>4y33 z{f}-gnyFtGntr>xPcP*s_3dKcEs-tc7X`f)kypoSv*l4TXzOe#Sx39G5Mv$cnD)$e zNjjNbtM#$AMMdS%HflcGHtAl)zh5^dD`rnbyQPc~4?^shwU)Vg*ryq>uWh9DdM01g zFO*N-g!)>xVQ=w!Uf(chD+v9Mqp~W~4@-5;x)o763Zs8*od3_WyJd}cihfuImgJZt zxEDal=~E8UW#ON8w+=-={G_3II_ZS`c83mNxuNTou^bxJciSAkM;G1rF~Bs6c+y+c zQ7{&6w#?Y<86gjy_inM9wWvOS<<^+At;gB$gmNqlmoLs|0^F00C_@Eej5Xm@c{ zf|}5(9=z_by3*D#>L}g$^(f?{Z!kEtc=6z-#ML0cKzfZ-)YE6dDP83&L#@gjDnWgy^ NV0-$rYCU4I{tE^`U)BHs diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf.p100.png b/webroot/rsrc/image/icon/fatcow/thumbnails/pdf.p100.png deleted file mode 100644 index ad3a39b490701b073d84d8e84756782481356adf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2363 zcmaJ@X;f3!7LLp_5n;(BTmlLTAqgZ5fgl+TLL@>cgCbbRMa2(?z;C3`+VQG_c?p7v+noIRVysb z9L-=bm}LZ=5~J;Ty3fQ&+e-TOv9z57L`{HV#p@wCTMEL6JTV6ZB82QTFa~7vvNK!3 zP#8?#gU?EU5*Sf0(>!8TtEQE2k=8<{7`7^8-T=M@wfoIUm)<|L1?{6 zdF$~p6xs)0+8F_{0fHoW6iP0a`^x=&#nLnsCO9})r{U*^)FP0wY!Sp(AVo6wIRy$R z<4XAwh%XiaIz=`|oCOgO+DPATK`8ksE0TRUChdWt6l@6!{C}uW_z^9G zV!%)FeoX<+$l0Qh#|3TgIEHPV*>y$ z2Aj(l=?qKXF&GSdgh&RlMO-j~LO^IWeEEDHo*YOb1^8jfR5FQ(!BG8!f=FaC5sk)B z{Ly4w0CtW`5p%PIpa`1d@;-4XU*+nGK`7CBrhroZMvzC7iiN4rh=oYQ2Kp0e zw6E%Y;PSo@%R4R!js7YZr40t9OYLW=o-=9dM>qUfxZ26b(g#J_dY5Vo`|U*OY8cG$ zOaz69}jBY)#6`G#cZc+D89Jsyb^y;-AS$ zH+){r?%7kKz8_MeY6^aNNR~$Xv);0epb0WCAk8;XG7U(8trBiR1$5K*?PtbMOCOAL zcwFxI#X(L1{K%cDDXWIMwt64llyHAK@|fwoTUhI`l;mWjHAOQtSpIX%?dFTkC&xYD zx#f=b_Ge4iBz5<{n#gd{-_;&^$F*rq$Hd_{mpwNN&yO83+|XTTJe;1+j7xl#5gXb5 zTbAH%bd+%W zTTa(k`rQ9xu;QM2;A->bCMJtjFs{*@$TPN@0L^XV^;>WEjlZ(!pPn)Gir%}=E;72+ z%&Xe)#BjQ!&0wjoO+#^YF5F}q;~5x;$&1^&e_?yU-}ybq6>H_xnqy%z)zO2ix7Vx0 zHxbxpz2=z34)r{6}Ca{W+mKH#+T=A-`RiZ!Zzh(t<~vevQVRCi#GOX=y%Fhegd1MgG>DVDpdI=!rwy6a(tdQ z0=xe@P^+mXauSOZ&cTB+Z8#Y|W*+N;%=AjOE{)s;Ch-pr)k)@ccIn>>5GQRW2)N=)@fC#@ zyKxPCE!{Ml9cy#ZQ={LHq#qnpy>iS)i7qKx$>{j7t#*$dJvn+|+Nt;1jeY%_h&|mk z4?FH3Zp$|D=+f+U&IqxyNR}JTwmn9UHKkY&zw|eG`qsI~H9B0~wCErGALdU(6_2QW zjog;pXZjQ7heE=FyYJW?nLZQNB&i$uEwbA~sXl47`f_kR&t`O#cxn*#re`R1@(AtU zhpGx5Y!>dn?sEAq7}*(DQ#II?f5kKYpBE_(HEI#%C}2ac(u-DIl-yYMltnKxL3rk3 z8Y*3~kw-6GcwBO_qHY21SLoDpz>(}uDBR+{0-nb#3#Z(hc$vKL_W7li{BbIUpEm&C zG`jfVSf+w!(coecr993nI5Z)T>fU{h$iJ!~SqueA;XN*b;U_IJmkX;V;ilL8Ec(|y zxqj6(@4MK^Qsyt2-v64GIFVAa+Sr!=**0)@5{lj8l5O z3~DcOz|FSl7y`Z{+@t^j*E?=f5bmYt2Dh5`+4VMe1%CA+tgC7=w1>GrJ4hd5QPjHM NnF#7CN;N4p|9?z`!d(CW diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/zip.p100.png b/webroot/rsrc/image/icon/fatcow/thumbnails/zip.p100.png deleted file mode 100644 index 86fa739b3b5bcbbafabf59b4aa990df899d2f954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2067 zcmaJ?c~lcu7awpbv>!@qL5kRpF)E5#Nuo&!TY?ZFDTYMhpkhcSFpbH?WPq?FSfp%H zsu*f*1xf*n3q{hZCn!?o6p^6f!Xm{}DisltPgyF^5BMf3=pUtXX5M@EzTfY6@4N4w zGufenK!>+o-v$7{ffvLTVzbQp*)7CI_5A`7Hhq8wgri}I98HjDAiz(CL_;7?DT#rE zkVKZa=PKj{0Q2y$C>#yvhp?oGk|?oah&rVjV*`Mfk4`O-Dj*b$hGJk98~5MybvO`~ zv2okzd@^6nfnwpHBn=dnBoIlH6jG)P=i?1}=~$S65<(@QPPtp9W$Dyc)FeEz>fmC9MP z78OFT^ZiF*tte3qk%W*IiPK22i<7UkhElUQ8c2d78WDo*o~dGJEP^81SVRqS!Wf`C zUm}H7R>#^G3_hR5Q)y9&N(%9~Y#gRRgkc$rv%%k=L8X%eIR1VVN`U9ajs6^tADK+y zdXhPG25pARMWk^`NQKUDWv{uEIl0zmP^z)WTu1}QL$Xa8LSIn^%Lnj2aX0S zmTYT&HaV76YRWsTG#X;9}*9&P(bT`M z7iLsPSN9d0(I*adJ8{J4Dn(=eDMND9h4n3#%O@9lTu9>oTKy&3-LgV+H!Nc)ah<`% zQ~JF}eZBFjtnX=_^6u@WRkg7T7cgZR#V1`u@NU`6ys((AE0g0>2KzlmDY;e459&EP z0;ko~)VMUYb@*0$AGgRX_~2!b`J%UW=hV;xpU+MwUd8Kok@^P*^Bj!fK=xVS7O>Cp ziH|N+z|f0V9U69jkVtnAj8|DSf@;^CQC~a_u5`^U{R-CFrqO>XvDjY;xy)#kn!dyg0fYv_vDzJBqEoolOBuX=W| zHW{wRB``lC%9Gy$rh{DEK0XY9r8iT4+Td~!qX-Clx3t$}Yhe9`c|$bMIXZR2`P z5GU28Xs9G}U@@CJu{t&72V{5apWga+JI;88je2|3pBZIxw{~sm4Q=V2NOQyOa(lGR zk2#Rhd*_3*g3i7brVq_jvhRo6gu>Fu}h)e+xr z{7sWl?l%49$u^91j97GcIs-fa z*soD@d9$L1Ip8$h_voh>dF6_8e{8%|JrWqTWP7IDE!)L#-=XwtMdXP;?U#QXZ)~fI zw2+H#*PeeKdt>k(?bjYtZ&qpa`8Zq&jG{(oVqaO$8o@VP^UCYS+i}A z(<~Y6Z#)S12Ck2W*GgzSE;rC=Uiv!4U-=I8jmzDO#x@@^5Q@H&_bP!~ zX$Qo^#)@nHtrxl%9JqV1qTp<0XL+SfwM*_9-;tkVmR3&575j_#hrd(SY##4z%%Xl7 z89C~ Nc>x0MkN&&T{tqesJH-G1 From 7e365eb8ae8cbae4d12d4018371eb816d6a52609 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 May 2015 15:50:46 -0700 Subject: [PATCH 60/70] Convert "profile" image transforms to the new pathway Summary: Ref T7707. This ends up being sort of complicated: to support 100x100 images in T4406, we need to scale small images //up// so they look OK when we scale them back down with `background-size` in CSS. The rest of it is mostly straightforward. Test Plan: - Did an OAuth handshake and saw a scaled-up, scaled-down profile picture that looked correct. - Used Pholio, edited pholio, embedded pholio. - Uploaded a bunch of small/weird/big images and regenerated all their transforms. - Uploaded some text files into Pholio. - Grepped for removed methods, etc. Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7707 Differential Revision: https://secure.phabricator.com/D12818 --- .../auth/view/PhabricatorAuthAccountView.php | 8 +- .../files/PhabricatorImageTransformer.php | 30 ------ .../PhabricatorFileTransformController.php | 86 +++--------------- .../files/storage/PhabricatorFile.php | 8 -- .../PhabricatorFileImageTransform.php | 36 ++++++-- .../PhabricatorFileThumbnailTransform.php | 37 ++++++-- .../query/PhabricatorMacroSearchEngine.php | 8 +- .../pholio/query/PholioMockSearchEngine.php | 11 ++- .../pholio/view/PholioMockEmbedView.php | 16 ++-- .../pholio/view/PholioMockImagesView.php | 9 +- .../pholio/view/PholioUploadedImageView.php | 6 +- webroot/rsrc/css/application/auth/auth.css | 1 + .../icon/fatcow/thumbnails/default280x210.png | Bin 2123 -> 0 bytes .../icon/fatcow/thumbnails/image280x210.png | Bin 2600 -> 0 bytes .../icon/fatcow/thumbnails/pdf280x210.png | Bin 2824 -> 0 bytes .../icon/fatcow/thumbnails/zip280x210.png | Bin 2511 -> 0 bytes 16 files changed, 115 insertions(+), 141 deletions(-) delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/default280x210.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/image280x210.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/pdf280x210.png delete mode 100644 webroot/rsrc/image/icon/fatcow/thumbnails/zip280x210.png diff --git a/src/applications/auth/view/PhabricatorAuthAccountView.php b/src/applications/auth/view/PhabricatorAuthAccountView.php index a68cdfff07..13574a9b6a 100644 --- a/src/applications/auth/view/PhabricatorAuthAccountView.php +++ b/src/applications/auth/view/PhabricatorAuthAccountView.php @@ -89,13 +89,17 @@ final class PhabricatorAuthAccountView extends AphrontView { $account_uri); } - $image_uri = $account->getProfileImageFile()->getProfileThumbURI(); + $image_file = $account->getProfileImageFile(); + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE); + $image_uri = $image_file->getURIForTransform($xform); + list($x, $y) = $xform->getTransformedDimensions($image_file); return phutil_tag( 'div', array( 'class' => 'auth-account-view', - 'style' => 'background-image: url('.$image_uri.')', + 'style' => 'background-image: url('.$image_uri.');', ), $content); } diff --git a/src/applications/files/PhabricatorImageTransformer.php b/src/applications/files/PhabricatorImageTransformer.php index 0bbcdb9b31..34cdf67023 100644 --- a/src/applications/files/PhabricatorImageTransformer.php +++ b/src/applications/files/PhabricatorImageTransformer.php @@ -20,21 +20,6 @@ final class PhabricatorImageTransformer { )); } - public function executeThumbTransform( - PhabricatorFile $file, - $x, - $y) { - - $image = $this->crudelyScaleTo($file, $x, $y); - - return PhabricatorFile::newFromFileData( - $image, - array( - 'name' => 'thumb-'.$file->getName(), - 'canCDN' => true, - )); - } - public function executeProfileTransform( PhabricatorFile $file, $x, @@ -123,21 +108,6 @@ final class PhabricatorImageTransformer { return self::saveImageDataInAnyFormat($dst, $file->getMimeType()); } - - /** - * Very crudely scale an image up or down to an exact size. - */ - private function crudelyScaleTo(PhabricatorFile $file, $dx, $dy) { - $scaled = $this->applyScaleWithImagemagick($file, $dx, $dy); - - if ($scaled != null) { - return $scaled; - } - - $dst = $this->applyScaleTo($file, $dx, $dy); - return self::saveImageDataInAnyFormat($dst, $file->getMimeType()); - } - private function getBlankDestinationFile($dx, $dy) { $dst = imagecreatetruecolor($dx, $dy); imagesavealpha($dst, true); diff --git a/src/applications/files/controller/PhabricatorFileTransformController.php b/src/applications/files/controller/PhabricatorFileTransformController.php index ae31990c06..5062fe76fc 100644 --- a/src/applications/files/controller/PhabricatorFileTransformController.php +++ b/src/applications/files/controller/PhabricatorFileTransformController.php @@ -44,50 +44,33 @@ final class PhabricatorFileTransformController } } - $type = $file->getMimeType(); - - if (!$file->isViewableInBrowser() || !$file->isTransformableImage()) { - return $this->buildDefaultTransformation($file, $transform); + $xforms = PhabricatorFileTransform::getAllTransforms(); + if (!isset($xforms[$transform])) { + return new Aphront404Response(); } + $xform = $xforms[$transform]; + // We're essentially just building a cache here and don't need CSRF // protection. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $xformed_file = null; - - $xforms = PhabricatorFileTransform::getAllTransforms(); - if (isset($xforms[$transform])) { - $xform = $xforms[$transform]; - if ($xform->canApplyTransform($file)) { - try { - $xformed_file = $xforms[$transform]->applyTransform($file); - } catch (Exception $ex) { - // In normal transform mode, we ignore failures and generate a - // default transform below. If we're explicitly regenerating the - // thumbnail, rethrow the exception. - if ($is_regenerate) { - throw $ex; - } + if ($xform->canApplyTransform($file)) { + try { + $xformed_file = $xforms[$transform]->applyTransform($file); + } catch (Exception $ex) { + // In normal transform mode, we ignore failures and generate a + // default transform below. If we're explicitly regenerating the + // thumbnail, rethrow the exception. + if ($is_regenerate) { + throw $ex; } } - - if (!$xformed_file) { - $xformed_file = $xform->getDefaultTransform($file); - } } if (!$xformed_file) { - switch ($transform) { - case 'thumb-profile': - $xformed_file = $this->executeThumbTransform($file, 50, 50); - break; - case 'thumb-280x210': - $xformed_file = $this->executeThumbTransform($file, 280, 210); - break; - default: - return new Aphront400Response(); - } + $xformed_file = $xform->getDefaultTransform($file); } if (!$xformed_file) { @@ -103,40 +86,6 @@ final class PhabricatorFileTransformController return $this->buildTransformedFileResponse($xform); } - private function buildDefaultTransformation( - PhabricatorFile $file, - $transform) { - static $regexps = array( - '@application/zip@' => 'zip', - '@image/@' => 'image', - '@application/pdf@' => 'pdf', - '@.*@' => 'default', - ); - - $type = $file->getMimeType(); - $prefix = 'default'; - foreach ($regexps as $regexp => $implied_prefix) { - if (preg_match($regexp, $type)) { - $prefix = $implied_prefix; - break; - } - } - - switch ($transform) { - case 'thumb-280x210': - $suffix = '280x210'; - break; - default: - throw new Exception('Unsupported transformation type!'); - } - - $path = celerity_get_resource_uri( - "rsrc/image/icon/fatcow/thumbnails/{$prefix}{$suffix}.png"); - - return id(new AphrontRedirectResponse()) - ->setURI($path); - } - private function buildTransformedFileResponse( PhabricatorTransformedFile $xform) { @@ -154,11 +103,6 @@ final class PhabricatorFileTransformController return $file->getRedirectResponse(); } - private function executeThumbTransform(PhabricatorFile $file, $x, $y) { - $xformer = new PhabricatorImageTransformer(); - return $xformer->executeThumbTransform($file, $x, $y); - } - private function destroyTransform(PhabricatorTransformedFile $xform) { $file = id(new PhabricatorFileQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 9ff75cce17..3de1bdc9f6 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -784,14 +784,6 @@ final class PhabricatorFile extends PhabricatorFileDAO return PhabricatorEnv::getCDNURI($path); } - public function getProfileThumbURI() { - return $this->getTransformedURI('thumb-profile'); - } - - public function getThumb280x210URI() { - return $this->getTransformedURI('thumb-280x210'); - } - public function isViewableInBrowser() { return ($this->getViewableMimeType() !== null); } diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php index 250aa887dd..b6e6844653 100644 --- a/src/applications/files/transform/PhabricatorFileImageTransform.php +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -42,13 +42,20 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { $dst_w, $dst_h, $src_x, $src_y, $src_w, $src_h, - $use_w, $use_h) { + $use_w, $use_h, + $scale_up) { + + // Figure out the effective destination width, height, and offsets. + $cpy_w = min($dst_w, $use_w); + $cpy_h = min($dst_h, $use_h); + + // If we aren't scaling up, and are copying a very small source image, + // we're just going to center it in the destination image. + if (!$scale_up) { + $cpy_w = min($cpy_w, $src_w); + $cpy_h = min($cpy_h, $src_h); + } - // Figure out the effective destination width, height, and offsets. We - // never want to scale images up, so if we're copying a very small source - // image we're just going to center it in the destination image. - $cpy_w = min($dst_w, $src_w, $use_w); - $cpy_h = min($dst_h, $src_h, $use_h); $off_x = ($dst_w - $cpy_w) / 2; $off_y = ($dst_h - $cpy_h) / 2; @@ -58,11 +65,18 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { $argv[] = '-shave'; $argv[] = $src_x.'x'.$src_y; $argv[] = '-resize'; - $argv[] = $dst_w.'x'.$dst_h.'>'; + + if ($scale_up) { + $argv[] = $dst_w.'x'.$dst_h; + } else { + $argv[] = $dst_w.'x'.$dst_h.'>'; + } + $argv[] = '-bordercolor'; $argv[] = 'rgba(255, 255, 255, 0)'; $argv[] = '-border'; $argv[] = $off_x.'x'.$off_y; + return $this->applyImagemagick($argv); } @@ -117,7 +131,13 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { * @param string Raw file data. */ protected function newFileFromData($data) { - $name = $this->getTransformKey().'-'.$this->file->getName(); + if ($this->file) { + $name = $this->file->getName(); + } else { + $name = 'default.png'; + } + + $name = $this->getTransformKey().'-'.$name; return PhabricatorFile::newFromFileData( $data, diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php index 3a72b5cf9e..328742f2e8 100644 --- a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -12,6 +12,7 @@ final class PhabricatorFileThumbnailTransform private $key; private $dstX; private $dstY; + private $scaleUp; public function setName($name) { $this->name = $name; @@ -29,6 +30,11 @@ final class PhabricatorFileThumbnailTransform return $this; } + public function setScaleUp($scale) { + $this->scaleUp = $scale; + return $this; + } + public function getTransformName() { return $this->name; } @@ -42,7 +48,8 @@ final class PhabricatorFileThumbnailTransform id(new PhabricatorFileThumbnailTransform()) ->setName(pht("Profile (100px \xC3\x97 100px)")) ->setKey(self::TRANSFORM_PROFILE) - ->setDimensions(100, 100), + ->setDimensions(100, 100) + ->setScaleUp(true), id(new PhabricatorFileThumbnailTransform()) ->setName(pht("Pinboard (280px \xC3\x97 210px)")) ->setKey(self::TRANSFORM_PINBOARD) @@ -86,7 +93,8 @@ final class PhabricatorFileThumbnailTransform $copy_x, $copy_y, $use_x, - $use_y); + $use_y, + $this->scaleUp); } @@ -144,22 +152,35 @@ final class PhabricatorFileThumbnailTransform $copy_x = $src_x; $copy_y = $src_y; } else { + $scale_up = $this->scaleUp; + // Otherwise, both dimensions are fixed. Figure out how much we'd have to // scale the image down along each dimension to get the entire thing to // fit. - $scale_x = min(($dst_x / $src_x), 1); - $scale_y = min(($dst_y / $src_y), 1); + $scale_x = ($dst_x / $src_x); + $scale_y = ($dst_y / $src_y); + + if (!$scale_up) { + $scale_x = min($scale_x, 1); + $scale_y = min($scale_y, 1); + } if ($scale_x > $scale_y) { // This image is relatively tall and narrow. We're going to crop off the // top and bottom. - $copy_x = $src_x; - $copy_y = min($src_y, $dst_y / $scale_x); + $scale = $scale_x; } else { // This image is relatively short and wide. We're going to crop off the // left and right. - $copy_x = min($src_x, $dst_x / $scale_y); - $copy_y = $src_y; + $scale = $scale_y; + } + + $copy_x = $dst_x / $scale_x; + $copy_y = $dst_y / $scale_x; + + if (!$scale_up) { + $copy_x = min($src_x, $copy_x); + $copy_y = min($src_y, $copy_y); } // In this mode, we always use the entire destination image. We may diff --git a/src/applications/macro/query/PhabricatorMacroSearchEngine.php b/src/applications/macro/query/PhabricatorMacroSearchEngine.php index 8a810c886f..8d632e818f 100644 --- a/src/applications/macro/query/PhabricatorMacroSearchEngine.php +++ b/src/applications/macro/query/PhabricatorMacroSearchEngine.php @@ -179,14 +179,18 @@ final class PhabricatorMacroSearchEngine assert_instances_of($macros, 'PhabricatorFileImageMacro'); $viewer = $this->requireViewer(); + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD); + $pinboard = new PHUIPinboardView(); foreach ($macros as $macro) { $file = $macro->getFile(); $item = new PHUIPinboardItemView(); if ($file) { - $item->setImageURI($file->getThumb280x210URI()); - $item->setImageSize(280, 210); + $item->setImageURI($file->getURIForTransform($xform)); + list($x, $y) = $xform->getTransformedDimensions($file); + $item->setImageSize($x, $y); } if ($macro->getDateCreated()) { diff --git a/src/applications/pholio/query/PholioMockSearchEngine.php b/src/applications/pholio/query/PholioMockSearchEngine.php index 226f225e11..057a370649 100644 --- a/src/applications/pholio/query/PholioMockSearchEngine.php +++ b/src/applications/pholio/query/PholioMockSearchEngine.php @@ -141,15 +141,22 @@ final class PholioMockSearchEngine extends PhabricatorApplicationSearchEngine { $viewer = $this->requireViewer(); + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD); + $board = new PHUIPinboardView(); foreach ($mocks as $mock) { + $image = $mock->getCoverFile(); + $image_uri = $image->getURIForTransform($xform); + list($x, $y) = $xform->getTransformedDimensions($image); + $header = 'M'.$mock->getID().' '.$mock->getName(); $item = id(new PHUIPinboardItemView()) ->setHeader($header) ->setURI('/M'.$mock->getID()) - ->setImageURI($mock->getCoverFile()->getThumb280x210URI()) - ->setImageSize(280, 210) + ->setImageURI($image_uri) + ->setImageSize($x, $y) ->setDisabled($mock->isClosed()) ->addIconCount('fa-picture-o', count($mock->getImages())) ->addIconCount('fa-trophy', $mock->getTokenCount()); diff --git a/src/applications/pholio/view/PholioMockEmbedView.php b/src/applications/pholio/view/PholioMockEmbedView.php index 81dfa670a3..b20283d48b 100644 --- a/src/applications/pholio/view/PholioMockEmbedView.php +++ b/src/applications/pholio/view/PholioMockEmbedView.php @@ -28,25 +28,29 @@ final class PholioMockEmbedView extends AphrontView { $this->mock->getImages(), array_flip($this->images)); } + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD); + if ($images_to_show) { - foreach ($images_to_show as $image) { - $thumbfile = $image->getFile(); - $thumbnail = $thumbfile->getThumb280x210URI(); - } + $image = head($images_to_show); + $thumbfile = $image->getFile(); $header = 'M'.$mock->getID().' '.$mock->getName(). ' (#'.$image->getID().')'; $uri = '/M'.$this->mock->getID().'/'.$image->getID().'/'; } else { - $thumbnail = $mock->getCoverFile()->getThumb280x210URI(); + $thumbfile = $mock->getCoverFile(); $header = 'M'.$mock->getID().' '.$mock->getName(); $uri = '/M'.$this->mock->getID(); } + $thumbnail = $thumbfile->getURIForTransform($xform); + list($x, $y) = $xform->getTransformedDimensions($thumbfile); + $item = id(new PHUIPinboardItemView()) ->setHeader($header) ->setURI($uri) ->setImageURI($thumbnail) - ->setImageSize(280, 210) + ->setImageSize($x, $y) ->setDisabled($mock->isClosed()) ->addIconCount('fa-picture-o', count($mock->getImages())) ->addIconCount('fa-trophy', $mock->getTokenCount()); diff --git a/src/applications/pholio/view/PholioMockImagesView.php b/src/applications/pholio/view/PholioMockImagesView.php index f894c698f2..d59e701579 100644 --- a/src/applications/pholio/view/PholioMockImagesView.php +++ b/src/applications/pholio/view/PholioMockImagesView.php @@ -68,8 +68,11 @@ final class PholioMockImagesView extends AphrontView { // TODO: We could maybe do a better job with tailoring this, which is the // image shown on the review stage. - $nonimage_uri = celerity_get_resource_uri( - 'rsrc/image/icon/fatcow/thumbnails/default.p100.png'); + $default_name = 'image-100x100.png'; + $builtins = PhabricatorFile::loadBuiltins( + $this->getUser(), + array($default_name)); + $default = $builtins[$default_name]; $engine = id(new PhabricatorMarkupEngine()) ->setViewer($this->getUser()); @@ -97,7 +100,7 @@ final class PholioMockImagesView extends AphrontView { 'fullURI' => $file->getBestURI(), 'stageURI' => ($file->isViewableImage() ? $file->getBestURI() - : $nonimage_uri), + : $default->getBestURI()), 'pageURI' => $this->getImagePageURI($image, $mock), 'downloadURI' => $file->getDownloadURI(), 'historyURI' => $history_uri, diff --git a/src/applications/pholio/view/PholioUploadedImageView.php b/src/applications/pholio/view/PholioUploadedImageView.php index fb8a82431e..2ff3ba0390 100644 --- a/src/applications/pholio/view/PholioUploadedImageView.php +++ b/src/applications/pholio/view/PholioUploadedImageView.php @@ -38,11 +38,15 @@ final class PholioUploadedImageView extends AphrontView { ->setSigil('image-description') ->setLabel(pht('Description')); + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PINBOARD); + $thumbnail_uri = $file->getURIForTransform($xform); + $thumb_frame = phutil_tag( 'div', array( 'class' => 'pholio-thumb-frame', - 'style' => 'background-image: url('.$file->getThumb280x210URI().');', + 'style' => 'background-image: url('.$thumbnail_uri.');', )); $handle = javelin_tag( diff --git a/webroot/rsrc/css/application/auth/auth.css b/webroot/rsrc/css/application/auth/auth.css index 219c8795a3..8211eb1484 100644 --- a/webroot/rsrc/css/application/auth/auth.css +++ b/webroot/rsrc/css/application/auth/auth.css @@ -28,6 +28,7 @@ border: 1px solid {$lightblueborder}; background-repeat: no-repeat; background-position: 4px 4px; + background-size: 50px 50px; padding: 4px 4px 4px 62px; min-height: 50px; border-radius: 2px; diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/default280x210.png b/webroot/rsrc/image/icon/fatcow/thumbnails/default280x210.png deleted file mode 100644 index 7288c81954ec6a5e5916cdbbe6c93c2eb660390e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2123 zcmbtVYfuwc6kcM=fQ1%1mPU(ZjUcEb*$ogN8Wa*xDhUvx;3y>_SxAIrW3reaf?x$- zrBy5CQHo;`tVQFXqKG`AR4J`gg^mvpd8r5zu{^2*g>E8Xf0X_xJ3IT`d*^)L`Odj# zX7`2g)>zM7Fc$y->%g@fJ^+}J@O7hwIsQGgzpWRC`IvtM7KX-P8j%VCd?jc!0tU)O zu?Qa#NfNi#BP#)5j-yNvfkkkGm||2;6&YiwTDcNu1Hj5vTBS%FhhShd5-U@%$o-dV z$e>KZB8Pi$A+C~*NM&octB|nmJb`$7oY-4JUgZO>)G~1aIf98mtvp_#W@=evlP(iq z8;@yZ&=i8jvB+;sMQ}qvHmX8E52_nQ48btSU{GOq4+aBv1?dn>r$PA3pup}-4+fL& z2~Iv_Jex`q!{l=UCUfCC7Fmj6N+yk_(P*d|H!7-%rNQ3b-bM{Noq|VD)QJjAq@^g- zi>DMgh+3?YDKQzU0F8>GXfy$1k#VPQN{}n3WfkhlHsKAUX+=sJOofakO$Bnf{~aoq zPe-dUKJwP@zY41biAsdVN7QJ7N{nAz%wl6IC6lc}L>Q_PplJM57ek~dhN`8g5@d%l zKo_n^EK?W{mYNt`E;CS}#zYD+63AhZaSf_WCSkHY{rnho4|jjIpDzsiyLox}vDv;5 z1asUVwgre}xgIlejl&>U;+Z*!O12G=1gKCsXeyd1o7oF}M!iX{ zWM(gJGjeIT8Jh8G|8dn*NBI00m(zobZ>C2dQQ-5g!Uy|YJMkp|5UT_C zthd*9sGnEfvn2NT5r}qYZ6iz7W_?c5PZRH#Y?VjU?@C{!!+JgvqGF2U^~{ua?&qa0 z63u}JmQaYjHy-C-ax0^ApB~rPJ@N@wH_a=)Sl`n?n{Y!jk&Wi58(mz@KqyrYSXljk z00?u8^jQIJ!KgATFjatlf5AzrBZ-qHvW|uq{MpqroRT|Uls{sBnw+5^ZBBa8;N0Dm zJ)ubW_N*)Ag8tU(D*NoZaGv3*V@>NwlBna_!25A+1yu*D`!;PLX2k3AHv5&2*Hzh+ zoP1K&cEtIx??|N*+qpMtob*fo&Ge$1JlBHd+1Ulj&h)XmWXQVBDf6QLY0L?0+;*ZW zv#_G|)!b!Q8%9eyeoyK@{FUFEs25gU7AlW_-?}v16&yy8t=HJf3xR< z@=>pRpNe3K4QWYP;~iv^n!NS!-?XaKAj|SetG`JRof`TBz=lYN{w+o8b z-+2DO!fFZWK&l=peD~PiGHZK&x*meBSXdEi2-&Md`n_R z!`ExZT{J$LN0!AyRx9xR$GP^|Db{Dwb%eoQbE~vthnR&IWnP2hX}S5MxlVXn;=)&) zs{U)3cKPmL%ceWSF`=a+ErG8eOhk0X+^l<;cIrya80lqkaDTBl#e6h(hlmwaXTUO9 zXPXJ^K|)hKJBV4~HeyR_Ed54TnovT4fUD can(E_;7Go=d7t;0fV`M5v1TsA{9!$3_?&NB;gR? zBjQIv9EcyGj_ZbwAP|OM2>pW;LCj-hfkcGm>tnDgkrZYl5J%iqQobMtQUKvllu%4T zPd7B90bv9M9q7aanNk`QE%Z*5LEJ=^zaTM2;1Yp$cLR>7$gqG2Qt$zlC{`>dt0?FV zT{67ZALG!#Mu;MYf_`f%i0KE=Br*ta!a8CEARZ48iC8?riAcoT0}deG0SCe_5rZd? zorq)y67cRp!`WmJkz@|t^Ia}@M?ps`6jCw{r&KDjN=K|j7KOvRxVY#w92_uk1V*kF zEBGpmSZ=+kK!@Z4nNX?_O2mL(ksmIJS5VNf)3+svq+7CL`MWm34a2GUQXC!&>Py-T zWHSGMs7SOGEmv@$|N4Eeu-soQg>W25E{T^3;ERj2)~Aw^X)=hfkjVTclGx2I`bA3= z5_z;l3edPjz?R7u2*vt?LmLbxlk6jwEBImoV>%mj-^2s~-lD6wXYCWWod}!c!&@0UJe= zh41k~BYAib9h`^^ng{j$UfyyeKIp}R@j)***bGj8wf}R~n@8~c=$BiA3vaeY9}>g! zE`tZVn?$;UKp5rw(5e2afyrkNGpzb7T7Ii3ElzTDjUu%X%PNDzMTmed`yHRtO1(Eeh_4nnhfPxv>RnsV} zH|3K(CweGc!yjJc$yrKvplHyjC9I^-;42*4 z7V|EYmRji6R~mGWstfG)F)N~)qx`jHoSNyz`QIrERicL1KmXaJ^-gO3i`gZvvOPAZ z>7+b9VP2A` z$3L#sS~b0y=&Ja8qhI;h0n3rj!KpWYGk%=fW2P&z?_}^$OAJ3R^QzwQYG3PYuffi6 z${P1`*Zno^5BiQacf9;$nG-)aTWeBq^mNdTS;F9A^MVUPs*}_FglbKnTQ^^J%yPG z#_qd~x(9%C(wJ?GojR)mm+8C~_s}jbLBVZ%x&S>`UTj+Fug!j-vckKrdfE?E1rcKK zx|JiPzm7u4%5%pC57fk^GjmXXF#O^*-PRY>1F6fu+$y@)S$Wwv@dn?T9y5Be%*&w$WfBS}g zI3|DbYk#}sxbk*1Gqd^${%L)Qy_Vds7lRrphT^t?2d z^{D;w!lOLQ^yu=eAif!ODZ9qXoR4h%YPkH=GhhE@fx}eTF>Tm2V|9KdIV{t4DiPo&lrZEL655s zGfK5@+D}z9oN`+m^ClF8jC`bqCOMzxA05S0{$P z1eePS_}YT;SL4@k+<>C=`<<4-r%h4Cr^~^RK765gK~smEWB9XmK?%i{6>05Rwm;NZ z^cFEZctN*v31y3OkrtPF^Qxm|WhB=hm64$au3eNF$B; zosn?y+HxJvEhSOA{&L|;s*~iE@CyyUzq+7*p1rrkgpcevg!*@H^kOllM^rJ0FTpmZ zbEA=G53UVs&7Ou#toKY)d8o8*-&ia4n=#Ew9V4lX#Tf!FzIY-oTve*5)1X)PKlj#Z zSlOj^Jr94Y+<)$FGCO>2&v~wJnS3I~O~vG)ym=ktk*S||P0rlDZ`S|y05O?Z)q%IU0 diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/pdf280x210.png b/webroot/rsrc/image/icon/fatcow/thumbnails/pdf280x210.png deleted file mode 100644 index 8036981aca18575e4fed1ebfa343188e5fed852c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2824 zcmbtWc~p~E7LQUD1$EHMsuE)@D3FDO2!RkVVTmP(ka0i(2_b<1SxABj2}{K(03_q})S?{}AX&%IxQ zo2%32P5PSv0KjIvGtM0VSfi_4cdD;fe$x_%UMhti*TIwP!K89|BsK+LAHejd0P%EE zAjO?R3Wx~1Pq6_2)V`q+J-MC)7ZjOE2a{AfU_PCtL<0ae7(RbO`^oLWP1UDd-$)*4;z;H7%6b1t#kzg3Y0*Qo~0?nZ?a|l%VBF$h3 zlm!xHeh~Qa0V%Dq1E?r>oa0AZ${iXM#O1P35D1UQ1M}cuCOZ%Uv$C>MahRK%DK*SE z5ezPgZ^q#4U1h*gIAk`B#icPBKouj&pBc_YgOr~B5dxj{iI&0nm?mYyAbb)F0s}); zA+0JB2>)A^PXDCM;kr})^!L9KbBGZv3dEhlVTQBG%8R4!RheR;uxtv6%VZOo%+S>= zx&<-0OimD!1;lzFfyM+9nZ{5Z>|a3;2q-**!zD4u6g&nY!Kja_C$W4Y=9oWleeq>?U3x*;N*Zsm3EPDtnPLXL-CJA1;epAhfW&r5YrK( z?gfcIyB(j@IOCk%4XR5!1U#NUV$dcc2#&@B>7qv)=q?-0jwW8mpE$55aON_hlvn+# zRpwJu?VE001PQ zrnLU@9Ysa|hr%8!!Mh-vD&EwaK8q0B3`Hpz>e4g(m-2!K4a>5#TO{cl0_8Iqp_0Za zudj1%Gm@>(#`%vW?#LUcq4%d)_uTYC=QKa^J-o#}r`_Ag^?P@*E-l+P%q8?hjh&n? zn@gCOno7NR<_vGizo)pT^jganv$gj|$K17&m);&G`&au*BK03X+2Q2$K~F>5G=8k* zlG)P|&T~?ly2 zqUW9mc@OB|j~v0(?w&W0vlUa3zcu!1Rj%)PZDg0;N*}#_Q+P*2?!6|8e8>Y$zDz<$ z+R(Q3nsLkg_1nGt{HMCKctt6mR&K39AW77&LGd{~%`Ua`)YJRh*!czB@`!+GsN!uw zH%BK16K1ESdlj5URqW0T8gMv#37oM zM)!FA-3WH>?zV*eG51>cZ7L|QJVz8x3>(KNiYGjUPQ=AaxcS$R=Y~Zom2L(@?L#Nj zHMOL?=n_XpWt3 zD$(t)FcIYJl`dD;_8D3_PL%L@m%XNcx;`{5uAFbpvShb~A1S!lK6n%7^MooL zc38HogylW699fGsz?bn6zkHp6F!N{2>lQ`RirJ32{-3U+7iM%9?`foYFUp-UET4Bv zTr#W$AHC?^x;VIX#Ch8+(0NqQ9hg#T z==9z&zHdDDXavg#v88V3KyRn9m^fI|@|*28iC(3xeDmO)`NAkgUQL1dvWbzwtfFHf z&ib^LZePBsbo2h&d5=fuMR24x8L~TR=6!K$M1S-)pF#{=+CnhRO7!Z;3J$%IYx7Uq z=qS8nVMJ_WO5P$^3`@K1v;4c!z~L+N*yLWzrC=g+*}b9bp_7x7;C^L8e&y(cTO+4t zUxe;#X>vxU1d7J5=iGe+-kwRFc_*#rJv6BsWKU~;tKlN@P8sY;Z8#hAu8dDp)RxGu zY#4cuiG&7A2G3Lc5zEs9-?b+u##oDYWQ5+ceC2#w5i$Nov6v=T`N+mdRWy7`@Y> za_JDcFC*Hz=c_omx=ZxkaNqK}`IrTBE6kqpyqh(`p^3i^%nm)dk#j6pKwq3o4@BP1 zGLXVE61f7Y!7jCzgjMwj|2STcrHXiC>(~XO zHby%|dZ=a{Go4es@jphx7q$4cYG2&qm2*ZlAyzv5|0kAC&Se190KZG}4f=hzW`(Ui{k(Q diff --git a/webroot/rsrc/image/icon/fatcow/thumbnails/zip280x210.png b/webroot/rsrc/image/icon/fatcow/thumbnails/zip280x210.png deleted file mode 100644 index 8db127b282c1ea95127548ba0022eabd8475315a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2511 zcmbtWYgAKL7LG^}A=1{WJc^Qu53E3PLqdR%h#>)_Bmn{n_#`Bk5FoiBxsdP%>!O7g zEz|O96~;=db&45^@{osVMNk4#UZOy)ND(4f1d5@cFgH}}{21oPa97s-&N*v;-~RSK z`>vHNe~$MC6Dt!G3bny!Ka-0>t+7DXoyJDUJ2(56CkR-=o*{4`6ag!EG7v=$L_r~N2%AOWLt-3HAA?hhr3f2^qPi=kJbp9?W5Pk9NJ7I- z)LzG8L;@N%*oh6Wr3^4qv_DY>1}1WX_=(YcR{_?2FNUh5AOd0#=3$iL7>S&sq+yqI zDacxXjK^Y@Lf~i`_D@qGY<~;`l7Sc}9MORf5C|AD8Al*Fk;w#mj3YpB!~@8W>_8w< zoX8YMXUwYyi)51tA}Cy@*Q;E}j)skdVJQWVS11%X1rZ0ygm{9htE*na(a`~kaF8cR zV4l)JBHzBOzy#%dnMewYAPGjV$P0(!U>X*2`ezAZ>1$bu{8gKfhT)YwDV~4>^d&6^ zvf2MTR4jfSEr+?_Uw;3quskS13gWq-9Ey|ik&BDiu1_VUFk~PPhGany6tmn#|40ai zl{W+o^T#e)Jb86?Im6-^PX z?8RwCy;oep%3jDTa`A{6y#8wco`R6iVqvs+D-NbxkNiZ?@c<0;nEZIW=>l5=D<-SG3 z^`*n!m>rpEbhCvu#-uRx=N1|H`Ipi&!+UiRRqQlY*S407@sZE!Sn=_FySmz(C)&m( z2UWdm)m@*uy^*d)8LY(ss{mv45`8s*SkTi~1xpoBKkhqo;%*)o`gFd_+>5OEuz2ow z_+s$Zcf339Zt3gqXB3YN-tG=6%;sN9NG=8k`;O1J9e;B?)oJgGswWfOV{x{J!y@Tp z&WfI-__W!M@7F!o=?Zf1S16!+HT>qf5if7=BpVwyU4O2huJjf){!lp+8|p@^t&WxN z7_hFn8u`h<9Bp80t@&P8C2-|tQLFacIjI8vE*a+<_n0-R)vhn8zg|D_rOln6KdS9J zd(bd*YSFzy>7ywjWq%a{JZh*;8*R+GQfji6Kq5Bh`(CU*TkUR{n3Tj9JlJe$n0b>+ z<&rupqtMna{@a}47qc(ajR9k$#^+=6D`JPda_M0g%~&&$Od*iD^L|xY8l`%``TTi4 zd-S`Wi0#vV+!EZRFm9ZcT~U^yty6&yFZ` z<1+0}>n3JWk{%^r^AD8AA18WP2Z@pq33aex2hJuG#0o~Mi+ zyDJMSk~Bt_sg2PNHibJQfVSbE{N+X!D%THu=C>_O@1}lqo+^p==q`MFMlxDdv^6xd z3Rq0G!k^rflO1rfDgNQUAuE;%JNLOTr@vR+7GQ4?W^^Mv0?@t$E*Pz2lpVB#f=5KI zn{~pjp5B;b5{hW?_``FztU5w~DeN8pzTU>+fZ#tk4~VvYP0ixrg-b)400C#W!HpL? zJAc%ve{H+38TP$?vR~2kWBuf9Aa=1`Iy{vpOG|8cQcq0S1sJ2J^U0pmoX&xO#!$@{ z(x-DXQ}sCQFn3c}u{W&n><{*R1-OSzMJ>;I{yF$;kTIC!plYsuVTD_O z3F8m0+EKIGFX-yhEzz3RuuBDQqW4Hts`W8ts(AL1M%5xtJo6m?a?-$eM{8K&yM=Xg zX51*x?Ax=A%F!p|_EkC6q>?woPpJQO!m^b+-^mq)Cu6&#&JM>v|ExR0&KPZX$S(Nk z2cqA6t1e9VaWYRG@~&Z@7M7S>7-kL&0qq^Xlph+JSUXc}CmNY(R#Mr(mNqr%H;V%c zWZO*{JkYna;MfuKOx3YH1zy_A9pA81H1@fMMj1TG#?D&Eyza*J;+`96gx%h!Q>#X2 zuej5W$Hm_Jetib7`1dtUEq-s^{FH?+clRUa#efxmGc@uoeLv6USZjU*-C?PHMD1Kl zREkL=8SvN+aOlyRkz|D?s+}tP#dK-^$|hn(sb3}TIr_#oS8Z=K zSYONw_P%cyJI%4yHcUsUgq@~ir(IG{smk9LBHwbo8ot@4iG|(2cAo@JmCsrw{TgG6 zR$r2BHharrUl_{J=>G>)#u`0+Re)H~tE>w26{HJ07Y)#;fDm$WUH8Xl^nbB@JUPtE I9$_c`4M=*qd;kCd From 47b14c9bdecf59def4eab628f431357eab14f0db Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 May 2015 17:32:06 -0700 Subject: [PATCH 61/70] Convert inline profile image transforms to new transformations Summary: Ref T7707. Fixes T7879. Fixes T4406. When creating profile images: - Use the new transforms; - mark them as "profile" images so they're forced to the most-open policies. Test Plan: - Set restrictive default file policies. - Changed profile picture, project pictures, etc. Verified they were visible to logged-out users. - Registered via OAuth. - Updated a Conpherence thread image. - Browsed around looking for profile images, fixed sizing on everything I could find. Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7879, T7707, T4406 Differential Revision: https://secure.phabricator.com/D12821 --- .../PhabricatorAuthRegisterController.php | 14 +- .../files/PhabricatorImageTransformer.php | 167 +----------------- .../PhabricatorFileComposeController.php | 7 +- .../PhabricatorFileInfoController.php | 6 + .../files/storage/PhabricatorFile.php | 18 ++ .../PhabricatorFileImageTransform.php | 6 +- .../PhabricatorFileThumbnailTransform.php | 10 ++ .../transform/PhabricatorFileTransform.php | 12 ++ ...bricatorPeopleProfilePictureController.php | 9 +- ...habricatorProjectEditPictureController.php | 9 +- webroot/rsrc/css/phui/phui-header-view.css | 1 + webroot/rsrc/css/phui/phui-timeline-view.css | 1 + 12 files changed, 67 insertions(+), 193 deletions(-) diff --git a/src/applications/auth/controller/PhabricatorAuthRegisterController.php b/src/applications/auth/controller/PhabricatorAuthRegisterController.php index 6c40375227..d27a644480 100644 --- a/src/applications/auth/controller/PhabricatorAuthRegisterController.php +++ b/src/applications/auth/controller/PhabricatorAuthRegisterController.php @@ -604,17 +604,9 @@ final class PhabricatorAuthRegisterController return null; } - try { - $xformer = new PhabricatorImageTransformer(); - return $xformer->executeProfileTransform( - $file, - $width = 50, - $min_height = 50, - $max_height = 50); - } catch (Exception $ex) { - phlog($ex); - return null; - } + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE); + return $xform->executeTransform($file); } protected function renderError($message) { diff --git a/src/applications/files/PhabricatorImageTransformer.php b/src/applications/files/PhabricatorImageTransformer.php index 34cdf67023..829f5d9f2d 100644 --- a/src/applications/files/PhabricatorImageTransformer.php +++ b/src/applications/files/PhabricatorImageTransformer.php @@ -20,22 +20,6 @@ final class PhabricatorImageTransformer { )); } - public function executeProfileTransform( - PhabricatorFile $file, - $x, - $min_y, - $max_y) { - - $image = $this->crudelyCropTo($file, $x, $min_y, $max_y); - - return PhabricatorFile::newFromFileData( - $image, - array( - 'name' => 'profile-'.$file->getName(), - 'canCDN' => true, - )); - } - public function executeConpherenceTransform( PhabricatorFile $file, $top, @@ -54,38 +38,11 @@ final class PhabricatorImageTransformer { $image, array( 'name' => 'conpherence-'.$file->getName(), + 'profile' => true, 'canCDN' => true, )); } - private function crudelyCropTo(PhabricatorFile $file, $x, $min_y, $max_y) { - $data = $file->loadFileData(); - $img = imagecreatefromstring($data); - $sx = imagesx($img); - $sy = imagesy($img); - - $scaled_y = ($x / $sx) * $sy; - if ($scaled_y > $max_y) { - // This image is very tall and thin. - $scaled_y = $max_y; - } else if ($scaled_y < $min_y) { - // This image is very short and wide. - $scaled_y = $min_y; - } - - $cropped = $this->applyScaleWithImagemagick($file, $x, $scaled_y); - if ($cropped != null) { - return $cropped; - } - - $img = $this->applyScaleTo( - $file, - $x, - $scaled_y); - - return self::saveImageDataInAnyFormat($img, $file->getMimeType()); - } - private function crasslyCropTo(PhabricatorFile $file, $top, $left, $w, $h) { $data = $file->loadFileData(); $src = imagecreatefromstring($data); @@ -116,34 +73,6 @@ final class PhabricatorImageTransformer { return $dst; } - private function applyScaleTo(PhabricatorFile $file, $dx, $dy) { - $data = $file->loadFileData(); - $src = imagecreatefromstring($data); - - $x = imagesx($src); - $y = imagesy($src); - - $scale = min(($dx / $x), ($dy / $y), 1); - - $sdx = $scale * $x; - $sdy = $scale * $y; - - $dst = $this->getBlankDestinationFile($dx, $dy); - imagesavealpha($dst, true); - imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 255, 255, 255, 127)); - - imagecopyresampled( - $dst, - $src, - ($dx - $sdx) / 2, ($dy - $sdy) / 2, - 0, 0, - $sdx, $sdy, - $x, $y); - - return $dst; - - } - public static function getScaleForCrop( PhabricatorFile $file, $des_width, @@ -302,49 +231,6 @@ final class PhabricatorImageTransformer { ); } - private function applyScaleWithImagemagick(PhabricatorFile $file, $dx, $dy) { - $img_type = $file->getMimeType(); - $imagemagick = PhabricatorEnv::getEnvConfig('files.enable-imagemagick'); - - if ($img_type != 'image/gif' || $imagemagick == false) { - return null; - } - - $data = $file->loadFileData(); - $src = imagecreatefromstring($data); - - $x = imagesx($src); - $y = imagesy($src); - - if (self::isEnormousGIF($x, $y)) { - return null; - } - - $scale = min(($dx / $x), ($dy / $y), 1); - - $sdx = $scale * $x; - $sdy = $scale * $y; - - $input = new TempFile(); - Filesystem::writeFile($input, $data); - - $resized = new TempFile(); - - $future = new ExecFuture( - 'convert %s -coalesce -resize %sX%s%s %s', - $input, - $sdx, - $sdy, - '!', - $resized); - - // Don't spend more than 10 seconds resizing; just fail if it takes longer - // than that. - $future->setTimeout(10)->resolvex(); - - return Filesystem::readFile($resized); - } - private function applyMemeWithImagemagick( $input, $above, @@ -382,57 +268,6 @@ final class PhabricatorImageTransformer { return Filesystem::readFile($output); } -/* -( Detecting Enormous Files )------------------------------------------- */ - - - /** - * Determine if an image is enormous (too large to transform). - * - * Attackers can perform a denial of service attack by uploading highly - * compressible images with enormous dimensions but a very small filesize. - * Transforming them (e.g., into thumbnails) may consume huge quantities of - * memory and CPU relative to the resources required to transmit the file. - * - * In general, we respond to these images by declining to transform them, and - * using a default thumbnail instead. - * - * @param int Width of the image, in pixels. - * @param int Height of the image, in pixels. - * @return bool True if this image is enormous (too large to transform). - * @task enormous - */ - public static function isEnormousImage($x, $y) { - // This is just a sanity check, but if we don't have valid dimensions we - // shouldn't be trying to transform the file. - if (($x <= 0) || ($y <= 0)) { - return true; - } - - return ($x * $y) > (4096 * 4096); - } - - - /** - * Determine if a GIF is enormous (too large to transform). - * - * For discussion, see @{method:isEnormousImage}. We need to be more - * careful about GIFs, because they can also have a large number of frames - * despite having a very small filesize. We're more conservative about - * calling GIFs enormous than about calling images in general enormous. - * - * @param int Width of the GIF, in pixels. - * @param int Height of the GIF, in pixels. - * @return bool True if this image is enormous (too large to transform). - * @task enormous - */ - public static function isEnormousGIF($x, $y) { - if (self::isEnormousImage($x, $y)) { - return true; - } - - return ($x * $y) > (800 * 800); - } - /* -( Saving Image Data )-------------------------------------------------- */ diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php index 6903cff5d3..bb95cb67eb 100644 --- a/src/applications/files/controller/PhabricatorFileComposeController.php +++ b/src/applications/files/controller/PhabricatorFileComposeController.php @@ -58,7 +58,7 @@ final class PhabricatorFileComposeController } $root = dirname(phutil_get_library_root('phabricator')); - $icon_file = $root.'/resources/sprite/projects_1x/'.$icon.'.png'; + $icon_file = $root.'/resources/sprite/projects_2x/'.$icon.'.png'; $icon_data = Filesystem::readFile($icon_file); @@ -68,6 +68,7 @@ final class PhabricatorFileComposeController $data, array( 'name' => 'project.png', + 'profile' => true, 'canCDN' => true, )); @@ -325,10 +326,10 @@ final class PhabricatorFileComposeController $color_string = idx($map, $color, '#ff00ff'); $color_const = hexdec(trim($color_string, '#')); - $canvas = imagecreatetruecolor(50, 50); + $canvas = imagecreatetruecolor(100, 100); imagefill($canvas, 0, 0, $color_const); - imagecopy($canvas, $icon_img, 0, 0, 0, 0, 50, 50); + imagecopy($canvas, $icon_img, 0, 0, 0, 0, 100, 100); return PhabricatorImageTransformer::saveImageDataInAnyFormat( $canvas, diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index fdc5a7773c..0e3d041eac 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -247,6 +247,12 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $finfo->addProperty(pht('Builtin'), $builtin_string); + $is_profile = $file->getIsProfileImage() + ? pht('Yes') + : pht('No'); + + $finfo->addProperty(pht('Profile'), $is_profile); + $storage_properties = new PHUIPropertyListView(); $box->addPropertyList($storage_properties, pht('Storage')); diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 3de1bdc9f6..34a2bb5df5 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -34,6 +34,7 @@ final class PhabricatorFile extends PhabricatorFileDAO const METADATA_CAN_CDN = 'canCDN'; const METADATA_BUILTIN = 'builtin'; const METADATA_PARTIAL = 'partial'; + const METADATA_PROFILE = 'profile'; protected $name; protected $mimeType; @@ -1112,6 +1113,15 @@ final class PhabricatorFile extends PhabricatorFileDAO return $this; } + public function getIsProfileImage() { + return idx($this->metadata, self::METADATA_PROFILE); + } + + public function setIsProfileImage($value) { + $this->metadata[self::METADATA_PROFILE] = $value; + return $this; + } + protected function generateOneTimeToken() { $key = Filesystem::readRandomCharacters(16); @@ -1213,6 +1223,11 @@ final class PhabricatorFile extends PhabricatorFileDAO $this->setBuiltinName($builtin); } + $profile = idx($params, 'profile'); + if ($profile) { + $this->setIsProfileImage(true); + } + $mime_type = idx($params, 'mime-type'); if ($mime_type) { $this->setMimeType($mime_type); @@ -1280,6 +1295,9 @@ final class PhabricatorFile extends PhabricatorFileDAO if ($this->isBuiltin()) { return PhabricatorPolicies::getMostOpenPolicy(); } + if ($this->getIsProfileImage()) { + return PhabricatorPolicies::getMostOpenPolicy(); + } return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return PhabricatorPolicies::POLICY_NOONE; diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php index b6e6844653..6c40e6bbfa 100644 --- a/src/applications/files/transform/PhabricatorFileImageTransform.php +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -38,6 +38,10 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { $this->imageY = null; } + protected function getFileProperties() { + return array(); + } + protected function applyCropAndScale( $dst_w, $dst_h, $src_x, $src_y, @@ -144,7 +148,7 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { array( 'name' => $name, 'canCDN' => true, - )); + ) + $this->getFileProperties()); } diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php index 328742f2e8..c7b579e836 100644 --- a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -43,6 +43,16 @@ final class PhabricatorFileThumbnailTransform return $this->key; } + protected function getFileProperties() { + $properties = array(); + switch ($this->key) { + case self::TRANSFORM_PROFILE: + $properties['profile'] = true; + break; + } + return $properties; + } + public function generateTransforms() { return array( id(new PhabricatorFileThumbnailTransform()) diff --git a/src/applications/files/transform/PhabricatorFileTransform.php b/src/applications/files/transform/PhabricatorFileTransform.php index 633a80887a..caaf46920d 100644 --- a/src/applications/files/transform/PhabricatorFileTransform.php +++ b/src/applications/files/transform/PhabricatorFileTransform.php @@ -15,6 +15,18 @@ abstract class PhabricatorFileTransform extends Phobject { return array($this); } + public function executeTransform(PhabricatorFile $file) { + if ($this->canApplyTransform($file)) { + try { + return $this->applyTransform($file); + } catch (Exception $ex) { + // Ignore. + } + } + + return $this->getDefaultTransform($file); + } + public static function getAllTransforms() { static $map; diff --git a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php index 231181614d..0f59e23286 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php @@ -70,12 +70,9 @@ final class PhabricatorPeopleProfilePictureController 'This server only supports these image formats: %s.', implode(', ', $supported_formats)); } else { - $xformer = new PhabricatorImageTransformer(); - $xformed = $xformer->executeProfileTransform( - $file, - $width = 50, - $min_height = 50, - $max_height = 50); + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE); + $xformed = $xform->executeTransform($file); } } diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php index 58e345930e..ca99159718 100644 --- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php +++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php @@ -68,12 +68,9 @@ final class PhabricatorProjectEditPictureController 'This server only supports these image formats: %s.', implode(', ', $supported_formats)); } else { - $xformer = new PhabricatorImageTransformer(); - $xformed = $xformer->executeProfileTransform( - $file, - $width = 50, - $min_height = 50, - $max_height = 50); + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE); + $xformed = $xform->executeTransform($file); } } diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index ef707191e5..5b48f722c6 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -90,6 +90,7 @@ body.device-phone .phui-header-view { .phui-header-image { display: inline-block; background-repeat: no-repeat; + background-size: 50px; border: 2px solid white; width: 50px; height: 50px; diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index b82c9385a7..6e956037aa 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -92,6 +92,7 @@ .phui-timeline-image { background-repeat: no-repeat; + background-size: 50px; position: absolute; border-radius: 3px; } From b06b3d79b71c25bf5b8a4b883fee1058e5e51e87 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 May 2015 08:14:47 -0700 Subject: [PATCH 62/70] Make CSS agnostic to underlying profile image size Summary: Ref T7707. My analysis there was a bit confused and this isn't really all that important, but seems cleaner and desirable to be agnostic to the underlying image size. Test Plan: Tested Safari, Firefox and Chrome with a variety of profile image sizes. Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7707 Differential Revision: https://secure.phabricator.com/D12825 --- .../auth/view/PhabricatorAuthAccountView.php | 13 +++++++++++-- webroot/rsrc/css/application/auth/auth.css | 19 ++++++++++++++----- .../application/conpherence/message-pane.css | 2 +- .../application/conpherence/notification.css | 2 +- webroot/rsrc/css/phui/phui-header-view.css | 2 +- .../css/phui/phui-object-item-list-view.css | 2 +- webroot/rsrc/css/phui/phui-timeline-view.css | 2 +- 7 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/applications/auth/view/PhabricatorAuthAccountView.php b/src/applications/auth/view/PhabricatorAuthAccountView.php index 13574a9b6a..8eb73144aa 100644 --- a/src/applications/auth/view/PhabricatorAuthAccountView.php +++ b/src/applications/auth/view/PhabricatorAuthAccountView.php @@ -95,13 +95,22 @@ final class PhabricatorAuthAccountView extends AphrontView { $image_uri = $image_file->getURIForTransform($xform); list($x, $y) = $xform->getTransformedDimensions($image_file); + $profile_image = phutil_tag( + 'div', + array( + 'class' => 'auth-account-view-profile-image', + 'style' => 'background-image: url('.$image_uri.');', + )); + return phutil_tag( 'div', array( 'class' => 'auth-account-view', - 'style' => 'background-image: url('.$image_uri.');', ), - $content); + array( + $profile_image, + $content, + )); } } diff --git a/webroot/rsrc/css/application/auth/auth.css b/webroot/rsrc/css/application/auth/auth.css index 8211eb1484..07eaccd46c 100644 --- a/webroot/rsrc/css/application/auth/auth.css +++ b/webroot/rsrc/css/application/auth/auth.css @@ -26,12 +26,21 @@ .auth-account-view { background-color: #fff; border: 1px solid {$lightblueborder}; - background-repeat: no-repeat; - background-position: 4px 4px; - background-size: 50px 50px; - padding: 4px 4px 4px 62px; - min-height: 50px; border-radius: 2px; + min-height: 50px; + position: relative; + padding: 4px 4px 4px 64px; +} + +.auth-account-view-profile-image { + width: 50px; + height: 50px; + top: 4px; + left: 4px; + + background-repeat: no-repeat; + background-size: 100%; + position: absolute; } .auth-account-view-name { diff --git a/webroot/rsrc/css/application/conpherence/message-pane.css b/webroot/rsrc/css/application/conpherence/message-pane.css index e702b34083..c4b7228ac7 100644 --- a/webroot/rsrc/css/application/conpherence/message-pane.css +++ b/webroot/rsrc/css/application/conpherence/message-pane.css @@ -162,7 +162,7 @@ .conpherence-message-pane .conpherence-transaction-view { padding: 2px 0px; margin: 4px 12px; - background-size: 35px; + background-size: 100%; min-height: auto; } diff --git a/webroot/rsrc/css/application/conpherence/notification.css b/webroot/rsrc/css/application/conpherence/notification.css index 75403f991d..e5bd40274a 100644 --- a/webroot/rsrc/css/application/conpherence/notification.css +++ b/webroot/rsrc/css/application/conpherence/notification.css @@ -26,7 +26,7 @@ position: absolute; width: 30px; height: 30px; - background-size: 30px; + background-size: 100%; } .phabricator-notification .conpherence-menu-item-view diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index 5b48f722c6..6790ed09e8 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -90,7 +90,7 @@ body.device-phone .phui-header-view { .phui-header-image { display: inline-block; background-repeat: no-repeat; - background-size: 50px; + background-size: 100%; border: 2px solid white; width: 50px; height: 50px; diff --git a/webroot/rsrc/css/phui/phui-object-item-list-view.css b/webroot/rsrc/css/phui/phui-object-item-list-view.css index e07d25f9d3..40aa67ee8a 100644 --- a/webroot/rsrc/css/phui/phui-object-item-list-view.css +++ b/webroot/rsrc/css/phui/phui-object-item-list-view.css @@ -599,7 +599,7 @@ ul.phui-object-item-icons { .phui-object-item-image { width: 40px; height: 40px; - background-size: 40px; + background-size: 100%; margin: 6px; position: absolute; background-color: {$lightbluebackground}; diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index 6e956037aa..d35a5458ee 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -92,7 +92,7 @@ .phui-timeline-image { background-repeat: no-repeat; - background-size: 50px; + background-size: 100%; position: absolute; border-radius: 3px; } From b36a8fa88563719051c7e3f2b291239371b9cf82 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 May 2015 09:46:25 -0700 Subject: [PATCH 63/70] Implement a user profile image cache Summary: Ref T7707. The general form of this can probably be refined somewhat over time as we have more use cases. I put this cache on the user object itself because we essentially always need this data and it's trivial to invalidate the cache (we can do it implicilty during reads). Also fix an issue with short, wide images not thumbnailing properly after recent changes. Test Plan: - Loaded some pages; saw caches write; saw good pictures. - Reloaded; saw cache reads; saw good pictures. - Changed profile picture; saw immediate update. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7707 Differential Revision: https://secure.phabricator.com/D12826 --- resources/celerity/map.php | 42 ++++------- .../sql/autopatches/20150513.user.cache.1.sql | 2 + .../PhabricatorFileImageTransform.php | 14 ++-- .../PhabricatorFileThumbnailTransform.php | 5 +- .../people/query/PhabricatorPeopleQuery.php | 65 ++++++++++++----- .../people/storage/PhabricatorUser.php | 72 +++++++++++++++++++ 6 files changed, 144 insertions(+), 56 deletions(-) create mode 100644 resources/sql/autopatches/20150513.user.cache.1.sql diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 3afab6cdb9..c4bb78b856 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'ed3d6355', + 'core.pkg.css' => '7ac320f1', 'core.pkg.js' => 'ac41c400', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', @@ -33,7 +33,7 @@ return array( 'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c', 'rsrc/css/aphront/typeahead.css' => '0e403212', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', - 'rsrc/css/application/auth/auth.css' => '1e655982', + 'rsrc/css/application/auth/auth.css' => '44975d4b', 'rsrc/css/application/base/main-menu-view.css' => '663e3810', 'rsrc/css/application/base/notification-menu.css' => '3c9d8aa1', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '16ca323f', @@ -47,8 +47,8 @@ return array( 'rsrc/css/application/config/unhandled-exception.css' => '37d4f9a2', 'rsrc/css/application/conpherence/durable-column.css' => '8c43d6ac', 'rsrc/css/application/conpherence/menu.css' => 'f389e048', - 'rsrc/css/application/conpherence/message-pane.css' => '0e75feef', - 'rsrc/css/application/conpherence/notification.css' => 'd208f806', + 'rsrc/css/application/conpherence/message-pane.css' => '5bb4b76d', + 'rsrc/css/application/conpherence/notification.css' => '919974b6', 'rsrc/css/application/conpherence/transaction.css' => '42a457f6', 'rsrc/css/application/conpherence/update.css' => '1099a660', 'rsrc/css/application/conpherence/widget-pane.css' => '2af42ebe', @@ -135,14 +135,14 @@ return array( 'rsrc/css/phui/phui-fontkit.css' => 'dd8ddf27', 'rsrc/css/phui/phui-form-view.css' => '94ae3032', 'rsrc/css/phui/phui-form.css' => 'f535f938', - 'rsrc/css/phui/phui-header-view.css' => 'da4586b1', + 'rsrc/css/phui/phui-header-view.css' => '75aaf372', 'rsrc/css/phui/phui-icon.css' => 'bc766998', 'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => 'c6f0aef8', 'rsrc/css/phui/phui-list.css' => '2e25ebfb', 'rsrc/css/phui/phui-object-box.css' => '7d160002', - 'rsrc/css/phui/phui-object-item-list-view.css' => '9db65899', + 'rsrc/css/phui/phui-object-item-list-view.css' => 'f3a22696', 'rsrc/css/phui/phui-pinboard-view.css' => 'eaab2b1b', 'rsrc/css/phui/phui-property-list-view.css' => '5b671934', 'rsrc/css/phui/phui-remarkup-preview.css' => '19ad512b', @@ -150,7 +150,7 @@ return array( 'rsrc/css/phui/phui-status.css' => '888cedb8', 'rsrc/css/phui/phui-tag-view.css' => '402691cc', 'rsrc/css/phui/phui-text.css' => 'cf019f54', - 'rsrc/css/phui/phui-timeline-view.css' => 'b0fbc4d7', + 'rsrc/css/phui/phui-timeline-view.css' => 'a85542c8', 'rsrc/css/phui/phui-workboard-view.css' => '3279cbbf', 'rsrc/css/phui/phui-workpanel-view.css' => 'e495a5cc', 'rsrc/css/sprite-gradient.css' => '4bdb98a7', @@ -281,22 +281,6 @@ return array( 'rsrc/image/icon/fatcow/source/mobile.png' => 'f1321264', 'rsrc/image/icon/fatcow/source/tablet.png' => '49396799', 'rsrc/image/icon/fatcow/source/web.png' => '136ccb5d', - 'rsrc/image/icon/fatcow/thumbnails/default.p100.png' => '7d490b01', - 'rsrc/image/icon/fatcow/thumbnails/default160x120.png' => 'f2e8a2eb', - 'rsrc/image/icon/fatcow/thumbnails/default280x210.png' => '43e8926a', - 'rsrc/image/icon/fatcow/thumbnails/default60x45.png' => '0118abed', - 'rsrc/image/icon/fatcow/thumbnails/image.p100.png' => 'da23cf97', - 'rsrc/image/icon/fatcow/thumbnails/image160x120.png' => '79bb556a', - 'rsrc/image/icon/fatcow/thumbnails/image280x210.png' => '91ae054a', - 'rsrc/image/icon/fatcow/thumbnails/image60x45.png' => 'c5e1685e', - 'rsrc/image/icon/fatcow/thumbnails/pdf.p100.png' => '87d5e065', - 'rsrc/image/icon/fatcow/thumbnails/pdf160x120.png' => 'ac9edbf5', - 'rsrc/image/icon/fatcow/thumbnails/pdf280x210.png' => '1c585653', - 'rsrc/image/icon/fatcow/thumbnails/pdf60x45.png' => 'c0db4143', - 'rsrc/image/icon/fatcow/thumbnails/zip.p100.png' => '6ea5aae4', - 'rsrc/image/icon/fatcow/thumbnails/zip160x120.png' => '75f9cd0f', - 'rsrc/image/icon/fatcow/thumbnails/zip280x210.png' => 'dfda5b8e', - 'rsrc/image/icon/fatcow/thumbnails/zip60x45.png' => 'af11bf3e', 'rsrc/image/icon/lightbox/close-2.png' => 'cc40e7c8', 'rsrc/image/icon/lightbox/close-hover-2.png' => 'fb5d6d9e', 'rsrc/image/icon/lightbox/left-arrow-2.png' => '8426133b', @@ -507,15 +491,15 @@ return array( 'aphront-tooltip-css' => '7672b60f', 'aphront-two-column-view-css' => '16ab3ad2', 'aphront-typeahead-control-css' => '0e403212', - 'auth-css' => '1e655982', + 'auth-css' => '44975d4b', 'changeset-view-manager' => '58562350', 'conduit-api-css' => '7bc725c4', 'config-options-css' => '7fedf08b', 'config-welcome-css' => '6abd79be', 'conpherence-durable-column-view' => '8c43d6ac', 'conpherence-menu-css' => 'f389e048', - 'conpherence-message-pane-css' => '0e75feef', - 'conpherence-notification-css' => 'd208f806', + 'conpherence-message-pane-css' => '5bb4b76d', + 'conpherence-notification-css' => '919974b6', 'conpherence-thread-manager' => 'b7342ddb', 'conpherence-transaction-css' => '42a457f6', 'conpherence-update-css' => '1099a660', @@ -787,7 +771,7 @@ return array( 'phui-fontkit-css' => 'dd8ddf27', 'phui-form-css' => 'f535f938', 'phui-form-view-css' => '94ae3032', - 'phui-header-view-css' => 'da4586b1', + 'phui-header-view-css' => '75aaf372', 'phui-icon-view-css' => 'bc766998', 'phui-image-mask-css' => '5a8b09c8', 'phui-info-panel-css' => '27ea50a1', @@ -795,7 +779,7 @@ return array( 'phui-inline-comment-view-css' => '2174771a', 'phui-list-view-css' => '2e25ebfb', 'phui-object-box-css' => '7d160002', - 'phui-object-item-list-view-css' => '9db65899', + 'phui-object-item-list-view-css' => 'f3a22696', 'phui-pinboard-view-css' => 'eaab2b1b', 'phui-property-list-view-css' => '5b671934', 'phui-remarkup-preview-css' => '19ad512b', @@ -803,7 +787,7 @@ return array( 'phui-status-list-view-css' => '888cedb8', 'phui-tag-view-css' => '402691cc', 'phui-text-css' => 'cf019f54', - 'phui-timeline-view-css' => 'b0fbc4d7', + 'phui-timeline-view-css' => 'a85542c8', 'phui-workboard-view-css' => '3279cbbf', 'phui-workpanel-view-css' => 'e495a5cc', 'phuix-action-list-view' => 'b5c256b8', diff --git a/resources/sql/autopatches/20150513.user.cache.1.sql b/resources/sql/autopatches/20150513.user.cache.1.sql new file mode 100644 index 0000000000..f6bf6e1e6a --- /dev/null +++ b/resources/sql/autopatches/20150513.user.cache.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_user.user + ADD profileImageCache VARCHAR(255) COLLATE {$COLLATE_TEXT}; diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php index 6c40e6bbfa..ff8bd14683 100644 --- a/src/applications/files/transform/PhabricatorFileImageTransform.php +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -141,14 +141,14 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { $name = 'default.png'; } - $name = $this->getTransformKey().'-'.$name; + $defaults = array( + 'canCDN' => true, + 'name' => $this->getTransformKey().'-'.$name, + ); - return PhabricatorFile::newFromFileData( - $data, - array( - 'name' => $name, - 'canCDN' => true, - ) + $this->getFileProperties()); + $properties = $this->getFileProperties() + $defaults; + + return PhabricatorFile::newFromFileData($data, $properties); } diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php index c7b579e836..af97f8a9fa 100644 --- a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -48,6 +48,7 @@ final class PhabricatorFileThumbnailTransform switch ($this->key) { case self::TRANSFORM_PROFILE: $properties['profile'] = true; + $properties['name'] = 'profile'; break; } return $properties; @@ -185,8 +186,8 @@ final class PhabricatorFileThumbnailTransform $scale = $scale_y; } - $copy_x = $dst_x / $scale_x; - $copy_y = $dst_y / $scale_x; + $copy_x = $dst_x / $scale; + $copy_y = $dst_y / $scale; if (!$scale_up) { $copy_x = min($src_x, $copy_x); diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index ac9d8b0200..b8b7ad1e29 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -148,26 +148,55 @@ final class PhabricatorPeopleQuery } if ($this->needProfileImage) { - $user_profile_file_phids = mpull($users, 'getProfileImagePHID'); - $user_profile_file_phids = array_filter($user_profile_file_phids); - if ($user_profile_file_phids) { - $files = id(new PhabricatorFileQuery()) - ->setParentQuery($this) - ->setViewer($this->getViewer()) - ->withPHIDs($user_profile_file_phids) - ->execute(); - $files = mpull($files, null, 'getPHID'); - } else { - $files = array(); - } + $rebuild = array(); foreach ($users as $user) { - $image_phid = $user->getProfileImagePHID(); - if (isset($files[$image_phid])) { - $profile_image_uri = $files[$image_phid]->getBestURI(); - } else { - $profile_image_uri = PhabricatorUser::getDefaultProfileImageURI(); + $image_uri = $user->getProfileImageCache(); + if ($image_uri) { + // This user has a valid cache, so we don't need to fetch any + // data or rebuild anything. + + $user->attachProfileImageURI($image_uri); + continue; + } + + // This user's cache is invalid or missing, so we're going to rebuild + // it. + $rebuild[] = $user; + } + + if ($rebuild) { + $file_phids = mpull($rebuild, 'getProfileImagePHID'); + $file_phids = array_filter($file_phids); + + if ($file_phids) { + // NOTE: We're using the omnipotent user here because older profile + // images do not have the 'profile' flag, so they may not be visible + // to the executing viewer. At some point, we could migrate to add + // this flag and then use the real viewer, or just use the real + // viewer after enough time has passed to limit the impact of old + // data. The consequence of missing here is that we cache a default + // image when a real image exists. + $files = id(new PhabricatorFileQuery()) + ->setParentQuery($this) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($file_phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); + } else { + $files = array(); + } + + foreach ($rebuild as $user) { + $image_phid = $user->getProfileImagePHID(); + if (isset($files[$image_phid])) { + $image_uri = $files[$image_phid]->getBestURI(); + } else { + $image_uri = PhabricatorUser::getDefaultProfileImageURI(); + } + + $user->writeProfileImageCache($image_uri); + $user->attachProfileImageURI($image_uri); } - $user->attachProfileImageURI($profile_image_uri); } } diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index a6c9f6f349..3fbfac062f 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -1,6 +1,7 @@ 'uint32', 'accountSecret' => 'bytes64', 'isEnrolledInMultiFactor' => 'bool', + 'profileImageCache' => 'text255?', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -160,6 +163,9 @@ final class PhabricatorUser 'columns' => array('isApproved'), ), ), + self::CONFIG_NO_MUTATE => array( + 'profileImageCache' => true, + ), ) + parent::getConfiguration(); } @@ -721,6 +727,72 @@ EOBODY; } +/* -( Profile Image Cache )------------------------------------------------ */ + + + /** + * Get this user's cached profile image URI. + * + * @return string|null Cached URI, if a URI is cached. + * @task image-cache + */ + public function getProfileImageCache() { + $version = $this->getProfileImageVersion(); + + $parts = explode(',', $this->profileImageCache, 2); + if (count($parts) !== 2) { + return null; + } + + if ($parts[0] !== $version) { + return null; + } + + return $parts[1]; + } + + + /** + * Generate a new cache value for this user's profile image. + * + * @return string New cache value. + * @task image-cache + */ + public function writeProfileImageCache($uri) { + $version = $this->getProfileImageVersion(); + $cache = "{$version},{$uri}"; + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + queryfx( + $this->establishConnection('w'), + 'UPDATE %T SET profileImageCache = %s WHERE id = %d', + $this->getTableName(), + $cache, + $this->getID()); + unset($unguarded); + } + + + /** + * Get a version identifier for a user's profile image. + * + * This version will change if the image changes, or if any of the + * environment configuration which goes into generating a URI changes. + * + * @return string Cache version. + * @task image-cache + */ + private function getProfileImageVersion() { + $parts = array( + PhabricatorEnv::getCDNURI('/'), + PhabricatorEnv::getEnvConfig('cluster.instance'), + $this->getProfileImagePHID(), + ); + $parts = serialize($parts); + return PhabricatorHash::digestForIndex($parts); + } + + /* -( Multi-Factor Authentication )---------------------------------------- */ From f677394b6ff35112a9afb3cadadae026521659ab Mon Sep 17 00:00:00 2001 From: lkassianik Date: Wed, 13 May 2015 12:01:04 -0700 Subject: [PATCH 64/70] People calendar month view should pass AphrontFormDateControlValue for start range and end range to MonthView Summary: Fixes T8181, People calendar month view should pass AphrontFormDateControlValue for start range and end range to MonthView Test Plan: Open install/p/epriestley/calendar without error Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8181 Differential Revision: https://secure.phabricator.com/D12827 --- .../PhabricatorPeopleCalendarController.php | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/applications/people/controller/PhabricatorPeopleCalendarController.php b/src/applications/people/controller/PhabricatorPeopleCalendarController.php index 06c7033fd7..b2d1f74ab7 100644 --- a/src/applications/people/controller/PhabricatorPeopleCalendarController.php +++ b/src/applications/people/controller/PhabricatorPeopleCalendarController.php @@ -41,18 +41,37 @@ final class PhabricatorPeopleCalendarController "{$year}-{$month}-01", "{$year}-{$month}-31"); + $start_epoch = strtotime("{$year}-{$month}-01"); + $end_epoch = strtotime("{$year}-{$month}-01 next month"); + $statuses = id(new PhabricatorCalendarEventQuery()) ->setViewer($user) ->withInvitedPHIDs(array($user->getPHID())) ->withDateRange( - strtotime("{$year}-{$month}-01"), - strtotime("{$year}-{$month}-01 next month")) + $start_epoch, + $end_epoch) ->execute(); + $start_range_value = AphrontFormDateControlValue::newFromEpoch( + $user, + $start_epoch); + $end_range_value = AphrontFormDateControlValue::newFromEpoch( + $user, + $end_epoch); + if ($month == $month_d && $year == $year_d) { - $month_view = new PHUICalendarMonthView($month, $year, $day); + $month_view = new PHUICalendarMonthView( + $start_range_value, + $end_range_value, + $month, + $year, + $day); } else { - $month_view = new PHUICalendarMonthView($month, $year); + $month_view = new PHUICalendarMonthView( + $start_range_value, + $end_range_value, + $month, + $year); } $month_view->setBrowseURI($request->getRequestURI()); @@ -67,7 +86,7 @@ final class PhabricatorPeopleCalendarController $event = new AphrontCalendarEventView(); $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); $event->setUserPHID($status->getUserPHID()); - $event->setName($status->getHumanStatus()); + $event->setName($status->getName()); $event->setDescription($status->getDescription()); $event->setEventID($status->getID()); $month_view->addEvent($event); From f3d5e22a45055a84405d08b0f9e973ec5f26c1b5 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Thu, 14 May 2015 06:49:30 +1000 Subject: [PATCH 65/70] Destroy associated worker tasks and notifications Summary: Fixes T7107. When destroying an object, destroy associated worker tasks and notifications. Test Plan: # Manually updated the `phabricator_worker.worker_activetask` table by setting `objectPHID` to the desired object. # Removed object with `./bin/remove destroy`. # Queried `phabricator_worker.worker_activetask` table. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T7107 Differential Revision: https://secure.phabricator.com/D12822 --- .../engine/PhabricatorDestructionEngine.php | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/applications/system/engine/PhabricatorDestructionEngine.php b/src/applications/system/engine/PhabricatorDestructionEngine.php index e1f1045132..b55b336052 100644 --- a/src/applications/system/engine/PhabricatorDestructionEngine.php +++ b/src/applications/system/engine/PhabricatorDestructionEngine.php @@ -46,6 +46,9 @@ final class PhabricatorDestructionEngine extends Phobject { $template = $object->getApplicationTransactionTemplate(); $this->destroyTransactions($template, $object_phid); } + + $this->destroyWorkerTasks($object_phid); + $this->destroyNotifications($object_phid); } // Nuke any Herald transcripts of the object, because they may contain @@ -94,7 +97,28 @@ final class PhabricatorDestructionEngine extends Phobject { foreach ($xactions as $xaction) { $this->destroyObject($xaction); } + } + private function destroyWorkerTasks($object_phid) { + $tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere( + 'objectPHID = %s', + $object_phid); + + foreach ($tasks as $task) { + $task->archiveTask( + PhabricatorWorkerArchiveTask::RESULT_CANCELLED, + 0); + } + } + + private function destroyNotifications($object_phid) { + $notifications = id(new PhabricatorFeedStoryNotification())->loadAllWhere( + 'primaryObjectPHID = %s', + $object_phid); + + foreach ($notifications as $notification) { + $notification->delete(); + } } } From acb45968d889f21eb3c7614847ac816ab1b33be5 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Thu, 14 May 2015 06:50:28 +1000 Subject: [PATCH 66/70] Use `__CLASS__` instead of hard-coding class names Summary: Use `__CLASS__` instead of hard-coding class names. Depends on D12605. Test Plan: Eyeball it. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: hach-que, Korvin, epriestley Differential Revision: https://secure.phabricator.com/D12806 --- src/aphront/AphrontRequest.php | 2 +- src/aphront/response/AphrontProxyResponse.php | 5 ++- src/aphront/response/AphrontResponse.php | 2 +- .../PhabricatorAuditStatusConstants.php | 14 +++---- .../storage/PhabricatorAuditInlineComment.php | 2 +- .../base/PhabricatorApplication.php | 2 +- src/applications/cache/PhabricatorCaches.php | 2 +- .../storage/PhabricatorCalendarEvent.php | 2 +- .../resources/CelerityPhysicalResources.php | 2 +- .../conduit/method/ConduitAPIMethod.php | 6 +-- .../query/PhabricatorConduitLogQuery.php | 2 +- .../query/PhabricatorConduitTokenQuery.php | 2 +- .../storage/PhabricatorConduitToken.php | 2 +- .../config/check/PhabricatorSetupCheck.php | 2 +- .../PhabricatorApplicationConfigOptions.php | 16 +++++--- .../conpherence/storage/ConpherenceThread.php | 2 +- .../errorlog/DarkConsoleErrorLogPluginAPI.php | 2 +- .../query/PhabricatorDaemonLogQuery.php | 4 +- .../constants/DifferentialAction.php | 40 +++++++++---------- .../constants/DifferentialChangeType.php | 24 +++++------ .../storage/DifferentialTransaction.php | 2 +- .../diffusion/data/DiffusionPathChange.php | 4 +- .../diviner/storage/DivinerLiveSymbol.php | 4 +- .../DrydockBlueprintImplementation.php | 2 +- .../drydock/storage/DrydockLease.php | 2 +- .../feed/story/PhabricatorFeedStory.php | 2 +- .../PhabricatorFileComposeController.php | 2 +- .../files/storage/PhabricatorFile.php | 10 ++--- .../storage/FundInitiativeTransaction.php | 36 ++++++++--------- .../HarbormasterBuildStepImplementation.php | 2 +- .../storage/HarbormasterBuildable.php | 4 +- .../PhabricatorHelpApplication.php | 2 +- .../herald/adapter/HeraldAdapter.php | 2 +- .../PhabricatorMacroMemeController.php | 2 +- .../maniphest/query/ManiphestTaskQuery.php | 2 +- .../MetaMTAEmailTransactionCommand.php | 2 +- .../PhabricatorContentSource.php | 6 +-- .../nuance/storage/NuanceItem.php | 2 +- .../storage/PhabricatorPasteTransaction.php | 12 +++--- .../PhabricatorUserEditorTestCase.php | 4 +- .../handle/pool/PhabricatorHandleList.php | 5 ++- .../phid/type/PhabricatorPHIDType.php | 10 +++-- .../phortune/currency/PhortuneCurrency.php | 12 +++--- .../provider/PhortunePaymentProvider.php | 2 +- .../phortune/storage/PhortuneAccount.php | 2 +- .../phortune/storage/PhortuneCart.php | 2 +- .../phragment/storage/PhragmentFragment.php | 2 +- .../policy/constants/PhabricatorPolicies.php | 4 +- .../project/icon/PhabricatorProjectIcon.php | 2 +- .../storage/PhabricatorProjectColumn.php | 2 +- .../PhabricatorProjectColumnTransaction.php | 6 +-- .../storage/PhabricatorProjectTransaction.php | 34 ++++++++-------- .../releeph/storage/ReleephRequest.php | 4 +- .../storage/ReleephRequestTransaction.php | 28 ++++++------- .../PhabricatorRepositorySearchEngine.php | 2 +- .../storage/PhabricatorRepositoryPushLog.php | 8 ++-- .../storage/PhabricatorUserPreferences.php | 4 +- .../PhabricatorSlowvoteTransaction.php | 40 +++++++++---------- .../PhabricatorApplicationTransaction.php | 4 +- .../field/PhabricatorCustomField.php | 2 +- .../daemon/bot/PhabricatorBot.php | 8 +++- .../query/PhabricatorWorkerTriggerQuery.php | 2 +- src/infrastructure/env/PhabricatorEnv.php | 12 +++--- .../PhabricatorExampleEventListener.php | 7 +++- .../sms/storage/PhabricatorSMS.php | 6 +-- .../storage/patch/PhabricatorSQLPatchList.php | 2 +- src/infrastructure/time/PhabricatorTime.php | 7 +++- src/infrastructure/util/PhabricatorHash.php | 4 +- .../password/PhabricatorPasswordHasher.php | 6 +-- src/view/AphrontDialogView.php | 5 ++- src/view/form/AphrontFormView.php | 5 ++- src/view/form/PHUIFormLayoutView.php | 5 ++- src/view/form/PHUIInfoView.php | 1 - src/view/form/PHUIPagedFormView.php | 2 +- .../control/PhabricatorRemarkupControl.php | 5 ++- src/view/page/PhabricatorStandardPageView.php | 4 +- src/view/phui/PHUIDocumentView.php | 2 +- support/PhabricatorStartup.php | 4 +- 78 files changed, 268 insertions(+), 233 deletions(-) diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php index 197e5a437c..ab6e0d9820 100644 --- a/src/aphront/AphrontRequest.php +++ b/src/aphront/AphrontRequest.php @@ -650,7 +650,7 @@ final class AphrontRequest { * safe. */ public function isProxiedClusterRequest() { - return (bool)AphrontRequest::getHTTPHeader('X-Phabricator-Cluster'); + return (bool)self::getHTTPHeader('X-Phabricator-Cluster'); } diff --git a/src/aphront/response/AphrontProxyResponse.php b/src/aphront/response/AphrontProxyResponse.php index 7e27ea2b1b..175f2d2c53 100644 --- a/src/aphront/response/AphrontProxyResponse.php +++ b/src/aphront/response/AphrontProxyResponse.php @@ -65,7 +65,10 @@ abstract class AphrontProxyResponse extends AphrontResponse { final public function buildResponseString() { throw new Exception( - 'AphrontProxyResponse must implement reduceProxyResponse().'); + pht( + '%s must implement %s.', + __CLASS__, + 'reduceProxyResponse()')); } } diff --git a/src/aphront/response/AphrontResponse.php b/src/aphront/response/AphrontResponse.php index 0bbe22b5ec..b1e72f5529 100644 --- a/src/aphront/response/AphrontResponse.php +++ b/src/aphront/response/AphrontResponse.php @@ -154,7 +154,7 @@ abstract class AphrontResponse { array_walk_recursive( $object, - array('AphrontResponse', 'processValueForJSONEncoding')); + array(__CLASS__, 'processValueForJSONEncoding')); $response = json_encode($object); diff --git a/src/applications/audit/constants/PhabricatorAuditStatusConstants.php b/src/applications/audit/constants/PhabricatorAuditStatusConstants.php index e1a4378ab3..78924905b9 100644 --- a/src/applications/audit/constants/PhabricatorAuditStatusConstants.php +++ b/src/applications/audit/constants/PhabricatorAuditStatusConstants.php @@ -60,19 +60,19 @@ final class PhabricatorAuditStatusConstants { public static function getStatusIcon($code) { switch ($code) { - case PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED: - case PhabricatorAuditStatusConstants::RESIGNED: + case self::AUDIT_NOT_REQUIRED: + case self::RESIGNED: $icon = PHUIStatusItemView::ICON_OPEN; break; - case PhabricatorAuditStatusConstants::AUDIT_REQUIRED: - case PhabricatorAuditStatusConstants::AUDIT_REQUESTED: + case self::AUDIT_REQUIRED: + case self::AUDIT_REQUESTED: $icon = PHUIStatusItemView::ICON_WARNING; break; - case PhabricatorAuditStatusConstants::CONCERNED: + case self::CONCERNED: $icon = PHUIStatusItemView::ICON_REJECT; break; - case PhabricatorAuditStatusConstants::ACCEPTED: - case PhabricatorAuditStatusConstants::CLOSED: + case self::ACCEPTED: + case self::CLOSED: $icon = PHUIStatusItemView::ICON_ACCEPT; break; default: diff --git a/src/applications/audit/storage/PhabricatorAuditInlineComment.php b/src/applications/audit/storage/PhabricatorAuditInlineComment.php index 8c0aa78d5d..292e0274a5 100644 --- a/src/applications/audit/storage/PhabricatorAuditInlineComment.php +++ b/src/applications/audit/storage/PhabricatorAuditInlineComment.php @@ -115,7 +115,7 @@ final class PhabricatorAuditInlineComment private static function buildProxies(array $inlines) { $results = array(); foreach ($inlines as $key => $inline) { - $results[$key] = PhabricatorAuditInlineComment::newFromModernComment( + $results[$key] = self::newFromModernComment( $inline); } return $results; diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index d5237adc11..295d4db180 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -361,7 +361,7 @@ abstract class PhabricatorApplication implements PhabricatorPolicyInterface { public static function getByClass($class_name) { $selected = null; - $applications = PhabricatorApplication::getAllApplications(); + $applications = self::getAllApplications(); foreach ($applications as $application) { if (get_class($application) == $class_name) { diff --git a/src/applications/cache/PhabricatorCaches.php b/src/applications/cache/PhabricatorCaches.php index 37708457b7..4e3af7a6a4 100644 --- a/src/applications/cache/PhabricatorCaches.php +++ b/src/applications/cache/PhabricatorCaches.php @@ -270,7 +270,7 @@ final class PhabricatorCaches { } private static function addNamespaceToCaches(array $caches) { - $namespace = PhabricatorCaches::getNamespace(); + $namespace = self::getNamespace(); if (!$namespace) { return $caches; } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index af756b5273..3dd984ef7d 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -200,7 +200,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO public function getTerseSummary(PhabricatorUser $viewer) { $until = phabricator_date($this->dateTo, $viewer); - if ($this->status == PhabricatorCalendarEvent::STATUS_SPORADIC) { + if ($this->status == self::STATUS_SPORADIC) { return pht('Sporadic until %s', $until); } else { return pht('Away until %s', $until); diff --git a/src/applications/celerity/resources/CelerityPhysicalResources.php b/src/applications/celerity/resources/CelerityPhysicalResources.php index 995b9f1a55..41665d15f2 100644 --- a/src/applications/celerity/resources/CelerityPhysicalResources.php +++ b/src/applications/celerity/resources/CelerityPhysicalResources.php @@ -25,7 +25,7 @@ abstract class CelerityPhysicalResources extends CelerityResources { $resources_map = array(); $resources_list = id(new PhutilSymbolLoader()) - ->setAncestorClass('CelerityPhysicalResources') + ->setAncestorClass(__CLASS__) ->loadObjects(); foreach ($resources_list as $resources) { diff --git a/src/applications/conduit/method/ConduitAPIMethod.php b/src/applications/conduit/method/ConduitAPIMethod.php index a048e3e0f0..f6d464cfc5 100644 --- a/src/applications/conduit/method/ConduitAPIMethod.php +++ b/src/applications/conduit/method/ConduitAPIMethod.php @@ -100,9 +100,9 @@ abstract class ConduitAPIMethod $name = $this->getAPIMethodName(); $map = array( - ConduitAPIMethod::METHOD_STATUS_STABLE => 0, - ConduitAPIMethod::METHOD_STATUS_UNSTABLE => 1, - ConduitAPIMethod::METHOD_STATUS_DEPRECATED => 2, + self::METHOD_STATUS_STABLE => 0, + self::METHOD_STATUS_UNSTABLE => 1, + self::METHOD_STATUS_DEPRECATED => 2, ); $ord = idx($map, $this->getMethodStatus(), 0); diff --git a/src/applications/conduit/query/PhabricatorConduitLogQuery.php b/src/applications/conduit/query/PhabricatorConduitLogQuery.php index 4d66cccae5..a08fd25a68 100644 --- a/src/applications/conduit/query/PhabricatorConduitLogQuery.php +++ b/src/applications/conduit/query/PhabricatorConduitLogQuery.php @@ -22,7 +22,7 @@ final class PhabricatorConduitLogQuery $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); - return $table->loadAllFromArray($data);; + return $table->loadAllFromArray($data); } protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { diff --git a/src/applications/conduit/query/PhabricatorConduitTokenQuery.php b/src/applications/conduit/query/PhabricatorConduitTokenQuery.php index 870043cac8..44586f1815 100644 --- a/src/applications/conduit/query/PhabricatorConduitTokenQuery.php +++ b/src/applications/conduit/query/PhabricatorConduitTokenQuery.php @@ -46,7 +46,7 @@ final class PhabricatorConduitTokenQuery $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); - return $table->loadAllFromArray($data);; + return $table->loadAllFromArray($data); } protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { diff --git a/src/applications/conduit/storage/PhabricatorConduitToken.php b/src/applications/conduit/storage/PhabricatorConduitToken.php index ab4d88335e..f5673fdab3 100644 --- a/src/applications/conduit/storage/PhabricatorConduitToken.php +++ b/src/applications/conduit/storage/PhabricatorConduitToken.php @@ -65,7 +65,7 @@ final class PhabricatorConduitToken // to expire) so generate a new token. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $token = PhabricatorConduitToken::initializeNewToken( + $token = self::initializeNewToken( $user->getPHID(), self::TYPE_CLUSTER); $token->save(); diff --git a/src/applications/config/check/PhabricatorSetupCheck.php b/src/applications/config/check/PhabricatorSetupCheck.php index b40dd4c8cc..a689d14943 100644 --- a/src/applications/config/check/PhabricatorSetupCheck.php +++ b/src/applications/config/check/PhabricatorSetupCheck.php @@ -113,7 +113,7 @@ abstract class PhabricatorSetupCheck { final public static function runAllChecks() { $symbols = id(new PhutilSymbolLoader()) - ->setAncestorClass('PhabricatorSetupCheck') + ->setAncestorClass(__CLASS__) ->setConcreteOnly(true) ->selectAndLoadSymbols(); diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 6982ef513f..7ac1df3e16 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -187,7 +187,7 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { final public static function loadAll($external_only = false) { $symbols = id(new PhutilSymbolLoader()) - ->setAncestorClass('PhabricatorApplicationConfigOptions') + ->setAncestorClass(__CLASS__) ->setConcreteOnly(true) ->selectAndLoadSymbols(); @@ -204,8 +204,12 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { $nclass = $symbol['name']; throw new Exception( - "Multiple PhabricatorApplicationConfigOptions subclasses have the ". - "same key ('{$key}'): {$pclass}, {$nclass}."); + pht( + "Multiple %s subclasses have the same key ('%s'): %s, %s.", + __CLASS__, + $key, + $pclass, + $nclass)); } $groups[$key] = $obj; } @@ -222,8 +226,10 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { $key = $option->getKey(); if (isset($options[$key])) { throw new Exception( - "Mulitple PhabricatorApplicationConfigOptions subclasses contain ". - "an option named '{$key}'!"); + pht( + "Mulitple % subclasses contain an option named '%s'!", + __CLASS__, + $key)); } $options[$key] = $option; } diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index 03f8f6997e..8ed0e4fd18 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -407,7 +407,7 @@ final class ConpherenceThread extends ConpherenceDAO PhabricatorUser $viewer, array $conpherences) { - assert_instances_of($conpherences, 'ConpherenceThread'); + assert_instances_of($conpherences, __CLASS__); $grouped = mgroup($conpherences, 'getIsRoom'); $rooms = idx($grouped, 1, array()); diff --git a/src/applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php b/src/applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php index 0708c98eec..5d9723cc4b 100644 --- a/src/applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php +++ b/src/applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php @@ -12,7 +12,7 @@ final class DarkConsoleErrorLogPluginAPI { // reenter autoloaders). PhutilReadableSerializer::printableValue(null); PhutilErrorHandler::setErrorListener( - array('DarkConsoleErrorLogPluginAPI', 'handleErrors')); + array(__CLASS__, 'handleErrors')); } public static function enableDiscardMode() { diff --git a/src/applications/daemon/query/PhabricatorDaemonLogQuery.php b/src/applications/daemon/query/PhabricatorDaemonLogQuery.php index 5822194d06..961c1cfc61 100644 --- a/src/applications/daemon/query/PhabricatorDaemonLogQuery.php +++ b/src/applications/daemon/query/PhabricatorDaemonLogQuery.php @@ -68,8 +68,8 @@ final class PhabricatorDaemonLogQuery } protected function willFilterPage(array $daemons) { - $unknown_delay = PhabricatorDaemonLogQuery::getTimeUntilUnknown(); - $dead_delay = PhabricatorDaemonLogQuery::getTimeUntilDead(); + $unknown_delay = self::getTimeUntilUnknown(); + $dead_delay = self::getTimeUntilDead(); $status_running = PhabricatorDaemonLog::STATUS_RUNNING; $status_unknown = PhabricatorDaemonLog::STATUS_UNKNOWN; diff --git a/src/applications/differential/constants/DifferentialAction.php b/src/applications/differential/constants/DifferentialAction.php index 9238990161..48a263c45a 100644 --- a/src/applications/differential/constants/DifferentialAction.php +++ b/src/applications/differential/constants/DifferentialAction.php @@ -22,71 +22,71 @@ final class DifferentialAction { public static function getBasicStoryText($action, $author_name) { switch ($action) { - case DifferentialAction::ACTION_COMMENT: + case self::ACTION_COMMENT: $title = pht('%s commented on this revision.', $author_name); break; - case DifferentialAction::ACTION_ACCEPT: + case self::ACTION_ACCEPT: $title = pht('%s accepted this revision.', $author_name); break; - case DifferentialAction::ACTION_REJECT: + case self::ACTION_REJECT: $title = pht('%s requested changes to this revision.', $author_name); break; - case DifferentialAction::ACTION_RETHINK: + case self::ACTION_RETHINK: $title = pht('%s planned changes to this revision.', $author_name); break; - case DifferentialAction::ACTION_ABANDON: + case self::ACTION_ABANDON: $title = pht('%s abandoned this revision.', $author_name); break; - case DifferentialAction::ACTION_CLOSE: + case self::ACTION_CLOSE: $title = pht('%s closed this revision.', $author_name); break; - case DifferentialAction::ACTION_REQUEST: + case self::ACTION_REQUEST: $title = pht('%s requested a review of this revision.', $author_name); break; - case DifferentialAction::ACTION_RECLAIM: + case self::ACTION_RECLAIM: $title = pht('%s reclaimed this revision.', $author_name); break; - case DifferentialAction::ACTION_UPDATE: + case self::ACTION_UPDATE: $title = pht('%s updated this revision.', $author_name); break; - case DifferentialAction::ACTION_RESIGN: + case self::ACTION_RESIGN: $title = pht('%s resigned from this revision.', $author_name); break; - case DifferentialAction::ACTION_SUMMARIZE: + case self::ACTION_SUMMARIZE: $title = pht('%s summarized this revision.', $author_name); break; - case DifferentialAction::ACTION_TESTPLAN: + case self::ACTION_TESTPLAN: $title = pht('%s explained the test plan for this revision.', $author_name); break; - case DifferentialAction::ACTION_CREATE: + case self::ACTION_CREATE: $title = pht('%s created this revision.', $author_name); break; - case DifferentialAction::ACTION_ADDREVIEWERS: + case self::ACTION_ADDREVIEWERS: $title = pht('%s added reviewers to this revision.', $author_name); break; - case DifferentialAction::ACTION_ADDCCS: + case self::ACTION_ADDCCS: $title = pht('%s added CCs to this revision.', $author_name); break; - case DifferentialAction::ACTION_CLAIM: + case self::ACTION_CLAIM: $title = pht('%s commandeered this revision.', $author_name); break; - case DifferentialAction::ACTION_REOPEN: + case self::ACTION_REOPEN: $title = pht('%s reopened this revision.', $author_name); break; @@ -127,9 +127,9 @@ final class DifferentialAction { } public static function allowReviewers($action) { - if ($action == DifferentialAction::ACTION_ADDREVIEWERS || - $action == DifferentialAction::ACTION_REQUEST || - $action == DifferentialAction::ACTION_RESIGN) { + if ($action == self::ACTION_ADDREVIEWERS || + $action == self::ACTION_REQUEST || + $action == self::ACTION_RESIGN) { return true; } return false; diff --git a/src/applications/differential/constants/DifferentialChangeType.php b/src/applications/differential/constants/DifferentialChangeType.php index 0a4f7861cc..2ce5392165 100644 --- a/src/applications/differential/constants/DifferentialChangeType.php +++ b/src/applications/differential/constants/DifferentialChangeType.php @@ -52,42 +52,42 @@ final class DifferentialChangeType { public static function isOldLocationChangeType($type) { static $types = array( - DifferentialChangeType::TYPE_MOVE_AWAY => true, - DifferentialChangeType::TYPE_COPY_AWAY => true, - DifferentialChangeType::TYPE_MULTICOPY => true, + self::TYPE_MOVE_AWAY => true, + self::TYPE_COPY_AWAY => true, + self::TYPE_MULTICOPY => true, ); return isset($types[$type]); } public static function isNewLocationChangeType($type) { static $types = array( - DifferentialChangeType::TYPE_MOVE_HERE => true, - DifferentialChangeType::TYPE_COPY_HERE => true, + self::TYPE_MOVE_HERE => true, + self::TYPE_COPY_HERE => true, ); return isset($types[$type]); } public static function isDeleteChangeType($type) { static $types = array( - DifferentialChangeType::TYPE_DELETE => true, - DifferentialChangeType::TYPE_MOVE_AWAY => true, - DifferentialChangeType::TYPE_MULTICOPY => true, + self::TYPE_DELETE => true, + self::TYPE_MOVE_AWAY => true, + self::TYPE_MULTICOPY => true, ); return isset($types[$type]); } public static function isCreateChangeType($type) { static $types = array( - DifferentialChangeType::TYPE_ADD => true, - DifferentialChangeType::TYPE_COPY_HERE => true, - DifferentialChangeType::TYPE_MOVE_HERE => true, + self::TYPE_ADD => true, + self::TYPE_COPY_HERE => true, + self::TYPE_MOVE_HERE => true, ); return isset($types[$type]); } public static function isModifyChangeType($type) { static $types = array( - DifferentialChangeType::TYPE_CHANGE => true, + self::TYPE_CHANGE => true, ); return isset($types[$type]); } diff --git a/src/applications/differential/storage/DifferentialTransaction.php b/src/applications/differential/storage/DifferentialTransaction.php index 5cb6fd7e87..2857d24272 100644 --- a/src/applications/differential/storage/DifferentialTransaction.php +++ b/src/applications/differential/storage/DifferentialTransaction.php @@ -568,7 +568,7 @@ final class DifferentialTransaction extends PhabricatorApplicationTransaction { 'this revision.'); } break; - case DifferentialTransaction::TYPE_ACTION: + case self::TYPE_ACTION: switch ($this->getNewValue()) { case DifferentialAction::ACTION_CLOSE: return pht('This revision is already closed.'); diff --git a/src/applications/diffusion/data/DiffusionPathChange.php b/src/applications/diffusion/data/DiffusionPathChange.php index b1d7286fdd..6f96057014 100644 --- a/src/applications/diffusion/data/DiffusionPathChange.php +++ b/src/applications/diffusion/data/DiffusionPathChange.php @@ -119,7 +119,7 @@ final class DiffusionPathChange { } final public static function convertToArcanistChanges(array $changes) { - assert_instances_of($changes, 'DiffusionPathChange'); + assert_instances_of($changes, __CLASS__); $direct = array(); $result = array(); foreach ($changes as $path) { @@ -145,7 +145,7 @@ final class DiffusionPathChange { final public static function convertToDifferentialChangesets( PhabricatorUser $user, array $changes) { - assert_instances_of($changes, 'DiffusionPathChange'); + assert_instances_of($changes, __CLASS__); $arcanist_changes = self::convertToArcanistChanges($changes); $diff = DifferentialDiff::newEphemeralFromRawChanges( $arcanist_changes); diff --git a/src/applications/diviner/storage/DivinerLiveSymbol.php b/src/applications/diviner/storage/DivinerLiveSymbol.php index b6a7cde226..ba35eed255 100644 --- a/src/applications/diviner/storage/DivinerLiveSymbol.php +++ b/src/applications/diviner/storage/DivinerLiveSymbol.php @@ -174,7 +174,7 @@ final class DivinerLiveSymbol extends DivinerDAO } public function attachExtends(array $extends) { - assert_instances_of($extends, 'DivinerLiveSymbol'); + assert_instances_of($extends, __CLASS__); $this->extends = $extends; return $this; } @@ -184,7 +184,7 @@ final class DivinerLiveSymbol extends DivinerDAO } public function attachChildren(array $children) { - assert_instances_of($children, 'DivinerLiveSymbol'); + assert_instances_of($children, __CLASS__); $this->children = $children; return $this; } diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index 18ca597a90..43891d0d07 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -377,7 +377,7 @@ abstract class DrydockBlueprintImplementation { if ($list === null) { $blueprints = id(new PhutilSymbolLoader()) ->setType('class') - ->setAncestorClass('DrydockBlueprintImplementation') + ->setAncestorClass(__CLASS__) ->setConcreteOnly(true) ->selectAndLoadSymbols(); $list = ipull($blueprints, 'name', 'name'); diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index cb61e54490..252c922065 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -149,7 +149,7 @@ final class DrydockLease extends DrydockDAO } public static function waitForLeases(array $leases) { - assert_instances_of($leases, 'DrydockLease'); + assert_instances_of($leases, __CLASS__); $task_ids = array_filter(mpull($leases, 'getTaskID')); diff --git a/src/applications/feed/story/PhabricatorFeedStory.php b/src/applications/feed/story/PhabricatorFeedStory.php index 0c989d4948..f94c2601e8 100644 --- a/src/applications/feed/story/PhabricatorFeedStory.php +++ b/src/applications/feed/story/PhabricatorFeedStory.php @@ -48,7 +48,7 @@ abstract class PhabricatorFeedStory try { $ok = class_exists($class) && - is_subclass_of($class, 'PhabricatorFeedStory'); + is_subclass_of($class, __CLASS__); } catch (PhutilMissingSymbolException $ex) { $ok = false; } diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php index bb95cb67eb..d1e6041131 100644 --- a/src/applications/files/controller/PhabricatorFileComposeController.php +++ b/src/applications/files/controller/PhabricatorFileComposeController.php @@ -242,7 +242,7 @@ final class PhabricatorFileComposeController } $dialog_id = celerity_generate_unique_node_id(); - $color_input_id = celerity_generate_unique_node_id();; + $color_input_id = celerity_generate_unique_node_id(); $icon_input_id = celerity_generate_unique_node_id(); $preview_id = celerity_generate_unique_node_id(); diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 34a2bb5df5..f8e99bc965 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -215,7 +215,7 @@ final class PhabricatorFile extends PhabricatorFileDAO if (!$file) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $file = PhabricatorFile::newFromFileData($data, $params); + $file = self::newFromFileData($data, $params); unset($unguarded); } @@ -236,7 +236,7 @@ final class PhabricatorFile extends PhabricatorFileDAO $copy_of_byte_size = $file->getByteSize(); $copy_of_mime_type = $file->getMimeType(); - $new_file = PhabricatorFile::initializeNewFile(); + $new_file = self::initializeNewFile(); $new_file->setByteSize($copy_of_byte_size); @@ -262,7 +262,7 @@ final class PhabricatorFile extends PhabricatorFileDAO $length, array $params) { - $file = PhabricatorFile::initializeNewFile(); + $file = self::initializeNewFile(); $file->setByteSize($length); @@ -316,7 +316,7 @@ final class PhabricatorFile extends PhabricatorFileDAO throw new Exception(pht('No valid storage engines are available!')); } - $file = PhabricatorFile::initializeNewFile(); + $file = self::initializeNewFile(); $data_handle = null; $engine_identifier = null; @@ -1017,7 +1017,7 @@ final class PhabricatorFile extends PhabricatorFileDAO ); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $file = PhabricatorFile::newFromFileData($data, $params); + $file = self::newFromFileData($data, $params); $xform = id(new PhabricatorTransformedFile()) ->setOriginalPHID(PhabricatorPHIDConstants::PHID_VOID) ->setTransform('builtin:'.$name) diff --git a/src/applications/fund/storage/FundInitiativeTransaction.php b/src/applications/fund/storage/FundInitiativeTransaction.php index 3531ff3e5e..c06d62e229 100644 --- a/src/applications/fund/storage/FundInitiativeTransaction.php +++ b/src/applications/fund/storage/FundInitiativeTransaction.php @@ -38,7 +38,7 @@ final class FundInitiativeTransaction $type = $this->getTransactionType(); switch ($type) { - case FundInitiativeTransaction::TYPE_MERCHANT: + case self::TYPE_MERCHANT: if ($old) { $phids[] = $old; } @@ -46,7 +46,7 @@ final class FundInitiativeTransaction $phids[] = $new; } break; - case FundInitiativeTransaction::TYPE_REFUND: + case self::TYPE_REFUND: $phids[] = $this->getMetadataValue(self::PROPERTY_BACKER); break; } @@ -63,7 +63,7 @@ final class FundInitiativeTransaction $type = $this->getTransactionType(); switch ($type) { - case FundInitiativeTransaction::TYPE_NAME: + case self::TYPE_NAME: if ($old === null) { return pht( '%s created this initiative.', @@ -76,15 +76,15 @@ final class FundInitiativeTransaction $new); } break; - case FundInitiativeTransaction::TYPE_RISKS: + case self::TYPE_RISKS: return pht( '%s edited the risks for this initiative.', $this->renderHandleLink($author_phid)); - case FundInitiativeTransaction::TYPE_DESCRIPTION: + case self::TYPE_DESCRIPTION: return pht( '%s edited the description of this initiative.', $this->renderHandleLink($author_phid)); - case FundInitiativeTransaction::TYPE_STATUS: + case self::TYPE_STATUS: switch ($new) { case FundInitiative::STATUS_OPEN: return pht( @@ -96,14 +96,14 @@ final class FundInitiativeTransaction $this->renderHandleLink($author_phid)); } break; - case FundInitiativeTransaction::TYPE_BACKER: + case self::TYPE_BACKER: $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); $amount = PhortuneCurrency::newFromString($amount); return pht( '%s backed this initiative with %s.', $this->renderHandleLink($author_phid), $amount->formatForDisplay()); - case FundInitiativeTransaction::TYPE_REFUND: + case self::TYPE_REFUND: $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); $amount = PhortuneCurrency::newFromString($amount); @@ -114,7 +114,7 @@ final class FundInitiativeTransaction $this->renderHandleLink($author_phid), $amount->formatForDisplay(), $this->renderHandleLink($backer_phid)); - case FundInitiativeTransaction::TYPE_MERCHANT: + case self::TYPE_MERCHANT: if ($old === null) { return pht( '%s set this initiative to pay to %s.', @@ -142,7 +142,7 @@ final class FundInitiativeTransaction $type = $this->getTransactionType(); switch ($type) { - case FundInitiativeTransaction::TYPE_NAME: + case self::TYPE_NAME: if ($old === null) { return pht( '%s created %s.', @@ -156,12 +156,12 @@ final class FundInitiativeTransaction $this->renderHandleLink($object_phid)); } break; - case FundInitiativeTransaction::TYPE_DESCRIPTION: + case self::TYPE_DESCRIPTION: return pht( '%s updated the description for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); - case FundInitiativeTransaction::TYPE_STATUS: + case self::TYPE_STATUS: switch ($new) { case FundInitiative::STATUS_OPEN: return pht( @@ -175,7 +175,7 @@ final class FundInitiativeTransaction $this->renderHandleLink($object_phid)); } break; - case FundInitiativeTransaction::TYPE_BACKER: + case self::TYPE_BACKER: $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); $amount = PhortuneCurrency::newFromString($amount); return pht( @@ -183,7 +183,7 @@ final class FundInitiativeTransaction $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $amount->formatForDisplay()); - case FundInitiativeTransaction::TYPE_REFUND: + case self::TYPE_REFUND: $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); $amount = PhortuneCurrency::newFromString($amount); @@ -223,8 +223,8 @@ final class FundInitiativeTransaction public function shouldHide() { $old = $this->getOldValue(); switch ($this->getTransactionType()) { - case FundInitiativeTransaction::TYPE_DESCRIPTION: - case FundInitiativeTransaction::TYPE_RISKS: + case self::TYPE_DESCRIPTION: + case self::TYPE_RISKS: return ($old === null); } return parent::shouldHide(); @@ -232,8 +232,8 @@ final class FundInitiativeTransaction public function hasChangeDetails() { switch ($this->getTransactionType()) { - case FundInitiativeTransaction::TYPE_DESCRIPTION: - case FundInitiativeTransaction::TYPE_RISKS: + case self::TYPE_DESCRIPTION: + case self::TYPE_RISKS: return ($this->getOldValue() !== null); } diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php index 5057097fac..4fe1128ac0 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -4,7 +4,7 @@ abstract class HarbormasterBuildStepImplementation { public static function getImplementations() { return id(new PhutilSymbolLoader()) - ->setAncestorClass('HarbormasterBuildStepImplementation') + ->setAncestorClass(__CLASS__) ->loadObjects(); } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index b53e2bc2d3..81f8e66d07 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -88,7 +88,7 @@ final class HarbormasterBuildable extends HarbormasterDAO if ($buildable) { return $buildable; } - $buildable = HarbormasterBuildable::initializeNewBuildable($actor) + $buildable = self::initializeNewBuildable($actor) ->setBuildablePHID($buildable_object_phid) ->setContainerPHID($container_object_phid); $buildable->save(); @@ -116,7 +116,7 @@ final class HarbormasterBuildable extends HarbormasterDAO return; } - $buildable = HarbormasterBuildable::createOrLoadExisting( + $buildable = self::createOrLoadExisting( PhabricatorUser::getOmnipotentUser(), $phid, $container_phid); diff --git a/src/applications/help/application/PhabricatorHelpApplication.php b/src/applications/help/application/PhabricatorHelpApplication.php index c86d5cd0d0..b1f66b02cd 100644 --- a/src/applications/help/application/PhabricatorHelpApplication.php +++ b/src/applications/help/application/PhabricatorHelpApplication.php @@ -43,7 +43,7 @@ final class PhabricatorHelpApplication extends PhabricatorApplication { array( 'bubbleID' => $help_id, 'dropdownID' => 'phabricator-help-menu', - 'applicationClass' => 'PhabricatorHelpApplication', + 'applicationClass' => __CLASS__, 'local' => true, 'desktop' => true, 'right' => true, diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 6a51ade55f..f56572a398 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -1083,7 +1083,7 @@ abstract class HeraldAdapter { public static function getEnabledAdapterMap(PhabricatorUser $viewer) { $map = array(); - $adapters = HeraldAdapter::getAllAdapters(); + $adapters = self::getAllAdapters(); foreach ($adapters as $adapter) { if (!$adapter->isAvailableToUser($viewer)) { continue; diff --git a/src/applications/macro/controller/PhabricatorMacroMemeController.php b/src/applications/macro/controller/PhabricatorMacroMemeController.php index badb1759ad..ff25a8a8a4 100644 --- a/src/applications/macro/controller/PhabricatorMacroMemeController.php +++ b/src/applications/macro/controller/PhabricatorMacroMemeController.php @@ -14,7 +14,7 @@ final class PhabricatorMacroMemeController $lower_text = $request->getStr('lowertext'); $user = $request->getUser(); - $uri = PhabricatorMacroMemeController::generateMacro($user, $macro_name, + $uri = self::generateMacro($user, $macro_name, $upper_text, $lower_text); if ($uri === false) { return new Aphront404Response(); diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index 60e3425af6..4a6381581b 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -754,7 +754,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { $id = $result->getID(); if ($this->groupBy == self::GROUP_PROJECT) { - return rtrim($id.'.'.$result->getGroupByProjectPHID(), '.');; + return rtrim($id.'.'.$result->getGroupByProjectPHID(), '.'); } return $id; diff --git a/src/applications/metamta/command/MetaMTAEmailTransactionCommand.php b/src/applications/metamta/command/MetaMTAEmailTransactionCommand.php index e6011768df..cc78df3492 100644 --- a/src/applications/metamta/command/MetaMTAEmailTransactionCommand.php +++ b/src/applications/metamta/command/MetaMTAEmailTransactionCommand.php @@ -91,7 +91,7 @@ abstract class MetaMTAEmailTransactionCommand extends Phobject { } public static function getCommandMap(array $commands) { - assert_instances_of($commands, 'MetaMTAEmailTransactionCommand'); + assert_instances_of($commands, __CLASS__); $map = array(); foreach ($commands as $command) { diff --git a/src/applications/metamta/contentsource/PhabricatorContentSource.php b/src/applications/metamta/contentsource/PhabricatorContentSource.php index a1cbd061f2..e5a29572c1 100644 --- a/src/applications/metamta/contentsource/PhabricatorContentSource.php +++ b/src/applications/metamta/contentsource/PhabricatorContentSource.php @@ -46,13 +46,13 @@ final class PhabricatorContentSource { public static function newConsoleSource() { return self::newForSource( - PhabricatorContentSource::SOURCE_CONSOLE, + self::SOURCE_CONSOLE, array()); } public static function newFromRequest(AphrontRequest $request) { return self::newForSource( - PhabricatorContentSource::SOURCE_WEB, + self::SOURCE_WEB, array( 'ip' => $request->getRemoteAddr(), )); @@ -60,7 +60,7 @@ final class PhabricatorContentSource { public static function newFromConduitRequest(ConduitAPIRequest $request) { return self::newForSource( - PhabricatorContentSource::SOURCE_CONDUIT, + self::SOURCE_CONDUIT, array()); } diff --git a/src/applications/nuance/storage/NuanceItem.php b/src/applications/nuance/storage/NuanceItem.php index 2db403cd2e..50d7ee72ef 100644 --- a/src/applications/nuance/storage/NuanceItem.php +++ b/src/applications/nuance/storage/NuanceItem.php @@ -20,7 +20,7 @@ final class NuanceItem public static function initializeNewItem(PhabricatorUser $user) { return id(new NuanceItem()) ->setDateNuanced(time()) - ->setStatus(NuanceItem::STATUS_OPEN); + ->setStatus(self::STATUS_OPEN); } protected function getConfiguration() { diff --git a/src/applications/paste/storage/PhabricatorPasteTransaction.php b/src/applications/paste/storage/PhabricatorPasteTransaction.php index cc4e36d2ff..34cc6bd92d 100644 --- a/src/applications/paste/storage/PhabricatorPasteTransaction.php +++ b/src/applications/paste/storage/PhabricatorPasteTransaction.php @@ -67,7 +67,7 @@ final class PhabricatorPasteTransaction $type = $this->getTransactionType(); switch ($type) { - case PhabricatorPasteTransaction::TYPE_CONTENT: + case self::TYPE_CONTENT: if ($old === null) { return pht( '%s created this paste.', @@ -78,13 +78,13 @@ final class PhabricatorPasteTransaction $this->renderHandleLink($author_phid)); } break; - case PhabricatorPasteTransaction::TYPE_TITLE: + case self::TYPE_TITLE: return pht( '%s updated the paste\'s title to "%s".', $this->renderHandleLink($author_phid), $new); break; - case PhabricatorPasteTransaction::TYPE_LANGUAGE: + case self::TYPE_LANGUAGE: return pht( "%s updated the paste's language.", $this->renderHandleLink($author_phid)); @@ -103,7 +103,7 @@ final class PhabricatorPasteTransaction $type = $this->getTransactionType(); switch ($type) { - case PhabricatorPasteTransaction::TYPE_CONTENT: + case self::TYPE_CONTENT: if ($old === null) { return pht( '%s created %s.', @@ -116,13 +116,13 @@ final class PhabricatorPasteTransaction $this->renderHandleLink($object_phid)); } break; - case PhabricatorPasteTransaction::TYPE_TITLE: + case self::TYPE_TITLE: return pht( '%s updated the title for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; - case PhabricatorPasteTransaction::TYPE_LANGUAGE: + case self::TYPE_LANGUAGE: return pht( '%s updated the language for %s.', $this->renderHandleLink($author_phid), diff --git a/src/applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php b/src/applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php index f5c3c73a9a..6cc6abf37b 100644 --- a/src/applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php +++ b/src/applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php @@ -14,7 +14,7 @@ final class PhabricatorUserEditorTestCase extends PhabricatorTestCase { $this->registerUser( 'PhabricatorUserEditorTestCaseOK', - 'PhabricatorUserEditorTestCase@example.com'); + 'PhabricatorUserEditorTest@example.com'); $this->assertTrue(true); } @@ -45,7 +45,7 @@ final class PhabricatorUserEditorTestCase extends PhabricatorTestCase { try { $this->registerUser( 'PhabricatorUserEditorTestCaseDomain', - 'PhabricatorUserEditorTestCase@whitehouse.gov'); + 'PhabricatorUserEditorTest@whitehouse.gov'); } catch (Exception $ex) { $caught = $ex; } diff --git a/src/applications/phid/handle/pool/PhabricatorHandleList.php b/src/applications/phid/handle/pool/PhabricatorHandleList.php index 21a695b258..7a8fed529f 100644 --- a/src/applications/phid/handle/pool/PhabricatorHandleList.php +++ b/src/applications/phid/handle/pool/PhabricatorHandleList.php @@ -158,8 +158,9 @@ final class PhabricatorHandleList private function raiseImmutableException() { throw new Exception( pht( - 'Trying to mutate a PhabricatorHandleList, but this is not permitted; '. - 'handle lists are immutable.')); + 'Trying to mutate a %s, but this is not permitted; '. + 'handle lists are immutable.', + __CLASS__)); } diff --git a/src/applications/phid/type/PhabricatorPHIDType.php b/src/applications/phid/type/PhabricatorPHIDType.php index 4445ab863d..fd64ab7bd1 100644 --- a/src/applications/phid/type/PhabricatorPHIDType.php +++ b/src/applications/phid/type/PhabricatorPHIDType.php @@ -169,9 +169,13 @@ abstract class PhabricatorPHIDType { $that_class = $original[$type]; $this_class = get_class($object); throw new Exception( - "Two PhabricatorPHIDType classes ({$that_class}, {$this_class}) ". - "both handle PHID type '{$type}'. A type may be handled by only ". - "one class."); + pht( + "Two %s classes (%s, %s) both handle PHID type '%s'. ". + "A type may be handled by only one class.", + __CLASS__, + $that_class, + $this_class, + $type)); } $original[$type] = get_class($object); diff --git a/src/applications/phortune/currency/PhortuneCurrency.php b/src/applications/phortune/currency/PhortuneCurrency.php index a473738ed6..b59d9b144a 100644 --- a/src/applications/phortune/currency/PhortuneCurrency.php +++ b/src/applications/phortune/currency/PhortuneCurrency.php @@ -68,10 +68,10 @@ final class PhortuneCurrency extends Phobject { } public static function newFromList(array $list) { - assert_instances_of($list, 'PhortuneCurrency'); + assert_instances_of($list, __CLASS__); if (!$list) { - return PhortuneCurrency::newEmptyCurrency(); + return self::newEmptyCurrency(); } $total = null; @@ -201,8 +201,8 @@ final class PhortuneCurrency extends Phobject { */ public function assertInRange($minimum, $maximum) { if ($minimum !== null && $maximum !== null) { - $min = PhortuneCurrency::newFromString($minimum); - $max = PhortuneCurrency::newFromString($maximum); + $min = self::newFromString($minimum); + $max = self::newFromString($maximum); if ($min->value > $max->value) { throw new Exception( pht( @@ -213,7 +213,7 @@ final class PhortuneCurrency extends Phobject { } if ($minimum !== null) { - $min = PhortuneCurrency::newFromString($minimum); + $min = self::newFromString($minimum); if ($min->value > $this->value) { throw new Exception( pht( @@ -223,7 +223,7 @@ final class PhortuneCurrency extends Phobject { } if ($maximum !== null) { - $max = PhortuneCurrency::newFromString($maximum); + $max = self::newFromString($maximum); if ($max->value < $this->value) { throw new Exception( pht( diff --git a/src/applications/phortune/provider/PhortunePaymentProvider.php b/src/applications/phortune/provider/PhortunePaymentProvider.php index 49c80dcd72..da36779d06 100644 --- a/src/applications/phortune/provider/PhortunePaymentProvider.php +++ b/src/applications/phortune/provider/PhortunePaymentProvider.php @@ -118,7 +118,7 @@ abstract class PhortunePaymentProvider { public static function getAllProviders() { return id(new PhutilSymbolLoader()) - ->setAncestorClass('PhortunePaymentProvider') + ->setAncestorClass(__CLASS__) ->loadObjects(); } diff --git a/src/applications/phortune/storage/PhortuneAccount.php b/src/applications/phortune/storage/PhortuneAccount.php index facb9d5089..e86fd53df2 100644 --- a/src/applications/phortune/storage/PhortuneAccount.php +++ b/src/applications/phortune/storage/PhortuneAccount.php @@ -27,7 +27,7 @@ final class PhortuneAccount extends PhortuneDAO PhabricatorUser $actor, PhabricatorContentSource $content_source) { - $account = PhortuneAccount::initializeNewAccount($actor); + $account = self::initializeNewAccount($actor); $xactions = array(); $xactions[] = id(new PhortuneAccountTransaction()) diff --git a/src/applications/phortune/storage/PhortuneCart.php b/src/applications/phortune/storage/PhortuneCart.php index 46d474cc93..70ecbb21c9 100644 --- a/src/applications/phortune/storage/PhortuneCart.php +++ b/src/applications/phortune/storage/PhortuneCart.php @@ -136,7 +136,7 @@ final class PhortuneCart extends PhortuneDAO } $charge->save(); - $this->setStatus(PhortuneCart::STATUS_PURCHASING)->save(); + $this->setStatus(self::STATUS_PURCHASING)->save(); $this->endReadLocking(); $this->saveTransaction(); diff --git a/src/applications/phragment/storage/PhragmentFragment.php b/src/applications/phragment/storage/PhragmentFragment.php index 3f5719178b..574283d7a7 100644 --- a/src/applications/phragment/storage/PhragmentFragment.php +++ b/src/applications/phragment/storage/PhragmentFragment.php @@ -256,7 +256,7 @@ final class PhragmentFragment extends PhragmentDAO $mappings[$path], array('name' => basename($path))); } - PhragmentFragment::createFromFile( + self::createFromFile( $viewer, $file, $base_path.'/'.$path, diff --git a/src/applications/policy/constants/PhabricatorPolicies.php b/src/applications/policy/constants/PhabricatorPolicies.php index 859010eca2..c33b9bf909 100644 --- a/src/applications/policy/constants/PhabricatorPolicies.php +++ b/src/applications/policy/constants/PhabricatorPolicies.php @@ -15,9 +15,9 @@ final class PhabricatorPolicies extends PhabricatorPolicyConstants { */ public static function getMostOpenPolicy() { if (PhabricatorEnv::getEnvConfig('policy.allow-public')) { - return PhabricatorPolicies::POLICY_PUBLIC; + return self::POLICY_PUBLIC; } else { - return PhabricatorPolicies::POLICY_USER; + return self::POLICY_USER; } } diff --git a/src/applications/project/icon/PhabricatorProjectIcon.php b/src/applications/project/icon/PhabricatorProjectIcon.php index ae6efa9b6e..9411baa6a9 100644 --- a/src/applications/project/icon/PhabricatorProjectIcon.php +++ b/src/applications/project/icon/PhabricatorProjectIcon.php @@ -44,7 +44,7 @@ final class PhabricatorProjectIcon extends Phobject { } public static function renderIconForChooser($icon) { - $project_icons = PhabricatorProjectIcon::getIconMap(); + $project_icons = self::getIconMap(); return phutil_tag( 'span', diff --git a/src/applications/project/storage/PhabricatorProjectColumn.php b/src/applications/project/storage/PhabricatorProjectColumn.php index 8fcb6ade9c..63b8a8748f 100644 --- a/src/applications/project/storage/PhabricatorProjectColumn.php +++ b/src/applications/project/storage/PhabricatorProjectColumn.php @@ -111,7 +111,7 @@ final class PhabricatorProjectColumn ->setMetadata( array( 'tip' => $text, - ));; + )); } return null; diff --git a/src/applications/project/storage/PhabricatorProjectColumnTransaction.php b/src/applications/project/storage/PhabricatorProjectColumnTransaction.php index dd4f95d5f6..ed4bfed8a6 100644 --- a/src/applications/project/storage/PhabricatorProjectColumnTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectColumnTransaction.php @@ -21,7 +21,7 @@ final class PhabricatorProjectColumnTransaction $author_handle = $this->renderHandleLink($this->getAuthorPHID()); switch ($this->getTransactionType()) { - case PhabricatorProjectColumnTransaction::TYPE_NAME: + case self::TYPE_NAME: if ($old === null) { return pht( '%s created this column.', @@ -44,7 +44,7 @@ final class PhabricatorProjectColumnTransaction $author_handle); } } - case PhabricatorProjectColumnTransaction::TYPE_LIMIT: + case self::TYPE_LIMIT: if (!$old) { return pht( '%s set the point limit for this column to %s.', @@ -62,7 +62,7 @@ final class PhabricatorProjectColumnTransaction $new); } - case PhabricatorProjectColumnTransaction::TYPE_STATUS: + case self::TYPE_STATUS: switch ($new) { case PhabricatorProjectColumn::STATUS_ACTIVE: return pht( diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index 9b0ff0c340..3dceb0934c 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -28,12 +28,12 @@ final class PhabricatorProjectTransaction $req_phids = array(); switch ($this->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_MEMBERS: + case self::TYPE_MEMBERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); $req_phids = array_merge($add, $rem); break; - case PhabricatorProjectTransaction::TYPE_IMAGE: + case self::TYPE_IMAGE: $req_phids[] = $old; $req_phids[] = $new; break; @@ -48,7 +48,7 @@ final class PhabricatorProjectTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_STATUS: + case self::TYPE_STATUS: if ($old == 0) { return 'red'; } else { @@ -64,25 +64,25 @@ final class PhabricatorProjectTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_STATUS: + case self::TYPE_STATUS: if ($old == 0) { return 'fa-ban'; } else { return 'fa-check'; } - case PhabricatorProjectTransaction::TYPE_LOCKED: + case self::TYPE_LOCKED: if ($new) { return 'fa-lock'; } else { return 'fa-unlock'; } - case PhabricatorProjectTransaction::TYPE_ICON: + case self::TYPE_ICON: return $new; - case PhabricatorProjectTransaction::TYPE_IMAGE: + case self::TYPE_IMAGE: return 'fa-photo'; - case PhabricatorProjectTransaction::TYPE_MEMBERS: + case self::TYPE_MEMBERS: return 'fa-user'; - case PhabricatorProjectTransaction::TYPE_SLUGS: + case self::TYPE_SLUGS: return 'fa-tag'; } return parent::getIcon(); @@ -94,7 +94,7 @@ final class PhabricatorProjectTransaction $author_handle = $this->renderHandleLink($this->getAuthorPHID()); switch ($this->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: + case self::TYPE_NAME: if ($old === null) { return pht( '%s created this project.', @@ -106,7 +106,7 @@ final class PhabricatorProjectTransaction $old, $new); } - case PhabricatorProjectTransaction::TYPE_STATUS: + case self::TYPE_STATUS: if ($old == 0) { return pht( '%s archived this project.', @@ -116,7 +116,7 @@ final class PhabricatorProjectTransaction '%s activated this project.', $author_handle); } - case PhabricatorProjectTransaction::TYPE_IMAGE: + case self::TYPE_IMAGE: // TODO: Some day, it would be nice to show the images. if (!$old) { return pht( @@ -135,19 +135,19 @@ final class PhabricatorProjectTransaction $this->renderHandleLink($new)); } - case PhabricatorProjectTransaction::TYPE_ICON: + case self::TYPE_ICON: return pht( '%s set this project\'s icon to %s.', $author_handle, PhabricatorProjectIcon::getLabel($new)); - case PhabricatorProjectTransaction::TYPE_COLOR: + case self::TYPE_COLOR: return pht( '%s set this project\'s color to %s.', $author_handle, PHUITagView::getShadeName($new)); - case PhabricatorProjectTransaction::TYPE_LOCKED: + case self::TYPE_LOCKED: if ($new) { return pht( '%s locked this project\'s membership.', @@ -158,7 +158,7 @@ final class PhabricatorProjectTransaction $author_handle); } - case PhabricatorProjectTransaction::TYPE_SLUGS: + case self::TYPE_SLUGS: $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -184,7 +184,7 @@ final class PhabricatorProjectTransaction $this->renderSlugList($rem)); } - case PhabricatorProjectTransaction::TYPE_MEMBERS: + case self::TYPE_MEMBERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); diff --git a/src/applications/releeph/storage/ReleephRequest.php b/src/applications/releeph/storage/ReleephRequest.php index 7f3f190a6d..7f2487107a 100644 --- a/src/applications/releeph/storage/ReleephRequest.php +++ b/src/applications/releeph/storage/ReleephRequest.php @@ -127,8 +127,8 @@ final class ReleephRequest extends ReleephDAO if ($this->getInBranch()) { return ReleephRequestStatus::STATUS_NEEDS_REVERT; } else { - $intent_pass = ReleephRequest::INTENT_PASS; - $intent_want = ReleephRequest::INTENT_WANT; + $intent_pass = self::INTENT_PASS; + $intent_want = self::INTENT_WANT; $has_been_in_branch = $this->getCommitIdentifier(); // Regardless of why we reverted something, always say reverted if it diff --git a/src/applications/releeph/storage/ReleephRequestTransaction.php b/src/applications/releeph/storage/ReleephRequestTransaction.php index bd17ad43c9..f4a4720c78 100644 --- a/src/applications/releeph/storage/ReleephRequestTransaction.php +++ b/src/applications/releeph/storage/ReleephRequestTransaction.php @@ -38,12 +38,12 @@ final class ReleephRequestTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case ReleephRequestTransaction::TYPE_REQUEST: - case ReleephRequestTransaction::TYPE_DISCOVERY: + case self::TYPE_REQUEST: + case self::TYPE_DISCOVERY: $phids[] = $new; break; - case ReleephRequestTransaction::TYPE_EDIT_FIELD: + case self::TYPE_EDIT_FIELD: self::searchForPHIDs($this->getOldValue(), $phids); self::searchForPHIDs($this->getNewValue(), $phids); break; @@ -60,18 +60,18 @@ final class ReleephRequestTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case ReleephRequestTransaction::TYPE_REQUEST: + case self::TYPE_REQUEST: return pht( '%s requested %s', $this->renderHandleLink($author_phid), $this->renderHandleLink($new)); break; - case ReleephRequestTransaction::TYPE_USER_INTENT: + case self::TYPE_USER_INTENT: return $this->getIntentTitle(); break; - case ReleephRequestTransaction::TYPE_EDIT_FIELD: + case self::TYPE_EDIT_FIELD: $field = newv($this->getMetadataValue('fieldClass'), array()); $name = $field->getName(); @@ -89,7 +89,7 @@ final class ReleephRequestTransaction $field->normalizeForTransactionView($this, $new)); break; - case ReleephRequestTransaction::TYPE_PICK_STATUS: + case self::TYPE_PICK_STATUS: switch ($new) { case ReleephRequest::PICK_OK: return pht('%s found this request picks without error', @@ -109,7 +109,7 @@ final class ReleephRequestTransaction } break; - case ReleephRequestTransaction::TYPE_COMMIT: + case self::TYPE_COMMIT: $action_type = $this->getMetadataValue('action'); switch ($action_type) { case 'pick': @@ -126,7 +126,7 @@ final class ReleephRequestTransaction } break; - case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH: + case self::TYPE_MANUAL_IN_BRANCH: $action = $new ? pht('picked') : pht('reverted'); return pht( '%s marked this request as manually %s', @@ -134,7 +134,7 @@ final class ReleephRequestTransaction $action); break; - case ReleephRequestTransaction::TYPE_DISCOVERY: + case self::TYPE_DISCOVERY: return pht('%s discovered this commit as %s', $this->renderHandleLink($author_phid), $this->renderHandleLink($new)); @@ -173,7 +173,7 @@ final class ReleephRequestTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case ReleephRequestTransaction::TYPE_USER_INTENT: + case self::TYPE_USER_INTENT: switch ($new) { case ReleephRequest::INTENT_WANT: return PhabricatorTransactions::COLOR_GREEN; @@ -243,7 +243,7 @@ final class ReleephRequestTransaction public function shouldHide() { $type = $this->getTransactionType(); - if ($type === ReleephRequestTransaction::TYPE_USER_INTENT && + if ($type === self::TYPE_USER_INTENT && $this->getMetadataValue('isRQCreate')) { return true; @@ -255,7 +255,7 @@ final class ReleephRequestTransaction // ReleephSummaryFieldSpecification is usually blank when an RQ is created, // creating a transaction change from null to "". Hide these! - if ($type === ReleephRequestTransaction::TYPE_EDIT_FIELD) { + if ($type === self::TYPE_EDIT_FIELD) { if ($this->getOldValue() === null && $this->getNewValue() === '') { return true; } @@ -265,7 +265,7 @@ final class ReleephRequestTransaction public function isBoringPickStatus() { $type = $this->getTransactionType(); - if ($type === ReleephRequestTransaction::TYPE_PICK_STATUS) { + if ($type === self::TYPE_PICK_STATUS) { $new = $this->getNewValue(); if ($new === ReleephRequest::PICK_OK || $new === ReleephRequest::REVERT_OK) { diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 3131f9b9e6..21c7fe38ab 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -201,7 +201,7 @@ final class PhabricatorRepositorySearchEngine array $handles) { assert_instances_of($repositories, 'PhabricatorRepository'); - $viewer = $this->requireViewer();; + $viewer = $this->requireViewer(); $list = new PHUIObjectItemListView(); foreach ($repositories as $repository) { diff --git a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php index d5fb87fa8d..f16784bbf1 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php +++ b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php @@ -54,13 +54,13 @@ final class PhabricatorRepositoryPushLog public static function getHeraldChangeFlagConditionOptions() { return array( - PhabricatorRepositoryPushLog::CHANGEFLAG_ADD => + self::CHANGEFLAG_ADD => pht('change creates ref'), - PhabricatorRepositoryPushLog::CHANGEFLAG_DELETE => + self::CHANGEFLAG_DELETE => pht('change deletes ref'), - PhabricatorRepositoryPushLog::CHANGEFLAG_REWRITE => + self::CHANGEFLAG_REWRITE => pht('change rewrites ref'), - PhabricatorRepositoryPushLog::CHANGEFLAG_DANGEROUS => + self::CHANGEFLAG_DANGEROUS => pht('dangerous change'), ); } diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php index e07a045dc2..3248dc8b2a 100644 --- a/src/applications/settings/storage/PhabricatorUserPreferences.php +++ b/src/applications/settings/storage/PhabricatorUserPreferences.php @@ -74,14 +74,14 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO { } public function getPinnedApplications(array $apps, PhabricatorUser $viewer) { - $pref_pinned = PhabricatorUserPreferences::PREFERENCE_APP_PINNED; + $pref_pinned = self::PREFERENCE_APP_PINNED; $pinned = $this->getPreference($pref_pinned); if ($pinned) { return $pinned; } - $pref_tiles = PhabricatorUserPreferences::PREFERENCE_APP_TILES; + $pref_tiles = self::PREFERENCE_APP_TILES; $tiles = $this->getPreference($pref_tiles, array()); $full_tile = 'full'; diff --git a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php index abb73fd588..9abf1f5801 100644 --- a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php +++ b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php @@ -26,10 +26,10 @@ final class PhabricatorSlowvoteTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION: - case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: - case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: - case PhabricatorSlowvoteTransaction::TYPE_CLOSE: + case self::TYPE_DESCRIPTION: + case self::TYPE_RESPONSES: + case self::TYPE_SHUFFLE: + case self::TYPE_CLOSE: return ($old === null); } @@ -43,7 +43,7 @@ final class PhabricatorSlowvoteTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorSlowvoteTransaction::TYPE_QUESTION: + case self::TYPE_QUESTION: if ($old === null) { return pht( '%s created this poll.', @@ -56,16 +56,16 @@ final class PhabricatorSlowvoteTransaction $new); } break; - case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION: + case self::TYPE_DESCRIPTION: return pht( '%s updated the description for this poll.', $this->renderHandleLink($author_phid)); - case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: + case self::TYPE_RESPONSES: // TODO: This could be more detailed return pht( '%s changed who can see the responses.', $this->renderHandleLink($author_phid)); - case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: + case self::TYPE_SHUFFLE: if ($new) { return pht( '%s made poll responses appear in a random order.', @@ -76,7 +76,7 @@ final class PhabricatorSlowvoteTransaction $this->renderHandleLink($author_phid)); } break; - case PhabricatorSlowvoteTransaction::TYPE_CLOSE: + case self::TYPE_CLOSE: if ($new) { return pht( '%s closed this poll.', @@ -98,18 +98,18 @@ final class PhabricatorSlowvoteTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorSlowvoteTransaction::TYPE_QUESTION: + case self::TYPE_QUESTION: if ($old === null) { return 'fa-plus'; } else { return 'fa-pencil'; } - case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION: - case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: + case self::TYPE_DESCRIPTION: + case self::TYPE_RESPONSES: return 'fa-pencil'; - case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: + case self::TYPE_SHUFFLE: return 'fa-refresh'; - case PhabricatorSlowvoteTransaction::TYPE_CLOSE: + case self::TYPE_CLOSE: if ($new) { return 'fa-ban'; } else { @@ -126,11 +126,11 @@ final class PhabricatorSlowvoteTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorSlowvoteTransaction::TYPE_QUESTION: - case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION: - case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: - case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: - case PhabricatorSlowvoteTransaction::TYPE_CLOSE: + case self::TYPE_QUESTION: + case self::TYPE_DESCRIPTION: + case self::TYPE_RESPONSES: + case self::TYPE_SHUFFLE: + case self::TYPE_CLOSE: return PhabricatorTransactions::COLOR_BLUE; } @@ -139,7 +139,7 @@ final class PhabricatorSlowvoteTransaction public function hasChangeDetails() { switch ($this->getTransactionType()) { - case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION: + case self::TYPE_DESCRIPTION: return true; } return parent::hasChangeDetails(); diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index c6755ccfcc..355f15c2a7 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -1073,7 +1073,7 @@ abstract class PhabricatorApplicationTransaction } public function attachTransactionGroup(array $group) { - assert_instances_of($group, 'PhabricatorApplicationTransaction'); + assert_instances_of($group, __CLASS__); $this->transactionGroup = $group; return $this; } @@ -1165,7 +1165,7 @@ abstract class PhabricatorApplicationTransaction } $old_target = $xaction->getRenderingTarget(); - $new_target = PhabricatorApplicationTransaction::TARGET_TEXT; + $new_target = self::TARGET_TEXT; $xaction->setRenderingTarget($new_target); if ($publisher->getRenderWithImpliedContext()) { diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php index ec5a02337e..28110e7334 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomField.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php @@ -65,7 +65,7 @@ abstract class PhabricatorCustomField { "object of class '{$obj_class}'."); } - $fields = PhabricatorCustomField::buildFieldList( + $fields = self::buildFieldList( $base_class, $spec, $object); diff --git a/src/infrastructure/daemon/bot/PhabricatorBot.php b/src/infrastructure/daemon/bot/PhabricatorBot.php index 18a0c74648..ef949cb9ff 100644 --- a/src/infrastructure/daemon/bot/PhabricatorBot.php +++ b/src/infrastructure/daemon/bot/PhabricatorBot.php @@ -19,7 +19,11 @@ final class PhabricatorBot extends PhabricatorDaemon { protected function run() { $argv = $this->getArgv(); if (count($argv) !== 1) { - throw new Exception('usage: PhabricatorBot '); + throw new Exception( + pht( + 'Usage: %s %s', + __CLASS__, + '')); } $json_raw = Filesystem::readFile($argv[0]); @@ -72,7 +76,7 @@ final class PhabricatorBot extends PhabricatorDaemon { $response = $conduit->callMethodSynchronous( 'conduit.connect', array( - 'client' => 'PhabricatorBot', + 'client' => __CLASS__, 'clientVersion' => '1.0', 'clientDescription' => php_uname('n').':'.$nick, 'host' => $conduit_host, diff --git a/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php b/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php index 34cab99afa..c569f0c695 100644 --- a/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php +++ b/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php @@ -150,7 +150,7 @@ final class PhabricatorWorkerTriggerQuery if (($this->nextEpochMin !== null) || ($this->nextEpochMax !== null) || - ($this->order == PhabricatorWorkerTriggerQuery::ORDER_EXECUTION)) { + ($this->order == self::ORDER_EXECUTION)) { $joins[] = qsprintf( $conn_r, 'JOIN %T e ON e.triggerID = t.id', diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php index 05ab7c677d..39f911e3c5 100644 --- a/src/infrastructure/env/PhabricatorEnv.php +++ b/src/infrastructure/env/PhabricatorEnv.php @@ -88,7 +88,7 @@ final class PhabricatorEnv { // Force a valid timezone. If both PHP and Phabricator configuration are // invalid, use UTC. - $tz = PhabricatorEnv::getEnvConfig('phabricator.timezone'); + $tz = self::getEnvConfig('phabricator.timezone'); if ($tz) { @date_default_timezone_set($tz); } @@ -102,7 +102,7 @@ final class PhabricatorEnv { $phabricator_path = dirname(phutil_get_library_root('phabricator')); $support_path = $phabricator_path.'/support/bin'; $env_path = $support_path.PATH_SEPARATOR.$env_path; - $append_dirs = PhabricatorEnv::getEnvConfig('environment.append-paths'); + $append_dirs = self::getEnvConfig('environment.append-paths'); if (!empty($append_dirs)) { $append_path = implode(PATH_SEPARATOR, $append_dirs); $env_path = $env_path.PATH_SEPARATOR.$append_path; @@ -116,7 +116,7 @@ final class PhabricatorEnv { // If an instance identifier is defined, write it into the environment so // it's available to subprocesses. - $instance = PhabricatorEnv::getEnvConfig('cluster.instance'); + $instance = self::getEnvConfig('cluster.instance'); if (strlen($instance)) { putenv('PHABRICATOR_INSTANCE='.$instance); $_ENV['PHABRICATOR_INSTANCE'] = $instance; @@ -139,7 +139,7 @@ final class PhabricatorEnv { $translations = PhutilTranslation::getTranslationMapForLocale( $locale_code); - $override = PhabricatorEnv::getEnvConfig('translation.override'); + $override = self::getEnvConfig('translation.override'); if (!is_array($override)) { $override = array(); } @@ -178,7 +178,7 @@ final class PhabricatorEnv { // If the install overrides the database adapter, we might need to load // the database adapter class before we can push on the database config. // This config is locked and can't be edited from the web UI anyway. - foreach (PhabricatorEnv::getEnvConfig('load-libraries') as $library) { + foreach (self::getEnvConfig('load-libraries') as $library) { phutil_load_library($library); } @@ -809,7 +809,7 @@ final class PhabricatorEnv { } public static function isClusterAddress($address) { - $cluster_addresses = PhabricatorEnv::getEnvConfig('cluster.addresses'); + $cluster_addresses = self::getEnvConfig('cluster.addresses'); if (!$cluster_addresses) { throw new Exception( pht( diff --git a/src/infrastructure/events/PhabricatorExampleEventListener.php b/src/infrastructure/events/PhabricatorExampleEventListener.php index 0a7bd699df..fb85678da1 100644 --- a/src/infrastructure/events/PhabricatorExampleEventListener.php +++ b/src/infrastructure/events/PhabricatorExampleEventListener.php @@ -21,8 +21,11 @@ final class PhabricatorExampleEventListener extends PhabricatorEventListener { $console = PhutilConsole::getConsole(); $console->writeOut( - "PhabricatorExampleEventListener got test event at %d\n", - $event->getValue('time')); + "%s\n", + pht( + '% got test event at %d', + __CLASS__, + $event->getValue('time'))); } } diff --git a/src/infrastructure/sms/storage/PhabricatorSMS.php b/src/infrastructure/sms/storage/PhabricatorSMS.php index c17d8f16da..7b6c85f159 100644 --- a/src/infrastructure/sms/storage/PhabricatorSMS.php +++ b/src/infrastructure/sms/storage/PhabricatorSMS.php @@ -38,8 +38,8 @@ final class PhabricatorSMS // and ProviderSMSID are totally garbage data before a send it attempted. return id(new PhabricatorSMS()) ->setBody($body) - ->setSendStatus(PhabricatorSMS::STATUS_UNSENT) - ->setProviderShortName(PhabricatorSMS::SHORTNAME_PLACEHOLDER) + ->setSendStatus(self::STATUS_UNSENT) + ->setProviderShortName(self::SHORTNAME_PLACEHOLDER) ->setProviderSMSID(Filesystem::readRandomCharacters(40)); } @@ -70,6 +70,6 @@ final class PhabricatorSMS public function hasBeenSentAtLeastOnce() { return ($this->getProviderShortName() != - PhabricatorSMS::SHORTNAME_PLACEHOLDER); + self::SHORTNAME_PLACEHOLDER); } } diff --git a/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php b/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php index 0eefe68069..390d591a07 100644 --- a/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php @@ -38,7 +38,7 @@ abstract class PhabricatorSQLPatchList { final public static function buildAllPatches() { $patch_lists = id(new PhutilSymbolLoader()) - ->setAncestorClass('PhabricatorSQLPatchList') + ->setAncestorClass(__CLASS__) ->setConcreteOnly(true) ->selectAndLoadSymbols(); diff --git a/src/infrastructure/time/PhabricatorTime.php b/src/infrastructure/time/PhabricatorTime.php index d2866d99c2..03495d8412 100644 --- a/src/infrastructure/time/PhabricatorTime.php +++ b/src/infrastructure/time/PhabricatorTime.php @@ -25,7 +25,10 @@ final class PhabricatorTime { public static function popTime($key) { if ($key !== last_key(self::$stack)) { - throw new Exception('PhabricatorTime::popTime with bad key.'); + throw new Exception( + pht( + '%s with bad key.', + __METHOD__)); } array_pop(self::$stack); @@ -49,7 +52,7 @@ final class PhabricatorTime { $old_zone = date_default_timezone_get(); date_default_timezone_set($user->getTimezoneIdentifier()); - $timestamp = (int)strtotime($time, PhabricatorTime::getNow()); + $timestamp = (int)strtotime($time, self::getNow()); if ($timestamp <= 0) { $timestamp = null; } diff --git a/src/infrastructure/util/PhabricatorHash.php b/src/infrastructure/util/PhabricatorHash.php index dd158d1168..df1fbfa08b 100644 --- a/src/infrastructure/util/PhabricatorHash.php +++ b/src/infrastructure/util/PhabricatorHash.php @@ -35,7 +35,7 @@ final class PhabricatorHash extends Phobject { } for ($ii = 0; $ii < 1000; $ii++) { - $result = PhabricatorHash::digest($result, $salt); + $result = self::digest($result, $salt); } return $result; @@ -113,7 +113,7 @@ final class PhabricatorHash extends Phobject { // who can control the inputs from intentionally using the hashed form // of a string to cause a collision. - $hash = PhabricatorHash::digestForIndex($string); + $hash = self::digestForIndex($string); $prefix = substr($string, 0, ($length - ($min_length - 1))); diff --git a/src/infrastructure/util/password/PhabricatorPasswordHasher.php b/src/infrastructure/util/password/PhabricatorPasswordHasher.php index 4409163509..3b136b4ec3 100644 --- a/src/infrastructure/util/password/PhabricatorPasswordHasher.php +++ b/src/infrastructure/util/password/PhabricatorPasswordHasher.php @@ -213,7 +213,7 @@ abstract class PhabricatorPasswordHasher extends Phobject { */ public static function getAllHashers() { $objects = id(new PhutilSymbolLoader()) - ->setAncestorClass('PhabricatorPasswordHasher') + ->setAncestorClass(__CLASS__) ->loadObjects(); $map = array(); @@ -404,7 +404,7 @@ abstract class PhabricatorPasswordHasher extends Phobject { } try { - $current_hasher = PhabricatorPasswordHasher::getHasherForHash($hash); + $current_hasher = self::getHasherForHash($hash); return $current_hasher->getHumanReadableName(); } catch (Exception $ex) { $info = self::parseHashFromStorage($hash); @@ -421,7 +421,7 @@ abstract class PhabricatorPasswordHasher extends Phobject { */ public static function getBestAlgorithmName() { try { - $best_hasher = PhabricatorPasswordHasher::getBestHasher(); + $best_hasher = self::getBestHasher(); return $best_hasher->getHumanReadableName(); } catch (Exception $ex) { return pht('Unknown'); diff --git a/src/view/AphrontDialogView.php b/src/view/AphrontDialogView.php index 3d79197bd8..925d76a565 100644 --- a/src/view/AphrontDialogView.php +++ b/src/view/AphrontDialogView.php @@ -215,7 +215,10 @@ final class AphrontDialogView extends AphrontView { if (!$this->user) { throw new Exception( - pht('You must call setUser() when rendering an AphrontDialogView.')); + pht( + 'You must call %s when rendering an %s.', + 'setUser()', + __CLASS__)); } $more = $this->class; diff --git a/src/view/form/AphrontFormView.php b/src/view/form/AphrontFormView.php index 6b30656131..f9f281ff20 100644 --- a/src/view/form/AphrontFormView.php +++ b/src/view/form/AphrontFormView.php @@ -126,7 +126,10 @@ final class AphrontFormView extends AphrontView { $layout = $this->buildLayoutView(); if (!$this->user) { - throw new Exception(pht('You must pass the user to AphrontFormView.')); + throw new Exception( + pht( + 'You must pass the user to %s.', + __CLASS__)); } $sigils = $this->sigils; diff --git a/src/view/form/PHUIFormLayoutView.php b/src/view/form/PHUIFormLayoutView.php index 87e35e03c1..3e7941257e 100644 --- a/src/view/form/PHUIFormLayoutView.php +++ b/src/view/form/PHUIFormLayoutView.php @@ -33,7 +33,10 @@ final class PHUIFormLayoutView extends AphrontView { public function appendRemarkupInstructions($remarkup) { if ($this->getUser() === null) { throw new Exception( - 'Call `setUser` before appending Remarkup to PHUIFormLayoutView.'); + pht( + 'Call %s before appending Remarkup to %s.', + 'setUser()', + __CLASS__)); } return $this->appendInstructions( diff --git a/src/view/form/PHUIInfoView.php b/src/view/form/PHUIInfoView.php index a079e3f6ca..497fcf3f78 100644 --- a/src/view/form/PHUIInfoView.php +++ b/src/view/form/PHUIInfoView.php @@ -41,7 +41,6 @@ final class PHUIInfoView extends AphrontView { } final public function render() { - require_celerity_resource('phui-info-view-css'); $errors = $this->errors; diff --git a/src/view/form/PHUIPagedFormView.php b/src/view/form/PHUIPagedFormView.php index 82e58c7f58..ff72f67ca0 100644 --- a/src/view/form/PHUIPagedFormView.php +++ b/src/view/form/PHUIPagedFormView.php @@ -152,7 +152,7 @@ final class PHUIPagedFormView extends AphrontView { $is_attempt_complete = false; if ($this->prevPage) { - $prev_index = $this->getPageIndex($selected->getKey()) - 1;; + $prev_index = $this->getPageIndex($selected->getKey()) - 1; $index = max(0, $prev_index); $selected = $this->getPageByIndex($index); } else if ($this->nextPage) { diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php index 7d5451a1b2..b6ed3b5e0c 100644 --- a/src/view/form/control/PhabricatorRemarkupControl.php +++ b/src/view/form/control/PhabricatorRemarkupControl.php @@ -25,7 +25,10 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { $viewer = $this->getUser(); if (!$viewer) { throw new Exception( - pht('Call setUser() before rendering a PhabricatorRemarkupControl!')); + pht( + 'Call %s before rendering a %s!', + 'setUser()', + __CLASS__)); } // We need to have this if previews render images, since Ajax can not diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index 3c8891ba80..7c5dcaf1cb 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -3,7 +3,6 @@ /** * This is a standard Phabricator page with menus, Javelin, DarkConsole, and * basic styles. - * */ final class PhabricatorStandardPageView extends PhabricatorBarePageView { @@ -160,7 +159,8 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView { if (!$this->getRequest()) { throw new Exception( pht( - 'You must set the Request to render a PhabricatorStandardPageView.')); + 'You must set the Request to render a %s.', + __CLASS__)); } $console = $this->getConsole(); diff --git a/src/view/phui/PHUIDocumentView.php b/src/view/phui/PHUIDocumentView.php index 833bb48fe5..a27805e1c7 100644 --- a/src/view/phui/PHUIDocumentView.php +++ b/src/view/phui/PHUIDocumentView.php @@ -74,7 +74,7 @@ final class PHUIDocumentView extends AphrontTagView { if ($this->offset) { $classes[] = 'phui-document-offset'; - }; + } if ($this->fluid) { $classes[] = 'phui-document-fluid'; diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php index 3945049ffb..30e8f3c937 100644 --- a/support/PhabricatorStartup.php +++ b/support/PhabricatorStartup.php @@ -255,7 +255,7 @@ final class PhabricatorStartup { static $initialized; if (!$initialized) { declare(ticks=1); - register_tick_function(array('PhabricatorStartup', 'onDebugTick')); + register_tick_function(array(__CLASS__, 'onDebugTick')); } } @@ -647,7 +647,7 @@ final class PhabricatorStartup { // populated into $_POST, but it wasn't. $config = ini_get('post_max_size'); - PhabricatorStartup::didFatal( + self::didFatal( "As received by the server, this request had a nonzero content length ". "but no POST data.\n\n". "Normally, this indicates that it exceeds the 'post_max_size' setting ". From 61b178f44e480fc1acee813f79d309f3c691dc62 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Thu, 14 May 2015 07:53:52 +1000 Subject: [PATCH 67/70] Use PhutilInvalidStateException Summary: Use `PhutilInvalidStateException`. Depends on D12803. Test Plan: Unit tests pass. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D12829 --- .../auth/provider/PhabricatorAuthProvider.php | 3 +-- src/applications/celerity/CelerityResourceGraph.php | 4 +--- .../config/option/PhabricatorConfigOption.php | 3 +-- .../config/schema/PhabricatorConfigSchemaQuery.php | 2 +- .../daemon/view/PhabricatorDaemonLogEventsView.php | 2 +- .../daemon/view/PhabricatorDaemonLogListView.php | 2 +- .../view/DifferentialLocalCommitsView.php | 2 +- .../view/DifferentialRevisionListView.php | 3 +-- .../diffusion/query/DiffusionLintCountQuery.php | 4 ++-- .../query/lowlevel/DiffusionLowLevelQuery.php | 2 +- src/applications/diviner/atom/DivinerAtom.php | 6 ++---- .../doorkeeper/view/DoorkeeperTagView.php | 2 +- .../controller/PhabricatorFileDataController.php | 2 +- src/applications/fund/phortune/FundBackerCart.php | 3 +-- .../multimeter/data/MultimeterControl.php | 2 +- src/applications/paste/view/PasteEmbedView.php | 2 +- .../phid/query/PhabricatorObjectQuery.php | 2 +- src/applications/pholio/view/PholioMockEmbedView.php | 2 +- .../phortune/cart/PhortuneSubscriptionCart.php | 3 +-- .../PhabricatorApplicationSearchController.php | 10 ++-------- .../engine/PhabricatorApplicationSearchEngine.php | 2 +- src/applications/slowvote/view/SlowvoteEmbedView.php | 2 +- .../editor/PhabricatorSubscriptionsEditor.php | 2 +- ...habricatorApplicationTransactionCommentEditor.php | 3 +-- .../PhabricatorApplicationTransactionEditor.php | 3 +-- .../PhabricatorApplicationTransactionCommentView.php | 2 +- .../diff/view/PHUIDiffInlineCommentEditView.php | 4 ++-- src/view/control/AphrontCursorPagerView.php | 12 ++++-------- src/view/control/AphrontPagerView.php | 3 +-- src/view/form/PHUIFormLayoutView.php | 6 +----- src/view/form/control/PhabricatorRemarkupControl.php | 6 +----- src/view/phui/PHUIRemarkupPreviewPanel.php | 11 +++++++---- src/view/phui/calendar/PHUICalendarMonthView.php | 2 +- .../widget/hovercard/PhabricatorHovercardView.php | 2 +- 34 files changed, 47 insertions(+), 74 deletions(-) diff --git a/src/applications/auth/provider/PhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorAuthProvider.php index b1b064c363..5ffb18b41a 100644 --- a/src/applications/auth/provider/PhabricatorAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAuthProvider.php @@ -15,8 +15,7 @@ abstract class PhabricatorAuthProvider { public function getProviderConfig() { if ($this->providerConfig === null) { - throw new Exception( - 'Call attachProviderConfig() before getProviderConfig()!'); + throw new PhutilInvalidStateException('attachProviderConfig'); } return $this->providerConfig; } diff --git a/src/applications/celerity/CelerityResourceGraph.php b/src/applications/celerity/CelerityResourceGraph.php index a85c3ffbd8..715a64c35c 100644 --- a/src/applications/celerity/CelerityResourceGraph.php +++ b/src/applications/celerity/CelerityResourceGraph.php @@ -7,9 +7,7 @@ final class CelerityResourceGraph extends AbstractDirectedGraph { protected function loadEdges(array $nodes) { if (!$this->graphSet) { - throw new Exception( - 'Call setResourceGraph before loading the graph!' - ); + throw new PhutilInvalidStateException('setResourceGraph'); } $graph = $this->getResourceGraph(); diff --git a/src/applications/config/option/PhabricatorConfigOption.php b/src/applications/config/option/PhabricatorConfigOption.php index f11c436402..e5c9773611 100644 --- a/src/applications/config/option/PhabricatorConfigOption.php +++ b/src/applications/config/option/PhabricatorConfigOption.php @@ -122,8 +122,7 @@ final class PhabricatorConfigOption return $this->enumOptions; } - throw new Exception( - 'Call setEnumOptions() before trying to access them!'); + throw new PhutilInvalidStateException('setEnumOptions'); } public function setKey($key) { diff --git a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php index 7a11d64684..57db889956 100644 --- a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php @@ -11,7 +11,7 @@ final class PhabricatorConfigSchemaQuery extends Phobject { protected function getAPI() { if (!$this->api) { - throw new Exception(pht('Call setAPI() before issuing a query!')); + throw new PhutilInvalidStateException('setAPI'); } return $this->api; } diff --git a/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php b/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php index a3598bfe25..b06ebdd7d2 100644 --- a/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php +++ b/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php @@ -27,7 +27,7 @@ final class PhabricatorDaemonLogEventsView extends AphrontView { $rows = array(); if (!$this->user) { - throw new Exception('Call setUser() before rendering!'); + throw new PhutilInvalidStateException('setUser'); } foreach ($this->events as $event) { diff --git a/src/applications/daemon/view/PhabricatorDaemonLogListView.php b/src/applications/daemon/view/PhabricatorDaemonLogListView.php index 88a92ced98..f45606e9e7 100644 --- a/src/applications/daemon/view/PhabricatorDaemonLogListView.php +++ b/src/applications/daemon/view/PhabricatorDaemonLogListView.php @@ -14,7 +14,7 @@ final class PhabricatorDaemonLogListView extends AphrontView { $rows = array(); if (!$this->user) { - throw new Exception('Call setUser() before rendering!'); + throw new PhutilInvalidStateException('setUser'); } $env_hash = PhabricatorEnv::calculateEnvironmentHash(); diff --git a/src/applications/differential/view/DifferentialLocalCommitsView.php b/src/applications/differential/view/DifferentialLocalCommitsView.php index 489674b0ab..ab83016e2a 100644 --- a/src/applications/differential/view/DifferentialLocalCommitsView.php +++ b/src/applications/differential/view/DifferentialLocalCommitsView.php @@ -19,7 +19,7 @@ final class DifferentialLocalCommitsView extends AphrontView { public function render() { $user = $this->user; if (!$user) { - throw new Exception('Call setUser() before render()-ing this view.'); + throw new PhutilInvalidStateException('setUser'); } $local = $this->localCommits; diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php index 1dd5a5cc57..121792585d 100644 --- a/src/applications/differential/view/DifferentialRevisionListView.php +++ b/src/applications/differential/view/DifferentialRevisionListView.php @@ -57,10 +57,9 @@ final class DifferentialRevisionListView extends AphrontView { } public function render() { - $user = $this->user; if (!$user) { - throw new Exception('Call setUser() before render()!'); + throw new PhutilInvalidStateException('setUser'); } $fresh = PhabricatorEnv::getEnvConfig('differential.days-fresh'); diff --git a/src/applications/diffusion/query/DiffusionLintCountQuery.php b/src/applications/diffusion/query/DiffusionLintCountQuery.php index f85505af76..4441ccf3ef 100644 --- a/src/applications/diffusion/query/DiffusionLintCountQuery.php +++ b/src/applications/diffusion/query/DiffusionLintCountQuery.php @@ -23,11 +23,11 @@ final class DiffusionLintCountQuery extends PhabricatorQuery { public function execute() { if (!$this->paths) { - throw new Exception(pht('Call withPaths() before execute()!')); + throw new PhutilInvalidStateException('withPaths'); } if (!$this->branchIDs) { - throw new Exception(pht('Call withBranchIDs() before execute()!')); + throw new PhutilInvalidStateException('withBranchIDs'); } $conn_r = id(new PhabricatorRepositoryCommit())->establishConnection('r'); diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php index 0e8472c24d..e14198371c 100644 --- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php +++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php @@ -17,7 +17,7 @@ abstract class DiffusionLowLevelQuery extends Phobject { public function execute() { if (!$this->getRepository()) { - throw new Exception('Call setRepository() before execute()!'); + throw new PhutilInvalidStateException('setRepository'); } return $this->executeQuery(); diff --git a/src/applications/diviner/atom/DivinerAtom.php b/src/applications/diviner/atom/DivinerAtom.php index 46c1c70a9e..ef1a079a41 100644 --- a/src/applications/diviner/atom/DivinerAtom.php +++ b/src/applications/diviner/atom/DivinerAtom.php @@ -95,16 +95,14 @@ final class DivinerAtom { public function getDocblockText() { if ($this->docblockText === null) { - throw new Exception( - pht('Call %s before %s!', 'setDocblockRaw()', 'getDocblockText()')); + throw new PhutilInvalidStateException('setDocblockRaw'); } return $this->docblockText; } public function getDocblockMeta() { if ($this->docblockMeta === null) { - throw new Exception( - pht('Call %s before %s!', 'setDocblockRaw()', 'getDocblockMeta()')); + throw new PhutilInvalidStateException('setDocblockRaw'); } return $this->docblockMeta; } diff --git a/src/applications/doorkeeper/view/DoorkeeperTagView.php b/src/applications/doorkeeper/view/DoorkeeperTagView.php index 66f759f43c..8dee79d72b 100644 --- a/src/applications/doorkeeper/view/DoorkeeperTagView.php +++ b/src/applications/doorkeeper/view/DoorkeeperTagView.php @@ -12,7 +12,7 @@ final class DoorkeeperTagView extends AphrontView { public function render() { $xobj = $this->xobj; if (!$xobj) { - throw new Exception('Call setExternalObject() before render()!'); + throw new PhutilInvalidStateException('setExternalObject'); } $tag_id = celerity_generate_unique_node_id(); diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php index 2a66a3807e..e528eb15f2 100644 --- a/src/applications/files/controller/PhabricatorFileDataController.php +++ b/src/applications/files/controller/PhabricatorFileDataController.php @@ -227,7 +227,7 @@ final class PhabricatorFileDataController extends PhabricatorFileController { private function getFile() { if (!$this->file) { - throw new Exception(pht('Call loadFile() before getFile()!')); + throw new PhutilInvalidStateException('loadFile'); } return $this->file; } diff --git a/src/applications/fund/phortune/FundBackerCart.php b/src/applications/fund/phortune/FundBackerCart.php index 3dc25d4bbf..9cf530f23e 100644 --- a/src/applications/fund/phortune/FundBackerCart.php +++ b/src/applications/fund/phortune/FundBackerCart.php @@ -33,8 +33,7 @@ final class FundBackerCart extends PhortuneCartImplementation { $initiative = $this->getInitiative(); if (!$initiative) { - throw new Exception( - pht('Call setInitiative() before building a cart!')); + throw new PhutilInvalidStateException('setInitiative'); } $cart->setMetadataValue('initiativePHID', $initiative->getPHID()); diff --git a/src/applications/multimeter/data/MultimeterControl.php b/src/applications/multimeter/data/MultimeterControl.php index 2bc849c185..892e9e56cd 100644 --- a/src/applications/multimeter/data/MultimeterControl.php +++ b/src/applications/multimeter/data/MultimeterControl.php @@ -88,7 +88,7 @@ final class MultimeterControl { } if ($this->sampleRate === null) { - throw new Exception(pht('Call setSampleRate() before saving events!')); + throw new PhutilInvalidStateException('setSampleRate'); } $this->addServiceEvents(); diff --git a/src/applications/paste/view/PasteEmbedView.php b/src/applications/paste/view/PasteEmbedView.php index 46301e5a31..650905c46c 100644 --- a/src/applications/paste/view/PasteEmbedView.php +++ b/src/applications/paste/view/PasteEmbedView.php @@ -28,7 +28,7 @@ final class PasteEmbedView extends AphrontView { public function render() { if (!$this->paste) { - throw new Exception('Call setPaste() before render()!'); + throw new PhutilInvalidStateException('setPaste'); } $lines = phutil_split_lines($this->paste->getContent()); diff --git a/src/applications/phid/query/PhabricatorObjectQuery.php b/src/applications/phid/query/PhabricatorObjectQuery.php index cf8f8da4b2..26d0668cc2 100644 --- a/src/applications/phid/query/PhabricatorObjectQuery.php +++ b/src/applications/phid/query/PhabricatorObjectQuery.php @@ -78,7 +78,7 @@ final class PhabricatorObjectQuery public function getNamedResults() { if ($this->namedResults === null) { - throw new Exception('Call execute() before getNamedResults()!'); + throw new PhutilInvalidStateException('execute'); } return $this->namedResults; } diff --git a/src/applications/pholio/view/PholioMockEmbedView.php b/src/applications/pholio/view/PholioMockEmbedView.php index b20283d48b..3429cfd569 100644 --- a/src/applications/pholio/view/PholioMockEmbedView.php +++ b/src/applications/pholio/view/PholioMockEmbedView.php @@ -17,7 +17,7 @@ final class PholioMockEmbedView extends AphrontView { public function render() { if (!$this->mock) { - throw new Exception('Call setMock() before render()!'); + throw new PhutilInvalidStateException('setMock'); } $mock = $this->mock; diff --git a/src/applications/phortune/cart/PhortuneSubscriptionCart.php b/src/applications/phortune/cart/PhortuneSubscriptionCart.php index 6c17e00331..ff71106932 100644 --- a/src/applications/phortune/cart/PhortuneSubscriptionCart.php +++ b/src/applications/phortune/cart/PhortuneSubscriptionCart.php @@ -34,8 +34,7 @@ final class PhortuneSubscriptionCart $subscription = $this->getSubscription(); if (!$subscription) { - throw new Exception( - pht('Call setSubscription() before building a cart!')); + throw new PhutilInvalidStateException('setSubscription'); } $cart->setMetadataValue('subscriptionPHID', $subscription->getPHID()); diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index d3ba1cf9ae..ac67585268 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -55,18 +55,12 @@ final class PhabricatorApplicationSearchController $engine = $this->getSearchEngine(); if (!$engine) { - throw new Exception( - pht( - 'Call %s before delegating to this controller!', - 'setEngine()')); + throw new PhutilInvalidStateException('setEngine'); } $nav = $this->getNavigation(); if (!$nav) { - throw new Exception( - pht( - 'Call %s before delegating to this controller!', - 'setNavigation()')); + throw new PhutilInvalidStateException('setNavigation'); } $engine->setViewer($this->getRequest()->getUser()); diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index a0e7245a76..cd0a9655fa 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -33,7 +33,7 @@ abstract class PhabricatorApplicationSearchEngine { protected function requireViewer() { if (!$this->viewer) { - throw new Exception('Call setViewer() before using an engine!'); + throw new PhutilInvalidStateException('setViewer'); } return $this->viewer; } diff --git a/src/applications/slowvote/view/SlowvoteEmbedView.php b/src/applications/slowvote/view/SlowvoteEmbedView.php index 79e4c0bb1d..4ff920bb7d 100644 --- a/src/applications/slowvote/view/SlowvoteEmbedView.php +++ b/src/applications/slowvote/view/SlowvoteEmbedView.php @@ -22,7 +22,7 @@ final class SlowvoteEmbedView extends AphrontView { public function render() { if (!$this->poll) { - throw new Exception('Call setPoll() before render()!'); + throw new PhutilInvalidStateException('setPoll'); } $poll = $this->poll; diff --git a/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php b/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php index 498b01a04f..3ee4b5f6e8 100644 --- a/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php +++ b/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php @@ -56,7 +56,7 @@ final class PhabricatorSubscriptionsEditor extends PhabricatorEditor { public function save() { if (!$this->object) { - throw new Exception('Call setObject() before save()!'); + throw new PhutilInvalidStateException('setObject'); } $actor = $this->requireActor(); diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php index ee09a76eda..23b77fbd1b 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php @@ -133,8 +133,7 @@ final class PhabricatorApplicationTransactionCommentEditor } if (!$this->getContentSource()) { - throw new Exception( - 'Call setContentSource() before applyEdit()!'); + throw new PhutilInvalidStateException('applyEdit'); } $actor = $this->requireActor(); diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 1e75bd0432..dfd4bba286 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -970,8 +970,7 @@ abstract class PhabricatorApplicationTransactionEditor array $xactions) { if (!$this->getContentSource()) { - throw new Exception( - 'Call setContentSource() before applyTransactions()!'); + throw new PhutilInvalidStateException('setContentSource'); } // Do a bunch of sanity checks that the incoming transactions are fresh. diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php index 77cc6a6371..311c7e8b9f 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php @@ -149,7 +149,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView { } if (!$this->getObjectPHID()) { - throw new Exception('Call setObjectPHID() before render()!'); + throw new PhutilInvalidStateException('setObjectPHID', 'render'); } return id(new AphrontFormView()) diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php index 6634c397f9..3a9bc616d9 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php @@ -76,10 +76,10 @@ final class PHUIDiffInlineCommentEditView public function render() { if (!$this->uri) { - throw new Exception('Call setSubmitURI() before render()!'); + throw new PhutilInvalidStateException('setSubmitURI'); } if (!$this->user) { - throw new Exception('Call setUser() before render()!'); + throw new PhutilInvalidStateException('setUser'); } $content = phabricator_form( diff --git a/src/view/control/AphrontCursorPagerView.php b/src/view/control/AphrontCursorPagerView.php index a683eb3d6d..81ab33df81 100644 --- a/src/view/control/AphrontCursorPagerView.php +++ b/src/view/control/AphrontCursorPagerView.php @@ -92,8 +92,7 @@ final class AphrontCursorPagerView extends AphrontView { public function getFirstPageURI() { if (!$this->uri) { - throw new Exception( - pht('You must call setURI() before you can call getFirstPageURI().')); + throw new PhutilInvalidStateException('setURI'); } if (!$this->afterID && !($this->beforeID && $this->moreResults)) { @@ -107,8 +106,7 @@ final class AphrontCursorPagerView extends AphrontView { public function getPrevPageURI() { if (!$this->uri) { - throw new Exception( - pht('You must call setURI() before you can call getPrevPageURI().')); + throw new PhutilInvalidStateException('getPrevPageURI'); } if (!$this->prevPageID) { @@ -122,8 +120,7 @@ final class AphrontCursorPagerView extends AphrontView { public function getNextPageURI() { if (!$this->uri) { - throw new Exception( - pht('You must call setURI() before you can call getNextPageURI().')); + throw new PhutilInvalidStateException('setURI'); } if (!$this->nextPageID) { @@ -137,8 +134,7 @@ final class AphrontCursorPagerView extends AphrontView { public function render() { if (!$this->uri) { - throw new Exception( - pht('You must call setURI() before you can call render().')); + throw new PhutilInvalidStateException('setURI'); } $links = array(); diff --git a/src/view/control/AphrontPagerView.php b/src/view/control/AphrontPagerView.php index ba6a98736c..8b08b15c5a 100644 --- a/src/view/control/AphrontPagerView.php +++ b/src/view/control/AphrontPagerView.php @@ -109,8 +109,7 @@ final class AphrontPagerView extends AphrontView { public function render() { if (!$this->uri) { - throw new Exception( - pht('You must call setURI() before you can call render().')); + throw new PhutilInvalidStateException('setURI'); } require_celerity_resource('aphront-pager-view-css'); diff --git a/src/view/form/PHUIFormLayoutView.php b/src/view/form/PHUIFormLayoutView.php index 3e7941257e..1c5ccbb7f2 100644 --- a/src/view/form/PHUIFormLayoutView.php +++ b/src/view/form/PHUIFormLayoutView.php @@ -32,11 +32,7 @@ final class PHUIFormLayoutView extends AphrontView { public function appendRemarkupInstructions($remarkup) { if ($this->getUser() === null) { - throw new Exception( - pht( - 'Call %s before appending Remarkup to %s.', - 'setUser()', - __CLASS__)); + throw new PhutilInvalidStateException('setUser'); } return $this->appendInstructions( diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php index b6ed3b5e0c..f384dd3ba3 100644 --- a/src/view/form/control/PhabricatorRemarkupControl.php +++ b/src/view/form/control/PhabricatorRemarkupControl.php @@ -24,11 +24,7 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { $viewer = $this->getUser(); if (!$viewer) { - throw new Exception( - pht( - 'Call %s before rendering a %s!', - 'setUser()', - __CLASS__)); + throw new PhutilInvalidStateException('setUser'); } // We need to have this if previews render images, since Ajax can not diff --git a/src/view/phui/PHUIRemarkupPreviewPanel.php b/src/view/phui/PHUIRemarkupPreviewPanel.php index 8fa02f983c..9ca2f620f9 100644 --- a/src/view/phui/PHUIRemarkupPreviewPanel.php +++ b/src/view/phui/PHUIRemarkupPreviewPanel.php @@ -42,8 +42,11 @@ final class PHUIRemarkupPreviewPanel extends AphrontTagView { ); if (empty($skins[$skin])) { - $valid = implode(', ', array_keys($skins)); - throw new Exception("Invalid skin '{$skin}'. Valid skins are: {$valid}."); + throw new Exception( + pht( + "Invalid skin '%s'. Valid skins are: %s.", + $skin, + implode(', ', array_keys($skins)))); } $this->skin = $skin; @@ -69,10 +72,10 @@ final class PHUIRemarkupPreviewPanel extends AphrontTagView { protected function getTagContent() { if ($this->previewURI === null) { - throw new Exception('Call setPreviewURI() before rendering!'); + throw new PhutilInvalidStateException('setPreviewURI'); } if ($this->controlID === null) { - throw new Exception('Call setControlID() before rendering!'); + throw new PhutilInvalidStateException('setControlID'); } $preview_id = celerity_generate_unique_node_id(); diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index 573ee3ba8b..972a025150 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -59,7 +59,7 @@ final class PHUICalendarMonthView extends AphrontView { public function render() { if (empty($this->user)) { - throw new Exception('Call setUser() before render()!'); + throw new PhutilInvalidStateException('setUser'); } $events = msort($this->events, 'getEpochStart'); diff --git a/src/view/widget/hovercard/PhabricatorHovercardView.php b/src/view/widget/hovercard/PhabricatorHovercardView.php index 40cb6fa30e..58131a6a6b 100644 --- a/src/view/widget/hovercard/PhabricatorHovercardView.php +++ b/src/view/widget/hovercard/PhabricatorHovercardView.php @@ -62,7 +62,7 @@ final class PhabricatorHovercardView extends AphrontView { public function render() { if (!$this->handle) { - throw new Exception('Call setObjectHandle() before calling render()!'); + throw new PhutilInvalidStateException('setObjectHandle'); } $handle = $this->handle; From 97611958b0ead8280514d0678634ab9763fc2347 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Wed, 13 May 2015 15:58:46 -0700 Subject: [PATCH 68/70] Refactor `PHUICalendarMonthView` to be a little more readable Summary: Refactor `PHUICalendarMonthView` to be a little more readable Test Plan: Make sure month view still works Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D12831 --- .../PhabricatorPeopleCalendarController.php | 7 - .../phui/calendar/PHUICalendarMonthView.php | 309 +++++++++--------- 2 files changed, 146 insertions(+), 170 deletions(-) diff --git a/src/applications/people/controller/PhabricatorPeopleCalendarController.php b/src/applications/people/controller/PhabricatorPeopleCalendarController.php index b2d1f74ab7..168c1def70 100644 --- a/src/applications/people/controller/PhabricatorPeopleCalendarController.php +++ b/src/applications/people/controller/PhabricatorPeopleCalendarController.php @@ -35,12 +35,6 @@ final class PhabricatorPeopleCalendarController $month = $request->getInt('month', $month_d); $day = phabricator_format_local_time($now, $user, 'j'); - - $holidays = id(new PhabricatorCalendarHoliday())->loadAllWhere( - 'day BETWEEN %s AND %s', - "{$year}-{$month}-01", - "{$year}-{$month}-31"); - $start_epoch = strtotime("{$year}-{$month}-01"); $end_epoch = strtotime("{$year}-{$month}-01 next month"); @@ -76,7 +70,6 @@ final class PhabricatorPeopleCalendarController $month_view->setBrowseURI($request->getRequestURI()); $month_view->setUser($user); - $month_view->setHolidays($holidays); $month_view->setImage($picture); $phids = mpull($statuses, 'getUserPHID'); diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index 972a025150..5e74af0ff8 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -7,7 +7,6 @@ final class PHUICalendarMonthView extends AphrontView { private $day; private $month; private $year; - private $holidays = array(); private $events = array(); private $browseURI; private $image; @@ -36,12 +35,6 @@ final class PHUICalendarMonthView extends AphrontView { return $this; } - public function setHolidays(array $holidays) { - assert_instances_of($holidays, 'PhabricatorCalendarHoliday'); - $this->holidays = mpull($holidays, null, 'getDay'); - return $this; - } - public function __construct( $range_start, $range_end, @@ -71,7 +64,7 @@ final class PHUICalendarMonthView extends AphrontView { $first = reset($days); $empty = $first->format('w'); - $markup = array(); + $cell_lists = array(); $empty_cell = array( 'list' => null, 'date' => null, @@ -81,47 +74,28 @@ final class PHUICalendarMonthView extends AphrontView { ); for ($ii = 0; $ii < $empty; $ii++) { - $markup[] = $empty_cell; + $cell_lists[] = $empty_cell; } - $show_events = array(); - foreach ($days as $day) { $day_number = $day->format('j'); - $holiday = idx($this->holidays, $day->format('Y-m-d')); $class = 'phui-calendar-month-day'; $weekday = $day->format('w'); - if ($holiday || $weekday == 0 || $weekday == 6) { - $class .= ' phui-calendar-not-work-day'; - } - $day->setTime(0, 0, 0); - $epoch_start = $day->format('U'); - - - $epoch_end = id(clone $day)->modify('+1 day')->format('U'); - - if ($weekday == 0) { - $show_events = array(); - } else { - $show_events = array_fill_keys( - array_keys($show_events), - phutil_tag_div( - 'phui-calendar-event phui-calendar-event-empty', - "\xC2\xA0")); //   - } + $day_start_epoch = $day->format('U'); + $day_end_epoch = id(clone $day)->modify('+1 day')->format('U'); $list_events = array(); $all_day_events = array(); + foreach ($events as $event) { - if ($event->getEpochStart() >= $epoch_end) { - // This list is sorted, so we can stop looking. + if ($event->getEpochStart() >= $day_end_epoch) { break; } - if ($event->getEpochStart() < $epoch_end && - $event->getEpochEnd() > $epoch_start) { + if ($event->getEpochStart() < $day_end_epoch && + $event->getEpochEnd() > $day_start_epoch) { if ($event->getIsAllDay()) { $all_day_events[] = $event; } else { @@ -144,7 +118,7 @@ final class PHUICalendarMonthView extends AphrontView { $day->format('m').'/'. $day->format('d').'/'; - $markup[] = array( + $cell_lists[] = array( 'list' => $list, 'date' => $day, 'uri' => $uri, @@ -153,149 +127,34 @@ final class PHUICalendarMonthView extends AphrontView { ); } - $table = array(); - $rows = array_chunk($markup, 7); + $rows = array(); + $cell_lists_by_week = array_chunk($cell_lists, 7); - foreach ($rows as $row) { + foreach ($cell_lists_by_week as $week_of_cell_lists) { $cells = array(); - $event_count_badge = null; - - while (count($row) < 7) { - $row[] = $empty_cell; + while (count($week_of_cell_lists) < 7) { + $week_of_cell_lists[] = $empty_cell; } - foreach ($row as $cell) { - $cell_list = $cell['list']; - $class = $cell['class']; - $uri = $cell['uri']; - $count = $cell['count']; - - $event_count = null; - if ($count > 0) { - $event_count = phutil_tag( - 'div', - array( - 'class' => 'phui-calendar-month-count-badge', - ), - $count); - } - - $event_count_badge = phutil_tag( - 'div', - array( - 'class' => 'phui-calendar-month-event-count', - ), - $event_count); - - $cell_day_secret_link = phutil_tag( - 'a', - array( - 'class' => 'phui-calendar-month-secret-link', - 'href' => $uri, - ), - null); - - $cell_div = phutil_tag( - 'div', - array( - 'class' => 'phui-calendar-month-cell-div', - ), - array( - $cell_day_secret_link, - $event_count_badge, - $cell_list, - )); - - $cells[] = phutil_tag( - 'td', - array( - 'class' => 'phui-calendar-month-event-list '.$class, - ), - $cell_div); + foreach ($week_of_cell_lists as $cell_list) { + $cells[] = $this->getEventListCell($cell_list); } - $table[] = phutil_tag('tr', array(), $cells); + $rows[] = phutil_tag('tr', array(), $cells); $cells = array(); - foreach ($row as $cell) { - $class = $cell['class']; - $cell_day_secret_link = null; - - if ($cell['date']) { - $cell_day = $cell['date']; - $uri = $cell['uri']; - - $cell_day_secret_link = phutil_tag( - 'a', - array( - 'class' => 'phui-calendar-month-secret-link', - 'href' => $uri, - ), - null); - - $cell_day = phutil_tag( - 'a', - array( - 'class' => 'phui-calendar-date-number', - 'href' => $uri, - ), - $cell_day->format('j')); - - } else { - $cell_day = null; - } - - if ($cell['date'] && $cell['date']->format('j') == $this->day) { - $today_class = 'phui-calendar-today-slot phui-calendar-today'; - } else { - $today_class = 'phui-calendar-today-slot'; - } - - $today_slot = phutil_tag ( - 'div', - array( - 'class' => $today_class, - ), - null); - - $cell_div = phutil_tag( - 'div', - array( - 'class' => 'phui-calendar-month-cell-div', - ), - array( - $cell_day_secret_link, - $cell_day, - $today_slot, - )); - - $cells[] = phutil_tag( - 'td', - array( - 'class' => 'phui-calendar-date-number-container '.$class, - ), - $cell_div); + foreach ($week_of_cell_lists as $cell_list) { + $cells[] = $this->getDayNumberCell($cell_list); } - $table[] = phutil_tag('tr', array(), $cells); + $rows[] = phutil_tag('tr', array(), $cells); } - $header = phutil_tag( - 'tr', - array('class' => 'phui-calendar-day-of-week-header'), - array( - phutil_tag('th', array(), pht('Sun')), - phutil_tag('th', array(), pht('Mon')), - phutil_tag('th', array(), pht('Tue')), - phutil_tag('th', array(), pht('Wed')), - phutil_tag('th', array(), pht('Thu')), - phutil_tag('th', array(), pht('Fri')), - phutil_tag('th', array(), pht('Sat')), - )); + $header = $this->getDayNamesHeader(); $table = phutil_tag( 'table', array('class' => 'phui-calendar-view'), array( $header, - phutil_implode_html("\n", $table), + $rows, )); $warnings = $this->getQueryRangeWarning(); @@ -312,6 +171,130 @@ final class PHUICalendarMonthView extends AphrontView { return $box; } + private function getEventListCell($event_list) { + $list = $event_list['list']; + $class = $event_list['class']; + $uri = $event_list['uri']; + $count = $event_list['count']; + + $event_count_badge = $this->getEventCountBadge($count); + $cell_day_secret_link = $this->getHiddenDayLink($uri); + + $cell_data_div = phutil_tag( + 'div', + array( + 'class' => 'phui-calendar-month-cell-div', + ), + array( + $cell_day_secret_link, + $event_count_badge, + $list, + )); + + return phutil_tag( + 'td', + array( + 'class' => 'phui-calendar-month-event-list '.$class, + ), + $cell_data_div); + } + + private function getDayNumberCell($event_list) { + $class = $event_list['class']; + $date = $event_list['date']; + $cell_day_secret_link = null; + + if ($date) { + $uri = $event_list['uri']; + $cell_day_secret_link = $this->getHiddenDayLink($uri); + + $cell_day = phutil_tag( + 'a', + array( + 'class' => 'phui-calendar-date-number', + 'href' => $uri, + ), + $date->format('j')); + } else { + $cell_day = null; + } + + if ($date && $date->format('j') == $this->day) { + $today_class = 'phui-calendar-today-slot phui-calendar-today'; + } else { + $today_class = 'phui-calendar-today-slot'; + } + + $today_slot = phutil_tag ( + 'div', + array( + 'class' => $today_class, + ), + null); + + $cell_div = phutil_tag( + 'div', + array( + 'class' => 'phui-calendar-month-cell-div', + ), + array( + $cell_day_secret_link, + $cell_day, + $today_slot, + )); + + return phutil_tag( + 'td', + array( + 'class' => 'phui-calendar-date-number-container '.$class, + ), + $cell_div); + } + + private function getEventCountBadge($count) { + $event_count = null; + if ($count > 0) { + $event_count = phutil_tag( + 'div', + array( + 'class' => 'phui-calendar-month-count-badge', + ), + $count); + } + + return phutil_tag( + 'div', + array( + 'class' => 'phui-calendar-month-event-count', + ), + $event_count); + } + + private function getHiddenDayLink($uri) { + return phutil_tag( + 'a', + array( + 'class' => 'phui-calendar-month-secret-link', + 'href' => $uri, + ), + null); + } + + private function getDayNamesHeader() { + return phutil_tag( + 'tr', + array('class' => 'phui-calendar-day-of-week-header'), + array( + phutil_tag('th', array(), pht('Sun')), + phutil_tag('th', array(), pht('Mon')), + phutil_tag('th', array(), pht('Tue')), + phutil_tag('th', array(), pht('Wed')), + phutil_tag('th', array(), pht('Thu')), + phutil_tag('th', array(), pht('Fri')), + phutil_tag('th', array(), pht('Sat')), + )); + } + private function renderCalendarHeader(DateTime $date) { $button_bar = null; From a76d3a8bb5fc4c1890e15d2df6f57fb580149c74 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Wed, 13 May 2015 19:02:33 -0700 Subject: [PATCH 69/70] Leading and trailing days should be considered part of the month in month view. Summary: Closes T8177, Leading and trailing days should be considered part of the month in month view. Test Plan: Open month view, no days should be empty unless they don't have events. Modify query, make sure month view still obeys query. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8177 Differential Revision: https://secure.phabricator.com/D12834 --- .../PhabricatorCalendarEventSearchEngine.php | 22 +++++++++ .../phui/calendar/PHUICalendarMonthView.php | 46 +++++++++++++------ 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 0c9e13b03f..c8a788adb8 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -76,11 +76,33 @@ final class PhabricatorCalendarEventSearchEngine $display_start = $start_day->format('U'); $display_end = $next->format('U'); + // 0 = Sunday is always the start of the week, for now + $start_of_week = 0; + $end_of_week = 6 - $start_of_week; + + $first_of_month = $start_day->format('w'); + $last_of_month = id(clone $next)->modify('-1 day')->format('w'); + if (!$min_range || ($min_range < $display_start)) { $min_range = $display_start; + + if ($this->isMonthView($saved) && + $first_of_month > $start_of_week) { + $min_range = id(clone $start_day) + ->modify('-'.$first_of_month.' days') + ->format('U'); + } } if (!$max_range || ($max_range > $display_end)) { $max_range = $display_end; + + if ($this->isMonthView($saved) && + $last_of_month < $end_of_week) { + $max_range = id(clone $next) + ->modify('+'.(6 - $first_of_month).' days') + ->format('U'); + } + } } diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index 5e74af0ff8..7eb35f76a5 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -56,14 +56,8 @@ final class PHUICalendarMonthView extends AphrontView { } $events = msort($this->events, 'getEpochStart'); - $days = $this->getDatesInMonth(); - require_celerity_resource('phui-calendar-month-css'); - - $first = reset($days); - $empty = $first->format('w'); - $cell_lists = array(); $empty_cell = array( 'list' => null, @@ -73,6 +67,13 @@ final class PHUICalendarMonthView extends AphrontView { 'class' => null, ); + require_celerity_resource('phui-calendar-month-css'); + + $first = reset($days); + $start_of_week = 0; + + $empty = $first->format('w'); + for ($ii = 0; $ii < $empty; $ii++) { $cell_lists[] = $empty_cell; } @@ -409,22 +410,41 @@ final class PHUICalendarMonthView extends AphrontView { $month = $this->month; $year = $this->year; - // Get the year and month numbers of the following month, so we can - // determine when this month ends. list($next_year, $next_month) = $this->getNextYearAndMonth(); - $end_date = new DateTime("{$next_year}-{$next_month}-01", $timezone); - $end_epoch = $end_date->format('U'); + + $start_of_week = 0; + $end_of_week = 6 - $start_of_week; + $days_in_month = id(clone $end_date)->modify('-1 day')->format('d'); + + $first_month_day_date = new DateTime("{$year}-{$month}-01", $timezone); + $last_month_day_date = id(clone $end_date)->modify('-1 day'); + + $first_weekday_of_month = $first_month_day_date->format('w'); + $last_weekday_of_month = $last_month_day_date->format('w'); + + $num_days_display = $days_in_month; + if ($start_of_week < $first_weekday_of_month) { + $num_days_display += $first_weekday_of_month; + } + if ($end_of_week > $last_weekday_of_month) { + $num_days_display += (6 - $last_weekday_of_month); + $end_date->modify('+'.(6 - $last_weekday_of_month).' days'); + } $days = array(); - for ($day = 1; $day <= 31; $day++) { - $day_date = new DateTime("{$year}-{$month}-{$day}", $timezone); + $day_date = id(clone $first_month_day_date) + ->modify('-'.$first_weekday_of_month.' days'); + + for ($day = 1; $day <= $num_days_display; $day++) { $day_epoch = $day_date->format('U'); + $end_epoch = $end_date->format('U'); if ($day_epoch >= $end_epoch) { break; } else { - $days[] = $day_date; + $days[] = clone $day_date; } + $day_date->modify('+1 day'); } return $days; From f233672feb52429cd45c171565257e1d48049f48 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Wed, 13 May 2015 19:57:34 -0700 Subject: [PATCH 70/70] Multi-day non-all-day events should be displayed on middle day day views Summary: Fixes T8192, Multi-day non-all-day events should be displayed on middle day day views Test Plan: Create non-all-day event May 20 12pm - May23 12pm, open May 21 day view. Event should span the entire day. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8192 Differential Revision: https://secure.phabricator.com/D12835 --- .../phui/calendar/PHUICalendarDayView.php | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 35b41ffa52..21c893125d 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -55,14 +55,14 @@ final class PHUICalendarDayView extends AphrontView { $day_start = $this->getDateTime(); $day_end = id(clone $day_start)->modify('+1 day'); - $day_start = $day_start->format('U'); - $day_end = $day_end->format('U') - 1; + $day_start_epoch = $day_start->format('U'); + $day_end_epoch = $day_end->format('U') - 1; foreach ($all_day_events as $all_day_event) { $all_day_start = $all_day_event->getEpochStart(); $all_day_end = $all_day_event->getEpochEnd(); - if ($all_day_start < $day_end && $all_day_end > $day_start) { + if ($all_day_start < $day_end_epoch && $all_day_end > $day_start_epoch) { $today_all_day_events[] = $all_day_event; } } @@ -73,18 +73,22 @@ final class PHUICalendarDayView extends AphrontView { $hour_end = id(clone $hour)->modify('+1 hour')->format('U'); foreach ($this->events as $event) { - if ($event->getIsAllDay()) { - continue; - } - if ($event->getEpochStart() >= $hour_start - && $event->getEpochStart() < $hour_end) { + if ($event->getIsAllDay()) { + continue; + } + if (($hour == $day_start && + $event->getEpochStart() <= $hour_start && + $event->getEpochEnd() > $day_start_epoch) || + ($event->getEpochStart() >= $hour_start + && $event->getEpochStart() < $hour_end)) { $current_hour_events[] = $event; $this->todayEvents[] = $event; } } foreach ($current_hour_events as $event) { - $event_start = $event->getEpochStart(); - $event_end = min($event->getEpochEnd(), $day_end); + $day_start_epoch = $this->getDateTime()->format('U'); + $event_start = max($event->getEpochStart(), $day_start_epoch); + $event_end = min($event->getEpochEnd(), $day_end_epoch); $top = (($event_start - $hour_start) / ($hour_end - $hour_start)) * 100;