diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,10 +7,10 @@ */ return array( 'names' => array( - 'core.pkg.css' => '404f1f98', + 'core.pkg.css' => 'ab12d75f', 'core.pkg.js' => '75599122', 'darkconsole.pkg.js' => '8ab24e01', - 'differential.pkg.css' => '865a69a4', + 'differential.pkg.css' => '217276e8', 'differential.pkg.js' => 'e324301d', 'diffusion.pkg.css' => '591664fa', 'diffusion.pkg.js' => 'bfc0737b', @@ -49,13 +49,13 @@ 'rsrc/css/application/conpherence/message-pane.css' => 'e78e9d3c', 'rsrc/css/application/conpherence/notification.css' => '04a6e10a', 'rsrc/css/application/conpherence/update.css' => '1099a660', - 'rsrc/css/application/conpherence/widget-pane.css' => '9199d87c', + 'rsrc/css/application/conpherence/widget-pane.css' => '1979ee8c', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => '86b7b0a0', 'rsrc/css/application/dashboard/dashboard.css' => '17937d22', 'rsrc/css/application/diff/inline-comment-summary.css' => 'eb5f8e8c', 'rsrc/css/application/differential/add-comment.css' => 'c478bcaa', - 'rsrc/css/application/differential/changeset-view.css' => '9d89c9ce', + 'rsrc/css/application/differential/changeset-view.css' => 'c5d1e738', 'rsrc/css/application/differential/core.css' => '7ac3cabc', 'rsrc/css/application/differential/results-table.css' => '181aa9d9', 'rsrc/css/application/differential/revision-comment.css' => '48186045', @@ -136,7 +136,7 @@ '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' => '53deb25c', + 'rsrc/css/phui/phui-list.css' => '2e25ebfb', 'rsrc/css/phui/phui-object-box.css' => 'd68ce5dc', 'rsrc/css/phui/phui-object-item-list-view.css' => '9db65899', 'rsrc/css/phui/phui-pinboard-view.css' => '3dd4a269', @@ -352,12 +352,12 @@ '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' => '24561adb', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'bb928342', 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'eedc463c', - 'rsrc/js/application/conpherence/behavior-menu.js' => 'be9207ed', + 'rsrc/js/application/conpherence/behavior-menu.js' => 'de5579b4', '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' => '2c1cd7f5', + 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '1ec93bcf', 'rsrc/js/application/countdown/timer.js' => 'e4cc26b3', 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e', 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '82439934', @@ -517,10 +517,10 @@ 'conpherence-menu-css' => '9b37a261', 'conpherence-message-pane-css' => 'e78e9d3c', 'conpherence-notification-css' => '04a6e10a', - 'conpherence-thread-manager' => '24561adb', + 'conpherence-thread-manager' => 'bb928342', 'conpherence-update-css' => '1099a660', - 'conpherence-widget-pane-css' => '9199d87c', - 'differential-changeset-view-css' => '9d89c9ce', + 'conpherence-widget-pane-css' => '1979ee8c', + 'differential-changeset-view-css' => 'c5d1e738', 'differential-core-view-css' => '7ac3cabc', 'differential-inline-comment-editor' => 'b3412377', 'differential-results-table-css' => '181aa9d9', @@ -556,9 +556,9 @@ 'javelin-behavior-boards-dropdown' => '0ec56e1d', 'javelin-behavior-choose-control' => '6153c708', 'javelin-behavior-config-reorder-fields' => '14a827de', - 'javelin-behavior-conpherence-menu' => 'be9207ed', + 'javelin-behavior-conpherence-menu' => 'de5579b4', 'javelin-behavior-conpherence-pontificate' => '21ba5861', - 'javelin-behavior-conpherence-widget-pane' => '2c1cd7f5', + 'javelin-behavior-conpherence-widget-pane' => '1ec93bcf', 'javelin-behavior-countdown-timer' => 'e4cc26b3', 'javelin-behavior-dark-console' => '08883e8b', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', @@ -789,7 +789,7 @@ 'phui-image-mask-css' => '5a8b09c8', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => 'c6f0aef8', - 'phui-list-view-css' => '53deb25c', + 'phui-list-view-css' => '2e25ebfb', 'phui-object-box-css' => 'd68ce5dc', 'phui-object-item-list-view-css' => '9db65899', 'phui-pinboard-view-css' => '3dd4a269', @@ -947,6 +947,19 @@ 'javelin-dom', 'javelin-reactor-dom', ), + '1ec93bcf' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-util', + 'phabricator-notification', + 'javelin-behavior-device', + 'phuix-dropdown-menu', + 'phuix-action-list-view', + 'phuix-action-view', + 'conpherence-thread-manager', + ), '1feea462' => array( 'javelin-install', 'javelin-dom', @@ -980,16 +993,6 @@ 'javelin-workflow', 'javelin-util', ), - '24561adb' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), '2818f5ce' => array( 'javelin-install', 'javelin-util', @@ -1030,19 +1033,6 @@ 'javelin-stratcom', 'javelin-dom', ), - '2c1cd7f5' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-util', - 'phabricator-notification', - 'javelin-behavior-device', - 'phuix-dropdown-menu', - 'phuix-action-list-view', - 'phuix-action-view', - 'conpherence-thread-manager', - ), '2c426492' => array( 'javelin-behavior', 'javelin-dom', @@ -1681,6 +1671,16 @@ 'javelin-dom', 'javelin-util', ), + 'bb928342' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), 'bba9eedf' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1726,18 +1726,6 @@ 'javelin-util', 'phabricator-shaped-request', ), - 'be9207ed' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-behavior-device', - 'javelin-history', - 'javelin-vector', - 'phabricator-shaped-request', - 'conpherence-thread-manager', - ), 'c1700f6f' => array( 'javelin-install', 'javelin-util', @@ -1812,6 +1800,18 @@ 'javelin-typeahead-ondemand-source', 'javelin-dom', ), + 'de5579b4' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-behavior-device', + 'javelin-history', + 'javelin-vector', + 'phabricator-shaped-request', + 'conpherence-thread-manager', + ), 'e10f8e18' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/conpherence/controller/ConpherenceColumnViewController.php b/src/applications/conpherence/controller/ConpherenceColumnViewController.php --- a/src/applications/conpherence/controller/ConpherenceColumnViewController.php +++ b/src/applications/conpherence/controller/ConpherenceColumnViewController.php @@ -84,7 +84,12 @@ 'content' => hsprintf('%s', $durable_column), 'threadID' => $conpherence_id, 'threadPHID' => $conpherence_phid, - 'latestTransactionID' => $latest_transaction_id,); + 'latestTransactionID' => $latest_transaction_id, + 'canEdit' => PhabricatorPolicyFilter::hasCapability( + $user, + $conpherence, + PhabricatorPolicyCapability::CAN_EDIT), + ); return id(new AphrontAjaxResponse())->setContent($response); } diff --git a/src/applications/conpherence/controller/ConpherenceListController.php b/src/applications/conpherence/controller/ConpherenceListController.php --- a/src/applications/conpherence/controller/ConpherenceListController.php +++ b/src/applications/conpherence/controller/ConpherenceListController.php @@ -152,6 +152,7 @@ case self::UNSELECTED_MODE: default: $layout = id(new ConpherenceLayoutView()) + ->setUser($user) ->setBaseURI($this->getApplicationURI()) ->setThreadView($thread_view) ->setRole('list'); diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -3,35 +3,37 @@ final class ConpherenceUpdateController extends ConpherenceController { - private $conpherenceID; - - public function setConpherenceID($conpherence_id) { - $this->conpherenceID = $conpherence_id; - return $this; - } - public function getConpherenceID() { - return $this->conpherenceID; - } - public function willProcessRequest(array $data) { - $this->setConpherenceID(idx($data, 'id')); - } - - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { $user = $request->getUser(); - $conpherence_id = $this->getConpherenceID(); + $conpherence_id = $request->getURIData('id'); if (!$conpherence_id) { return new Aphront404Response(); } + $needed_capabilities = array(PhabricatorPolicyCapability::CAN_VIEW); + $action = $request->getStr('action', ConpherenceUpdateActions::METADATA); + switch ($action) { + case ConpherenceUpdateActions::REMOVE_PERSON: + $person_phid = $request->getStr('remove_person'); + if ($person_phid != $user->getPHID()) { + $needed_capabilities[] = PhabricatorPolicyCapability::CAN_EDIT; + } + break; + case ConpherenceUpdateActions::ADD_PERSON: + case ConpherenceUpdateActions::METADATA: + $needed_capabilities[] = PhabricatorPolicyCapability::CAN_EDIT; + break; + case ConpherenceUpdateActions::JOIN_ROOM: + $needed_capabilities[] = PhabricatorPolicyCapability::CAN_JOIN; + break; + } $conpherence = id(new ConpherenceThreadQuery()) ->setViewer($user) ->withIDs(array($conpherence_id)) ->needFilePHIDs(true) + ->requireCapabilities($needed_capabilities) ->executeOne(); - $action = $request->getStr('action', ConpherenceUpdateActions::METADATA); - $latest_transaction_id = null; $response_mode = $request->isAjax() ? 'ajax' : 'redirect'; $error_view = null; @@ -54,10 +56,6 @@ $draft->replaceOrDelete(); return new AphrontAjaxResponse(); case ConpherenceUpdateActions::JOIN_ROOM: - PhabricatorPolicyFilter::requireCapability( - $user, - $conpherence, - PhabricatorPolicyCapability::CAN_JOIN); $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( ConpherenceTransactionType::TYPE_PARTICIPANTS) @@ -257,14 +255,19 @@ $user = $request->getUser(); $remove_person = $request->getStr('remove_person'); $participants = $conpherence->getParticipants(); - $message = pht( - 'Are you sure you want to remove yourself from this conpherence? '); - if (count($participants) == 1) { - $message .= pht( - 'The conpherence will be inaccessible forever and ever.'); + if ($conpherence->getIsRoom()) { + $message = pht( + 'Are you sure you want to remove yourself from this room?'); } else { - $message .= pht( - 'Someone else in the conpherence can add you back later.'); + $message = pht( + 'Are you sure you want to remove yourself from this thread?'); + if (count($participants) == 1) { + $message .= pht( + 'The thread will be inaccessible forever and ever.'); + } else { + $message .= pht( + 'Someone else in the thread can add you back later.'); + } } $body = phutil_tag( 'p', diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -63,10 +63,15 @@ $content['threadID'] = $conpherence->getID(); $content['threadPHID'] = $conpherence->getPHID(); $content['latestTransactionID'] = $data['latest_transaction_id']; + $content['canEdit'] = PhabricatorPolicyFilter::hasCapability( + $user, + $conpherence, + PhabricatorPolicyCapability::CAN_EDIT); return id(new AphrontAjaxResponse())->setContent($content); } $layout = id(new ConpherenceLayoutView()) + ->setUser($user) ->setBaseURI($this->getApplicationURI()) ->setThread($conpherence) ->setHeader($header) diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -369,6 +369,51 @@ return $xactions; } + protected function requireCapabilities( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + parent::requireCapabilities($object, $xaction); + + switch ($xaction->getTransactionType()) { + case ConpherenceTransactionType::TYPE_PARTICIPANTS: + $old_map = array_fuse($xaction->getOldValue()); + $new_map = array_fuse($xaction->getNewValue()); + + $add = array_keys(array_diff_key($new_map, $old_map)); + $rem = array_keys(array_diff_key($old_map, $new_map)); + + $actor_phid = $this->requireActor()->getPHID(); + + $is_join = (($add === array($actor_phid)) && !$rem); + $is_leave = (($rem === array($actor_phid)) && !$add); + + if ($is_join) { + // You need CAN_JOIN to join a thread / room. + PhabricatorPolicyFilter::requireCapability( + $this->requireActor(), + $object, + PhabricatorPolicyCapability::CAN_JOIN); + } else if ($is_leave) { + // You don't need any capabilities to leave a conpherence thread. + } else { + // You need CAN_EDIT to change participants other than yourself. + PhabricatorPolicyFilter::requireCapability( + $this->requireActor(), + $object, + PhabricatorPolicyCapability::CAN_EDIT); + } + break; + case ConpherenceTransactionType::TYPE_FILES: + case ConpherenceTransactionType::TYPE_TITLE: + PhabricatorPolicyFilter::requireCapability( + $this->requireActor(), + $object, + PhabricatorPolicyCapability::CAN_EDIT); + break; + } + } + protected function mergeTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { @@ -496,7 +541,7 @@ switch ($type) { case ConpherenceTransactionType::TYPE_TITLE: - if (!$object->getIsRoom() && $this->getIsNewObject()) { + if (!$object->getIsRoom()) { continue; } $missing = $this->validateIsEmptyTextField( diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -266,7 +266,15 @@ } public function describeAutomaticCapability($capability) { - return pht('Participants in a thread can always view and edit it.'); + if ($this->getIsRoom()) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return pht('Participants in a room can always view it.'); + break; + } + } else { + return pht('Participants in a thread can always view and edit it.'); + } } } diff --git a/src/applications/conpherence/view/ConpherenceDurableColumnView.php b/src/applications/conpherence/view/ConpherenceDurableColumnView.php --- a/src/applications/conpherence/view/ConpherenceDurableColumnView.php +++ b/src/applications/conpherence/view/ConpherenceDurableColumnView.php @@ -237,6 +237,7 @@ ->setHref($action['href']) ->setName($action['name']) ->setIcon($action['icon']) + ->setDisabled($action['disabled']) ->addSigil('conpherence-durable-column-header-action') ->setMetadata(array( 'action' => $action['key'], @@ -303,27 +304,41 @@ } private function getHeaderActionsConfig(ConpherenceThread $conpherence) { + if ($conpherence->getIsRoom()) { + $rename_label = pht('Rename Room'); + } else { + $rename_label = pht('Rename Thread'); + } + $can_edit = PhabricatorPolicyFilter::hasCapability( + $this->getUser(), + $conpherence, + PhabricatorPolicyCapability::CAN_EDIT); + return array( array( 'name' => pht('Add Participants'), + 'disabled' => !$can_edit, 'href' => '/conpherence/update/'.$conpherence->getID().'/', 'icon' => 'fa-plus', 'key' => ConpherenceUpdateActions::ADD_PERSON, ), array( - 'name' => pht('Rename Thread'), + 'name' => $rename_label, + 'disabled' => !$can_edit, 'href' => '/conpherence/update/'.$conpherence->getID().'/', 'icon' => 'fa-pencil', 'key' => ConpherenceUpdateActions::METADATA, ), array( 'name' => pht('View in Conpherence'), + 'disabled' => false, 'href' => '/conpherence/'.$conpherence->getID().'/', 'icon' => 'fa-comments', 'key' => 'go_conpherence', ), array( 'name' => pht('Hide Column'), + 'disabled' => false, 'href' => '#', 'icon' => 'fa-times', 'key' => 'hide_column', diff --git a/src/applications/conpherence/view/ConpherenceLayoutView.php b/src/applications/conpherence/view/ConpherenceLayoutView.php --- a/src/applications/conpherence/view/ConpherenceLayoutView.php +++ b/src/applications/conpherence/view/ConpherenceLayoutView.php @@ -68,10 +68,15 @@ $selected_id = null; $selected_thread_id = null; $selected_thread_phid = null; + $can_edit_selected = null; if ($this->thread) { $selected_id = $this->thread->getPHID().'-nav-item'; $selected_thread_id = $this->thread->getID(); $selected_thread_phid = $this->thread->getPHID(); + $can_edit_selected = PhabricatorPolicyFilter::hasCapability( + $this->getUser(), + $this->thread, + PhabricatorPolicyCapability::CAN_EDIT); } $this->initBehavior('conpherence-menu', array( @@ -80,6 +85,7 @@ 'selectedID' => $selected_id, 'selectedThreadID' => $selected_thread_id, 'selectedThreadPHID' => $selected_thread_phid, + 'canEditSelectedThread' => $can_edit_selected, 'latestTransactionID' => $this->latestTransactionID, 'role' => $this->role, 'hasThreadList' => (bool)$this->threadView, diff --git a/src/view/phui/PHUIListItemView.php b/src/view/phui/PHUIListItemView.php --- a/src/view/phui/PHUIListItemView.php +++ b/src/view/phui/PHUIListItemView.php @@ -146,6 +146,10 @@ $classes[] = 'phui-list-item-selected'; } + if ($this->disabled) { + $classes[] = 'phui-list-item-disabled'; + } + if ($this->statusColor) { $classes[] = $this->statusColor; } diff --git a/webroot/rsrc/css/application/conpherence/widget-pane.css b/webroot/rsrc/css/application/conpherence/widget-pane.css --- a/webroot/rsrc/css/application/conpherence/widget-pane.css +++ b/webroot/rsrc/css/application/conpherence/widget-pane.css @@ -57,6 +57,10 @@ border-top-color: #000; } +.conpherence-widget-pane .widgets-header .phui-icon-view.disabled { + color: {$lightgreytext}; +} + .device-desktop .conpherence-layout .device-widgets-selector { display: none; } diff --git a/webroot/rsrc/css/phui/phui-list.css b/webroot/rsrc/css/phui/phui-list.css --- a/webroot/rsrc/css/phui/phui-list.css +++ b/webroot/rsrc/css/phui/phui-list.css @@ -61,6 +61,10 @@ line-height: 18px; } +.phui-list-sidenav .phui-list-item-disabled .phui-list-item-href { + color: {$lightgreytext}; +} + .phui-list-sidenav .phui-list-item-has-icon .phui-list-item-href { padding: 2px 10px; } diff --git a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js --- a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js +++ b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js @@ -26,6 +26,7 @@ _loadedThreadID: null, _loadedThreadPHID: null, _latestTransactionID: null, + _canEditLoadedThread: null, _updating: null, _minimalDisplay: false, _willLoadThreadCallback: JX.bag, @@ -80,6 +81,18 @@ return this; }, + setCanEditLoadedThread: function(bool) { + this._canEditLoadedThread = bool; + return this; + }, + + getCanEditLoadedThread: function() { + if (this._canEditLoadedThread === null) { + return false; + } + return this._canEditLoadedThread; + }, + setMinimalDisplay: function(bool) { this._minimalDisplay = bool; return this; @@ -255,6 +268,7 @@ 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); diff --git a/webroot/rsrc/js/application/conpherence/behavior-menu.js b/webroot/rsrc/js/application/conpherence/behavior-menu.js --- a/webroot/rsrc/js/application/conpherence/behavior-menu.js +++ b/webroot/rsrc/js/application/conpherence/behavior-menu.js @@ -185,6 +185,7 @@ threadManager.setLoadedThreadID(config.selectedThreadID); threadManager.setLoadedThreadPHID(config.selectedThreadPHID); threadManager.setLatestTransactionID(config.latestTransactionID); + threadManager.setCanEditLoadedThread(config.canEditSelectedThread); _scrollMessageWindow(); _focusTextarea(); } else { diff --git a/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js b/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js --- a/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js +++ b/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js @@ -184,6 +184,8 @@ var widget_pane = JX.DOM.find(root, 'div', 'conpherence-widget-pane'); var widget_header = JX.DOM.find(widget_pane, 'a', 'widgets-selector'); var adder = JX.DOM.find(widget_pane, 'a', 'conpherence-widget-adder'); + var threadManager = JX.ConpherenceThreadManager.getInstance(); + var disabled = !threadManager.getCanEditLoadedThread(); JX.DOM.setContent( widget_header, widget_data.name); @@ -192,6 +194,10 @@ JX.$N('span', { className : 'caret' })); if (widget_data.hasCreate) { JX.DOM.show(adder); + JX.DOM.alterClass( + adder, + 'disabled', + disabled); } else { JX.DOM.hide(adder); }