From 5eb4bc6ca960aa0eafaa25591baea102ff4e034b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 30 Sep 2016 22:28:51 -0700 Subject: [PATCH 01/36] Clean up more Quicksand Summary: Creates a background that renders inside the Quicksand frame, through sorcery. Test Plan: Turn on Quicksand, visit lots of pages. See correct background colors. This probably blows something up I'm not testing. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16642 --- resources/celerity/map.php | 26 +++++++++---------- .../PhabricatorConfigAllController.php | 2 +- ...PhabricatorConfigApplicationController.php | 2 +- .../PhabricatorConfigCacheController.php | 2 +- ...icatorConfigClusterDatabasesController.php | 2 +- ...orConfigClusterNotificationsController.php | 2 +- ...torConfigClusterRepositoriesController.php | 2 +- ...abricatorConfigDatabaseIssueController.php | 2 +- ...bricatorConfigDatabaseStatusController.php | 2 +- .../PhabricatorConfigGroupController.php | 2 +- .../PhabricatorConfigHistoryController.php | 2 +- .../PhabricatorConfigIssueListController.php | 2 +- .../PhabricatorConfigIssueViewController.php | 2 +- .../PhabricatorConfigListController.php | 2 +- .../PhabricatorConfigModuleController.php | 2 +- .../PhabricatorConfigVersionController.php | 2 +- .../PhabricatorHomeMainController.php | 2 +- .../PhabricatorProjectBoardViewController.php | 4 +-- ...PhabricatorApplicationSearchController.php | 4 +-- src/view/page/PhabricatorStandardPageView.php | 20 +++++++++++--- .../application/base/standard-page-view.css | 13 +++++++++- .../search/application-search-view.css | 10 +++++++ webroot/rsrc/css/core/core.css | 7 ++++- webroot/rsrc/css/phui/phui-basic-nav-view.css | 6 ++--- .../phui/workboards/phui-workboard-color.css | 4 +-- .../css/phui/workboards/phui-workboard.css | 9 ++++--- 26 files changed, 89 insertions(+), 46 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 78ec532eef..61cae7fc1d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'b1547973', 'conpherence.pkg.js' => '11f3e07e', - 'core.pkg.css' => 'cfc3eabe', + 'core.pkg.css' => 'ade19c40', 'core.pkg.js' => '975d6a27', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'e1d704ce', @@ -38,7 +38,7 @@ return array( 'rsrc/css/application/base/notification-menu.css' => 'b3ab500d', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601', 'rsrc/css/application/base/phui-theme.css' => '027ba77e', - 'rsrc/css/application/base/standard-page-view.css' => '79176f5a', + 'rsrc/css/application/base/standard-page-view.css' => 'e5d90676', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '0ede4c9b', @@ -102,12 +102,12 @@ return array( 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', - 'rsrc/css/application/search/application-search-view.css' => 'be6454ec', + 'rsrc/css/application/search/application-search-view.css' => '9c9bc8dc', 'rsrc/css/application/search/search-results.css' => '7dea472c', 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => 'd0801452', + 'rsrc/css/core/core.css' => '4f5111ea', 'rsrc/css/core/remarkup.css' => 'cd912f2c', 'rsrc/css/core/syntax.css' => '769d3498', 'rsrc/css/core/z-index.css' => '0d4e5558', @@ -125,7 +125,7 @@ return array( 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => '3baef8db', - 'rsrc/css/phui/phui-basic-nav-view.css' => '7093573b', + 'rsrc/css/phui/phui-basic-nav-view.css' => 'c377093a', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '5c8387cf', 'rsrc/css/phui/phui-button.css' => '4a5fbe3d', @@ -163,8 +163,8 @@ return array( 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => 'bc523970', 'rsrc/css/phui/phui-two-column-view.css' => 'fcfbe347', - 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', - 'rsrc/css/phui/workboards/phui-workboard.css' => 'e09eb53a', + 'rsrc/css/phui/workboards/phui-workboard-color.css' => '6da20b15', + 'rsrc/css/phui/workboards/phui-workboard.css' => 'fe70ad9c', 'rsrc/css/phui/workboards/phui-workcard.css' => '0c62d7c5', 'rsrc/css/phui/workboards/phui-workpanel.css' => '92197373', 'rsrc/css/sprite-login.css' => '6dbbbd97', @@ -611,7 +611,7 @@ return array( 'aphront-tokenizer-control-css' => '056da01b', 'aphront-tooltip-css' => '1a07aea8', 'aphront-typeahead-control-css' => 'd4f16145', - 'application-search-view-css' => 'be6454ec', + 'application-search-view-css' => '9c9bc8dc', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', 'changeset-view-manager' => 'a2828756', @@ -844,7 +844,7 @@ return array( 'phabricator-busy' => '59a7976a', 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => 'd0801452', + 'phabricator-core-css' => '4f5111ea', 'phabricator-countdown-css' => '16c52f5c', 'phabricator-dashboard-css' => 'bc6f2127', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', @@ -869,7 +869,7 @@ return array( 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => 'cbeef983', - 'phabricator-standard-page-view' => '79176f5a', + 'phabricator-standard-page-view' => 'e5d90676', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => 'df5e11d2', 'phabricator-tooltip' => '6323f942', @@ -896,7 +896,7 @@ return array( 'phriction-document-css' => '4282e4ad', 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => '3baef8db', - 'phui-basic-nav-view-css' => '7093573b', + 'phui-basic-nav-view-css' => 'c377093a', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '5c8387cf', 'phui-button-css' => '4a5fbe3d', @@ -942,8 +942,8 @@ return array( 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => 'bc523970', 'phui-two-column-view-css' => 'fcfbe347', - 'phui-workboard-color-css' => 'ac6fe6a7', - 'phui-workboard-view-css' => 'e09eb53a', + 'phui-workboard-color-css' => '6da20b15', + 'phui-workboard-view-css' => 'fe70ad9c', 'phui-workcard-view-css' => '0c62d7c5', 'phui-workpanel-view-css' => '92197373', 'phuix-action-list-view' => 'b5c256b8', diff --git a/src/applications/config/controller/PhabricatorConfigAllController.php b/src/applications/config/controller/PhabricatorConfigAllController.php index 421e0078cf..3881c9859f 100644 --- a/src/applications/config/controller/PhabricatorConfigAllController.php +++ b/src/applications/config/controller/PhabricatorConfigAllController.php @@ -71,7 +71,7 @@ final class PhabricatorConfigAllController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } diff --git a/src/applications/config/controller/PhabricatorConfigApplicationController.php b/src/applications/config/controller/PhabricatorConfigApplicationController.php index 10f639adc7..7b36cb9c8b 100644 --- a/src/applications/config/controller/PhabricatorConfigApplicationController.php +++ b/src/applications/config/controller/PhabricatorConfigApplicationController.php @@ -32,7 +32,7 @@ final class PhabricatorConfigApplicationController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } private function buildConfigOptionsList(array $groups, $type) { diff --git a/src/applications/config/controller/PhabricatorConfigCacheController.php b/src/applications/config/controller/PhabricatorConfigCacheController.php index 91b0be27cf..53c2b87381 100644 --- a/src/applications/config/controller/PhabricatorConfigCacheController.php +++ b/src/applications/config/controller/PhabricatorConfigCacheController.php @@ -37,7 +37,7 @@ final class PhabricatorConfigCacheController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } private function renderCodeBox() { diff --git a/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php b/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php index 44659ab24e..3f2dc62285 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php @@ -36,7 +36,7 @@ final class PhabricatorConfigClusterDatabasesController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } private function buildClusterDatabaseStatus() { diff --git a/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php b/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php index d0d26d0613..de38632ce7 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php @@ -36,7 +36,7 @@ final class PhabricatorConfigClusterNotificationsController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } private function buildClusterNotificationStatus() { diff --git a/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php b/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php index b3fd9915b1..3a7418affb 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php @@ -42,7 +42,7 @@ final class PhabricatorConfigClusterRepositoriesController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } private function buildClusterRepositoryStatus() { diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php index f1a91d4d5b..6302ccbb47 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php @@ -165,7 +165,7 @@ final class PhabricatorConfigDatabaseIssueController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } } diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php index bdeb254437..c21d284c17 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php @@ -117,7 +117,7 @@ final class PhabricatorConfigDatabaseStatusController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } diff --git a/src/applications/config/controller/PhabricatorConfigGroupController.php b/src/applications/config/controller/PhabricatorConfigGroupController.php index 6e713f7eab..1630fac007 100644 --- a/src/applications/config/controller/PhabricatorConfigGroupController.php +++ b/src/applications/config/controller/PhabricatorConfigGroupController.php @@ -43,7 +43,7 @@ final class PhabricatorConfigGroupController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } private function buildOptionList(array $options) { diff --git a/src/applications/config/controller/PhabricatorConfigHistoryController.php b/src/applications/config/controller/PhabricatorConfigHistoryController.php index eb7fbf607b..749ad3ffe4 100644 --- a/src/applications/config/controller/PhabricatorConfigHistoryController.php +++ b/src/applications/config/controller/PhabricatorConfigHistoryController.php @@ -50,7 +50,7 @@ final class PhabricatorConfigHistoryController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } } diff --git a/src/applications/config/controller/PhabricatorConfigIssueListController.php b/src/applications/config/controller/PhabricatorConfigIssueListController.php index a5553a34bb..b24c5e74ad 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueListController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueListController.php @@ -68,7 +68,7 @@ final class PhabricatorConfigIssueListController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } private function buildIssueList(array $issues, $group, $fonticon) { diff --git a/src/applications/config/controller/PhabricatorConfigIssueViewController.php b/src/applications/config/controller/PhabricatorConfigIssueViewController.php index 57d7b75137..dc7e5e2097 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueViewController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueViewController.php @@ -46,7 +46,7 @@ final class PhabricatorConfigIssueViewController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } private function renderIssue(PhabricatorSetupIssue $issue) { diff --git a/src/applications/config/controller/PhabricatorConfigListController.php b/src/applications/config/controller/PhabricatorConfigListController.php index 517fe014a9..0458dfd194 100644 --- a/src/applications/config/controller/PhabricatorConfigListController.php +++ b/src/applications/config/controller/PhabricatorConfigListController.php @@ -32,7 +32,7 @@ final class PhabricatorConfigListController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } private function buildConfigOptionsList(array $groups, $type) { diff --git a/src/applications/config/controller/PhabricatorConfigModuleController.php b/src/applications/config/controller/PhabricatorConfigModuleController.php index e10d70561b..7848e45eb2 100644 --- a/src/applications/config/controller/PhabricatorConfigModuleController.php +++ b/src/applications/config/controller/PhabricatorConfigModuleController.php @@ -36,7 +36,7 @@ final class PhabricatorConfigModuleController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } } diff --git a/src/applications/config/controller/PhabricatorConfigVersionController.php b/src/applications/config/controller/PhabricatorConfigVersionController.php index 15877ddbd7..82f2da6a23 100644 --- a/src/applications/config/controller/PhabricatorConfigVersionController.php +++ b/src/applications/config/controller/PhabricatorConfigVersionController.php @@ -31,7 +31,7 @@ final class PhabricatorConfigVersionController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addClass('white-background'); + ->addFrameClass('white-background'); } diff --git a/src/applications/home/controller/PhabricatorHomeMainController.php b/src/applications/home/controller/PhabricatorHomeMainController.php index 405959fe67..3984f63623 100644 --- a/src/applications/home/controller/PhabricatorHomeMainController.php +++ b/src/applications/home/controller/PhabricatorHomeMainController.php @@ -46,7 +46,7 @@ final class PhabricatorHomeMainController extends PhabricatorHomeController { return $this->newPage() ->setTitle('Phabricator') - ->addClass('phabricator-home') + ->addFrameClass('phabricator-home') ->appendChild($content); } diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 107d3dc0d0..a30201185e 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -455,8 +455,8 @@ final class PhabricatorProjectBoardViewController require_celerity_resource('phui-workboard-color-css'); $background_color_class = "phui-workboard-{$background}"; - $page->addClass('phui-workboard-color'); - $page->addClass($background_color_class); + $page->addFrameClass('phui-workboard-color'); + $page->addFrameClass($background_color_class); } return $page; diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index d36d279c55..818f16d055 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -320,7 +320,6 @@ final class PhabricatorApplicationSearchController $crumbs->addTextCrumb($title); } - $nav->addClass('application-search-view'); require_celerity_resource('application-search-view-css'); return $this->newPage() @@ -328,6 +327,7 @@ final class PhabricatorApplicationSearchController ->setTitle(pht('Query: %s', $title)) ->setCrumbs($crumbs) ->setNavigation($nav) + ->addFrameClass('application-search-view') ->appendChild($body); } @@ -419,7 +419,6 @@ final class PhabricatorApplicationSearchController ->setObjectList($list) ->addClass('application-search-results'); - $nav->addClass('application-search-view'); require_celerity_resource('application-search-view-css'); return $this->newPage() @@ -427,6 +426,7 @@ final class PhabricatorApplicationSearchController ->setTitle(pht('Saved Queries')) ->setCrumbs($crumbs) ->setNavigation($nav) + ->addFrameClass('application-search-view') ->appendChild($box); } diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index dc2dfe186e..53603c2eb6 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -13,6 +13,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView private $menuContent; private $showChrome = true; private $classes = array(); + private $frameClasses = array(); private $disableConsole; private $pageObjects = array(); private $applicationMenu; @@ -80,6 +81,11 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView return $this; } + public function addFrameClass($class) { + $this->frameClasses[] = $class; + return $this; + } + public function setPageObjectPHIDs(array $phids) { $this->pageObjects = $phids; return $this; @@ -528,7 +534,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView } $nav->appendChild($body); $nav->appendFooter($footer); - $content = phutil_implode_html('', array($nav->render())); + $content = $nav; } else { $content = array(); @@ -539,10 +545,18 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $content[] = $body; $content[] = $footer; - - $content = phutil_implode_html('', $content); } + $frame_classes = $this->frameClasses; + $frame_classes[] = 'main-page-background'; + + $content = phutil_tag( + 'div', + array( + 'class' => implode(' ', $frame_classes), + ), + $content); + return array( ($console ? hsprintf('') : null), $content, diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css index 36294ca288..5fc0d41b7e 100644 --- a/webroot/rsrc/css/application/base/standard-page-view.css +++ b/webroot/rsrc/css/application/base/standard-page-view.css @@ -17,7 +17,18 @@ clear: both; } -body.white-background { +.main-page-background { + position: absolute; + top: 44px; + bottom: 0; + left: 0; + right: 0; + overflow: scroll; + z-index: -1; + background: {$page.background}; +} + +.main-page-background.white-background { background: #fff; } diff --git a/webroot/rsrc/css/application/search/application-search-view.css b/webroot/rsrc/css/application/search/application-search-view.css index 4a573f5111..71b47bb563 100644 --- a/webroot/rsrc/css/application/search/application-search-view.css +++ b/webroot/rsrc/css/application/search/application-search-view.css @@ -11,6 +11,11 @@ padding: 0 16px 24px; } +.device-phone .application-search-view + .application-search-results.phui-object-box { + padding: 0 8px 24px; +} + .application-search-view .application-search-results .phui-profile-header { padding: 16px 8px; border-bottom: 1px solid {$thinblueborder}; @@ -53,3 +58,8 @@ .device-phone .application-search-pager { margin: 12px; } + +.device-phone .application-search-view .application-search-results + .phui-profile-header { + padding-top: 12px; +} diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css index af6732260b..f957a398c2 100644 --- a/webroot/rsrc/css/core/core.css +++ b/webroot/rsrc/css/core/core.css @@ -42,12 +42,17 @@ h1, h2, h3, h4, h5, h6 { font-weight: bold; } +html{ + min-height: 100%; + position: relative; +} + body { font: {$basefont}; direction: ltr; text-align: left; unicode-bidi: embed; - background: {$page.background}; + height: 100%; /* By default, the iPhone zooms all text on the page by some percentage when you rotate from portrait mode to landscape mode. Disable this, since it diff --git a/webroot/rsrc/css/phui/phui-basic-nav-view.css b/webroot/rsrc/css/phui/phui-basic-nav-view.css index 30647e3ece..109ba4252c 100644 --- a/webroot/rsrc/css/phui/phui-basic-nav-view.css +++ b/webroot/rsrc/css/phui/phui-basic-nav-view.css @@ -3,19 +3,19 @@ */ .device-desktop .phui-navigation-shell, -.phabricator-home.device .phui-navigation-shell { +.device .phabricator-home .phui-navigation-shell { display: table; width: 100%; height: calc(100vh - {$menu.main.height}); } .device-desktop .phui-navigation-shell .phabricator-nav, -.phabricator-home.device .phui-navigation-shell .phabricator-nav { +.device .phabricator-home .phui-navigation-shell .phabricator-nav { display: table-row; } .device-desktop .phui-navigation-shell .phabricator-nav-local, -.phabricator-home.device .phui-navigation-shell .phabricator-nav-local { +.device .phabricator-home .phui-navigation-shell .phabricator-nav-local { display: table-cell; position: relative; vertical-align: top; diff --git a/webroot/rsrc/css/phui/workboards/phui-workboard-color.css b/webroot/rsrc/css/phui/workboards/phui-workboard-color.css index d983622a99..185fa327b3 100644 --- a/webroot/rsrc/css/phui/workboards/phui-workboard-color.css +++ b/webroot/rsrc/css/phui/workboards/phui-workboard-color.css @@ -30,11 +30,11 @@ background-color: rgba({$alphawhite},.6); } -body.phui-workboard-color .phui-profile-menu .phabricator-side-menu { +body .phui-workboard-color .phui-profile-menu .phabricator-side-menu { background-color: rgba({$alphagrey},.3); } -body.phui-workboard-color .phabricator-side-menu .phui-profile-menu-footer-1 { +body .phui-workboard-color .phabricator-side-menu .phui-profile-menu-footer-1 { background-color: transparent; } diff --git a/webroot/rsrc/css/phui/workboards/phui-workboard.css b/webroot/rsrc/css/phui/workboards/phui-workboard.css index df51e7623d..0df8be4732 100644 --- a/webroot/rsrc/css/phui/workboards/phui-workboard.css +++ b/webroot/rsrc/css/phui/workboards/phui-workboard.css @@ -14,12 +14,11 @@ overflow-x: auto; overflow-y: hidden; position: absolute; - top: 79px; + top: 32px; bottom: 0; left: 0; right: 0; padding: 16px; - background-color: #fff; } .phui-workboard-view-shadow::-webkit-scrollbar { @@ -75,9 +74,13 @@ display: none; } +.device-desktop .phui-workboard-fullscreen .main-page-background { + top: 0; +} + .device-desktop .phui-workboard-fullscreen .phui-profile-menu .phui-workboard-view-shadow { - top: 35px; + top: 32px; left: 0; } From 2f2126ecce08821ac5480199b69c8cf965cc857b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Oct 2016 11:30:24 -0700 Subject: [PATCH 02/36] Add 'Persist Chat' option in Conpherence notification menu Summary: This exposes the chat window to a larger audience beside people who accidentaly hit `\`. Test Plan: Lots of clicks and reloads. {F1856043} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16643 --- resources/celerity/map.php | 32 +++++++------- ...ConpherenceNotificationPanelController.php | 42 +++++++++++++++++-- .../application/conpherence/notification.css | 17 +++++++- .../conpherence/behavior-durable-column.js | 7 ++-- 4 files changed, 75 insertions(+), 23 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 61cae7fc1d..377bdf2ff7 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,10 +7,10 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => 'b1547973', + 'conpherence.pkg.css' => 'c8fbe125', 'conpherence.pkg.js' => '11f3e07e', 'core.pkg.css' => 'ade19c40', - 'core.pkg.js' => '975d6a27', + 'core.pkg.js' => '03c1cb09', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'e1d704ce', 'differential.pkg.js' => '634399e9', @@ -50,7 +50,7 @@ return array( 'rsrc/css/application/conpherence/header-pane.css' => '517de9fe', 'rsrc/css/application/conpherence/menu.css' => '78c7b811', 'rsrc/css/application/conpherence/message-pane.css' => '0d7dff02', - 'rsrc/css/application/conpherence/notification.css' => '6cdcc253', + 'rsrc/css/application/conpherence/notification.css' => '65dd0e79', 'rsrc/css/application/conpherence/participant-pane.css' => '7bba0b56', 'rsrc/css/application/conpherence/transaction.css' => '46253e19', 'rsrc/css/application/conpherence/update.css' => '53bc527a', @@ -438,7 +438,7 @@ return array( 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', - 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'e287689f', + 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'a0e564c2', 'rsrc/js/application/conpherence/behavior-menu.js' => '9eb55204', 'rsrc/js/application/conpherence/behavior-participant-pane.js' => '8604caa8', 'rsrc/js/application/conpherence/behavior-pontificate.js' => 'f2e58483', @@ -622,7 +622,7 @@ return array( 'conpherence-header-pane-css' => '517de9fe', 'conpherence-menu-css' => '78c7b811', 'conpherence-message-pane-css' => '0d7dff02', - 'conpherence-notification-css' => '6cdcc253', + 'conpherence-notification-css' => '65dd0e79', 'conpherence-participant-pane-css' => '7bba0b56', 'conpherence-thread-manager' => '01774ab2', 'conpherence-transaction-css' => '46253e19', @@ -699,7 +699,7 @@ return array( 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-drydock-live-operation-status' => '901935ef', - 'javelin-behavior-durable-column' => 'e287689f', + 'javelin-behavior-durable-column' => 'a0e564c2', 'javelin-behavior-editengine-reorder-configs' => 'd7a74243', 'javelin-behavior-editengine-reorder-fields' => 'b59e1e96', 'javelin-behavior-error-log' => '6882e80a', @@ -1774,6 +1774,16 @@ return array( 'javelin-util', 'phabricator-keyboard-shortcut', ), + 'a0e564c2' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-behavior-device', + 'javelin-scrollbar', + 'javelin-quicksand', + 'phabricator-keyboard-shortcut', + 'conpherence-thread-manager', + ), 'a155550f' => array( 'javelin-install', 'javelin-dom', @@ -2109,16 +2119,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - 'e287689f' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-behavior-device', - 'javelin-scrollbar', - 'javelin-quicksand', - 'phabricator-keyboard-shortcut', - 'conpherence-thread-manager', - ), 'e292eaf4' => array( 'javelin-install', ), diff --git a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php index 63e777818c..429f7da9f1 100644 --- a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php +++ b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php @@ -70,13 +70,20 @@ final class ConpherenceNotificationPanelController } $content = $view->render(); } else { + $rooms_uri = phutil_tag( + 'a', + array( + 'href' => '/conpherence/', + 'class' => 'no-room-notification', + ), + pht('You have joined no rooms.')); + $content = phutil_tag_div( - 'phabricator-notification no-notifications', - pht('You have no messages.')); + 'phabricator-notification no-notifications', $rooms_uri); } $content = hsprintf( - '
%s
'. + '
%s%s
'. '%s', phutil_tag( 'a', @@ -84,6 +91,7 @@ final class ConpherenceNotificationPanelController 'href' => '/conpherence/', ), pht('Rooms')), + $this->renderPersistentOption(), $content); $unread = id(new ConpherenceParticipantCountQuery()) @@ -100,4 +108,32 @@ final class ConpherenceNotificationPanelController return id(new AphrontAjaxResponse())->setContent($json); } + private function renderPersistentOption() { + $viewer = $this->getViewer(); + $column_key = PhabricatorConpherenceColumnVisibleSetting::SETTINGKEY; + $show = (bool)$viewer->getUserSetting($column_key, false); + + $view = phutil_tag( + 'div', + array( + 'class' => 'persistent-option', + ), + array( + javelin_tag( + 'input', + array( + 'type' => 'checkbox', + 'checked' => ($show) ? 'checked' : null, + 'value' => !$show, + 'sigil' => 'conpherence-persist-column', + )), + phutil_tag( + 'span', + array(), + pht('Persistent Chat')), + )); + + return $view; + } + } diff --git a/webroot/rsrc/css/application/conpherence/notification.css b/webroot/rsrc/css/application/conpherence/notification.css index b88b9ef298..ec729a5170 100644 --- a/webroot/rsrc/css/application/conpherence/notification.css +++ b/webroot/rsrc/css/application/conpherence/notification.css @@ -27,7 +27,6 @@ width: 30px; height: 30px; background-size: 100%; - box-shadow: {$borderinset}; border-radius: 3px; } @@ -71,3 +70,19 @@ padding: 0 5px 1px; font-size: {$smallestfontsize}; } + +.phabricator-notification .no-room-notification { + color: {$lightgreytext}; + display: block; +} + +.phabricator-notification-header .persistent-option { + white-space: nowrap; + float: right; +} + +.phabricator-notification-header .persistent-option span { + margin-left: 4px; + font-weight: normal; + color: {$greytext}; +} diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js index 812be0f082..ecdb949152 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js +++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js @@ -89,9 +89,10 @@ JX.behavior('durable-column', function(config, statics) { JX.Stratcom.invoke('resize'); } - new JX.KeyboardShortcut('\\', 'Toggle Conpherence Column') - .setHandler(_toggleColumn) - .register(); + JX.Stratcom.listen( + 'click', + 'conpherence-persist-column', + _toggleColumn); JX.Stratcom.listen( 'click', From 87ebb80059f07f0d4e2a01a93d55e25117971348 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Oct 2016 12:35:13 -0700 Subject: [PATCH 03/36] Revert "Clean up more Quicksand" Summary: This reverts commit 5eb4bc6ca960aa0eafaa25591baea102ff4e034b. Test Plan: Reload homepage, no scrollbars Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16645 --- resources/celerity/map.php | 26 +++++++++---------- .../PhabricatorConfigAllController.php | 2 +- ...PhabricatorConfigApplicationController.php | 2 +- .../PhabricatorConfigCacheController.php | 2 +- ...icatorConfigClusterDatabasesController.php | 2 +- ...orConfigClusterNotificationsController.php | 2 +- ...torConfigClusterRepositoriesController.php | 2 +- ...abricatorConfigDatabaseIssueController.php | 2 +- ...bricatorConfigDatabaseStatusController.php | 2 +- .../PhabricatorConfigGroupController.php | 2 +- .../PhabricatorConfigHistoryController.php | 2 +- .../PhabricatorConfigIssueListController.php | 2 +- .../PhabricatorConfigIssueViewController.php | 2 +- .../PhabricatorConfigListController.php | 2 +- .../PhabricatorConfigModuleController.php | 2 +- .../PhabricatorConfigVersionController.php | 2 +- .../PhabricatorHomeMainController.php | 2 +- .../PhabricatorProjectBoardViewController.php | 4 +-- ...PhabricatorApplicationSearchController.php | 4 +-- src/view/page/PhabricatorStandardPageView.php | 20 +++----------- .../application/base/standard-page-view.css | 13 +--------- .../search/application-search-view.css | 10 ------- webroot/rsrc/css/core/core.css | 7 +---- webroot/rsrc/css/phui/phui-basic-nav-view.css | 6 ++--- .../phui/workboards/phui-workboard-color.css | 4 +-- .../css/phui/workboards/phui-workboard.css | 9 +++---- 26 files changed, 46 insertions(+), 89 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 377bdf2ff7..2f190f9691 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'c8fbe125', 'conpherence.pkg.js' => '11f3e07e', - 'core.pkg.css' => 'ade19c40', + 'core.pkg.css' => 'cfc3eabe', 'core.pkg.js' => '03c1cb09', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'e1d704ce', @@ -38,7 +38,7 @@ return array( 'rsrc/css/application/base/notification-menu.css' => 'b3ab500d', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601', 'rsrc/css/application/base/phui-theme.css' => '027ba77e', - 'rsrc/css/application/base/standard-page-view.css' => 'e5d90676', + 'rsrc/css/application/base/standard-page-view.css' => '79176f5a', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '0ede4c9b', @@ -102,12 +102,12 @@ return array( 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', - 'rsrc/css/application/search/application-search-view.css' => '9c9bc8dc', + 'rsrc/css/application/search/application-search-view.css' => 'be6454ec', 'rsrc/css/application/search/search-results.css' => '7dea472c', 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => '4f5111ea', + 'rsrc/css/core/core.css' => 'd0801452', 'rsrc/css/core/remarkup.css' => 'cd912f2c', 'rsrc/css/core/syntax.css' => '769d3498', 'rsrc/css/core/z-index.css' => '0d4e5558', @@ -125,7 +125,7 @@ return array( 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => '3baef8db', - 'rsrc/css/phui/phui-basic-nav-view.css' => 'c377093a', + 'rsrc/css/phui/phui-basic-nav-view.css' => '7093573b', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '5c8387cf', 'rsrc/css/phui/phui-button.css' => '4a5fbe3d', @@ -163,8 +163,8 @@ return array( 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => 'bc523970', 'rsrc/css/phui/phui-two-column-view.css' => 'fcfbe347', - 'rsrc/css/phui/workboards/phui-workboard-color.css' => '6da20b15', - 'rsrc/css/phui/workboards/phui-workboard.css' => 'fe70ad9c', + 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', + 'rsrc/css/phui/workboards/phui-workboard.css' => 'e09eb53a', 'rsrc/css/phui/workboards/phui-workcard.css' => '0c62d7c5', 'rsrc/css/phui/workboards/phui-workpanel.css' => '92197373', 'rsrc/css/sprite-login.css' => '6dbbbd97', @@ -611,7 +611,7 @@ return array( 'aphront-tokenizer-control-css' => '056da01b', 'aphront-tooltip-css' => '1a07aea8', 'aphront-typeahead-control-css' => 'd4f16145', - 'application-search-view-css' => '9c9bc8dc', + 'application-search-view-css' => 'be6454ec', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', 'changeset-view-manager' => 'a2828756', @@ -844,7 +844,7 @@ return array( 'phabricator-busy' => '59a7976a', 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => '4f5111ea', + 'phabricator-core-css' => 'd0801452', 'phabricator-countdown-css' => '16c52f5c', 'phabricator-dashboard-css' => 'bc6f2127', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', @@ -869,7 +869,7 @@ return array( 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => 'cbeef983', - 'phabricator-standard-page-view' => 'e5d90676', + 'phabricator-standard-page-view' => '79176f5a', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => 'df5e11d2', 'phabricator-tooltip' => '6323f942', @@ -896,7 +896,7 @@ return array( 'phriction-document-css' => '4282e4ad', 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => '3baef8db', - 'phui-basic-nav-view-css' => 'c377093a', + 'phui-basic-nav-view-css' => '7093573b', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '5c8387cf', 'phui-button-css' => '4a5fbe3d', @@ -942,8 +942,8 @@ return array( 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => 'bc523970', 'phui-two-column-view-css' => 'fcfbe347', - 'phui-workboard-color-css' => '6da20b15', - 'phui-workboard-view-css' => 'fe70ad9c', + 'phui-workboard-color-css' => 'ac6fe6a7', + 'phui-workboard-view-css' => 'e09eb53a', 'phui-workcard-view-css' => '0c62d7c5', 'phui-workpanel-view-css' => '92197373', 'phuix-action-list-view' => 'b5c256b8', diff --git a/src/applications/config/controller/PhabricatorConfigAllController.php b/src/applications/config/controller/PhabricatorConfigAllController.php index 3881c9859f..421e0078cf 100644 --- a/src/applications/config/controller/PhabricatorConfigAllController.php +++ b/src/applications/config/controller/PhabricatorConfigAllController.php @@ -71,7 +71,7 @@ final class PhabricatorConfigAllController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } diff --git a/src/applications/config/controller/PhabricatorConfigApplicationController.php b/src/applications/config/controller/PhabricatorConfigApplicationController.php index 7b36cb9c8b..10f639adc7 100644 --- a/src/applications/config/controller/PhabricatorConfigApplicationController.php +++ b/src/applications/config/controller/PhabricatorConfigApplicationController.php @@ -32,7 +32,7 @@ final class PhabricatorConfigApplicationController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } private function buildConfigOptionsList(array $groups, $type) { diff --git a/src/applications/config/controller/PhabricatorConfigCacheController.php b/src/applications/config/controller/PhabricatorConfigCacheController.php index 53c2b87381..91b0be27cf 100644 --- a/src/applications/config/controller/PhabricatorConfigCacheController.php +++ b/src/applications/config/controller/PhabricatorConfigCacheController.php @@ -37,7 +37,7 @@ final class PhabricatorConfigCacheController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } private function renderCodeBox() { diff --git a/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php b/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php index 3f2dc62285..44659ab24e 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php @@ -36,7 +36,7 @@ final class PhabricatorConfigClusterDatabasesController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } private function buildClusterDatabaseStatus() { diff --git a/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php b/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php index de38632ce7..d0d26d0613 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php @@ -36,7 +36,7 @@ final class PhabricatorConfigClusterNotificationsController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } private function buildClusterNotificationStatus() { diff --git a/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php b/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php index 3a7418affb..b3fd9915b1 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php @@ -42,7 +42,7 @@ final class PhabricatorConfigClusterRepositoriesController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } private function buildClusterRepositoryStatus() { diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php index 6302ccbb47..f1a91d4d5b 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php @@ -165,7 +165,7 @@ final class PhabricatorConfigDatabaseIssueController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } } diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php index c21d284c17..bdeb254437 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php @@ -117,7 +117,7 @@ final class PhabricatorConfigDatabaseStatusController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } diff --git a/src/applications/config/controller/PhabricatorConfigGroupController.php b/src/applications/config/controller/PhabricatorConfigGroupController.php index 1630fac007..6e713f7eab 100644 --- a/src/applications/config/controller/PhabricatorConfigGroupController.php +++ b/src/applications/config/controller/PhabricatorConfigGroupController.php @@ -43,7 +43,7 @@ final class PhabricatorConfigGroupController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } private function buildOptionList(array $options) { diff --git a/src/applications/config/controller/PhabricatorConfigHistoryController.php b/src/applications/config/controller/PhabricatorConfigHistoryController.php index 749ad3ffe4..eb7fbf607b 100644 --- a/src/applications/config/controller/PhabricatorConfigHistoryController.php +++ b/src/applications/config/controller/PhabricatorConfigHistoryController.php @@ -50,7 +50,7 @@ final class PhabricatorConfigHistoryController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } } diff --git a/src/applications/config/controller/PhabricatorConfigIssueListController.php b/src/applications/config/controller/PhabricatorConfigIssueListController.php index b24c5e74ad..a5553a34bb 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueListController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueListController.php @@ -68,7 +68,7 @@ final class PhabricatorConfigIssueListController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } private function buildIssueList(array $issues, $group, $fonticon) { diff --git a/src/applications/config/controller/PhabricatorConfigIssueViewController.php b/src/applications/config/controller/PhabricatorConfigIssueViewController.php index dc7e5e2097..57d7b75137 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueViewController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueViewController.php @@ -46,7 +46,7 @@ final class PhabricatorConfigIssueViewController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } private function renderIssue(PhabricatorSetupIssue $issue) { diff --git a/src/applications/config/controller/PhabricatorConfigListController.php b/src/applications/config/controller/PhabricatorConfigListController.php index 0458dfd194..517fe014a9 100644 --- a/src/applications/config/controller/PhabricatorConfigListController.php +++ b/src/applications/config/controller/PhabricatorConfigListController.php @@ -32,7 +32,7 @@ final class PhabricatorConfigListController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } private function buildConfigOptionsList(array $groups, $type) { diff --git a/src/applications/config/controller/PhabricatorConfigModuleController.php b/src/applications/config/controller/PhabricatorConfigModuleController.php index 7848e45eb2..e10d70561b 100644 --- a/src/applications/config/controller/PhabricatorConfigModuleController.php +++ b/src/applications/config/controller/PhabricatorConfigModuleController.php @@ -36,7 +36,7 @@ final class PhabricatorConfigModuleController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } } diff --git a/src/applications/config/controller/PhabricatorConfigVersionController.php b/src/applications/config/controller/PhabricatorConfigVersionController.php index 82f2da6a23..15877ddbd7 100644 --- a/src/applications/config/controller/PhabricatorConfigVersionController.php +++ b/src/applications/config/controller/PhabricatorConfigVersionController.php @@ -31,7 +31,7 @@ final class PhabricatorConfigVersionController ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) - ->addFrameClass('white-background'); + ->addClass('white-background'); } diff --git a/src/applications/home/controller/PhabricatorHomeMainController.php b/src/applications/home/controller/PhabricatorHomeMainController.php index 3984f63623..405959fe67 100644 --- a/src/applications/home/controller/PhabricatorHomeMainController.php +++ b/src/applications/home/controller/PhabricatorHomeMainController.php @@ -46,7 +46,7 @@ final class PhabricatorHomeMainController extends PhabricatorHomeController { return $this->newPage() ->setTitle('Phabricator') - ->addFrameClass('phabricator-home') + ->addClass('phabricator-home') ->appendChild($content); } diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index a30201185e..107d3dc0d0 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -455,8 +455,8 @@ final class PhabricatorProjectBoardViewController require_celerity_resource('phui-workboard-color-css'); $background_color_class = "phui-workboard-{$background}"; - $page->addFrameClass('phui-workboard-color'); - $page->addFrameClass($background_color_class); + $page->addClass('phui-workboard-color'); + $page->addClass($background_color_class); } return $page; diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index 818f16d055..d36d279c55 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -320,6 +320,7 @@ final class PhabricatorApplicationSearchController $crumbs->addTextCrumb($title); } + $nav->addClass('application-search-view'); require_celerity_resource('application-search-view-css'); return $this->newPage() @@ -327,7 +328,6 @@ final class PhabricatorApplicationSearchController ->setTitle(pht('Query: %s', $title)) ->setCrumbs($crumbs) ->setNavigation($nav) - ->addFrameClass('application-search-view') ->appendChild($body); } @@ -419,6 +419,7 @@ final class PhabricatorApplicationSearchController ->setObjectList($list) ->addClass('application-search-results'); + $nav->addClass('application-search-view'); require_celerity_resource('application-search-view-css'); return $this->newPage() @@ -426,7 +427,6 @@ final class PhabricatorApplicationSearchController ->setTitle(pht('Saved Queries')) ->setCrumbs($crumbs) ->setNavigation($nav) - ->addFrameClass('application-search-view') ->appendChild($box); } diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index 53603c2eb6..dc2dfe186e 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -13,7 +13,6 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView private $menuContent; private $showChrome = true; private $classes = array(); - private $frameClasses = array(); private $disableConsole; private $pageObjects = array(); private $applicationMenu; @@ -81,11 +80,6 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView return $this; } - public function addFrameClass($class) { - $this->frameClasses[] = $class; - return $this; - } - public function setPageObjectPHIDs(array $phids) { $this->pageObjects = $phids; return $this; @@ -534,7 +528,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView } $nav->appendChild($body); $nav->appendFooter($footer); - $content = $nav; + $content = phutil_implode_html('', array($nav->render())); } else { $content = array(); @@ -545,18 +539,10 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $content[] = $body; $content[] = $footer; + + $content = phutil_implode_html('', $content); } - $frame_classes = $this->frameClasses; - $frame_classes[] = 'main-page-background'; - - $content = phutil_tag( - 'div', - array( - 'class' => implode(' ', $frame_classes), - ), - $content); - return array( ($console ? hsprintf('') : null), $content, diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css index 5fc0d41b7e..36294ca288 100644 --- a/webroot/rsrc/css/application/base/standard-page-view.css +++ b/webroot/rsrc/css/application/base/standard-page-view.css @@ -17,18 +17,7 @@ clear: both; } -.main-page-background { - position: absolute; - top: 44px; - bottom: 0; - left: 0; - right: 0; - overflow: scroll; - z-index: -1; - background: {$page.background}; -} - -.main-page-background.white-background { +body.white-background { background: #fff; } diff --git a/webroot/rsrc/css/application/search/application-search-view.css b/webroot/rsrc/css/application/search/application-search-view.css index 71b47bb563..4a573f5111 100644 --- a/webroot/rsrc/css/application/search/application-search-view.css +++ b/webroot/rsrc/css/application/search/application-search-view.css @@ -11,11 +11,6 @@ padding: 0 16px 24px; } -.device-phone .application-search-view - .application-search-results.phui-object-box { - padding: 0 8px 24px; -} - .application-search-view .application-search-results .phui-profile-header { padding: 16px 8px; border-bottom: 1px solid {$thinblueborder}; @@ -58,8 +53,3 @@ .device-phone .application-search-pager { margin: 12px; } - -.device-phone .application-search-view .application-search-results - .phui-profile-header { - padding-top: 12px; -} diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css index f957a398c2..af6732260b 100644 --- a/webroot/rsrc/css/core/core.css +++ b/webroot/rsrc/css/core/core.css @@ -42,17 +42,12 @@ h1, h2, h3, h4, h5, h6 { font-weight: bold; } -html{ - min-height: 100%; - position: relative; -} - body { font: {$basefont}; direction: ltr; text-align: left; unicode-bidi: embed; - height: 100%; + background: {$page.background}; /* By default, the iPhone zooms all text on the page by some percentage when you rotate from portrait mode to landscape mode. Disable this, since it diff --git a/webroot/rsrc/css/phui/phui-basic-nav-view.css b/webroot/rsrc/css/phui/phui-basic-nav-view.css index 109ba4252c..30647e3ece 100644 --- a/webroot/rsrc/css/phui/phui-basic-nav-view.css +++ b/webroot/rsrc/css/phui/phui-basic-nav-view.css @@ -3,19 +3,19 @@ */ .device-desktop .phui-navigation-shell, -.device .phabricator-home .phui-navigation-shell { +.phabricator-home.device .phui-navigation-shell { display: table; width: 100%; height: calc(100vh - {$menu.main.height}); } .device-desktop .phui-navigation-shell .phabricator-nav, -.device .phabricator-home .phui-navigation-shell .phabricator-nav { +.phabricator-home.device .phui-navigation-shell .phabricator-nav { display: table-row; } .device-desktop .phui-navigation-shell .phabricator-nav-local, -.device .phabricator-home .phui-navigation-shell .phabricator-nav-local { +.phabricator-home.device .phui-navigation-shell .phabricator-nav-local { display: table-cell; position: relative; vertical-align: top; diff --git a/webroot/rsrc/css/phui/workboards/phui-workboard-color.css b/webroot/rsrc/css/phui/workboards/phui-workboard-color.css index 185fa327b3..d983622a99 100644 --- a/webroot/rsrc/css/phui/workboards/phui-workboard-color.css +++ b/webroot/rsrc/css/phui/workboards/phui-workboard-color.css @@ -30,11 +30,11 @@ background-color: rgba({$alphawhite},.6); } -body .phui-workboard-color .phui-profile-menu .phabricator-side-menu { +body.phui-workboard-color .phui-profile-menu .phabricator-side-menu { background-color: rgba({$alphagrey},.3); } -body .phui-workboard-color .phabricator-side-menu .phui-profile-menu-footer-1 { +body.phui-workboard-color .phabricator-side-menu .phui-profile-menu-footer-1 { background-color: transparent; } diff --git a/webroot/rsrc/css/phui/workboards/phui-workboard.css b/webroot/rsrc/css/phui/workboards/phui-workboard.css index 0df8be4732..df51e7623d 100644 --- a/webroot/rsrc/css/phui/workboards/phui-workboard.css +++ b/webroot/rsrc/css/phui/workboards/phui-workboard.css @@ -14,11 +14,12 @@ overflow-x: auto; overflow-y: hidden; position: absolute; - top: 32px; + top: 79px; bottom: 0; left: 0; right: 0; padding: 16px; + background-color: #fff; } .phui-workboard-view-shadow::-webkit-scrollbar { @@ -74,13 +75,9 @@ display: none; } -.device-desktop .phui-workboard-fullscreen .main-page-background { - top: 0; -} - .device-desktop .phui-workboard-fullscreen .phui-profile-menu .phui-workboard-view-shadow { - top: 32px; + top: 35px; left: 0; } From 45af6d7c0e4ed8945a940ad1f34eb5392ebb6ba7 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Oct 2016 16:33:43 -0700 Subject: [PATCH 04/36] Set body classes via Quicksand config Summary: Sends and stores additional body classes at the page level. Removes old ones, sets new ones. Test Plan: home -> application search -> colored workboard -> config -> home with persistent chat open and minimized. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16646 --- resources/celerity/map.php | 26 +++++++++---------- src/view/page/PhabricatorStandardPageView.php | 1 + .../conpherence/behavior-durable-column.js | 5 ++++ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2f190f9691..189cb29535 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => 'c8fbe125', 'conpherence.pkg.js' => '11f3e07e', 'core.pkg.css' => 'cfc3eabe', - 'core.pkg.js' => '03c1cb09', + 'core.pkg.js' => '26f1f9bf', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'e1d704ce', 'differential.pkg.js' => '634399e9', @@ -438,7 +438,7 @@ return array( 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', - 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'a0e564c2', + 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'f62bfb39', 'rsrc/js/application/conpherence/behavior-menu.js' => '9eb55204', 'rsrc/js/application/conpherence/behavior-participant-pane.js' => '8604caa8', 'rsrc/js/application/conpherence/behavior-pontificate.js' => 'f2e58483', @@ -699,7 +699,7 @@ return array( 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-drydock-live-operation-status' => '901935ef', - 'javelin-behavior-durable-column' => 'a0e564c2', + 'javelin-behavior-durable-column' => 'f62bfb39', 'javelin-behavior-editengine-reorder-configs' => 'd7a74243', 'javelin-behavior-editengine-reorder-fields' => 'b59e1e96', 'javelin-behavior-error-log' => '6882e80a', @@ -1774,16 +1774,6 @@ return array( 'javelin-util', 'phabricator-keyboard-shortcut', ), - 'a0e564c2' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-behavior-device', - 'javelin-scrollbar', - 'javelin-quicksand', - 'phabricator-keyboard-shortcut', - 'conpherence-thread-manager', - ), 'a155550f' => array( 'javelin-install', 'javelin-dom', @@ -2216,6 +2206,16 @@ return array( 'javelin-request', 'phabricator-keyboard-shortcut', ), + 'f62bfb39' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-behavior-device', + 'javelin-scrollbar', + 'javelin-quicksand', + 'phabricator-keyboard-shortcut', + 'conpherence-thread-manager', + ), 'f6555212' => array( 'javelin-install', 'javelin-reactornode', diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index dc2dfe186e..8fa73ac9f3 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -805,6 +805,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView return array( 'title' => $this->getTitle(), + 'bodyClasses' => $this->getBodyClasses(), 'aphlictDropdownData' => array( $dropdown_query->getNotificationData(), $dropdown_query->getConpherenceData(), diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js index ecdb949152..ad368edb9b 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js +++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js @@ -347,6 +347,11 @@ JX.behavior('durable-column', function(config, statics) { null, function (e) { var new_data = e.getData().newResponse; + var new_classes = new_data.bodyClasses; + if (userMinimize) { + new_classes = new_classes + ' minimize-column'; + } + document.body.className = new_classes; JX.Title.setTitle(new_data.title); }); From a591b86d910b0f7f869123e4c661c3849bf46ab7 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 1 Oct 2016 20:37:28 -0700 Subject: [PATCH 05/36] Add an icon to aphlict connection status Summary: A bit better styling, this adds an indication icon for if you're connected or not (and later, away, etc). Test Plan: Test in Notifications menu, Conpherence full, Durable Column. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16647 --- resources/celerity/map.php | 28 ++++++------- .../view/ConpherenceDurableColumnView.php | 3 ++ .../PhabricatorNotificationStatusView.php | 42 ++++++++++++++++++- .../application/base/notification-menu.css | 14 +++++-- .../conpherence/durable-column.css | 18 ++++++++ .../aphlict/behavior-aphlict-status.js | 12 +++++- 6 files changed, 95 insertions(+), 22 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 189cb29535..ed4163c4d6 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,9 +7,9 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => 'c8fbe125', + 'conpherence.pkg.css' => '5f3eb99c', 'conpherence.pkg.js' => '11f3e07e', - 'core.pkg.css' => 'cfc3eabe', + 'core.pkg.css' => '3fa66cb3', 'core.pkg.js' => '26f1f9bf', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'e1d704ce', @@ -35,7 +35,7 @@ return array( 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', 'rsrc/css/application/auth/auth.css' => '0877ed6e', 'rsrc/css/application/base/main-menu-view.css' => 'f03e17be', - 'rsrc/css/application/base/notification-menu.css' => 'b3ab500d', + 'rsrc/css/application/base/notification-menu.css' => '1e055865', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601', 'rsrc/css/application/base/phui-theme.css' => '027ba77e', 'rsrc/css/application/base/standard-page-view.css' => '79176f5a', @@ -46,7 +46,7 @@ return array( 'rsrc/css/application/config/config-template.css' => '8f18fa41', 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', - 'rsrc/css/application/conpherence/durable-column.css' => 'af11a2a7', + 'rsrc/css/application/conpherence/durable-column.css' => '6127de1b', 'rsrc/css/application/conpherence/header-pane.css' => '517de9fe', 'rsrc/css/application/conpherence/menu.css' => '78c7b811', 'rsrc/css/application/conpherence/message-pane.css' => '0d7dff02', @@ -428,7 +428,7 @@ return array( 'rsrc/js/application/aphlict/Aphlict.js' => '5359e785', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '49e20786', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'fb20ac8d', - 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', + 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'edd1ba66', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443', @@ -618,7 +618,7 @@ return array( 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', 'config-page-css' => '8798e14f', - 'conpherence-durable-column-view' => 'af11a2a7', + 'conpherence-durable-column-view' => '6127de1b', 'conpherence-header-pane-css' => '517de9fe', 'conpherence-menu-css' => '78c7b811', 'conpherence-message-pane-css' => '0d7dff02', @@ -653,7 +653,7 @@ return array( 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => '49e20786', 'javelin-behavior-aphlict-listen' => 'fb20ac8d', - 'javelin-behavior-aphlict-status' => 'ea681761', + 'javelin-behavior-aphlict-status' => '5e2634b9', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-crop' => 'fa0f4fc2', 'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22', @@ -860,7 +860,7 @@ return array( 'phabricator-nav-view-css' => 'b29426e9', 'phabricator-notification' => 'ccf1cbf8', 'phabricator-notification-css' => '3f6c89c9', - 'phabricator-notification-menu-css' => 'b3ab500d', + 'phabricator-notification-menu-css' => '1e055865', 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'cfd23f37', @@ -1424,6 +1424,12 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + '5e2634b9' => array( + 'javelin-behavior', + 'javelin-aphlict', + 'phabricator-phtize', + 'javelin-dom', + ), '5e9f347c' => array( 'javelin-behavior', 'multirow-row-manager', @@ -2159,12 +2165,6 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), - 'ea681761' => array( - 'javelin-behavior', - 'javelin-aphlict', - 'phabricator-phtize', - 'javelin-dom', - ), 'edd1ba66' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/conpherence/view/ConpherenceDurableColumnView.php b/src/applications/conpherence/view/ConpherenceDurableColumnView.php index ed5c072c5d..b4dc244d76 100644 --- a/src/applications/conpherence/view/ConpherenceDurableColumnView.php +++ b/src/applications/conpherence/view/ConpherenceDurableColumnView.php @@ -336,6 +336,8 @@ final class ConpherenceDurableColumnView extends AphrontTagView { )); } + $status = new PhabricatorNotificationStatusView(); + return phutil_tag( 'div', @@ -343,6 +345,7 @@ final class ConpherenceDurableColumnView extends AphrontTagView { 'class' => 'conpherence-durable-column-header-inner', ), array( + $status, javelin_tag( 'div', array( diff --git a/src/applications/notification/view/PhabricatorNotificationStatusView.php b/src/applications/notification/view/PhabricatorNotificationStatusView.php index 988587f65c..e962395bba 100644 --- a/src/applications/notification/view/PhabricatorNotificationStatusView.php +++ b/src/applications/notification/view/PhabricatorNotificationStatusView.php @@ -16,6 +16,20 @@ final class PhabricatorNotificationStatusView extends AphrontTagView { 'open' => pht('Connected'), 'closed' => pht('Disconnected'), ), + 'icon' => array( + 'open' => array( + 'icon' => 'fa-circle', + 'color' => 'green', + ), + 'setup' => array( + 'icon' => 'fa-circle', + 'color' => 'yellow', + ), + 'closed' => array( + 'icon' => 'fa-circle', + 'color' => 'red', + ), + ), )); return array( @@ -26,9 +40,33 @@ final class PhabricatorNotificationStatusView extends AphrontTagView { protected function getTagContent() { $have = PhabricatorEnv::getEnvConfig('notification.servers'); if ($have) { - return pht('Connecting...'); + $icon = id(new PHUIIconView()) + ->setIcon('fa-circle-o yellow'); + $text = pht('Connecting...'); + return phutil_tag( + 'span', + array( + 'class' => 'connection-status-text '. + 'aphlict-connection-status-connecting', + ), + array( + $icon, + $text, + )); } else { - return pht('Notification server not enabled'); + $text = pht('Notification server not enabled'); + $icon = id(new PHUIIconView()) + ->setIcon('fa-circle-o grey'); + return phutil_tag( + 'span', + array( + 'class' => 'connection-status-text '. + 'aphlict-connection-status-notenabled', + ), + array( + $icon, + $text, + )); } } diff --git a/webroot/rsrc/css/application/base/notification-menu.css b/webroot/rsrc/css/application/base/notification-menu.css index 84569c7d6b..82a5aa0220 100644 --- a/webroot/rsrc/css/application/base/notification-menu.css +++ b/webroot/rsrc/css/application/base/notification-menu.css @@ -124,10 +124,16 @@ color: {$lightgreytext}; } -.aphlict-connection-status .aphlict-connection-status-connected { - color: {$green}; +.aphlict-connection-status { + position: relative; } -.aphlict-connection-status .aphlict-connection-status-error { - color: {$red}; +.aphlict-connection-status .phui-icon-view { + font-size: 9px; + position: absolute; + top: 4px; +} + +.aphlict-connection-status .connection-status-text { + margin-left: 12px; } diff --git a/webroot/rsrc/css/application/conpherence/durable-column.css b/webroot/rsrc/css/application/conpherence/durable-column.css index f4320ed1a2..51d71f196d 100644 --- a/webroot/rsrc/css/application/conpherence/durable-column.css +++ b/webroot/rsrc/css/application/conpherence/durable-column.css @@ -45,6 +45,24 @@ display: none; } +.conpherence-durable-column-header .aphlict-connection-status { + width: 6px; + float: left; + height: 18px; + margin-left: 10px; + margin-top: 8px; +} + +.conpherence-durable-column-header .aphlict-connection-status + .connection-status-text { + display: none; +} + +.conpherence-durable-column-header .aphlict-connection-status + .phui-icon-view { + font-size: 10px; +} + .conpherence-durable-column-header .phabricator-application-menu .phui-list-item-view { margin: 0; diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-status.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-status.js index 9b8a87c6c9..cb35df235f 100644 --- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-status.js +++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-status.js @@ -24,14 +24,22 @@ JX.behavior('aphlict-status', function(config) { } var status = client.getStatus(); + var icon = config.icon[status]; var status_node = JX.$N( 'span', { - className: 'aphlict-connection-status-' + status + className: 'connection-status-text aphlict-connection-status-' + status }, pht(status)); - JX.DOM.setContent(node, status_node); + var icon_node = new JX.PHUIXIconView() + .setIcon(icon['icon']) + .setColor(icon['color']) + .getNode(); + + var content = [icon_node, ' ', status_node]; + + JX.DOM.setContent(node, content); } JX.Aphlict.listen('didChangeStatus', update); From da1ed2c63ac9b45c34fec4f14c178bd4f2a9fc0f Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 2 Oct 2016 20:09:43 -0700 Subject: [PATCH 06/36] Don't mark a thread as seen if durable column is minimized Summary: More work to do here on the JS side, but this at least makes sure users with a small chat window have some notification marked that new replies have not been seen. Test Plan: Open two windows. Window 1 has durable minimized, Window 2 is full conpherence. Send a message from Window 2, see header count in Window 1 increase. Repeat with durable open, see no change in window. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16650 --- .../controller/ConpherenceUpdateController.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 5a1ad4e8fa..dbcd02e50e 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -607,8 +607,13 @@ final class ConpherenceUpdateController $user, $conpherence, !$minimal_display); - $participant_obj = $conpherence->getParticipant($user->getPHID()); - $participant_obj->markUpToDate($conpherence, $data['latest_transaction']); + $key = PhabricatorConpherenceColumnMinimizeSetting::SETTINGKEY; + $minimized = $user->getUserSetting($key); + if (!$minimized) { + $participant_obj = $conpherence->getParticipant($user->getPHID()); + $participant_obj + ->markUpToDate($conpherence, $data['latest_transaction']); + } } else if ($need_transactions) { $non_update = true; $data = array(); From 55a56c09e7fd8c1dddc9c1d2fede8635c7723ef4 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 2 Oct 2016 19:41:11 -0700 Subject: [PATCH 07/36] Always reset scroll after durable column is maximized Summary: When the durable column is re-opened, scroll user to latest message. Though later we should scroll them to the last read. Test Plan: Minimize window, reload page, pop up window, see proper scroll position. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16649 --- resources/celerity/map.php | 26 +++++++++---------- .../conpherence/behavior-durable-column.js | 5 ++++ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index ed4163c4d6..d40b9bc210 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => '5f3eb99c', 'conpherence.pkg.js' => '11f3e07e', 'core.pkg.css' => '3fa66cb3', - 'core.pkg.js' => '26f1f9bf', + 'core.pkg.js' => '30185d95', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'e1d704ce', 'differential.pkg.js' => '634399e9', @@ -438,7 +438,7 @@ return array( 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', - 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'f62bfb39', + 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'c5238acb', 'rsrc/js/application/conpherence/behavior-menu.js' => '9eb55204', 'rsrc/js/application/conpherence/behavior-participant-pane.js' => '8604caa8', 'rsrc/js/application/conpherence/behavior-pontificate.js' => 'f2e58483', @@ -699,7 +699,7 @@ return array( 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-drydock-live-operation-status' => '901935ef', - 'javelin-behavior-durable-column' => 'f62bfb39', + 'javelin-behavior-durable-column' => 'c5238acb', 'javelin-behavior-editengine-reorder-configs' => 'd7a74243', 'javelin-behavior-editengine-reorder-fields' => 'b59e1e96', 'javelin-behavior-error-log' => '6882e80a', @@ -1963,6 +1963,16 @@ return array( 'javelin-install', 'javelin-dom', ), + 'c5238acb' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-behavior-device', + 'javelin-scrollbar', + 'javelin-quicksand', + 'phabricator-keyboard-shortcut', + 'conpherence-thread-manager', + ), 'c587b80f' => array( 'javelin-install', ), @@ -2206,16 +2216,6 @@ return array( 'javelin-request', 'phabricator-keyboard-shortcut', ), - 'f62bfb39' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-behavior-device', - 'javelin-scrollbar', - 'javelin-quicksand', - 'phabricator-keyboard-shortcut', - 'conpherence-thread-manager', - ), 'f6555212' => array( 'javelin-install', 'javelin-reactornode', diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js index ad368edb9b..a7fe3fbef7 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js +++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js @@ -66,6 +66,11 @@ JX.behavior('durable-column', function(config, statics) { JX.DOM.alterClass(document.body, 'minimize-column', userMinimize); JX.Stratcom.invoke('resize'); + if (!userMinimize) { + var messages = _getColumnMessagesNode(); + scrollbar.scrollTo(messages.scrollHeight); + } + new JX.Request(config.minimizeURI) .setData({value: (userMinimize ? 1 : 0)}) .send(); From c7a6cfd87c443fe72f5f998d7d7835c512ba663c Mon Sep 17 00:00:00 2001 From: Nevogd Date: Mon, 3 Oct 2016 10:14:30 -0700 Subject: [PATCH 08/36] Fix 'Branches' typo in ActionsManagementPanel Summary: Fix typo 'Branches' in the panel header for the Diffusion Actions management panel. Test Plan: Saw 'Actions' in the panel heading Reviewers: chad, epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16654 --- .../management/DiffusionRepositoryActionsManagementPanel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php index 3a82879b26..1f47a68de3 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php @@ -78,7 +78,7 @@ final class DiffusionRepositoryActionsManagementPanel $autoclose = phutil_tag('em', array(), $autoclose); $view->addProperty(pht('Autoclose'), $autoclose); - return $this->newBox(pht('Branches'), $view); + return $this->newBox(pht('Actions'), $view); } } From 1d00bc91801992fb9648defcce8d1095ed32b70b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 3 Oct 2016 10:32:43 -0700 Subject: [PATCH 09/36] Clean up nux state with durable column Summary: Remove policy icons from durable column, create a basic nux layout and style. Test Plan: leave all rooms, pop open chat, see helpful text and button. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16655 --- resources/celerity/map.php | 6 +-- .../view/ConpherenceDurableColumnView.php | 53 +++++++------------ .../conpherence/durable-column.css | 21 ++++---- 3 files changed, 33 insertions(+), 47 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d40b9bc210..cd5f778d74 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => '5f3eb99c', + 'conpherence.pkg.css' => '1bc6cd0d', 'conpherence.pkg.js' => '11f3e07e', 'core.pkg.css' => '3fa66cb3', 'core.pkg.js' => '30185d95', @@ -46,7 +46,7 @@ return array( 'rsrc/css/application/config/config-template.css' => '8f18fa41', 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', - 'rsrc/css/application/conpherence/durable-column.css' => '6127de1b', + 'rsrc/css/application/conpherence/durable-column.css' => '44bcaa19', 'rsrc/css/application/conpherence/header-pane.css' => '517de9fe', 'rsrc/css/application/conpherence/menu.css' => '78c7b811', 'rsrc/css/application/conpherence/message-pane.css' => '0d7dff02', @@ -618,7 +618,7 @@ return array( 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', 'config-page-css' => '8798e14f', - 'conpherence-durable-column-view' => '6127de1b', + 'conpherence-durable-column-view' => '44bcaa19', 'conpherence-header-pane-css' => '517de9fe', 'conpherence-menu-css' => '78c7b811', 'conpherence-message-pane-css' => '0d7dff02', diff --git a/src/applications/conpherence/view/ConpherenceDurableColumnView.php b/src/applications/conpherence/view/ConpherenceDurableColumnView.php index b4dc244d76..786cbea174 100644 --- a/src/applications/conpherence/view/ConpherenceDurableColumnView.php +++ b/src/applications/conpherence/view/ConpherenceDurableColumnView.php @@ -150,13 +150,14 @@ final class ConpherenceDurableColumnView extends AphrontTagView { $icon_bar = null; if ($this->conpherences) { - $icon_bar = phutil_tag( - 'div', - array( - 'class' => 'conpherence-durable-column-icon-bar', - ), - $this->buildIconBar()); + $icon_bar = $this->buildIconBar(); } + $icon_bar = phutil_tag( + 'div', + array( + 'class' => 'conpherence-durable-column-icon-bar', + ), + $icon_bar); $transactions = $this->buildTransactions(); @@ -198,19 +199,6 @@ final class ConpherenceDurableColumnView extends AphrontTagView { ); } - private function getPolicyIcon( - ConpherenceThread $conpherence, - array $policy_objects) { - - assert_instances_of($policy_objects, 'PhabricatorPolicy'); - - $icon = $conpherence->getPolicyIconName($policy_objects); - $icon = id(new PHUIIconView()) - ->addClass('mmr') - ->setIcon($icon); - return $icon; - } - private function buildIconBar() { $icons = array(); $selected_conpherence = $this->getSelectedConpherence(); @@ -222,12 +210,10 @@ final class ConpherenceDurableColumnView extends AphrontTagView { $classes[] = 'selected'; } $data = $conpherence->getDisplayData($this->getUser()); - $icon = $this->getPolicyIcon($conpherence, $this->getPolicyObjects()); $thread_title = phutil_tag( 'span', array(), array( - $icon, $data['title'], )); $image = $data['image']; @@ -324,17 +310,18 @@ final class ConpherenceDurableColumnView extends AphrontTagView { ->addMenuItem($minimize) ->addClass('phabricator-application-menu'); - $header = null; if ($conpherence) { $data = $conpherence->getDisplayData($this->getUser()); $header = phutil_tag( 'span', array(), - array( - $this->getPolicyIcon($conpherence, $this->getPolicyObjects()), - $data['title'], - )); - } + $data['title']); + } else { + $header = phutil_tag( + 'span', + array(), + pht('Conpherence')); + } $status = new PhabricatorNotificationStatusView(); @@ -406,22 +393,22 @@ final class ConpherenceDurableColumnView extends AphrontTagView { if (!$this->getVisible() || $this->getInitialLoad()) { return pht('Loading...'); } - return array( + $view = array( phutil_tag( 'div', array( - 'class' => 'mmb', + 'class' => 'column-no-rooms-text', ), - pht('You are not in any rooms yet.')), + pht('You have not joined any rooms yet.')), javelin_tag( 'a', array( - 'href' => '/conpherence/new/', + 'href' => '/conpherence/search/', 'class' => 'button grey', - 'sigil' => 'workflow', ), - pht('Create a Room')), + pht('Find Rooms')), ); + return phutil_tag_div('column-no-rooms', $view); } $data = ConpherenceTransactionRenderer::renderTransactions( diff --git a/webroot/rsrc/css/application/conpherence/durable-column.css b/webroot/rsrc/css/application/conpherence/durable-column.css index 51d71f196d..f94fa1f6b8 100644 --- a/webroot/rsrc/css/application/conpherence/durable-column.css +++ b/webroot/rsrc/css/application/conpherence/durable-column.css @@ -118,11 +118,6 @@ padding: 10px 8px 10px 8px; } -.conpherence-durable-column-header-text .phui-icon-view { - color: #fff; - text-shadow: 1px 1px 0 rgba(0,0,0,.6); -} - .conpherence-durable-column-icon-bar { width: 36px; background-color: {$lightgreybackground}; @@ -171,6 +166,16 @@ overflow-x: hidden; } +.conpherence-durable-column .column-no-rooms { + padding: 12px 8px; +} + +.conpherence-durable-column .column-no-rooms-text { + color: {$greytext}; + font-style: italic; + margin-bottom: 16px; +} + .conpherence-durable-column-transactions { padding: 8px 12px 0; } @@ -318,12 +323,6 @@ img { text-shadow: none; } -.minimize-column .conpherence-durable-column - .conpherence-durable-column-header-text .phui-icon-view { - color: {$darkbluetext}; - text-shadow: none; -} - .minimize-column .conpherence-durable-column .conpherence-durable-column-header .phabricator-application-menu .phui-list-item-icon.phui-font-fa { From 3ce3ce957d3fe9ef815582cfb076be64db068678 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 3 Oct 2016 12:20:00 -0700 Subject: [PATCH 10/36] Clean up css race condition in Conpherence notification menu Summary: Depending on when packages loaded, this CSS sometimes gets overwritten. Make it more specific and always present. Test Plan: Reload a lot Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16657 --- resources/celerity/map.php | 6 +++--- .../controller/ConpherenceNotificationPanelController.php | 2 +- webroot/rsrc/css/application/conpherence/notification.css | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index cd5f778d74..153b749f61 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => '1bc6cd0d', + 'conpherence.pkg.css' => 'ffcfadf9', 'conpherence.pkg.js' => '11f3e07e', 'core.pkg.css' => '3fa66cb3', 'core.pkg.js' => '30185d95', @@ -50,7 +50,7 @@ return array( 'rsrc/css/application/conpherence/header-pane.css' => '517de9fe', 'rsrc/css/application/conpherence/menu.css' => '78c7b811', 'rsrc/css/application/conpherence/message-pane.css' => '0d7dff02', - 'rsrc/css/application/conpherence/notification.css' => '65dd0e79', + 'rsrc/css/application/conpherence/notification.css' => '965db05b', 'rsrc/css/application/conpherence/participant-pane.css' => '7bba0b56', 'rsrc/css/application/conpherence/transaction.css' => '46253e19', 'rsrc/css/application/conpherence/update.css' => '53bc527a', @@ -622,7 +622,7 @@ return array( 'conpherence-header-pane-css' => '517de9fe', 'conpherence-menu-css' => '78c7b811', 'conpherence-message-pane-css' => '0d7dff02', - 'conpherence-notification-css' => '65dd0e79', + 'conpherence-notification-css' => '965db05b', 'conpherence-participant-pane-css' => '7bba0b56', 'conpherence-thread-manager' => '01774ab2', 'conpherence-transaction-css' => '46253e19', diff --git a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php index 429f7da9f1..9af802d2e6 100644 --- a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php +++ b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php @@ -6,6 +6,7 @@ final class ConpherenceNotificationPanelController public function handleRequest(AphrontRequest $request) { $user = $request->getUser(); $conpherences = array(); + require_celerity_resource('conpherence-notification-css'); $unread_status = ConpherenceParticipationStatus::BEHIND; $participant_data = id(new ConpherenceParticipantQuery()) @@ -25,7 +26,6 @@ final class ConpherenceNotificationPanelController } if ($conpherences) { - require_celerity_resource('conpherence-notification-css'); // re-order the conpherences based on participation data $conpherences = array_select_keys( $conpherences, array_keys($participant_data)); diff --git a/webroot/rsrc/css/application/conpherence/notification.css b/webroot/rsrc/css/application/conpherence/notification.css index ec729a5170..6347b40ee3 100644 --- a/webroot/rsrc/css/application/conpherence/notification.css +++ b/webroot/rsrc/css/application/conpherence/notification.css @@ -2,8 +2,7 @@ * @provides conpherence-notification-css */ -/* kill styles on phabricator-notification */ -.conpherence-notification { +.phabricator-notification.conpherence-notification { padding: 0; } From 75fe750ee3671170c6c0e7dc8fbeeb50d053edc8 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 3 Oct 2016 13:39:39 -0700 Subject: [PATCH 11/36] Allow Conpherence room images up to 200px Summary: Provide higher resolution for Conpherence room images. Fixes T11728 Test Plan: Upload a new photo, see it pulls in 200px image as background. {F1858660} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T11728 Differential Revision: https://secure.phabricator.com/D16659 --- .../conpherence/constants/ConpherenceImageData.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/conpherence/constants/ConpherenceImageData.php b/src/applications/conpherence/constants/ConpherenceImageData.php index 41f00a9850..788f7b2e06 100644 --- a/src/applications/conpherence/constants/ConpherenceImageData.php +++ b/src/applications/conpherence/constants/ConpherenceImageData.php @@ -5,7 +5,7 @@ final class ConpherenceImageData extends ConpherenceConstants { const SIZE_ORIG = 'original'; const SIZE_CROP = 'crop'; - const CROP_WIDTH = 35; - const CROP_HEIGHT = 35; + const CROP_WIDTH = 200; + const CROP_HEIGHT = 200; } From 60ce989247d6ac011bfcc33ad7aab8308aa690c7 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 3 Oct 2016 13:21:06 -0700 Subject: [PATCH 12/36] Return more transaction types in Conpherence notification menu Summary: Unclear these are worth sending, but mostly seems useful. Returns `getTitle` for the transaction if it's not a message. Fixes T10683 Test Plan: Leave rooms, change names, add pictures. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T10683 Differential Revision: https://secure.phabricator.com/D16658 --- .../conpherence/storage/ConpherenceThread.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index 80efc92f5a..d2e7ca8be1 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -284,11 +284,18 @@ final class ConpherenceThread extends ConpherenceDAO $message_title = null; if ($subtitle_mode == 'message') { $message_transaction = null; + $action_transaction = null; foreach ($transactions as $transaction) { switch ($transaction->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $message_transaction = $transaction; - break 2; + break; + case ConpherenceTransaction::TYPE_TITLE: + case ConpherenceTransaction::TYPE_TOPIC: + case ConpherenceTransaction::TYPE_PICTURE: + case ConpherenceTransaction::TYPE_PARTICIPANTS: + $action_transaction = $transaction; + break; default: break; } @@ -303,6 +310,11 @@ final class ConpherenceThread extends ConpherenceDAO ->truncateString( $message_transaction->getComment()->getContent())); } + if ($action_transaction) { + $message_title = id(clone $action_transaction) + ->setRenderingTarget(PhabricatorApplicationTransaction::TARGET_TEXT) + ->getTitle(); + } } switch ($subtitle_mode) { case 'recent': From e828e7f8898b05be0a55c27dc4af0b7498bf22dc Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 3 Oct 2016 11:12:21 -0700 Subject: [PATCH 13/36] Hide extra rooms when you have too many in Conpherence Summary: This probably stopped working when we switch to a standard nav view. Re-scope CSS. Also make scrolling to last room a little easier. Test Plan: Make 20 rooms, see the old ones vanish. Click See More, scroll to bottom, click last room Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16656 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/application/conpherence/menu.css | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 153b749f61..b71cbcaaa4 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => 'ffcfadf9', + 'conpherence.pkg.css' => 'd2356a2b', 'conpherence.pkg.js' => '11f3e07e', 'core.pkg.css' => '3fa66cb3', 'core.pkg.js' => '30185d95', @@ -48,7 +48,7 @@ return array( 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/conpherence/durable-column.css' => '44bcaa19', 'rsrc/css/application/conpherence/header-pane.css' => '517de9fe', - 'rsrc/css/application/conpherence/menu.css' => '78c7b811', + 'rsrc/css/application/conpherence/menu.css' => '4f51db5a', 'rsrc/css/application/conpherence/message-pane.css' => '0d7dff02', 'rsrc/css/application/conpherence/notification.css' => '965db05b', 'rsrc/css/application/conpherence/participant-pane.css' => '7bba0b56', @@ -620,7 +620,7 @@ return array( 'config-page-css' => '8798e14f', 'conpherence-durable-column-view' => '44bcaa19', 'conpherence-header-pane-css' => '517de9fe', - 'conpherence-menu-css' => '78c7b811', + 'conpherence-menu-css' => '4f51db5a', 'conpherence-message-pane-css' => '0d7dff02', 'conpherence-notification-css' => '965db05b', 'conpherence-participant-pane-css' => '7bba0b56', diff --git a/webroot/rsrc/css/application/conpherence/menu.css b/webroot/rsrc/css/application/conpherence/menu.css index aa5798bfff..e0719cdcc8 100644 --- a/webroot/rsrc/css/application/conpherence/menu.css +++ b/webroot/rsrc/css/application/conpherence/menu.css @@ -26,10 +26,14 @@ padding: 4px 0 4px 8px; } -.conpherence-menu-pane .phui-list-item-view.hidden { +.conpherence-menu-pane.phabricator-side-menu .phui-list-item-view.hidden { display: none; } +.phui-list-view.conpherence-menu { + margin-bottom: 20px; +} + .conpherence-menu-pane.phabricator-side-menu .room-list-href { padding: 10px 0 9px 8px; display: inline-block; From a3b2e422fee86a1bf760e364dc36bb78b8d21c61 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 4 Oct 2016 08:13:06 -0700 Subject: [PATCH 14/36] Use most recent transaction in Conpherence notification menu Summary: Not sure this ever worked correctly, but now once we have a supported action, skip the rest of the transactions. Currently you'll see a random old post. Test Plan: Test multiple rooms in various states with new messages, edits, new room titles, etc. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16660 --- src/applications/conpherence/storage/ConpherenceThread.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index d2e7ca8be1..ca0dc98cf5 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -286,6 +286,9 @@ final class ConpherenceThread extends ConpherenceDAO $message_transaction = null; $action_transaction = null; foreach ($transactions as $transaction) { + if ($message_transaction || $action_transaction) { + break; + } switch ($transaction->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $message_transaction = $transaction; From 02b5a2b39b79b0210524565a6b9dd32d3ed58e21 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 4 Oct 2016 16:15:52 -0700 Subject: [PATCH 15/36] Add Room Image to Conpherence header Summary: This adds the room image to the main header in full Conpherence. It's nice, plus I plan to move the image edit workflow to it to simplify the move to EditEngine. I plan to build some default images for Conpherence which should be better about denoting the room, not just the last person writing. Test Plan: Click on lots of rooms with and without topics. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16666 --- resources/celerity/map.php | 6 +++--- .../controller/ConpherenceController.php | 2 +- .../application/conpherence/header-pane.css | 21 ++++++++++++++----- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b71cbcaaa4..df61b63b29 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => 'd2356a2b', + 'conpherence.pkg.css' => '3c08b01f', 'conpherence.pkg.js' => '11f3e07e', 'core.pkg.css' => '3fa66cb3', 'core.pkg.js' => '30185d95', @@ -47,7 +47,7 @@ return array( 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/conpherence/durable-column.css' => '44bcaa19', - 'rsrc/css/application/conpherence/header-pane.css' => '517de9fe', + 'rsrc/css/application/conpherence/header-pane.css' => '6a032d4c', 'rsrc/css/application/conpherence/menu.css' => '4f51db5a', 'rsrc/css/application/conpherence/message-pane.css' => '0d7dff02', 'rsrc/css/application/conpherence/notification.css' => '965db05b', @@ -619,7 +619,7 @@ return array( 'config-options-css' => '0ede4c9b', 'config-page-css' => '8798e14f', 'conpherence-durable-column-view' => '44bcaa19', - 'conpherence-header-pane-css' => '517de9fe', + 'conpherence-header-pane-css' => '6a032d4c', 'conpherence-menu-css' => '4f51db5a', 'conpherence-message-pane-css' => '0d7dff02', 'conpherence-notification-css' => '965db05b', diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index c4750a08a8..fb0d0a710f 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -59,7 +59,7 @@ abstract class ConpherenceController extends PhabricatorController { $header = id(new PHUIHeaderView()) ->setHeader($data['title']) ->setSubheader($data['topic']) - ->addClass((!$data['topic']) ? 'conpherence-no-topic' : null); + ->setImage($data['image']); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, diff --git a/webroot/rsrc/css/application/conpherence/header-pane.css b/webroot/rsrc/css/application/conpherence/header-pane.css index 9b0c5e98e5..0403d9d633 100644 --- a/webroot/rsrc/css/application/conpherence/header-pane.css +++ b/webroot/rsrc/css/application/conpherence/header-pane.css @@ -2,9 +2,6 @@ * @provides conpherence-header-pane-css */ -.conpherence-header-pane { -} - .conpherence-header-pane .phui-header-shell { padding: 8px 16px 10px; min-height: 38px; @@ -23,8 +20,22 @@ margin: 0; } -.conpherence-header-pane .phui-header-shell.conpherence-no-topic { - padding: 15px 16px 5px; +.conpherence-header-pane .phui-header-col1 { + width: 46px; + height: 35px; +} + +.conpherence-header-pane .phui-header-image { + height: 35px; + width: 35px; + background-size: 35px; + position: absolute; + top: 4px; + left: 0; +} + +.conpherence-header-pane .phui-header-col2 { + height: 40px; } .conpherence-header-pane .phui-header-action-list .phui-header-action-item From 524906a4396a33974974977065bf0ae05ca7dc5c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 4 Oct 2016 14:01:24 -0700 Subject: [PATCH 16/36] Remove Crop Image from Conpherence Edit UI Summary: Ref T11730. Removes the front end crop feature. Will follow up with proper removal, but this seems broken outright. Test Plan: Edit a room, don't seen "Crop" feature. Upload new photo, works fine. - grep for `ConpherencePicCropControl` - grep for `aphront-crop` Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T11730 Differential Revision: https://secure.phabricator.com/D16665 --- resources/celerity/map.php | 8 -- src/__phutil_library_map__.php | 2 - .../ConpherenceUpdateController.php | 4 - .../view/ConpherencePicCropControl.php | 78 ------------------ webroot/rsrc/js/core/behavior-crop.js | 82 ------------------- 5 files changed, 174 deletions(-) delete mode 100644 src/applications/conpherence/view/ConpherencePicCropControl.php delete mode 100644 webroot/rsrc/js/core/behavior-crop.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index df61b63b29..510c2f0ddc 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -547,7 +547,6 @@ return array( 'rsrc/js/core/behavior-autofocus.js' => '7319e029', 'rsrc/js/core/behavior-badge-view.js' => '8ff5e24c', 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', - 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', 'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96', 'rsrc/js/core/behavior-device.js' => 'bb1dd507', @@ -655,7 +654,6 @@ return array( 'javelin-behavior-aphlict-listen' => 'fb20ac8d', 'javelin-behavior-aphlict-status' => '5e2634b9', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', - 'javelin-behavior-aphront-crop' => 'fa0f4fc2', 'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22', 'javelin-behavior-aphront-form-disable-on-submit' => '5c54cbf3', 'javelin-behavior-aphront-more' => 'a80d0378', @@ -2243,12 +2241,6 @@ return array( 'javelin-util', 'phabricator-busy', ), - 'fa0f4fc2' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-vector', - 'javelin-magical-init', - ), 'fb20ac8d' => array( 'javelin-behavior', 'javelin-aphlict', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f4a7ba2fd3..794c7ca851 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -305,7 +305,6 @@ phutil_register_library_map(array( 'ConpherenceParticipantQuery' => 'applications/conpherence/query/ConpherenceParticipantQuery.php', 'ConpherenceParticipantView' => 'applications/conpherence/view/ConpherenceParticipantView.php', 'ConpherenceParticipationStatus' => 'applications/conpherence/constants/ConpherenceParticipationStatus.php', - 'ConpherencePicCropControl' => 'applications/conpherence/view/ConpherencePicCropControl.php', 'ConpherenceQueryThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php', 'ConpherenceQueryTransactionConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryTransactionConduitAPIMethod.php', 'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php', @@ -4782,7 +4781,6 @@ phutil_register_library_map(array( 'ConpherenceParticipantQuery' => 'PhabricatorOffsetPagedQuery', 'ConpherenceParticipantView' => 'AphrontView', 'ConpherenceParticipationStatus' => 'ConpherenceConstants', - 'ConpherencePicCropControl' => 'AphrontFormControl', 'ConpherenceQueryThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceQueryTransactionConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler', diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index dbcd02e50e..d807c2b4dd 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -506,10 +506,6 @@ final class ConpherenceUpdateController array( 'src' => $crop_uri, )))) - ->appendChild( - id(new ConpherencePicCropControl()) - ->setLabel(pht('Crop Image')) - ->setValue($image)) ->appendChild( id(new ConpherenceFormDragAndDropUploadControl()) ->setLabel(pht('Change Image'))); diff --git a/src/applications/conpherence/view/ConpherencePicCropControl.php b/src/applications/conpherence/view/ConpherencePicCropControl.php deleted file mode 100644 index 2cb869f93c..0000000000 --- a/src/applications/conpherence/view/ConpherencePicCropControl.php +++ /dev/null @@ -1,78 +0,0 @@ -getValue(); - - if ($file === null) { - return phutil_tag( - 'img', - array( - 'src' => PhabricatorUser::getDefaultProfileImageURI(), - ), - ''); - } - - $c_id = celerity_generate_unique_node_id(); - $metadata = $file->getMetadata(); - $scale = PhabricatorImageTransformer::getScaleForCrop( - $file, - $width, - $height); - - Javelin::initBehavior( - 'aphront-crop', - array( - 'cropBoxID' => $c_id, - 'width' => $width, - 'height' => $height, - 'scale' => $scale, - 'imageH' => $metadata[PhabricatorFile::METADATA_IMAGE_HEIGHT], - 'imageW' => $metadata[PhabricatorFile::METADATA_IMAGE_WIDTH], - )); - - return javelin_tag( - 'div', - array( - 'id' => $c_id, - 'sigil' => 'crop-box', - 'mustcapture' => true, - 'class' => 'crop-box', - ), - array( - javelin_tag( - 'img', - array( - 'src' => $file->getBestURI(), - 'class' => 'crop-image', - 'sigil' => 'crop-image', - ), - ''), - javelin_tag( - 'input', - array( - 'type' => 'hidden', - 'name' => 'image_x', - 'sigil' => 'crop-x', - ), - ''), - javelin_tag( - 'input', - array( - 'type' => 'hidden', - 'name' => 'image_y', - 'sigil' => 'crop-y', - ), - ''), - )); - } - -} diff --git a/webroot/rsrc/js/core/behavior-crop.js b/webroot/rsrc/js/core/behavior-crop.js deleted file mode 100644 index 272ccbeff8..0000000000 --- a/webroot/rsrc/js/core/behavior-crop.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @provides javelin-behavior-aphront-crop - * @requires javelin-behavior - * javelin-dom - * javelin-vector - * javelin-magical-init - */ - - JX.behavior('aphront-crop', function(config) { - - var dragging = false; - var startX, startY; - var finalX, finalY; - - var cropBox = JX.$(config.cropBoxID); - var basePos = JX.$V(cropBox); - cropBox.style.height = config.height + 'px'; - cropBox.style.width = config.width + 'px'; - var baseD = JX.$V(config.width, config.height); - - var image = JX.DOM.find(cropBox, 'img', 'crop-image'); - image.style.height = (config.imageH * config.scale) + 'px'; - image.style.width = (config.imageW * config.scale) + 'px'; - var imageD = JX.$V( - config.imageW * config.scale, - config.imageH * config.scale - ); - var minLeft = baseD.x - imageD.x; - var minTop = baseD.y - imageD.y; - - var ondrag = function(e) { - e.kill(); - dragging = true; - var p = JX.$V(e); - startX = p.x; - startY = p.y; - }; - - var onmove = function(e) { - if (!dragging) { - return; - } - e.kill(); - - var p = JX.$V(e); - var dx = startX - p.x; - var dy = startY - p.y; - var imagePos = JX.$V(image); - var moveLeft = imagePos.x - basePos.x - dx; - var moveTop = imagePos.y - basePos.y - dy; - - image.style.left = Math.min(Math.max(minLeft, moveLeft), 0) + 'px'; - image.style.top = Math.min(Math.max(minTop, moveTop), 0) + 'px'; - - // reset these; a new beginning! - startX = p.x; - startY = p.y; - - // save off where we are right now - imagePos = JX.$V(image); - finalX = Math.abs(imagePos.x - basePos.x); - finalY = Math.abs(imagePos.y - basePos.y); - JX.DOM.find(cropBox, 'input', 'crop-x').value = finalX; - JX.DOM.find(cropBox, 'input', 'crop-y').value = finalY; - }; - - var ondrop = function() { - if (!dragging) { - return; - } - dragging = false; - }; - - // NOTE: Javelin does not dispatch mousemove by default. - JX.enableDispatch(cropBox, 'mousemove'); - - JX.DOM.listen(cropBox, 'mousedown', [], ondrag); - JX.DOM.listen(cropBox, 'mousemove', [], onmove); - JX.DOM.listen(cropBox, 'mouseup', [], ondrop); - JX.DOM.listen(cropBox, 'mouseout', [], ondrop); - -}); From d68c444ffada23b2330eaaea5f5ba6020e4b0cb3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 5 Oct 2016 12:03:57 -0700 Subject: [PATCH 17/36] Convert Conpherence to use normal picture setting flows Summary: This moves room pictures out of the dialog and into it's own PictureController. Also adds a standard image (and removes the "last person to chat" picture (though we could add that back. My plan is though that direct messages use auto use the other person's photo, after we have editengine and room pictures will have a plain, replaceable image. Test Plan: Set a new room picture, remove a picture. Run migration, see old images properly set with new image. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T11730 Differential Revision: https://secure.phabricator.com/D16669 --- resources/builtin/conpherence.png | Bin 0 -> 2186 bytes resources/celerity/map.php | 6 +- .../20161005.conpherence.image.1.sql | 2 + .../20161005.conpherence.image.2.php | 34 +++ src/__phutil_library_map__.php | 4 +- .../PhabricatorConpherenceApplication.php | 33 ++- .../constants/ConpherenceImageData.php | 11 - .../ConpherenceColumnViewController.php | 6 +- .../controller/ConpherenceController.php | 5 + .../controller/ConpherenceListController.php | 2 +- ...ConpherenceNotificationPanelController.php | 2 +- .../ConpherenceRoomPictureController.php | 234 ++++++++++++++++++ .../ConpherenceUpdateController.php | 74 +----- .../controller/ConpherenceViewController.php | 2 +- .../conpherence/editor/ConpherenceEditor.php | 51 +--- .../query/ConpherenceThreadQuery.php | 91 +++---- .../conpherence/storage/ConpherenceThread.php | 64 ++--- .../storage/ConpherenceTransaction.php | 2 +- .../view/ConpherenceTransactionView.php | 1 - .../application/conpherence/header-pane.css | 4 + 20 files changed, 371 insertions(+), 257 deletions(-) create mode 100644 resources/builtin/conpherence.png create mode 100644 resources/sql/autopatches/20161005.conpherence.image.1.sql create mode 100644 resources/sql/autopatches/20161005.conpherence.image.2.php delete mode 100644 src/applications/conpherence/constants/ConpherenceImageData.php create mode 100644 src/applications/conpherence/controller/ConpherenceRoomPictureController.php diff --git a/resources/builtin/conpherence.png b/resources/builtin/conpherence.png new file mode 100644 index 0000000000000000000000000000000000000000..78bb8bdc0548f41a4121e2ed5fb2d11cc6d6c429 GIT binary patch literal 2186 zcmaKu`#;l*AIF!LY`JU;jhvH7mP^MaxmD(T&S<7aBVkKS7jm1+PVRFX%_S>yZz3&G zkz6VnVJwx15UZJ6ZaI~;+={RB2Yk;D@AvzM_v7*U;rV_%UavRPiEvB-styH#KnnIa zEOB$5`?us|H#>Da#{~qEJ86&o(3h*Py|X>@B~1P;F^%R|70$)=JGddF+$ z>Ve;`&qAKEs8WT1VO(Wpq!Vt2FO#!Rc)r(D-_SsS8zBx%hIHf8XCb3@qo?ire14*+@3}xJM(tI%u2_St*I0Z$@YsWaY(o|Ms zAwxN=Q2h3zTQBJGAIa${+CJ#?5{@~uz(71vm+eMUq<@NpR_VW;#gV1jiC#5&?+(Jp zEtI9QNfSoBx;-AbwqEY?1A0j?DCa8n-oD-z&kVtZN??wbl5;AB%U$6sX24C=!*WP?UJ}F^%cY_ zr=}`5i^B@k7>(C!)Hu{3)Raj_I!D7me;9jIe{j(9rAk`a-aUJhJo<@h&aSR%o(rTM zdnZ(dcPuPklzAMw=zAhYyIbMvg9l`FfypkpEpftHr|Kf(*E)#4fY5*dWbJ!L{-$28 z0?VX^6~*!Pk%YW>a{eMZl^EIFK7?)IxoJn#)IMo{7yEg^oq15sJ|FiYINEhY;4PoA z<=HdIv*9|%eRIpjm5#JK;ku{ySJtn*MtcAjXkW8ELrw4w$+X1M@c`=Hy?#f4% z1H&vjKJ@8qCalKL5M|?Ye%*V_fN3=vQ!HNEIv+iUIYFUM^dUHN9B(Zy;xF{f-EHCZPE90cxJKHj z(qhHrq6ta~MiaF<7OVjYUK_MUu)oeZAZBBT%{vco8hLDkAnRrBpZ9|@tpe3f0$tM( z&*s7-*>KVo*eB(X3(Pc&g*BaTp#_8++p-Q!e)?1aRilfPx>YuTQclucm1HQ6{=In-YlEca z6M47j-BBj68K}gzlaV%q7AbT$*!tP z0`+plZ*gPQm|)dERvVwdA|##9%x#80cAsF^^Ii>{`ia)A&xLT~m5y0QondAwTm{;) z^L5IuwSBv|hbQ{AYi`Ar5Di~G2!MvvcN3h0d*;Kzc6*sg^r`;!IX*a{t>fwQF8i-u zMvv-q+FxE;w66P`4>zIlT?q5l#_tEz`X&o9B5P8=vCF{bMNAe_WgG@Q!v3{6(m5R#gpdJPL7~puD>z zPZA!f=b-?Q;HFd(R{`}ii~QWyeTG126Eu=UKnN zd7Vzrrk_Rp$SAjd{X6ewKy0q4VFo%N8aG4YVpfsQX&aF!2t#-nFFMTnv8Fgrx7#Rf*Gy5%-^^4(6&A zc6T)bzu)RtWqRURZdY>9??-zCh31<#|4A1AG{}!^WP~L~aY}32oc15Tczu(lsKwFM z5U-tF^R)|UJ~-Mz+qVhqL2iD9TxUF=l1$}3B7H5Dv0ddyRP*?#H(3b7o>K|%;YCqnL(#Z$QO_b`(&#UFPHT`Loi48 z5DgG=@C%e+G!-8l9oQ~sa8qd%e@yMqitiKM;kA?*y_nU>V}l|z>KaTpVdK|Ak}*bF zde_%EASl#L6~e{EKYBP@1dG`)H*AiBG^ft{UB|NzWk!~ML~MedpP#LU_j&a_{DI04 zm7A&S#w?2`vM@o^Py})yvF;beYPNI*&7p{+^)$4aW$z9mD-{{e+#2=baz>!D!O-v$ zFU7A01_YhdBS+9iWY{t)sikh3)Eh>Hh2e*X1A9vjqXUDdds}iV{uZ+Y-}9`~Z4-fO zi9}5SoxI7`uShr7b9BTEUfV5WnrZ$_uYFhLF$Au{{9;N^WP9zuV^VYpqrTPNbb7ZNvOq91!@SY*9|hd zw&t|%Ps?dtQX%ZDI$?rZ(oAj}_RCidsZ^F){q!N2^5T<>#{@Z!{ zWGL9d0X*P=92>^a4!*qmtIxPA*>`*m`mqXltlt{BKXtmT array( - 'conpherence.pkg.css' => '3c08b01f', + 'conpherence.pkg.css' => '4601645d', 'conpherence.pkg.js' => '11f3e07e', 'core.pkg.css' => '3fa66cb3', 'core.pkg.js' => '30185d95', @@ -47,7 +47,7 @@ return array( 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/conpherence/durable-column.css' => '44bcaa19', - 'rsrc/css/application/conpherence/header-pane.css' => '6a032d4c', + 'rsrc/css/application/conpherence/header-pane.css' => '20a7028c', 'rsrc/css/application/conpherence/menu.css' => '4f51db5a', 'rsrc/css/application/conpherence/message-pane.css' => '0d7dff02', 'rsrc/css/application/conpherence/notification.css' => '965db05b', @@ -618,7 +618,7 @@ return array( 'config-options-css' => '0ede4c9b', 'config-page-css' => '8798e14f', 'conpherence-durable-column-view' => '44bcaa19', - 'conpherence-header-pane-css' => '6a032d4c', + 'conpherence-header-pane-css' => '20a7028c', 'conpherence-menu-css' => '4f51db5a', 'conpherence-message-pane-css' => '0d7dff02', 'conpherence-notification-css' => '965db05b', diff --git a/resources/sql/autopatches/20161005.conpherence.image.1.sql b/resources/sql/autopatches/20161005.conpherence.image.1.sql new file mode 100644 index 0000000000..17950986b8 --- /dev/null +++ b/resources/sql/autopatches/20161005.conpherence.image.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_conpherence.conpherence_thread + ADD profileImagePHID VARBINARY(64); diff --git a/resources/sql/autopatches/20161005.conpherence.image.2.php b/resources/sql/autopatches/20161005.conpherence.image.2.php new file mode 100644 index 0000000000..f1eb134f4a --- /dev/null +++ b/resources/sql/autopatches/20161005.conpherence.image.2.php @@ -0,0 +1,34 @@ +establishConnection('w'); +$table_name = 'conpherence_thread'; + +foreach (new LiskRawMigrationIterator($conn, $table_name) as $row) { + + $images = phutil_json_decode($row['imagePHIDs']); + if (!$images) { + return; + } + + $file_phid = idx($images, 'original'); + + $file = id(new PhabricatorFileQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($file_phid)) + ->executeOne(); + + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE); + $xformed = $xform->executeTransform($file); + $new_phid = $xformed->getPHID(); + + queryfx( + $conn, + 'UPDATE %T SET profileImagePHID = %s WHERE id = %d', + $table->getTableName(), + $new_phid, + $row['id']); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 794c7ca851..72e7854799 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -292,7 +292,6 @@ phutil_register_library_map(array( 'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php', 'ConpherenceFormDragAndDropUploadControl' => 'applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php', 'ConpherenceFulltextQuery' => 'applications/conpherence/query/ConpherenceFulltextQuery.php', - 'ConpherenceImageData' => 'applications/conpherence/constants/ConpherenceImageData.php', 'ConpherenceIndex' => 'applications/conpherence/storage/ConpherenceIndex.php', 'ConpherenceLayoutView' => 'applications/conpherence/view/ConpherenceLayoutView.php', 'ConpherenceListController' => 'applications/conpherence/controller/ConpherenceListController.php', @@ -309,6 +308,7 @@ phutil_register_library_map(array( 'ConpherenceQueryTransactionConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryTransactionConduitAPIMethod.php', 'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php', 'ConpherenceRoomListController' => 'applications/conpherence/controller/ConpherenceRoomListController.php', + 'ConpherenceRoomPictureController' => 'applications/conpherence/controller/ConpherenceRoomPictureController.php', 'ConpherenceRoomTestCase' => 'applications/conpherence/__tests__/ConpherenceRoomTestCase.php', 'ConpherenceSchemaSpec' => 'applications/conpherence/storage/ConpherenceSchemaSpec.php', 'ConpherenceTestCase' => 'applications/conpherence/__tests__/ConpherenceTestCase.php', @@ -4768,7 +4768,6 @@ phutil_register_library_map(array( 'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor', 'ConpherenceFormDragAndDropUploadControl' => 'AphrontFormControl', 'ConpherenceFulltextQuery' => 'PhabricatorOffsetPagedQuery', - 'ConpherenceImageData' => 'ConpherenceConstants', 'ConpherenceIndex' => 'ConpherenceDAO', 'ConpherenceLayoutView' => 'AphrontTagView', 'ConpherenceListController' => 'ConpherenceController', @@ -4785,6 +4784,7 @@ phutil_register_library_map(array( 'ConpherenceQueryTransactionConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler', 'ConpherenceRoomListController' => 'ConpherenceController', + 'ConpherenceRoomPictureController' => 'ConpherenceController', 'ConpherenceRoomTestCase' => 'ConpherenceTestCase', 'ConpherenceSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'ConpherenceTestCase' => 'PhabricatorTestCase', diff --git a/src/applications/conpherence/application/PhabricatorConpherenceApplication.php b/src/applications/conpherence/application/PhabricatorConpherenceApplication.php index 4bc4ab70e1..3081919503 100644 --- a/src/applications/conpherence/application/PhabricatorConpherenceApplication.php +++ b/src/applications/conpherence/application/PhabricatorConpherenceApplication.php @@ -30,20 +30,31 @@ final class PhabricatorConpherenceApplication extends PhabricatorApplication { public function getRoutes() { return array( - '/Z(?P[1-9]\d*)' => 'ConpherenceViewController', + '/Z(?P[1-9]\d*)' + => 'ConpherenceViewController', '/conpherence/' => array( - '' => 'ConpherenceListController', - 'thread/(?P[1-9]\d*)/' => 'ConpherenceListController', - '(?P[1-9]\d*)/' => 'ConpherenceViewController', + '' + => 'ConpherenceListController', + 'thread/(?P[1-9]\d*)/' + => 'ConpherenceListController', + '(?P[1-9]\d*)/' + => 'ConpherenceViewController', '(?P[1-9]\d*)/(?P[1-9]\d*)/' - => 'ConpherenceViewController', - 'columnview/' => 'ConpherenceColumnViewController', - 'new/' => 'ConpherenceNewRoomController', + => 'ConpherenceViewController', + 'columnview/' + => 'ConpherenceColumnViewController', + 'new/' + => 'ConpherenceNewRoomController', + 'picture/(?P[1-9]\d*)/' + => 'ConpherenceRoomPictureController', 'search/(?:query/(?P[^/]+)/)?' - => 'ConpherenceRoomListController', - 'panel/' => 'ConpherenceNotificationPanelController', - 'participant/(?P[1-9]\d*)/' => 'ConpherenceParticipantController', - 'update/(?P[1-9]\d*)/' => 'ConpherenceUpdateController', + => 'ConpherenceRoomListController', + 'panel/' + => 'ConpherenceNotificationPanelController', + 'participant/(?P[1-9]\d*)/' + => 'ConpherenceParticipantController', + 'update/(?P[1-9]\d*)/' + => 'ConpherenceUpdateController', ), ); } diff --git a/src/applications/conpherence/constants/ConpherenceImageData.php b/src/applications/conpherence/constants/ConpherenceImageData.php deleted file mode 100644 index 788f7b2e06..0000000000 --- a/src/applications/conpherence/constants/ConpherenceImageData.php +++ /dev/null @@ -1,11 +0,0 @@ -setViewer($user) ->withPHIDs($conpherence_phids) - ->needCropPics(true) + ->needProfileImage(true) ->needParticipantCache(true) ->execute(); $latest_conpherences = mpull($latest_conpherences, null, 'getPHID'); @@ -31,7 +31,7 @@ final class ConpherenceColumnViewController extends $conpherence = id(new ConpherenceThreadQuery()) ->setViewer($user) ->withIDs(array($request->getInt('id'))) - ->needCropPics(true) + ->needProfileImage(true) ->needTransactions(true) ->setTransactionLimit(ConpherenceThreadQuery::TRANSACTION_LIMIT) ->executeOne(); @@ -41,7 +41,7 @@ final class ConpherenceColumnViewController extends $conpherence = id(new ConpherenceThreadQuery()) ->setViewer($user) ->withPHIDs(array($participant->getConpherencePHID())) - ->needCropPics(true) + ->needProfileImage(true) ->needTransactions(true) ->setTransactionLimit(ConpherenceThreadQuery::TRANSACTION_LIMIT) ->executeOne(); diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index fb0d0a710f..389a8ce9b7 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -66,6 +66,11 @@ abstract class ConpherenceController extends PhabricatorController { $conpherence, PhabricatorPolicyCapability::CAN_EDIT); + if ($can_edit) { + $header->setImageURL( + $this->getApplicationURI('picture/'.$conpherence->getID().'/')); + } + $participating = $conpherence->getParticipantIfExists($viewer->getPHID()); $can_join = PhabricatorPolicyFilter::hasCapability( $viewer, diff --git a/src/applications/conpherence/controller/ConpherenceListController.php b/src/applications/conpherence/controller/ConpherenceListController.php index 6f06a36fb0..4e81526004 100644 --- a/src/applications/conpherence/controller/ConpherenceListController.php +++ b/src/applications/conpherence/controller/ConpherenceListController.php @@ -158,7 +158,7 @@ final class ConpherenceListController extends ConpherenceController { $conpherences = id(new ConpherenceThreadQuery()) ->setViewer($user) ->withPHIDs($conpherence_phids) - ->needCropPics(true) + ->needProfileImage(true) ->needParticipantCache(true) ->execute(); diff --git a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php index 9af802d2e6..c72490f60f 100644 --- a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php +++ b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php @@ -18,7 +18,7 @@ final class ConpherenceNotificationPanelController $conpherences = id(new ConpherenceThreadQuery()) ->setViewer($user) ->withPHIDs(array_keys($participant_data)) - ->needCropPics(true) + ->needProfileImage(true) ->needTransactions(true) ->setTransactionLimit(3 * 5) ->needParticipantCache(true) diff --git a/src/applications/conpherence/controller/ConpherenceRoomPictureController.php b/src/applications/conpherence/controller/ConpherenceRoomPictureController.php new file mode 100644 index 0000000000..8ddeae7098 --- /dev/null +++ b/src/applications/conpherence/controller/ConpherenceRoomPictureController.php @@ -0,0 +1,234 @@ +getViewer(); + $id = $request->getURIData('id'); + + $conpherence = id(new ConpherenceThreadQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->needProfileImage(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$conpherence) { + return new Aphront404Response(); + } + + $monogram = $conpherence->getMonogram(); + + $supported_formats = PhabricatorFile::getTransformableImageFormats(); + $e_file = true; + $errors = array(); + + if ($request->isFormPost()) { + $phid = $request->getStr('phid'); + $is_default = false; + if ($phid == PhabricatorPHIDConstants::PHID_VOID) { + $phid = null; + $is_default = true; + } else if ($phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($phid)) + ->executeOne(); + } else { + if ($request->getFileExists('picture')) { + $file = PhabricatorFile::newFromPHPUpload( + $_FILES['picture'], + array( + 'authorPHID' => $viewer->getPHID(), + 'canCDN' => true, + )); + } else { + $e_file = pht('Required'); + $errors[] = pht( + 'You must choose a file when uploading a new room picture.'); + } + } + + if (!$errors && !$is_default) { + if (!$file->isTransformableImage()) { + $e_file = pht('Not Supported'); + $errors[] = pht( + 'This server only supports these image formats: %s.', + implode(', ', $supported_formats)); + } else { + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE); + $xformed = $xform->executeTransform($file); + } + } + + if (!$errors) { + if ($is_default) { + $new_value = null; + } else { + $xformed->attachToObject($conpherence->getPHID()); + $new_value = $xformed->getPHID(); + } + + $xactions = array(); + $xactions[] = id(new ConpherenceTransaction()) + ->setTransactionType(ConpherenceTransaction::TYPE_PICTURE) + ->setNewValue($new_value); + + $editor = id(new ConpherenceEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($conpherence, $xactions); + + return id(new AphrontRedirectResponse())->setURI('/'.$monogram); + } + } + + $title = pht('Edit Room Picture'); + + $form = id(new PHUIFormLayoutView()) + ->setUser($viewer); + + $default_image = PhabricatorFile::loadBuiltin($viewer, 'conpherence.png'); + + $images = array(); + + $current = $conpherence->getProfileImagePHID(); + $has_current = false; + if ($current) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($current)) + ->executeOne(); + if ($file) { + if ($file->isTransformableImage()) { + $has_current = true; + $images[$current] = array( + 'uri' => $file->getBestURI(), + 'tip' => pht('Current Picture'), + ); + } + } + } + + $images[PhabricatorPHIDConstants::PHID_VOID] = array( + 'uri' => $default_image->getBestURI(), + 'tip' => pht('Default Picture'), + ); + + require_celerity_resource('people-profile-css'); + Javelin::initBehavior('phabricator-tooltips', array()); + + $buttons = array(); + foreach ($images as $phid => $spec) { + $button = javelin_tag( + 'button', + array( + 'class' => 'grey profile-image-button', + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $spec['tip'], + 'size' => 300, + ), + ), + phutil_tag( + 'img', + array( + 'height' => 50, + 'width' => 50, + 'src' => $spec['uri'], + ))); + + $button = array( + phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => 'phid', + 'value' => $phid, + )), + $button, + ); + + $button = phabricator_form( + $viewer, + array( + 'class' => 'profile-image-form', + 'method' => 'POST', + ), + $button); + + $buttons[] = $button; + } + + if ($has_current) { + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Current Picture')) + ->setValue(array_shift($buttons))); + } + + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Use Picture')) + ->setValue($buttons)); + + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $upload_form = id(new AphrontFormView()) + ->setUser($viewer) + ->setEncType('multipart/form-data') + ->appendChild( + id(new AphrontFormFileControl()) + ->setName('picture') + ->setLabel(pht('Upload Picture')) + ->setError($e_file) + ->setCaption( + pht('Supported formats: %s', implode(', ', $supported_formats)))) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton('/'.$monogram) + ->setValue(pht('Upload Picture'))); + + $upload_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Upload New Picture')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($upload_form); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($conpherence->getTitle(), '/'.$monogram); + $crumbs->addTextCrumb(pht('Room Picture')); + $crumbs->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Room Picture')) + ->setHeaderIcon('fa-camera'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + $upload_box, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, + )); + + } +} diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index d807c2b4dd..db21b3d02f 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -36,8 +36,6 @@ final class ConpherenceUpdateController $conpherence = id(new ConpherenceThreadQuery()) ->setViewer($user) ->withIDs(array($conpherence_id)) - ->needOrigPics(true) - ->needCropPics(true) ->needParticipants($need_participants) ->requireCapabilities($needed_capabilities) ->executeOne(); @@ -131,57 +129,14 @@ final class ConpherenceUpdateController break; case ConpherenceUpdateActions::METADATA: - $top = $request->getInt('image_y'); - $left = $request->getInt('image_x'); - $file_id = $request->getInt('file_id'); $title = $request->getStr('title'); $topic = $request->getStr('topic'); - if ($file_id) { - $orig_file = id(new PhabricatorFileQuery()) - ->setViewer($user) - ->withIDs(array($file_id)) - ->executeOne(); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_PICTURE) - ->setNewValue($orig_file); - $okay = $orig_file->isTransformableImage(); - if ($okay) { - $xformer = new PhabricatorImageTransformer(); - $crop_file = $xformer->executeConpherenceTransform( - $orig_file, - 0, - 0, - ConpherenceImageData::CROP_WIDTH, - ConpherenceImageData::CROP_HEIGHT); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType( - ConpherenceTransaction::TYPE_PICTURE_CROP) - ->setNewValue($crop_file->getPHID()); - } - $response_mode = 'redirect'; - } // all other metadata updates are continue requests if (!$request->isContinueRequest()) { break; } - if ($top !== null || $left !== null) { - $file = $conpherence->getImage(ConpherenceImageData::SIZE_ORIG); - $xformer = new PhabricatorImageTransformer(); - $xformed = $xformer->executeConpherenceTransform( - $file, - $top, - $left, - ConpherenceImageData::CROP_WIDTH, - ConpherenceImageData::CROP_HEIGHT); - $image_phid = $xformed->getPHID(); - - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType( - ConpherenceTransaction::TYPE_PICTURE_CROP) - ->setNewValue($image_phid); - } $title = $request->getStr('title'); $topic = $request->getStr('topic'); $xactions[] = id(new ConpherenceTransaction()) @@ -491,31 +446,6 @@ final class ConpherenceUpdateController ->setName('topic') ->setValue($conpherence->getTopic())); - $nopic = $this->getRequest()->getExists('nopic'); - $image = $conpherence->getImage(ConpherenceImageData::SIZE_ORIG); - if ($nopic) { - // do not render any pic related controls - } else if ($image) { - $crop_uri = $conpherence->loadImageURI(ConpherenceImageData::SIZE_CROP); - $form - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Image')) - ->setValue(phutil_tag( - 'img', - array( - 'src' => $crop_uri, - )))) - ->appendChild( - id(new ConpherenceFormDragAndDropUploadControl()) - ->setLabel(pht('Change Image'))); - } else { - $form - ->appendChild( - id(new ConpherenceFormDragAndDropUploadControl()) - ->setLabel(pht('Image'))); - } - $policies = id(new PhabricatorPolicyQuery()) ->setViewer($user) ->setObject($conpherence) @@ -567,7 +497,6 @@ final class ConpherenceUpdateController $latest_transaction_id) { $minimal_display = $this->getRequest()->getExists('minimal_display'); - $need_widget_data = false; $need_transactions = false; $need_participant_cache = true; switch ($action) { @@ -578,7 +507,6 @@ final class ConpherenceUpdateController case ConpherenceUpdateActions::MESSAGE: case ConpherenceUpdateActions::ADD_PERSON: $need_transactions = true; - $need_widget_data = !$minimal_display; break; case ConpherenceUpdateActions::REMOVE_PERSON: case ConpherenceUpdateActions::NOTIFICATIONS: @@ -590,7 +518,7 @@ final class ConpherenceUpdateController $conpherence = id(new ConpherenceThreadQuery()) ->setViewer($user) ->setAfterTransactionID($latest_transaction_id) - ->needCropPics(true) + ->needProfileImage(true) ->needParticipantCache($need_participant_cache) ->needParticipants(true) ->needTransactions($need_transactions) diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 4aec1d0e0a..029a6115a2 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -19,7 +19,7 @@ final class ConpherenceViewController extends $query = id(new ConpherenceThreadQuery()) ->setViewer($user) ->withIDs(array($conpherence_id)) - ->needCropPics(true) + ->needProfileImage(true) ->needParticipantCache(true) ->needTransactions(true) ->setTransactionLimit($this->getMainQueryLimit()); diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 8507e9817f..2a57711840 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -91,7 +91,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $types[] = ConpherenceTransaction::TYPE_TOPIC; $types[] = ConpherenceTransaction::TYPE_PARTICIPANTS; $types[] = ConpherenceTransaction::TYPE_PICTURE; - $types[] = ConpherenceTransaction::TYPE_PICTURE_CROP; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; @@ -109,9 +108,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { case ConpherenceTransaction::TYPE_TOPIC: return $object->getTopic(); case ConpherenceTransaction::TYPE_PICTURE: - return $object->getImagePHID(ConpherenceImageData::SIZE_ORIG); - case ConpherenceTransaction::TYPE_PICTURE_CROP: - return $object->getImagePHID(ConpherenceImageData::SIZE_CROP); + return $object->getProfileImagePHID(); case ConpherenceTransaction::TYPE_PARTICIPANTS: if ($this->getIsNewObject()) { return array(); @@ -127,11 +124,8 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { switch ($xaction->getTransactionType()) { case ConpherenceTransaction::TYPE_TITLE: case ConpherenceTransaction::TYPE_TOPIC: - case ConpherenceTransaction::TYPE_PICTURE_CROP: - return $xaction->getNewValue(); case ConpherenceTransaction::TYPE_PICTURE: - $file = $xaction->getNewValue(); - return $file->getPHID(); + return $xaction->getNewValue(); case ConpherenceTransaction::TYPE_PARTICIPANTS: return $this->getPHIDTransactionNewValue($xaction); } @@ -224,14 +218,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $object->setTopic($xaction->getNewValue()); break; case ConpherenceTransaction::TYPE_PICTURE: - $object->setImagePHID( - $xaction->getNewValue(), - ConpherenceImageData::SIZE_ORIG); - break; - case ConpherenceTransaction::TYPE_PICTURE_CROP: - $object->setImagePHID( - $xaction->getNewValue(), - ConpherenceImageData::SIZE_CROP); + $object->setProfileImagePHID($xaction->getNewValue()); break; case ConpherenceTransaction::TYPE_PARTICIPANTS: if (!$this->getIsNewObject()) { @@ -339,6 +326,10 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { PhabricatorLiskDAO $object, array $xactions) { + if (!$xactions) { + return $xactions; + } + $message_count = 0; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { @@ -571,19 +562,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return true; } - protected function extractFilePHIDsFromCustomTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_PICTURE: - case ConpherenceTransaction::TYPE_PICTURE_CROP: - return array($xaction->getNewValue()); - } - - return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); - } - protected function validateTransaction( PhabricatorLiskDAO $object, $type, @@ -612,21 +590,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $errors[] = $error; } break; - case ConpherenceTransaction::TYPE_PICTURE: - foreach ($xactions as $xaction) { - $file = $xaction->getNewValue(); - if (!$file->isTransformableImage()) { - $detail = pht('This server only supports these image formats: %s.', - implode(', ', PhabricatorFile::getTransformableImageFormats())); - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - $detail, - last($xactions)); - $errors[] = $error; - } - } - break; case ConpherenceTransaction::TYPE_PARTICIPANTS: foreach ($xactions as $xaction) { $new_phids = $this->getPHIDTransactionNewValue($xaction, array()); diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index c3d5322c15..640a19a612 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -9,14 +9,13 @@ final class ConpherenceThreadQuery private $ids; private $participantPHIDs; private $needParticipants; - private $needCropPics; - private $needOrigPics; private $needTransactions; private $needParticipantCache; private $afterTransactionID; private $beforeTransactionID; private $transactionLimit; private $fulltext; + private $needProfileImage; public function needParticipantCache($participant_cache) { $this->needParticipantCache = $participant_cache; @@ -28,13 +27,8 @@ final class ConpherenceThreadQuery return $this; } - public function needCropPics($need) { - $this->needCropPics = $need; - return $this; - } - - public function needOrigPics($need_widget_data) { - $this->needOrigPics = $need_widget_data; + public function needProfileImage($need) { + $this->needProfileImage = $need; return $this; } @@ -110,14 +104,33 @@ final class ConpherenceThreadQuery if ($this->needTransactions) { $this->loadTransactionsAndHandles($conpherences); } - if ($this->needOrigPics || $this->needCropPics) { - $this->initImages($conpherences); - } - if ($this->needOrigPics) { - $this->loadOrigPics($conpherences); - } - if ($this->needCropPics) { - $this->loadCropPics($conpherences); + if ($this->needProfileImage) { + $default = null; + $file_phids = mpull($conpherences, 'getProfileImagePHID'); + $file_phids = array_filter($file_phids); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($file_phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); + } else { + $files = array(); + } + + foreach ($conpherences as $conpherence) { + $file = idx($files, $conpherence->getProfileImagePHID()); + if (!$file) { + if (!$default) { + $default = PhabricatorFile::loadBuiltin( + $this->getViewer(), + 'conpherence.png'); + } + $file = $default; + } + $conpherence->attachProfileImageFile($file); + } } } @@ -266,50 +279,6 @@ final class ConpherenceThreadQuery return $this; } - private function loadOrigPics(array $conpherences) { - return $this->loadPics( - $conpherences, - ConpherenceImageData::SIZE_ORIG); - } - - private function loadCropPics(array $conpherences) { - return $this->loadPics( - $conpherences, - ConpherenceImageData::SIZE_CROP); - } - - private function initImages($conpherences) { - foreach ($conpherences as $conpherence) { - $conpherence->attachImages(array()); - } - } - - private function loadPics(array $conpherences, $size) { - $conpherence_pic_phids = array(); - foreach ($conpherences as $conpherence) { - $phid = $conpherence->getImagePHID($size); - if ($phid) { - $conpherence_pic_phids[$conpherence->getPHID()] = $phid; - } - } - - if (!$conpherence_pic_phids) { - return $this; - } - - $files = id(new PhabricatorFileQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs($conpherence_pic_phids) - ->execute(); - $files = mpull($files, null, 'getPHID'); - - foreach ($conpherence_pic_phids as $conpherence_phid => $pic_phid) { - $conpherences[$conpherence_phid]->setImage($files[$pic_phid], $size); - } - - return $this; - } - public function getQueryApplicationClass() { return 'PhabricatorConpherenceApplication'; } diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index ca0dc98cf5..36930f6151 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -9,7 +9,8 @@ final class ConpherenceThread extends ConpherenceDAO protected $title; protected $topic; - protected $imagePHIDs = array(); + protected $imagePHIDs = array(); // TODO; nuke after migrations + protected $profileImagePHID; protected $messageCount; protected $recentParticipantPHIDs = array(); protected $mailKey; @@ -19,8 +20,8 @@ final class ConpherenceThread extends ConpherenceDAO private $participants = self::ATTACHABLE; private $transactions = self::ATTACHABLE; + private $profileImageFile = self::ATTACHABLE; private $handles = self::ATTACHABLE; - private $images = self::ATTACHABLE; public static function initializeNewRoom(PhabricatorUser $sender) { $default_policy = id(new ConpherenceThreadMembersPolicyRule()) @@ -30,7 +31,6 @@ final class ConpherenceThread extends ConpherenceDAO ->setTitle('') ->setTopic('') ->attachParticipants(array()) - ->attachImages(array()) ->setViewPolicy($default_policy) ->setEditPolicy($default_policy) ->setJoinPolicy($default_policy); @@ -49,6 +49,7 @@ final class ConpherenceThread extends ConpherenceDAO 'messageCount' => 'uint64', 'mailKey' => 'text20', 'joinPolicy' => 'policy', + 'profileImagePHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -76,46 +77,21 @@ final class ConpherenceThread extends ConpherenceDAO return 'Z'.$this->getID(); } - public function getImagePHID($size) { - $image_phids = $this->getImagePHIDs(); - return idx($image_phids, $size); - } - public function setImagePHID($phid, $size) { - $image_phids = $this->getImagePHIDs(); - $image_phids[$size] = $phid; - return $this->setImagePHIDs($image_phids); - } - - public function getImage($size) { - $images = $this->getImages(); - return idx($images, $size); - } - public function setImage(PhabricatorFile $file, $size) { - $files = $this->getImages(); - $files[$size] = $file; - return $this->attachImages($files); - } - public function attachImages(array $files) { - assert_instances_of($files, 'PhabricatorFile'); - $this->images = $files; - return $this; - } - private function getImages() { - return $this->assertAttached($this->images); - } - public function attachParticipants(array $participants) { assert_instances_of($participants, 'ConpherenceParticipant'); $this->participants = $participants; return $this; } + public function getParticipants() { return $this->assertAttached($this->participants); } + public function getParticipant($phid) { $participants = $this->getParticipants(); return $participants[$phid]; } + public function getParticipantIfExists($phid, $default = null) { $participants = $this->getParticipants(); return idx($participants, $phid, $default); @@ -131,6 +107,7 @@ final class ConpherenceThread extends ConpherenceDAO $this->handles = $handles; return $this; } + public function getHandles() { return $this->assertAttached($this->handles); } @@ -140,9 +117,11 @@ final class ConpherenceThread extends ConpherenceDAO $this->transactions = $transactions; return $this; } + public function getTransactions($assert_attached = true) { return $this->assertAttached($this->transactions); } + public function hasAttachedTransactions() { return $this->transactions !== self::ATTACHABLE; } @@ -156,14 +135,17 @@ final class ConpherenceThread extends ConpherenceDAO $amount); } - public function loadImageURI($size) { - $file = $this->getImage($size); + public function getProfileImageURI() { + return $this->getProfileImageFile()->getBestURI(); + } - if ($file) { - return $file->getBestURI(); - } + public function attachProfileImageFile(PhabricatorFile $file) { + $this->profileImageFile = $file; + return $this; + } - return PhabricatorUser::getDefaultProfileImageURI(); + public function getProfileImageFile() { + return $this->assertAttached($this->profileImageFile); } /** @@ -273,13 +255,7 @@ final class ConpherenceThread extends ConpherenceDAO $lucky_handle = reset($handles); } - $img_src = null; - $size = ConpherenceImageData::SIZE_CROP; - if ($this->getImagePHID($size)) { - $img_src = $this->getImage($size)->getBestURI(); - } else if ($lucky_handle) { - $img_src = $lucky_handle->getImageURI(); - } + $img_src = $this->getProfileImageURI(); $message_title = null; if ($subtitle_mode == 'message') { diff --git a/src/applications/conpherence/storage/ConpherenceTransaction.php b/src/applications/conpherence/storage/ConpherenceTransaction.php index 29d23dfbc3..b311c80ca5 100644 --- a/src/applications/conpherence/storage/ConpherenceTransaction.php +++ b/src/applications/conpherence/storage/ConpherenceTransaction.php @@ -7,7 +7,7 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { const TYPE_PARTICIPANTS = 'participants'; const TYPE_DATE_MARKER = 'date-marker'; const TYPE_PICTURE = 'picture'; - const TYPE_PICTURE_CROP = 'picture-crop'; + const TYPE_PICTURE_CROP = 'picture-crop'; // TODO: Nuke these from DB. public function getApplicationName() { return 'conpherence'; diff --git a/src/applications/conpherence/view/ConpherenceTransactionView.php b/src/applications/conpherence/view/ConpherenceTransactionView.php index 3b1c29ebcb..12123aafcd 100644 --- a/src/applications/conpherence/view/ConpherenceTransactionView.php +++ b/src/applications/conpherence/view/ConpherenceTransactionView.php @@ -228,7 +228,6 @@ final class ConpherenceTransactionView extends AphrontView { case ConpherenceTransaction::TYPE_TITLE: case ConpherenceTransaction::TYPE_TOPIC: case ConpherenceTransaction::TYPE_PICTURE: - case ConpherenceTransaction::TYPE_PICTURE_CROP: case ConpherenceTransaction::TYPE_PARTICIPANTS: case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: diff --git a/webroot/rsrc/css/application/conpherence/header-pane.css b/webroot/rsrc/css/application/conpherence/header-pane.css index 0403d9d633..522771cc15 100644 --- a/webroot/rsrc/css/application/conpherence/header-pane.css +++ b/webroot/rsrc/css/application/conpherence/header-pane.css @@ -34,6 +34,10 @@ left: 0; } +.conpherence-header-pane .phui-header-image-href { + position: inherit; +} + .conpherence-header-pane .phui-header-col2 { height: 40px; } From cc230ed98a2591dd5cca0dbe920f3cbaf5123c2d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 5 Oct 2016 12:29:14 -0700 Subject: [PATCH 18/36] Fix migration on conpherence images --- resources/sql/autopatches/20161005.conpherence.image.2.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/sql/autopatches/20161005.conpherence.image.2.php b/resources/sql/autopatches/20161005.conpherence.image.2.php index f1eb134f4a..4345cdd08b 100644 --- a/resources/sql/autopatches/20161005.conpherence.image.2.php +++ b/resources/sql/autopatches/20161005.conpherence.image.2.php @@ -10,7 +10,7 @@ foreach (new LiskRawMigrationIterator($conn, $table_name) as $row) { $images = phutil_json_decode($row['imagePHIDs']); if (!$images) { - return; + continue; } $file_phid = idx($images, 'original'); From 385bf4f815b6dcf07d1c1ef6c1975ab2c3723779 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 5 Oct 2016 13:19:07 -0700 Subject: [PATCH 19/36] Remove phui-theme sidebar colors Summary: Removes the special CSS to change the sidebar when the header is called. Also switched to using a more standard background color. I'd also like to make collapsed the default, but can't find best way to do that. Test Plan: Vist profile, projects, collapse and open nav, try a workboard color. Still all good. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16673 --- resources/celerity/map.php | 10 ++-- .../rsrc/css/application/base/phui-theme.css | 33 ------------ webroot/rsrc/css/phui/phui-profile-menu.css | 51 +++++-------------- 3 files changed, 18 insertions(+), 76 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4d75a0318b..84010e230c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '4601645d', 'conpherence.pkg.js' => '11f3e07e', - 'core.pkg.css' => '3fa66cb3', + 'core.pkg.css' => 'edd0a773', 'core.pkg.js' => '30185d95', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'e1d704ce', @@ -37,7 +37,7 @@ return array( 'rsrc/css/application/base/main-menu-view.css' => 'f03e17be', 'rsrc/css/application/base/notification-menu.css' => '1e055865', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601', - 'rsrc/css/application/base/phui-theme.css' => '027ba77e', + 'rsrc/css/application/base/phui-theme.css' => '798c69b8', 'rsrc/css/application/base/standard-page-view.css' => '79176f5a', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', @@ -154,7 +154,7 @@ return array( 'rsrc/css/phui/phui-object-item-list-view.css' => '87278fa0', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', - 'rsrc/css/phui/phui-profile-menu.css' => '8a3fc181', + 'rsrc/css/phui/phui-profile-menu.css' => '12727a44', 'rsrc/css/phui/phui-property-list-view.css' => '6d8e58ac', 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', 'rsrc/css/phui/phui-segment-bar-view.css' => '46342871', @@ -930,14 +930,14 @@ return array( 'phui-object-item-list-view-css' => '87278fa0', 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', - 'phui-profile-menu-css' => '8a3fc181', + 'phui-profile-menu-css' => '12727a44', 'phui-property-list-view-css' => '6d8e58ac', 'phui-remarkup-preview-css' => '1a8f2591', 'phui-segment-bar-view-css' => '46342871', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => 'd5263e49', 'phui-tag-view-css' => '6bbd83e2', - 'phui-theme-css' => '027ba77e', + 'phui-theme-css' => '798c69b8', 'phui-timeline-view-css' => 'bc523970', 'phui-two-column-view-css' => 'fcfbe347', 'phui-workboard-color-css' => 'ac6fe6a7', diff --git a/webroot/rsrc/css/application/base/phui-theme.css b/webroot/rsrc/css/application/base/phui-theme.css index e0bfbf36a6..5313f2099b 100644 --- a/webroot/rsrc/css/application/base/phui-theme.css +++ b/webroot/rsrc/css/application/base/phui-theme.css @@ -28,36 +28,3 @@ background: #124A1B; } -/*--- Profile Nav Colors -----------------------------------------------------*/ - - -.phui-theme-blindigo .phui-profile-menu .phabricator-side-menu, -.phui-theme-blindigo .phabricator-side-menu .phui-profile-menu-footer-1 { - background: #525867; -} - -.phui-theme-dark .phui-profile-menu .phabricator-side-menu, -.phui-theme-dark .phabricator-side-menu .phui-profile-menu-footer-1 { - background: #30333A; -} - -.phui-theme-indigo .phui-profile-menu .phabricator-side-menu, -.phui-theme-indigo .phabricator-side-menu .phui-profile-menu-footer-1 { - background: #423658; -} - -.phui-theme-red .phui-profile-menu .phabricator-side-menu, -.phui-theme-red .phabricator-side-menu .phui-profile-menu-footer-1 { - background: #420C0C; -} - -.phui-theme-blue .phui-profile-menu .phabricator-side-menu, -.phui-theme-blue .phabricator-side-menu .phui-profile-menu-footer-1 { - background: #062842; -} - -.phui-theme-green .phui-profile-menu .phabricator-side-menu, -.phui-theme-green .phabricator-side-menu .phui-profile-menu-footer-1 { - background: #122916; -} - diff --git a/webroot/rsrc/css/phui/phui-profile-menu.css b/webroot/rsrc/css/phui/phui-profile-menu.css index 275bff1dde..3af8efcf79 100644 --- a/webroot/rsrc/css/phui/phui-profile-menu.css +++ b/webroot/rsrc/css/phui/phui-profile-menu.css @@ -17,7 +17,7 @@ } .phui-profile-menu .phabricator-side-menu { - background: #525867; + background: rgba({$alphablue}, 0.15); width: 240px; } @@ -32,9 +32,9 @@ height: 48px; font-size: {$biggerfontsize}; -webkit-font-smoothing: antialiased; - color: {$menu.profile.text}; line-height: 22px; overflow: hidden; + color: {$darkbluetext}; text-overflow: ellipsis; line-height: 48px; } @@ -50,7 +50,7 @@ height: 24px; line-height: 24px; text-align: center; - color: {$menu.profile.text}; + color: {$darkbluetext}; background-size: 100%; } @@ -81,7 +81,7 @@ .phui-profile-menu .phabricator-side-menu .phui-list-item-disabled .phui-list-item-icon { - color: {$menu.profile.icon.disabled}; + color: {$lightgreytext}; } .phui-profile-menu .phabricator-side-menu .phui-icon-view { @@ -90,28 +90,17 @@ .device-desktop .phui-profile-menu .phabricator-side-menu .phui-list-item-href:hover { - background-color: rgba({$alphablack},0.15); - color: {$menu.profile.text.selected}; -} - -.phui-profile-menu .phabricator-side-menu - .phui-list-item-selected - .phui-list-item-icon, -.device-desktop .phui-profile-menu .phabricator-side-menu - .phui-list-item-href:hover - .phui-list-item-icon { - color: {$menu.profile.text.selected}; + background-color: rgba({$alphablack},0.05); } .phui-profile-menu .phabricator-side-menu .phui-list-item-selected .phui-list-item-href { - background-color: rgba({$alphablack},0.3); - color: {$menu.profile.text.selected}; + background-color: rgba({$alphablack},0.1); } .phui-profile-menu .phabricator-side-menu .phui-list-item-selected .phui-list-item-href:hover { - background-color: rgba({$alphablack},0.45); + background-color: rgba({$alphablack},0.15); } .phui-profile-menu .phabricator-side-menu .phui-divider { @@ -123,13 +112,13 @@ white-space: normal; padding: 18px 15px; font-size: 12px; - color: {$menu.profile.text}; + color: {$darkbluetext}; } .phui-profile-menu .phabricator-side-menu .phui-motivator .phui-icon-view { position: static; font-size: 12px; - color: {$menu.profile.text}; + color: {$darkbluetext}; } .phui-profile-menu .phabricator-side-menu .phui-profile-menu-error { @@ -150,11 +139,11 @@ .phui-list-item-href, .phui-profile-menu .phui-list-sidenav .phui-list-item-disabled .phui-list-item-href { - color: rgba({$alphawhite}, 0.5); + color: rgba({$lightgreytext}); } .phui-profile-menu .phabricator-side-menu .phui-profile-segment-bar { - color: {$menu.profile.text}; + color: {$darkbluetext}; font-size: {$smallerfontsize}; -webkit-font-smoothing: antialiased; padding: 8px 12px 16px; @@ -201,25 +190,11 @@ } .phui-profile-menu .phui-profile-menu-footer .phui-icon-circle { - border-color: {$menu.profile.text}; + border-color: {$darkbluetext}; } .phui-profile-menu .phui-profile-menu-footer .phui-icon-circle .phui-icon-view { - color: {$menu.profile.text}; -} - -.phui-profile-menu .phui-profile-menu-footer .phui-list-item-href:hover - .phui-icon-circle, -.phui-profile-menu .phui-list-item-selected.phui-profile-menu-footer - .phui-icon-circle { - border-color: {$menu.profile.text.selected}; -} - -.phui-profile-menu .phui-profile-menu-footer .phui-list-item-href:hover - .phui-icon-circle .phui-icon-view, -.phui-profile-menu .phui-list-item-selected.phui-profile-menu-footer - .phui-icon-circle .phui-icon-view { - color: {$menu.profile.text.selected}; + color: {$darkbluetext}; } .phui-profile-menu .phui-profile-menu-footer From 98f22d95230194e3d175249d55fed86bbc78a388 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 5 Oct 2016 14:36:12 -0700 Subject: [PATCH 20/36] Fix collapse background color on profile-menu Summary: This color needs to be fixed since we want the collapse icon over the menu. Test Plan: Shrink window so area has to scroll, see collapse above the rest of the menu. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16674 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-profile-menu.css | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 84010e230c..badb3d06fb 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '4601645d', 'conpherence.pkg.js' => '11f3e07e', - 'core.pkg.css' => 'edd0a773', + 'core.pkg.css' => 'de918edf', 'core.pkg.js' => '30185d95', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'e1d704ce', @@ -154,7 +154,7 @@ return array( 'rsrc/css/phui/phui-object-item-list-view.css' => '87278fa0', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', - 'rsrc/css/phui/phui-profile-menu.css' => '12727a44', + 'rsrc/css/phui/phui-profile-menu.css' => '4768721a', 'rsrc/css/phui/phui-property-list-view.css' => '6d8e58ac', 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', 'rsrc/css/phui/phui-segment-bar-view.css' => '46342871', @@ -930,7 +930,7 @@ return array( 'phui-object-item-list-view-css' => '87278fa0', 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', - 'phui-profile-menu-css' => '12727a44', + 'phui-profile-menu-css' => '4768721a', 'phui-property-list-view-css' => '6d8e58ac', 'phui-remarkup-preview-css' => '1a8f2591', 'phui-segment-bar-view-css' => '46342871', diff --git a/webroot/rsrc/css/phui/phui-profile-menu.css b/webroot/rsrc/css/phui/phui-profile-menu.css index 3af8efcf79..f056e34f23 100644 --- a/webroot/rsrc/css/phui/phui-profile-menu.css +++ b/webroot/rsrc/css/phui/phui-profile-menu.css @@ -17,10 +17,14 @@ } .phui-profile-menu .phabricator-side-menu { - background: rgba({$alphablue}, 0.15); + background: #dee0e7; width: 240px; } +.phabricator-side-menu .phui-profile-menu-footer-1 { + background: #dee0e7; +} + .phui-profile-menu .phabricator-side-menu .phui-list-item-view { position: relative; } From 0ce7eacaf1c186c04bfcbd4bcc057acbe491049b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 3 Oct 2016 08:32:06 -0700 Subject: [PATCH 21/36] Introduce Calendar "UTC Epoch" columns for query windowing Summary: Ref T10747. Currently, Calendar events are mostly epoch-based and cheat a little bit for all-day events. This already felt a little flimsy, and can't reasonably accommodate the full range of `.ics` events, which include "floating" events (e.g., occurs at 3PM regardless of timezone, like "Tea Time"). As a secondary issue, we identify instances of a recurring event by instance number (1, 2, 3, etc.). This can't accommodate the full range of `.ics` events, which include arbitrary additional "RDATE" events (e.g., recurrs every week, and also on these specific extra days). However, we do need to store some epoch information so we can do query windowing: when the user looks at "October 2016", we want to select the smallest number of events that we can from the database initially, before refining them down to generate instances. We can't reasonably query the actual dates no matter how we store them because this depends on computing things like UNTIL, COUNT, initial dates, whether events are recurring or not, timezones, etc. Instead, when we save an event compute the earliest second it occurs on in UTC and the latest second it occurs on in UTC. We can then query for a small superset of possible events in "October 2016" for any viewer pretty easily. Also, start laying the groundwork for using fewer epochs in the rest of the code, and for reducing the role of sequence indexes (I plan to keep some sequences indexes around, probably, since they're nice in the UI, but not all child events will have indexes since there's no index for an RDATE event). This doesn't migrate existing events yet or actually read these new columns -- that will come later once the new code is a little more solid. Test Plan: - Ran `bin/storage upgrade`. - Created a new event. - Saved an existing event. - Viewed database, saw sensible-looking "UTC Epoch" values. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16652 --- .../autopatches/20161003.cal.01.utcepoch.sql | 8 ++ .../storage/PhabricatorCalendarEvent.php | 126 +++++++++++++++++- 2 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 resources/sql/autopatches/20161003.cal.01.utcepoch.sql diff --git a/resources/sql/autopatches/20161003.cal.01.utcepoch.sql b/resources/sql/autopatches/20161003.cal.01.utcepoch.sql new file mode 100644 index 0000000000..0e1aa0d534 --- /dev/null +++ b/resources/sql/autopatches/20161003.cal.01.utcepoch.sql @@ -0,0 +1,8 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD utcInitialEpoch INT UNSIGNED NOT NULL; + +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD utcUntilEpoch INT UNSIGNED; + +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD utcInstanceEpoch INT UNSIGNED; diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 63e5f2082a..66af8dfe5f 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -41,11 +41,16 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $spacePHID; + protected $utcInitialEpoch; + protected $utcUntilEpoch; + protected $utcInstanceEpoch; + private $parentEvent = self::ATTACHABLE; private $invitees = self::ATTACHABLE; private $viewerDateFrom; private $viewerDateTo; + private $viewerTimezone; // Frequency Constants const FREQUENCY_DAILY = 'daily'; @@ -298,6 +303,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $zone); } + $this->viewerTimezone = $viewer->getTimezoneIdentifier(); + return $this; } @@ -323,11 +330,62 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $dst->format('U'); } + public function updateUTCEpochs() { + // The "intitial" epoch is the start time of the event, in UTC. + $start_date = $this->newStartDateTime() + ->setViewerTimezone('UTC'); + $start_epoch = $start_date->getEpoch(); + $this->setUTCInitialEpoch($start_epoch); + + // The "until" epoch is the last UTC epoch on which any instance of this + // event occurs. For infinitely recurring events, it is `null`. + + if (!$this->getIsRecurring()) { + $end_date = $this->newEndDateTime() + ->setViewerTimezone('UTC'); + $until_epoch = $end_date->getEpoch(); + } else { + $until_epoch = null; + $until_date = $this->newUntilDateTime() + ->setViewerTimezone('UTC'); + if ($until_date) { + $duration = $this->newDuration(); + $until_epoch = id(new PhutilCalendarRelativeDateTime()) + ->setOrigin($until_date) + ->setDuration($duration) + ->getEpoch(); + } + } + $this->setUTCUntilEpoch($until_epoch); + + // The "instance" epoch is a property of instances of recurring events. + // It's the original UTC epoch on which the instance started. Usually that + // is the same as the start date, but they may be different if the instance + // has been edited. + + // The ICS format uses this value (original start time) to identify event + // instances, and must do so because it allows additional arbitrary + // instances to be added (with "RDATE"). + + $instance_epoch = null; + $instance_date = $this->newInstanceDateTime(); + if ($instance_date) { + $instance_epoch = $instance_date + ->setViewerTimezone('UTC') + ->getEpoch(); + } + $this->setUTCInstanceEpoch($instance_epoch); + + return $this; + } + public function save() { if (!$this->mailKey) { $this->mailKey = Filesystem::readRandomCharacters(20); } + $this->updateUTCEpochs(); + return parent::save(); } @@ -363,6 +421,9 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO 'instanceOfEventPHID' => 'phid?', 'sequenceIndex' => 'uint32?', 'isStub' => 'bool', + 'utcInitialEpoch' => 'epoch', + 'utcUntilEpoch' => 'epoch?', + 'utcInstanceEpoch' => 'epoch?', ), self::CONFIG_KEY_SCHEMA => array( 'key_date' => array( @@ -372,6 +433,13 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO 'columns' => array('instanceOfEventPHID', 'sequenceIndex'), 'unique' => true, ), + 'key_epoch' => array( + 'columns' => array('utcInitialEpoch', 'utcUntilEpoch'), + ), + 'key_rdate' => array( + 'columns' => array('instanceOfEventPHID', 'utcInstanceEpoch'), + 'unique' => true, + ), ), self::CONFIG_SERIALIZATION => array( 'recurrenceFrequency' => self::SERIALIZATION_JSON, @@ -641,11 +709,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $modified = $this->getDateModified(); $modified = PhutilCalendarAbsoluteDateTime::newFromEpoch($modified); - $date_start = $this->getDateFrom(); - $date_start = PhutilCalendarAbsoluteDateTime::newFromEpoch($date_start); - - $date_end = $this->getDateTo(); - $date_end = PhutilCalendarAbsoluteDateTime::newFromEpoch($date_end); + $date_start = $this->newStartDateTime(); + $date_end = $this->newEndDateTime(); if ($this->getIsAllDay()) { $date_start->setIsAllDay(true); @@ -719,6 +784,57 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $node; } + public function newStartDateTime() { + $epoch = $this->getDateFrom(); + return $this->newDateTimeFromEpoch($epoch); + } + + public function newEndDateTime() { + $epoch = $this->getDateTo(); + return $this->newDateTimeFromEpoch($epoch); + } + + public function newUntilDateTime() { + $epoch = $this->getRecurrenceEndDate(); + if (!$epoch) { + return null; + } + return $this->newDateTimeFromEpoch($epoch); + } + + public function newDuration() { + return id(new PhutilCalendarDuration()) + ->setSeconds($this->getDuration()); + } + + public function newInstanceDateTime() { + if (!$this->getIsRecurring()) { + return null; + } + + $epochs = $this->getParent()->getSequenceIndexEpochs( + new PhabricatorUser(), + $this->getSequenceIndex(), + $this->getDuration()); + + $epoch = $epochs['dateFrom']; + return $this->newDateTimeFromEpoch($epoch); + } + + private function newDateTimeFromEpoch($epoch) { + $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($epoch); + + $viewer_timezone = $this->viewerTimezone; + if ($viewer_timezone) { + $datetime->setViewerTimezone($viewer_timezone); + } + + if ($this->getIsAllDay()) { + $datetime->setIsAllDay(true); + } + + return $datetime; + } /* -( Markup Interface )--------------------------------------------------- */ From e042533375d3a0f04640c73f5ec115153563980b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 3 Oct 2016 10:14:14 -0700 Subject: [PATCH 22/36] Store "start", "end", and "until" event dates as CalendarDateTime objects Summary: Ref T10747. This does double-writes and starts generating/writing CalendarDateTimes. This greater flexibility is necessary to support the full range of ICS-specifiable events, including "floating" events. This doesn't do anything yet. Test Plan: Created and edited events, verified sensible representations of corresponding datetimes appeared in the database. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16661 --- .../20161003.cal.02.parameters.sql | 5 ++ .../storage/PhabricatorCalendarEvent.php | 67 +++++++++++++++++-- ...bricatorCalendarEventAllDayTransaction.php | 19 ++++++ ...ricatorCalendarEventEndDateTransaction.php | 8 ++- ...catorCalendarEventStartDateTransaction.php | 8 ++- ...catorCalendarEventUntilDateTransaction.php | 9 +++ 6 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 resources/sql/autopatches/20161003.cal.02.parameters.sql diff --git a/resources/sql/autopatches/20161003.cal.02.parameters.sql b/resources/sql/autopatches/20161003.cal.02.parameters.sql new file mode 100644 index 0000000000..9fd3783a5b --- /dev/null +++ b/resources/sql/autopatches/20161003.cal.02.parameters.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD parameters LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; + +UPDATE {$NAMESPACE}_calendar.calendar_event + SET parameters = '{}' WHERE parameters = ''; diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 66af8dfe5f..ab563dfde1 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -44,6 +44,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $utcInitialEpoch; protected $utcUntilEpoch; protected $utcInstanceEpoch; + protected $parameters = array(); private $parentEvent = self::ATTACHABLE; private $invitees = self::ATTACHABLE; @@ -87,6 +88,11 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $default_icon = 'fa-calendar'; + $datetime_start = PhutilCalendarAbsoluteDateTime::newFromEpoch( + $now, + $actor->getTimezoneIdentifier()); + $datetime_end = $datetime_start->newRelativeDateTime('PT1H'); + return id(new PhabricatorCalendarEvent()) ->setHostPHID($actor->getPHID()) ->setIsCancelled(0) @@ -106,6 +112,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setDateTo($epoch_max) ->setAllDayDateFrom($now_min) ->setAllDayDateTo($now_max) + ->setStartDateTime($datetime_start) + ->setEndDateTime($datetime_end) ->applyViewerTimezone($actor); } @@ -443,6 +451,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ), self::CONFIG_SERIALIZATION => array( 'recurrenceFrequency' => self::SERIALIZATION_JSON, + 'parameters' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } @@ -785,16 +794,31 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } public function newStartDateTime() { + $datetime = $this->getParameter('startDateTime'); + if ($datetime) { + return $this->newDateTimeFromDictionary($datetime); + } + $epoch = $this->getDateFrom(); return $this->newDateTimeFromEpoch($epoch); } public function newEndDateTime() { + $datetime = $this->getParameter('endDateTime'); + if ($datetime) { + return $this->newDateTimeFromDictionary($datetime); + } + $epoch = $this->getDateTo(); return $this->newDateTimeFromEpoch($epoch); } public function newUntilDateTime() { + $datetime = $this->getParameter('untilDateTime'); + if ($datetime) { + return $this->newDateTimeFromDictionary($datetime); + } + $epoch = $this->getRecurrenceEndDate(); if (!$epoch) { return null; @@ -824,18 +848,53 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO private function newDateTimeFromEpoch($epoch) { $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($epoch); + if ($this->getIsAllDay()) { + $datetime->setIsAllDay(true); + } + + return $this->newDateTimeFromDateTime($datetime); + } + + private function newDateTimeFromDictionary(array $dict) { + $datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($dict); + return $this->newDateTimeFromDateTime($datetime); + } + + private function newDateTimeFromDateTime(PhutilCalendarDateTime $datetime) { $viewer_timezone = $this->viewerTimezone; if ($viewer_timezone) { $datetime->setViewerTimezone($viewer_timezone); } - if ($this->getIsAllDay()) { - $datetime->setIsAllDay(true); - } - return $datetime; } + public function getParameter($key, $default = null) { + return idx($this->parameters, $key, $default); + } + + public function setParameter($key, $value) { + $this->parameters[$key] = $value; + return $this; + } + + public function setStartDateTime(PhutilCalendarDateTime $datetime) { + return $this->setParameter( + 'startDateTime', + $datetime->newAbsoluteDateTime()->toDictionary()); + } + + public function setEndDateTime(PhutilCalendarDateTime $datetime) { + return $this->setParameter( + 'endDateTime', + $datetime->newAbsoluteDateTime()->toDictionary()); + } + + public function setUntilDateTime(PhutilCalendarDateTime $datetime) { + return $this->setParameter( + 'untilDateTime', + $datetime->newAbsoluteDateTime()->toDictionary()); + } /* -( Markup Interface )--------------------------------------------------- */ diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php index 1bcad16adf..eac229c395 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php @@ -15,6 +15,25 @@ final class PhabricatorCalendarEventAllDayTransaction public function applyInternalEffects($object, $value) { $object->setIsAllDay($value); + + // Adjust the flags on any other dates the event has. + $keys = array( + 'startDateTime', + 'endDateTime', + 'untilDateTime', + ); + + foreach ($keys as $key) { + $dict = $object->getParameter($key); + if (!$dict) { + continue; + } + + $datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($dict); + $datetime->setIsAllDay($value); + + $object->setParameter($key, $datetime->toDictionary()); + } } public function getTitle() { diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php index fc7f9859ba..91d9b396ff 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php @@ -12,8 +12,8 @@ final class PhabricatorCalendarEventEndDateTransaction public function applyInternalEffects($object, $value) { $actor = $this->getActor(); + // TODO: DEPRECATED. $object->setDateTo($value); - $object->setAllDayDateTo( $object->getDateEpochForTimezone( $value, @@ -21,6 +21,12 @@ final class PhabricatorCalendarEventEndDateTransaction 'Y-m-d 23:59:00', null, new DateTimeZone('UTC'))); + + $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( + $value, + $actor->getTimezoneIdentifier()); + $datetime->setIsAllDay($object->getIsAllDay()); + $object->setEndDateTime($datetime); } public function getTitle() { diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php index 9823ec336f..90e3b9c9c5 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php @@ -12,8 +12,8 @@ final class PhabricatorCalendarEventStartDateTransaction public function applyInternalEffects($object, $value) { $actor = $this->getActor(); + // TODO: DEPRECATED. $object->setDateFrom($value); - $object->setAllDayDateFrom( $object->getDateEpochForTimezone( $value, @@ -21,6 +21,12 @@ final class PhabricatorCalendarEventStartDateTransaction 'Y-m-d', null, new DateTimeZone('UTC'))); + + $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( + $value, + $actor->getTimezoneIdentifier()); + $datetime->setIsAllDay($object->getIsAllDay()); + $object->setStartDateTime($datetime); } public function getTitle() { diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php index 454d391293..4cffda8ff6 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php @@ -10,7 +10,16 @@ final class PhabricatorCalendarEventUntilDateTransaction } public function applyInternalEffects($object, $value) { + $actor = $this->getActor(); + + // TODO: DEPRECATED. $object->setRecurrenceEndDate($value); + + $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( + $value, + $actor->getTimezoneIdentifier()); + $datetime->setIsAllDay($object->getIsAllDay()); + $object->setUntilDateTime($datetime); } public function getTitle() { From 37f35e9ecca6cec1ffe4fd8d3804b218d67f8b72 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 4 Oct 2016 09:47:38 -0700 Subject: [PATCH 23/36] Remove "viewerDateFrom" / "viewerDateTo" in favor of CalendarDateTime methods Summary: Ref T10747. The CalendarDateTime object now carries the viewer timezone as part of its state, so we don't need to have separate accessors. Test Plan: - Viewed events, checked that crumbs render properly. - Edited events. - Created new events. - Viewed calendar. - Viewed event detail pages. - Viewed profile mini-calendar. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16662 --- ...PhabricatorCalendarEventViewController.php | 4 +- .../PhabricatorCalendarEventEditEngine.php | 77 ++++++++++--------- .../query/PhabricatorCalendarEventQuery.php | 20 ++--- .../PhabricatorCalendarEventSearchEngine.php | 8 +- .../storage/PhabricatorCalendarEvent.php | 68 +++++----------- ...PhabricatorPeopleProfileViewController.php | 4 +- .../people/query/PhabricatorPeopleQuery.php | 2 +- 7 files changed, 79 insertions(+), 104 deletions(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 57792e6717..8f7ee8929e 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -27,8 +27,8 @@ final class PhabricatorCalendarEventViewController $page_title = $monogram.' '.$event->getName(); $crumbs = $this->buildApplicationCrumbs(); - $start = new DateTime('@'.$event->getViewerDateFrom()); - $start->setTimeZone($viewer->getTimeZone()); + $start = $event->newStartDateTime() + ->newPHPDateTime(); $crumbs->addTextCrumb( $start->format('F Y'), diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php index 5fa6e95732..93642a959c 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php @@ -155,6 +155,13 @@ final class PhabricatorCalendarEventEditEngine } if ($this->getIsCreate() || $object->getIsRecurring()) { + $until_datetime = $object->newUntilDateTime(); + if ($until_datetime) { + $until_epoch = $until_datetime->getEpoch(); + } else { + $until_epoch = null; + } + $fields[] = id(new PhabricatorEpochEditField()) ->setAllowNull(true) ->setKey('until') @@ -164,50 +171,50 @@ final class PhabricatorCalendarEventEditEngine ->setDescription(pht('Last instance of the event.')) ->setConduitDescription(pht('Change when the event repeats until.')) ->setConduitTypeDescription(pht('New final event time.')) - ->setValue($object->getRecurrenceEndDate()); + ->setValue($until_epoch); } $fields[] = id(new PhabricatorBoolEditField()) - ->setKey('isAllDay') - ->setLabel(pht('All Day')) - ->setOptions(pht('Normal Event'), pht('All Day Event')) - ->setTransactionType( - PhabricatorCalendarEventAllDayTransaction::TRANSACTIONTYPE) - ->setDescription(pht('Marks this as an all day event.')) - ->setConduitDescription(pht('Make the event an all day event.')) - ->setConduitTypeDescription(pht('Mark the event as an all day event.')) - ->setValue($object->getIsAllDay()); + ->setKey('isAllDay') + ->setLabel(pht('All Day')) + ->setOptions(pht('Normal Event'), pht('All Day Event')) + ->setTransactionType( + PhabricatorCalendarEventAllDayTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Marks this as an all day event.')) + ->setConduitDescription(pht('Make the event an all day event.')) + ->setConduitTypeDescription(pht('Mark the event as an all day event.')) + ->setValue($object->getIsAllDay()); $fields[] = id(new PhabricatorEpochEditField()) - ->setKey('start') - ->setLabel(pht('Start')) - ->setTransactionType( - PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE) - ->setDescription(pht('Start time of the event.')) - ->setConduitDescription(pht('Change the start time of the event.')) - ->setConduitTypeDescription(pht('New event start time.')) - ->setValue($object->getViewerDateFrom()); + ->setKey('start') + ->setLabel(pht('Start')) + ->setTransactionType( + PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Start time of the event.')) + ->setConduitDescription(pht('Change the start time of the event.')) + ->setConduitTypeDescription(pht('New event start time.')) + ->setValue($object->getStartDateTimeEpoch()); $fields[] = id(new PhabricatorEpochEditField()) - ->setKey('end') - ->setLabel(pht('End')) - ->setTransactionType( - PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE) - ->setDescription(pht('End time of the event.')) - ->setConduitDescription(pht('Change the end time of the event.')) - ->setConduitTypeDescription(pht('New event end time.')) - ->setValue($object->getViewerDateTo()); + ->setKey('end') + ->setLabel(pht('End')) + ->setTransactionType( + PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE) + ->setDescription(pht('End time of the event.')) + ->setConduitDescription(pht('Change the end time of the event.')) + ->setConduitTypeDescription(pht('New event end time.')) + ->setValue($object->getEndDateTimeEpoch()); $fields[] = id(new PhabricatorIconSetEditField()) - ->setKey('icon') - ->setLabel(pht('Icon')) - ->setIconSet(new PhabricatorCalendarIconSet()) - ->setTransactionType( - PhabricatorCalendarEventIconTransaction::TRANSACTIONTYPE) - ->setDescription(pht('Event icon.')) - ->setConduitDescription(pht('Change the event icon.')) - ->setConduitTypeDescription(pht('New event icon.')) - ->setValue($object->getIcon()); + ->setKey('icon') + ->setLabel(pht('Icon')) + ->setIconSet(new PhabricatorCalendarIconSet()) + ->setTransactionType( + PhabricatorCalendarEventIconTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Event icon.')) + ->setConduitDescription(pht('Change the event icon.')) + ->setConduitTypeDescription(pht('New event icon.')) + ->setValue($object->getIcon()); return $fields; } diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index d0006ba3fe..6c10964b02 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -99,7 +99,7 @@ final class PhabricatorCalendarEventQuery protected function getPagingValueMap($cursor, array $keys) { $event = $this->loadCursorObject($cursor); return array( - 'start' => $event->getViewerDateFrom(), + 'start' => $event->getStartDateTimeEpoch(), 'id' => $event->getID(), ); } @@ -177,9 +177,9 @@ final class PhabricatorCalendarEventQuery $modify_key = '+1 '.$frequency; if (($this->rangeBegin !== null) && - ($this->rangeBegin > $event->getViewerDateFrom())) { + ($this->rangeBegin > $event->getStartDateTimeEpoch())) { $max_date = $this->rangeBegin - $duration; - $date = $event->getViewerDateFrom(); + $date = $event->getStartDateTimeEpoch(); $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); while ($date < $max_date) { @@ -191,7 +191,7 @@ final class PhabricatorCalendarEventQuery $start = $this->rangeBegin; } else { - $start = $event->getViewerDateFrom() - $duration; + $start = $event->getStartDateTimeEpoch() - $duration; } $date = $start; @@ -238,9 +238,9 @@ final class PhabricatorCalendarEventQuery if ($raw_limit) { if (count($events) > $raw_limit) { - $events = msort($events, 'getViewerDateFrom'); + $events = msort($events, 'getStartDateTimeEpoch'); $events = array_slice($events, 0, $raw_limit, true); - $enforced_end = last($events)->getViewerDateFrom(); + $enforced_end = last($events)->getStartDateTimeEpoch(); } } } @@ -308,7 +308,7 @@ final class PhabricatorCalendarEventQuery } } - $events = msort($events, 'getViewerDateFrom'); + $events = msort($events, 'getStartDateTimeEpoch'); return $events; } @@ -500,7 +500,7 @@ final class PhabricatorCalendarEventQuery } } - $events = msort($events, 'getViewerDateFrom'); + $events = msort($events, 'getStartDateTimeEpoch'); return $events; } @@ -510,8 +510,8 @@ final class PhabricatorCalendarEventQuery $range_end = $this->rangeEnd; foreach ($events as $key => $event) { - $event_start = $event->getViewerDateFrom(); - $event_end = $event->getViewerDateTo(); + $event_start = $event->getStartDateTimeEpoch(); + $event_end = $event->getEndDateTimeEpoch(); if ($range_start && $event_end < $range_start) { unset($events[$key]); diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 9420261a59..5058457cde 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -343,8 +343,8 @@ final class PhabricatorCalendarEventSearchEngine $month_view->setUser($viewer); foreach ($events as $event) { - $epoch_min = $event->getViewerDateFrom(); - $epoch_max = $event->getViewerDateTo(); + $epoch_min = $event->getStartDateTimeEpoch(); + $epoch_max = $event->getEndDateTimeEpoch(); $event_view = id(new AphrontCalendarEventView()) ->setHostPHID($event->getHostPHID()) @@ -408,8 +408,8 @@ final class PhabricatorCalendarEventSearchEngine $event, PhabricatorPolicyCapability::CAN_EDIT); - $epoch_min = $event->getViewerDateFrom(); - $epoch_max = $event->getViewerDateTo(); + $epoch_min = $event->getStartDateTimeEpoch(); + $epoch_max = $event->getEndDateTimeEpoch(); $status_icon = $event->getDisplayIcon($viewer); $status_color = $event->getDisplayIconColor($viewer); diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index ab563dfde1..6e4d0ce04e 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -49,8 +49,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO private $parentEvent = self::ATTACHABLE; private $invitees = self::ATTACHABLE; - private $viewerDateFrom; - private $viewerDateTo; private $viewerTimezone; // Frequency Constants @@ -273,46 +271,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $ghost; } - public function getViewerDateFrom() { - if ($this->viewerDateFrom === null) { - throw new PhutilInvalidStateException('applyViewerTimezone'); - } - - return $this->viewerDateFrom; - } - - public function getViewerDateTo() { - if ($this->viewerDateTo === null) { - throw new PhutilInvalidStateException('applyViewerTimezone'); - } - - return $this->viewerDateTo; - } - public function applyViewerTimezone(PhabricatorUser $viewer) { - if (!$this->getIsAllDay()) { - $this->viewerDateFrom = $this->getDateFrom(); - $this->viewerDateTo = $this->getDateTo(); - } else { - $zone = $viewer->getTimeZone(); - - $this->viewerDateFrom = $this->getDateEpochForTimezone( - $this->getAllDayDateFrom(), - new DateTimeZone('UTC'), - 'Y-m-d', - null, - $zone); - - $this->viewerDateTo = $this->getDateEpochForTimezone( - $this->getAllDayDateTo(), - new DateTimeZone('UTC'), - 'Y-m-d 23:59:00', - null, - $zone); - } - $this->viewerTimezone = $viewer->getTimezoneIdentifier(); - return $this; } @@ -407,7 +367,9 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO * @return int Event start date for availability caches. */ public function getDateFromForCache() { - return ($this->getViewerDateFrom() - phutil_units('15 minutes in seconds')); + $epoch = $this->getStartDateTimeEpoch(); + $window = phutil_units('15 minutes in seconds'); + return ($epoch - $window); } protected function getConfiguration() { @@ -593,14 +555,12 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO PhabricatorUser $viewer, $show_end) { - if ($show_end) { - $min_date = PhabricatorTime::getDateTimeFromEpoch( - $this->getViewerDateFrom(), - $viewer); + $start = $this->newStartDateTime(); + $end = $this->newEndDateTime(); - $max_date = PhabricatorTime::getDateTimeFromEpoch( - $this->getViewerDateTo(), - $viewer); + if ($show_end) { + $min_date = $start->newPHPDateTime(); + $max_date = $end->newPHPDateTime(); $min_day = $min_date->format('Y m d'); $max_day = $max_date->format('Y m d'); @@ -610,8 +570,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $show_end_date = false; } - $min_epoch = $this->getViewerDateFrom(); - $max_epoch = $this->getViewerDateTo(); + $min_epoch = $start->getEpoch(); + $max_epoch = $end->getEpoch(); if ($this->getIsAllDay()) { if ($show_end_date) { @@ -803,6 +763,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $this->newDateTimeFromEpoch($epoch); } + public function getStartDateTimeEpoch() { + return $this->newStartDateTime()->getEpoch(); + } + public function newEndDateTime() { $datetime = $this->getParameter('endDateTime'); if ($datetime) { @@ -813,6 +777,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $this->newDateTimeFromEpoch($epoch); } + public function getEndDateTimeEpoch() { + return $this->newEndDateTime()->getEpoch(); + } + public function newUntilDateTime() { $datetime = $this->getParameter('untilDateTime'); if ($datetime) { diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index 657fd0435e..ece5a88462 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -205,8 +205,8 @@ final class PhabricatorPeopleProfileViewController $event, PhabricatorPolicyCapability::CAN_EDIT); - $epoch_min = $event->getViewerDateFrom(); - $epoch_max = $event->getViewerDateTo(); + $epoch_min = $event->getStartDateTimeEpoch(); + $epoch_max = $event->getEndDateTimeEpoch(); $event_view = id(new AphrontCalendarEventView()) ->setCanEdit($can_edit) diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index 69ce099111..5d5650c860 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -432,7 +432,7 @@ final class PhabricatorPeopleQuery while (true) { foreach ($events as $event) { $from = $event->getDateFromForCache(); - $to = $event->getViewerDateTo(); + $to = $event->getEndDateTimeEpoch(); if (($from <= $cursor) && ($to > $cursor)) { $cursor = $to; continue 2; From fae0ec9220a7971d06b324bbc09709a58a5e294b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 4 Oct 2016 11:41:11 -0700 Subject: [PATCH 24/36] Use more CalendarDateTime and fewer epoch timestamps in Calendar Summary: Ref T10747. Moves away from getDateFrom() / getDateTo() and makes a few more date/time methods more consistent. Test Plan: Created, edited, viewed events. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16663 --- .../PhabricatorCalendarEventEditEngine.php | 9 +-------- .../editor/PhabricatorCalendarEventEditor.php | 8 ++++---- .../storage/PhabricatorCalendarEvent.php | 16 +++++++++++++--- ...habricatorCalendarEventEndDateTransaction.php | 3 ++- ...bricatorCalendarEventStartDateTransaction.php | 3 ++- ...bricatorCalendarEventUntilDateTransaction.php | 3 ++- .../people/query/PhabricatorPeopleQuery.php | 2 +- 7 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php index 93642a959c..63d33beab4 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php @@ -155,13 +155,6 @@ final class PhabricatorCalendarEventEditEngine } if ($this->getIsCreate() || $object->getIsRecurring()) { - $until_datetime = $object->newUntilDateTime(); - if ($until_datetime) { - $until_epoch = $until_datetime->getEpoch(); - } else { - $until_epoch = null; - } - $fields[] = id(new PhabricatorEpochEditField()) ->setAllowNull(true) ->setKey('until') @@ -171,7 +164,7 @@ final class PhabricatorCalendarEventEditEngine ->setDescription(pht('Last instance of the event.')) ->setConduitDescription(pht('Change when the event repeats until.')) ->setConduitTypeDescription(pht('New final event time.')) - ->setValue($until_epoch); + ->setValue($object->getUntilDateTimeEpoch()); } $fields[] = id(new PhabricatorBoolEditField()) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index d87746f347..5dd9b463c6 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -139,7 +139,7 @@ final class PhabricatorCalendarEventEditor WHERE phid IN (%Ls) AND availabilityCacheTTL >= %d', $user->getTableName(), $phids, - $object->getDateFromForCache()); + $object->getStartDateTimeEpochForCache()); } return $xactions; @@ -159,9 +159,9 @@ final class PhabricatorCalendarEventEditor $recurrence_end_xaction = PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE; - $start_date = $object->getDateFrom(); - $end_date = $object->getDateTo(); - $recurrence_end = $object->getRecurrenceEndDate(); + $start_date = $object->getStartDateTimeEpoch(); + $end_date = $object->getEndDateTimeEpoch(); + $recurrence_end = $object->getUntilDateTimeEpoch(); $is_recurring = $object->getIsRecurring(); $errors = array(); diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 6e4d0ce04e..2ddc71115f 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -184,7 +184,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setDescription($parent->getDescription()); $sequence = $this->getSequenceIndex(); - $duration = $this->getDuration(); + $duration = $parent->getDuration(); $epochs = $parent->getSequenceIndexEpochs($actor, $sequence, $duration); $this @@ -277,7 +277,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } public function getDuration() { - return $this->getDateTo() - $this->getDateFrom(); + return ($this->getEndDateTimeEpoch() - $this->getStartDateTimeEpoch()); } public function getDateEpochForTimezone( @@ -366,7 +366,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO * * @return int Event start date for availability caches. */ - public function getDateFromForCache() { + public function getStartDateTimeEpochForCache() { $epoch = $this->getStartDateTimeEpoch(); $window = phutil_units('15 minutes in seconds'); return ($epoch - $window); @@ -794,6 +794,16 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $this->newDateTimeFromEpoch($epoch); } + public function getUntilDateTimeEpoch() { + $datetime = $this->newUntilDateTime(); + + if (!$datetime) { + return null; + } + + return $datetime->getEpoch(); + } + public function newDuration() { return id(new PhutilCalendarDuration()) ->setSeconds($this->getDuration()); diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php index 91d9b396ff..2d5f10d290 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php @@ -6,7 +6,8 @@ final class PhabricatorCalendarEventEndDateTransaction const TRANSACTIONTYPE = 'calendar.enddate'; public function generateOldValue($object) { - return $object->getDateTo(); + // TODO: Upgrade this. + return $object->getEndDateTimeEpoch(); } public function applyInternalEffects($object, $value) { diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php index 90e3b9c9c5..48318edf7e 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php @@ -6,7 +6,8 @@ final class PhabricatorCalendarEventStartDateTransaction const TRANSACTIONTYPE = 'calendar.startdate'; public function generateOldValue($object) { - return $object->getDateFrom(); + // TODO: Upgrade this. + return $object->getStartDateTimeEpoch(); } public function applyInternalEffects($object, $value) { diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php index 4cffda8ff6..736ed13704 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php @@ -6,7 +6,8 @@ final class PhabricatorCalendarEventUntilDateTransaction const TRANSACTIONTYPE = 'calendar.recurrenceenddate'; public function generateOldValue($object) { - return $object->getRecurrenceEndDate(); + // TODO: Upgrade this. + return $object->getUntilDateTimeEpoch(); } public function applyInternalEffects($object, $value) { diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index 5d5650c860..07fbc8b941 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -431,7 +431,7 @@ final class PhabricatorPeopleQuery // because of an event, we check again for events after that one ends. while (true) { foreach ($events as $event) { - $from = $event->getDateFromForCache(); + $from = $event->getStartDateTimeEpochForCache(); $to = $event->getEndDateTimeEpoch(); if (($from <= $cursor) && ($to > $cursor)) { $cursor = $to; From d3fc1800f86aec03da4626683b2d9e240005d99c Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 4 Oct 2016 12:03:20 -0700 Subject: [PATCH 25/36] Migrate Calendar away from stored-epoch fields Summary: Ref T10747. This deprecates "dateFrom", "dateTo", "allDayDateFrom", "allDayDateTo", and "recurrenceEndDate". They are replaced with "utc*Epoch" fields (for querying) and CalendarDateTime objects (for start, end, until). These objects can represent the full range of dates and times expressible in ICS format, allowing us to import a wider range of ICS events. Test Plan: Ran migrations, viewed/edited Calendar, didn't catch anything catastrophcially broken. This likely needs some followups, I'll keep it local for a bit until I'm confident I didn't break anything too catastrophically. I'm retaining the old data for now so we can likely fix things if it turns out there is some sort of issue. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16664 --- .../autopatches/20160715.event.03.allday.php | 51 +------ .../autopatches/20161004.cal.01.noepoch.php | 125 ++++++++++++++++++ .../query/PhabricatorCalendarEventQuery.php | 4 +- .../storage/PhabricatorCalendarEvent.php | 109 ++++++--------- ...ricatorCalendarEventEndDateTransaction.php | 10 -- ...catorCalendarEventStartDateTransaction.php | 10 -- 6 files changed, 168 insertions(+), 141 deletions(-) create mode 100644 resources/sql/autopatches/20161004.cal.01.noepoch.php diff --git a/resources/sql/autopatches/20160715.event.03.allday.php b/resources/sql/autopatches/20160715.event.03.allday.php index 8bc3ffe568..4c2d73a368 100644 --- a/resources/sql/autopatches/20160715.event.03.allday.php +++ b/resources/sql/autopatches/20160715.event.03.allday.php @@ -1,52 +1,3 @@ establishConnection('w'); - -// Previously, "All Day" events were stored with a start and end date set to -// the earliest possible start and end seconds for the corresponding days. We -// now store all day events with their "date" epochs as UTC, separate from -// individual event times. -$zone_min = new DateTimeZone('Pacific/Midway'); -$zone_max = new DateTimeZone('Pacific/Kiritimati'); -$zone_utc = new DateTimeZone('UTC'); - -foreach (new LiskMigrationIterator($table) as $event) { - // If this event has already migrated, skip it. - if ($event->getAllDayDateFrom()) { - continue; - } - - $is_all_day = $event->getIsAllDay(); - - $epoch_min = $event->getDateFrom(); - $epoch_max = $event->getDateTo(); - - $date_min = new DateTime('@'.$epoch_min); - $date_max = new DateTime('@'.$epoch_max); - - if ($is_all_day) { - $date_min->setTimeZone($zone_min); - $date_min->modify('+2 days'); - $date_max->setTimeZone($zone_max); - $date_max->modify('-2 days'); - } else { - $date_min->setTimeZone($zone_utc); - $date_max->setTimeZone($zone_utc); - } - - $string_min = $date_min->format('Y-m-d'); - $string_max = $date_max->format('Y-m-d 23:59:00'); - - $allday_min = id(new DateTime($string_min, $zone_utc))->format('U'); - $allday_max = id(new DateTime($string_max, $zone_utc))->format('U'); - - queryfx( - $conn, - 'UPDATE %T SET allDayDateFrom = %d, allDayDateTo = %d - WHERE id = %d', - $table->getTableName(), - $allday_min, - $allday_max, - $event->getID()); -} +// This migration was replaced by "20161004.cal.01.noepoch.php". diff --git a/resources/sql/autopatches/20161004.cal.01.noepoch.php b/resources/sql/autopatches/20161004.cal.01.noepoch.php new file mode 100644 index 0000000000..6013376062 --- /dev/null +++ b/resources/sql/autopatches/20161004.cal.01.noepoch.php @@ -0,0 +1,125 @@ +establishConnection('w'); +$table_name = 'calendar_event'; + +// Long ago, "All Day" events were stored with a start and end date set to +// the earliest possible start and end seconds for the corresponding days. We +// then moved to store all day events with their "date" epochs as UTC, separate +// from individual event times. Both systems were later replaced with use of +// CalendarDateTime. +$zone_min = new DateTimeZone('Pacific/Midway'); +$zone_max = new DateTimeZone('Pacific/Kiritimati'); +$zone_utc = new DateTimeZone('UTC'); + +foreach (new LiskRawMigrationIterator($conn, $table_name) as $row) { + $parameters = phutil_json_decode($row['parameters']); + if (isset($parameters['startDateTime'])) { + // This event has already been migrated. + continue; + } + + $is_all_day = $row['isAllDay']; + + if (empty($row['allDayDateFrom'])) { + // No "allDayDateFrom" means this is an old event which was never migrated + // by the earlier "20160715.event.03.allday.php" migration. The dateFrom + // and dateTo will be minimum and maximum earthly seconds for the event. We + // convert them to UTC if they were in extreme timezones. + $epoch_min = $row['dateFrom']; + $epoch_max = $row['dateTo']; + + if ($is_all_day) { + $date_min = new DateTime('@'.$epoch_min); + $date_max = new DateTime('@'.$epoch_max); + + $date_min->setTimeZone($zone_min); + $date_min->modify('+2 days'); + $date_max->setTimeZone($zone_max); + $date_max->modify('-2 days'); + + $string_min = $date_min->format('Y-m-d'); + $string_max = $date_max->format('Y-m-d 23:59:00'); + + $utc_min = id(new DateTime($string_min, $zone_utc))->format('U'); + $utc_max = id(new DateTime($string_max, $zone_utc))->format('U'); + } else { + $utc_min = $epoch_min; + $utc_max = $epoch_max; + } + } else { + // This is an event which was migrated already. We can pick the correct + // epoch timestamps based on the "isAllDay" flag. + if ($is_all_day) { + $utc_min = $row['allDayDateFrom']; + $utc_max = $row['allDayDateTo']; + } else { + $utc_min = $row['dateFrom']; + $utc_max = $row['dateTo']; + } + } + + $utc_until = $row['recurrenceEndDate']; + + $start_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($utc_min); + if ($is_all_day) { + $start_datetime->setIsAllDay(true); + } + + $end_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($utc_max); + if ($is_all_day) { + $end_datetime->setIsAllDay(true); + } + + if ($utc_until) { + $until_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($utc_until); + } else { + $until_datetime = null; + } + + $parameters['startDateTime'] = $start_datetime->toDictionary(); + $parameters['endDateTime'] = $end_datetime->toDictionary(); + if ($until_datetime) { + $parameters['untilDateTime'] = $until_datetime->toDictionary(); + } + + queryfx( + $conn, + 'UPDATE %T SET parameters = %s WHERE id = %d', + $table_name, + phutil_json_encode($parameters), + $row['id']); +} + +// Generate UTC epochs for all events. We can't readily do this one at a +// time because instance UTC epochs rely on having the parent event. +$viewer = PhabricatorUser::getOmnipotentUser(); + +$all_events = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->execute(); +foreach ($all_events as $event) { + if ($event->getUTCInitialEpoch()) { + // Already migrated. + continue; + } + + try { + $event->updateUTCEpochs(); + } catch (Exception $ex) { + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET + utcInitialEpoch = %d, + utcUntilEpoch = %nd, + utcInstanceEpoch = %nd WHERE id = %d', + $table_name, + $event->getUTCInitialEpoch(), + $event->getUTCUntilEpoch(), + $event->getUTCInstanceEpoch(), + $event->getID()); +} diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 6c10964b02..1ba40d51a6 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -352,14 +352,14 @@ final class PhabricatorCalendarEventQuery if ($this->rangeBegin) { $where[] = qsprintf( $conn, - 'event.dateTo >= %d OR event.isRecurring = 1', + '(event.utcUntilEpoch >= %d) OR (event.utcUntilEpoch IS NULL)', $this->rangeBegin - phutil_units('16 hours in seconds')); } if ($this->rangeEnd) { $where[] = qsprintf( $conn, - 'event.dateFrom <= %d', + 'event.utcInitialEpoch <= %d', $this->rangeEnd + phutil_units('16 hours in seconds')); } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 2ddc71115f..8d93e839f2 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -17,10 +17,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $name; protected $hostPHID; - protected $dateFrom; - protected $dateTo; - protected $allDayDateFrom; - protected $allDayDateTo; protected $description; protected $isCancelled; protected $isAllDay; @@ -30,7 +26,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $isRecurring = 0; protected $recurrenceFrequency = array(); - protected $recurrenceEndDate; private $isGhostEvent = false; protected $instanceOfEventPHID; @@ -51,6 +46,13 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO private $viewerTimezone; + // TODO: DEPRECATED. Remove once we're sure the migrations worked. + protected $allDayDateFrom; + protected $allDayDateTo; + protected $dateFrom; + protected $dateTo; + protected $recurrenceEndDate; + // Frequency Constants const FREQUENCY_DAILY = 'daily'; const FREQUENCY_WEEKLY = 'weekly'; @@ -70,20 +72,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $now = PhabricatorTime::getNow(); - $start = new DateTime('@'.$now); - $start->setTimeZone($actor->getTimeZone()); - - $start->setTime($start->format('H'), 0, 0); - $start->modify('+1 hour'); - $end = id(clone $start)->modify('+1 hour'); - - $epoch_min = $start->format('U'); - $epoch_max = $end->format('U'); - - $now_date = new DateTime('@'.$now); - $now_min = id(clone $now_date)->setTime(0, 0)->format('U'); - $now_max = id(clone $now_date)->setTime(23, 59)->format('U'); - $default_icon = 'fa-calendar'; $datetime_start = PhutilCalendarAbsoluteDateTime::newFromEpoch( @@ -106,10 +94,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setEditPolicy($edit_policy) ->setSpacePHID($actor->getDefaultSpacePHID()) ->attachInvitees(array()) - ->setDateFrom($epoch_min) - ->setDateTo($epoch_max) - ->setAllDayDateFrom($now_min) - ->setAllDayDateTo($now_max) + ->setDateFrom(0) + ->setDateTo(0) + ->setAllDayDateFrom(0) + ->setAllDayDateTo(0) ->setStartDateTime($datetime_start) ->setEndDateTime($datetime_end) ->applyViewerTimezone($actor); @@ -130,7 +118,11 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setSequenceIndex($sequence) ->setIsRecurring(true) ->setRecurrenceFrequency($this->getRecurrenceFrequency()) - ->attachParentEvent($this); + ->attachParentEvent($this) + ->setAllDayDateFrom(0) + ->setAllDayDateTo(0) + ->setDateFrom(0) + ->setDateTo(0); return $child->copyFromParent($actor); } @@ -187,11 +179,16 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $duration = $parent->getDuration(); $epochs = $parent->getSequenceIndexEpochs($actor, $sequence, $duration); + $start_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( + $epochs['dateFrom'], + $parent->newStartDateTime()->getTimezone()); + $end_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( + $epochs['dateTo'], + $parent->newEndDateTime()->getTimezone()); + $this - ->setDateFrom($epochs['dateFrom']) - ->setDateTo($epochs['dateTo']) - ->setAllDayDateFrom($epochs['allDayDateFrom']) - ->setAllDayDateTo($epochs['allDayDateTo']); + ->setStartDateTime($start_datetime) + ->setEndDateTime($end_datetime); return $this; } @@ -213,12 +210,14 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $frequency = $this->getFrequencyUnit(); $modify_key = '+'.$sequence.' '.$frequency; - $date = $this->getDateFrom(); - $date_time = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); + $date_time = $this->newStartDateTime() + ->setViewerTimezone($viewer->getTimezoneIdentifier()) + ->newPHPDateTime(); + $date_time->modify($modify_key); $date = $date_time->format('U'); - $end_date = $this->getRecurrenceEndDate(); + $end_date = $this->getUntilDateTimeEpoch(); if ($end_date && $date > $end_date) { throw new Exception( pht( @@ -227,21 +226,9 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $sequence)); } - $utc = new DateTimeZone('UTC'); - - $allday_from = $this->getAllDayDateFrom(); - $allday_date = new DateTime('@'.$allday_from, $utc); - $allday_date->setTimeZone($utc); - $allday_date->modify($modify_key); - - $allday_min = $allday_date->format('U'); - $allday_duration = ($this->getAllDayDateTo() - $allday_from); - return array( 'dateFrom' => $date, 'dateTo' => $date + $duration, - 'allDayDateFrom' => $allday_min, - 'allDayDateTo' => $allday_min + $allday_duration, ); } @@ -280,24 +267,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return ($this->getEndDateTimeEpoch() - $this->getStartDateTimeEpoch()); } - public 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 updateUTCEpochs() { // The "intitial" epoch is the start time of the event, in UTC. $start_date = $this->newStartDateTime() @@ -314,9 +283,9 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $until_epoch = $end_date->getEpoch(); } else { $until_epoch = null; - $until_date = $this->newUntilDateTime() - ->setViewerTimezone('UTC'); + $until_date = $this->newUntilDateTime(); if ($until_date) { + $until_date->setViewerTimezone('UTC'); $duration = $this->newDuration(); $until_epoch = id(new PhutilCalendarRelativeDateTime()) ->setOrigin($until_date) @@ -377,23 +346,25 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text', - 'dateFrom' => 'epoch', - 'dateTo' => 'epoch', - 'allDayDateFrom' => 'epoch', - 'allDayDateTo' => 'epoch', 'description' => 'text', 'isCancelled' => 'bool', 'isAllDay' => 'bool', 'icon' => 'text32', 'mailKey' => 'bytes20', 'isRecurring' => 'bool', - 'recurrenceEndDate' => 'epoch?', 'instanceOfEventPHID' => 'phid?', 'sequenceIndex' => 'uint32?', 'isStub' => 'bool', 'utcInitialEpoch' => 'epoch', 'utcUntilEpoch' => 'epoch?', 'utcInstanceEpoch' => 'epoch?', + + // TODO: DEPRECATED. + 'allDayDateFrom' => 'epoch', + 'allDayDateTo' => 'epoch', + 'dateFrom' => 'epoch', + 'dateTo' => 'epoch', + 'recurrenceEndDate' => 'epoch?', ), self::CONFIG_KEY_SCHEMA => array( 'key_date' => array( @@ -814,7 +785,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return null; } - $epochs = $this->getParent()->getSequenceIndexEpochs( + $epochs = $this->getParentEvent()->getSequenceIndexEpochs( new PhabricatorUser(), $this->getSequenceIndex(), $this->getDuration()); diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php index 2d5f10d290..a0d127c32f 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php @@ -13,16 +13,6 @@ final class PhabricatorCalendarEventEndDateTransaction public function applyInternalEffects($object, $value) { $actor = $this->getActor(); - // TODO: DEPRECATED. - $object->setDateTo($value); - $object->setAllDayDateTo( - $object->getDateEpochForTimezone( - $value, - $actor->getTimeZone(), - 'Y-m-d 23:59:00', - null, - new DateTimeZone('UTC'))); - $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( $value, $actor->getTimezoneIdentifier()); diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php index 48318edf7e..e08bbac780 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php @@ -13,16 +13,6 @@ final class PhabricatorCalendarEventStartDateTransaction public function applyInternalEffects($object, $value) { $actor = $this->getActor(); - // TODO: DEPRECATED. - $object->setDateFrom($value); - $object->setAllDayDateFrom( - $object->getDateEpochForTimezone( - $value, - $actor->getTimeZone(), - 'Y-m-d', - null, - new DateTimeZone('UTC'))); - $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( $value, $actor->getTimezoneIdentifier()); From 5dfb672a80f0def971231d42f1c51e226136b373 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 3 Oct 2016 08:56:51 -0700 Subject: [PATCH 26/36] Mostly drive Calendar event recurrence with the RRULE engine Summary: Ref T10737. Today, we evalute recurrence twice: once when querying, and once in all other cases. This converts the second case to use the RRULE engine. Next up is making the query use the RRULE engine, too. Test Plan: Created a new recurring event, iterated through it by clicking "next instance", viewed it on Calendar view. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10737 Differential Revision: https://secure.phabricator.com/D16667 --- .../query/PhabricatorCalendarEventQuery.php | 4 +- .../storage/PhabricatorCalendarEvent.php | 104 ++++++++++-------- 2 files changed, 60 insertions(+), 48 deletions(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 1ba40d51a6..5d06001890 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -203,8 +203,8 @@ final class PhabricatorCalendarEventQuery $end_times[] = $this->rangeEnd; } - if ($event->getRecurrenceEndDate()) { - $end_times[] = $event->getRecurrenceEndDate(); + if ($event->getUntilDateTimeEpoch()) { + $end_times[] = $event->getUntilDateTimeEpoch(); } if ($enforced_end) { diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 8d93e839f2..db86d3980b 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -176,15 +176,15 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setDescription($parent->getDescription()); $sequence = $this->getSequenceIndex(); - $duration = $parent->getDuration(); - $epochs = $parent->getSequenceIndexEpochs($actor, $sequence, $duration); + $start_datetime = $parent->newSequenceIndexDateTime($sequence); - $start_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( - $epochs['dateFrom'], - $parent->newStartDateTime()->getTimezone()); - $end_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( - $epochs['dateTo'], - $parent->newEndDateTime()->getTimezone()); + if (!$start_datetime) { + throw new Exception( + "Sequence {$sequence} does not exist for event!"); + } + + $duration = $parent->newDuration(); + $end_datetime = $start_datetime->newRelativeDateTime($duration); $this ->setStartDateTime($start_datetime) @@ -194,42 +194,21 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } public function isValidSequenceIndex(PhabricatorUser $viewer, $sequence) { - try { - $this->getSequenceIndexEpochs($viewer, $sequence, $this->getDuration()); - return true; - } catch (Exception $ex) { - return false; - } + return (bool)$this->newSequenceIndexDateTime($sequence); } - private function getSequenceIndexEpochs( - PhabricatorUser $viewer, - $sequence, - $duration) { - - $frequency = $this->getFrequencyUnit(); - $modify_key = '+'.$sequence.' '.$frequency; - - $date_time = $this->newStartDateTime() - ->setViewerTimezone($viewer->getTimezoneIdentifier()) - ->newPHPDateTime(); - - $date_time->modify($modify_key); - $date = $date_time->format('U'); - - $end_date = $this->getUntilDateTimeEpoch(); - if ($end_date && $date > $end_date) { - throw new Exception( - pht( - 'Sequence "%s" is invalid for this event: it would occur after '. - 'the event stops repeating.', - $sequence)); + public function newSequenceIndexDateTime($sequence) { + $set = $this->newRecurrenceSet(); + if (!$set) { + return null; } - return array( - 'dateFrom' => $date, - 'dateTo' => $date + $duration, - ); + $instances = $set->getEventsBetween( + null, + $this->newUntilDateTime(), + $sequence + 1); + + return idx($instances, $sequence, null); } public function newStub(PhabricatorUser $actor, $sequence) { @@ -785,13 +764,12 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return null; } - $epochs = $this->getParentEvent()->getSequenceIndexEpochs( - new PhabricatorUser(), - $this->getSequenceIndex(), - $this->getDuration()); + $index = $this->getSequenceIndex(); + if (!$index) { + return null; + } - $epoch = $epochs['dateFrom']; - return $this->newDateTimeFromEpoch($epoch); + return $this->newSequenceIndexDateTime($index); } private function newDateTimeFromEpoch($epoch) { @@ -845,6 +823,40 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $datetime->newAbsoluteDateTime()->toDictionary()); } + + public function newRecurrenceRule() { + if ($this->isChildEvent()) { + return $this->getParentEvent()->newRecurrenceRule(); + } + + // TODO: This is a little fragile since it relies on the convenient + // definition of FREQUENCY constants here and in RecurrenceRule, but + // should be gone soon. + $map = array( + 'FREQ' => phutil_utf8_strtoupper($this->getFrequencyRule()), + ); + + $rrule = PhutilCalendarRecurrenceRule::newFromDictionary($map); + + $start = $this->newStartDateTime(); + $rrule->setStartDateTime($start); + + return $rrule; + } + + public function newRecurrenceSet() { + if ($this->isChildEvent()) { + return $this->getParentEvent()->newRecurrenceSet(); + } + + $set = new PhutilCalendarRecurrenceSet(); + + $rrule = $this->newRecurrenceRule(); + $set->addSource($rrule); + + return $set; + } + /* -( Markup Interface )--------------------------------------------------- */ From 20f7de91ceb557b79378d03cfc3be09466dec2d4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 3 Oct 2016 07:40:01 -0700 Subject: [PATCH 27/36] Drive calendar event queries through the RRULE engine Summary: Ref T10747. This drives event queries through RRULE, too. Test Plan: Created recurring events, saw them appear correctly on the calendar. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16668 --- .../query/PhabricatorCalendarEventQuery.php | 134 +++++++++++------- .../storage/PhabricatorCalendarEvent.php | 35 +++-- 2 files changed, 105 insertions(+), 64 deletions(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 5d06001890..06eb50cc54 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -165,70 +165,54 @@ final class PhabricatorCalendarEventQuery // discard anything outside of the time window. $events = $this->getEventsInRange($events); - $enforced_end = null; + $generate_from = $this->rangeBegin; + $generate_until = $this->rangeEnd; foreach ($parents as $key => $event) { - $sequence_start = 0; - $sequence_end = null; - $start = null; - $duration = $event->getDuration(); - $frequency = $event->getFrequencyUnit(); - $modify_key = '+1 '.$frequency; + $start_date = $this->getRecurrenceWindowStart( + $event, + $generate_from - $duration); - if (($this->rangeBegin !== null) && - ($this->rangeBegin > $event->getStartDateTimeEpoch())) { - $max_date = $this->rangeBegin - $duration; - $date = $event->getStartDateTimeEpoch(); - $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); + $end_date = $this->getRecurrenceWindowEnd( + $event, + $generate_until); - while ($date < $max_date) { - // TODO: optimize this to not loop through all off-screen events - $sequence_start++; - $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); - $date = $datetime->modify($modify_key)->format('U'); + $limit = $this->getRecurrenceLimit($event, $raw_limit); + + $set = $event->newRecurrenceSet(); + + $recurrences = $set->getEventsBetween( + null, + $end_date, + $limit + 1); + + // We're generating events from the beginning and then filtering them + // here (instead of only generating events starting at the start date) + // because we need to know the proper sequence indexes to generate ghost + // events. This may change after RDATE support. + if ($start_date) { + $start_epoch = $start_date->getEpoch(); + } else { + $start_epoch = null; + } + + foreach ($recurrences as $sequence_index => $sequence_datetime) { + if (!$sequence_index) { + // This is the parent event, which we already have. + continue; } - $start = $this->rangeBegin; - } else { - $start = $event->getStartDateTimeEpoch() - $duration; - } - - $date = $start; - $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); - - // Select the minimum end time we need to generate events until. - $end_times = array(); - if ($this->rangeEnd) { - $end_times[] = $this->rangeEnd; - } - - if ($event->getUntilDateTimeEpoch()) { - $end_times[] = $event->getUntilDateTimeEpoch(); - } - - if ($enforced_end) { - $end_times[] = $enforced_end; - } - - if ($end_times) { - $end = min($end_times); - $sequence_end = $sequence_start; - while ($date < $end) { - $sequence_end++; - $datetime->modify($modify_key); - $date = $datetime->format('U'); - if ($sequence_end > $raw_limit + $sequence_start) { - break; + if ($start_epoch) { + if ($sequence_datetime->getEpoch() < $start_epoch) { + continue; } } - } else { - $sequence_end = $raw_limit + $sequence_start; - } - $sequence_start = max(1, $sequence_start); - for ($index = $sequence_start; $index < $sequence_end; $index++) { - $events[] = $event->newGhost($viewer, $index); + $events[] = $event->newGhost( + $viewer, + $sequence_index, + $sequence_datetime); } // NOTE: We're slicing results every time because this makes it cheaper @@ -240,7 +224,7 @@ final class PhabricatorCalendarEventQuery if (count($events) > $raw_limit) { $events = msort($events, 'getStartDateTimeEpoch'); $events = array_slice($events, 0, $raw_limit, true); - $enforced_end = last($events)->getStartDateTimeEpoch(); + $generate_until = last($events)->getEndDateTimeEpoch(); } } } @@ -525,4 +509,44 @@ final class PhabricatorCalendarEventQuery return $events; } + private function getRecurrenceWindowStart( + PhabricatorCalendarEvent $event, + $generate_from) { + + if (!$generate_from) { + return null; + } + + return PhutilCalendarAbsoluteDateTime::newFromEpoch($generate_from); + } + + private function getRecurrenceWindowEnd( + PhabricatorCalendarEvent $event, + $generate_until) { + + $end_epochs = array(); + if ($generate_until) { + $end_epochs[] = $generate_until; + } + + $until_epoch = $event->getUntilDateTimeEpoch(); + if ($until_epoch) { + $end_epochs[] = $until_epoch; + } + + if (!$end_epochs) { + return null; + } + + return PhutilCalendarAbsoluteDateTime::newFromEpoch(min($end_epochs)); + } + + private function getRecurrenceLimit( + PhabricatorCalendarEvent $event, + $raw_limit) { + + return $raw_limit; + } + + } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index db86d3980b..060c6fe5c5 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -103,7 +103,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->applyViewerTimezone($actor); } - private function newChild(PhabricatorUser $actor, $sequence) { + private function newChild( + PhabricatorUser $actor, + $sequence, + PhutilCalendarDateTime $start = null) { if (!$this->isParentEvent()) { throw new Exception( pht( @@ -124,7 +127,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setDateFrom(0) ->setDateTo(0); - return $child->copyFromParent($actor); + return $child->copyFromParent($actor, $start); } protected function readField($field) { @@ -156,7 +159,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO } - public function copyFromParent(PhabricatorUser $actor) { + public function copyFromParent( + PhabricatorUser $actor, + PhutilCalendarDateTime $start = null) { + if (!$this->isChildEvent()) { throw new Exception( pht( @@ -176,11 +182,18 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setDescription($parent->getDescription()); $sequence = $this->getSequenceIndex(); - $start_datetime = $parent->newSequenceIndexDateTime($sequence); - if (!$start_datetime) { - throw new Exception( - "Sequence {$sequence} does not exist for event!"); + if ($start) { + $start_datetime = $start; + } else { + $start_datetime = $parent->newSequenceIndexDateTime($sequence); + + if (!$start_datetime) { + throw new Exception( + pht( + 'Sequence "%s" is not valid for event!', + $sequence)); + } } $duration = $parent->newDuration(); @@ -225,8 +238,12 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $stub; } - public function newGhost(PhabricatorUser $actor, $sequence) { - $ghost = $this->newChild($actor, $sequence); + public function newGhost( + PhabricatorUser $actor, + $sequence, + PhutilCalendarDateTime $start = null) { + + $ghost = $this->newChild($actor, $sequence, $start); $ghost ->setIsGhostEvent(true) From 3164ff68db586beedb4c1eb9492c5c9fa1dfa988 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 5 Oct 2016 10:51:09 -0700 Subject: [PATCH 28/36] Convert Calendar Events to use RRULE frequency constants in various other places Summary: Ref T10747. - Store recurrence as RRULEs internally. - Use RRULE constants. - Migrate existing rules to RRULEs. Test Plan: Ran migration, nothing seemed broken? Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16670 --- .../autopatches/20161005.cal.01.rrules.php | 44 +++++++++++ ...PhabricatorCalendarEventViewController.php | 19 +++-- .../PhabricatorCalendarEventEditEngine.php | 18 +++-- .../storage/PhabricatorCalendarEvent.php | 59 +++++---------- ...catorCalendarEventFrequencyTransaction.php | 74 ++++++++++++++----- 5 files changed, 146 insertions(+), 68 deletions(-) create mode 100644 resources/sql/autopatches/20161005.cal.01.rrules.php diff --git a/resources/sql/autopatches/20161005.cal.01.rrules.php b/resources/sql/autopatches/20161005.cal.01.rrules.php new file mode 100644 index 0000000000..e2e61ba30a --- /dev/null +++ b/resources/sql/autopatches/20161005.cal.01.rrules.php @@ -0,0 +1,44 @@ +establishConnection('w'); +$table_name = 'calendar_event'; + +foreach (new LiskRawMigrationIterator($conn, $table_name) as $row) { + $parameters = phutil_json_decode($row['parameters']); + if (isset($parameters['recurrenceRule'])) { + // This event has already been migrated. + continue; + } + + if (!$row['isRecurring']) { + continue; + } + + $old_rule = $row['recurrenceFrequency']; + if (!$old_rule) { + continue; + } + + try { + $frequency = phutil_json_decode($old_rule); + if ($frequency) { + $frequency_rule = $frequency['rule']; + $frequency_rule = phutil_utf8_strtoupper($frequency_rule); + + $rrule = id(new PhutilCalendarRecurrenceRule()) + ->setFrequency($frequency_rule); + } + } catch (Exception $ex) { + continue; + } + + $parameters['recurrenceRule'] = $rrule->toDictionary(); + + queryfx( + $conn, + 'UPDATE %T SET parameters = %s WHERE id = %d', + $table_name, + phutil_json_encode($parameters), + $row['id']); +} diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 8f7ee8929e..2a1e08ae89 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -348,9 +348,16 @@ final class PhabricatorCalendarEventViewController ->render(); } - $rule = $event->getFrequencyRule(); - switch ($rule) { - case PhabricatorCalendarEvent::FREQUENCY_DAILY: + $rrule = $event->newRecurrenceRule(); + + if ($rrule) { + $frequency = $rrule->getFrequency(); + } else { + $frequency = null; + } + + switch ($frequency) { + case PhutilCalendarRecurrenceRule::FREQUENCY_DAILY: if ($is_parent) { $message = pht('This event repeats every day.'); } else { @@ -359,7 +366,7 @@ final class PhabricatorCalendarEventViewController $parent_link); } break; - case PhabricatorCalendarEvent::FREQUENCY_WEEKLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY: if ($is_parent) { $message = pht('This event repeats every week.'); } else { @@ -368,7 +375,7 @@ final class PhabricatorCalendarEventViewController $parent_link); } break; - case PhabricatorCalendarEvent::FREQUENCY_MONTHLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY: if ($is_parent) { $message = pht('This event repeats every month.'); } else { @@ -377,7 +384,7 @@ final class PhabricatorCalendarEventViewController $parent_link); } break; - case PhabricatorCalendarEvent::FREQUENCY_YEARLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY: if ($is_parent) { $message = pht('This event repeats every year.'); } else { diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php index 63d33beab4..17ea7552e9 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php @@ -68,10 +68,10 @@ final class PhabricatorCalendarEventEditEngine } $frequency_options = array( - PhabricatorCalendarEvent::FREQUENCY_DAILY => pht('Daily'), - PhabricatorCalendarEvent::FREQUENCY_WEEKLY => pht('Weekly'), - PhabricatorCalendarEvent::FREQUENCY_MONTHLY => pht('Monthly'), - PhabricatorCalendarEvent::FREQUENCY_YEARLY => pht('Yearly'), + PhutilCalendarRecurrenceRule::FREQUENCY_DAILY => pht('Daily'), + PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY => pht('Weekly'), + PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY => pht('Monthly'), + PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY => pht('Yearly'), ); $fields = array( @@ -142,6 +142,14 @@ final class PhabricatorCalendarEventEditEngine ->setConduitTypeDescription(pht('Mark the event as a recurring event.')) ->setValue($object->getIsRecurring()); + + $rrule = $object->newRecurrenceRule(); + if ($rrule) { + $frequency = $rrule->getFrequency(); + } else { + $frequency = null; + } + $fields[] = id(new PhabricatorSelectEditField()) ->setKey('frequency') ->setLabel(pht('Frequency')) @@ -151,7 +159,7 @@ final class PhabricatorCalendarEventEditEngine ->setDescription(pht('Recurring event frequency.')) ->setConduitDescription(pht('Change the event frequency.')) ->setConduitTypeDescription(pht('New event frequency.')) - ->setValue($object->getFrequencyRule()); + ->setValue($frequency); } if ($this->getIsCreate() || $object->getIsRecurring()) { diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 060c6fe5c5..676d85b608 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -25,7 +25,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $isStub; protected $isRecurring = 0; - protected $recurrenceFrequency = array(); private $isGhostEvent = false; protected $instanceOfEventPHID; @@ -52,12 +51,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $dateFrom; protected $dateTo; protected $recurrenceEndDate; - - // Frequency Constants - const FREQUENCY_DAILY = 'daily'; - const FREQUENCY_WEEKLY = 'weekly'; - const FREQUENCY_MONTHLY = 'monthly'; - const FREQUENCY_YEARLY = 'yearly'; + protected $recurrenceFrequency = array(); public static function initializeNewCalendarEvent(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) @@ -85,10 +79,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setIsAllDay(0) ->setIsStub(0) ->setIsRecurring(0) - ->setRecurrenceFrequency( - array( - 'rule' => self::FREQUENCY_WEEKLY, - )) ->setIcon($default_icon) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy) @@ -120,7 +110,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setInstanceOfEventPHID($this->getPHID()) ->setSequenceIndex($sequence) ->setIsRecurring(true) - ->setRecurrenceFrequency($this->getRecurrenceFrequency()) ->attachParentEvent($this) ->setAllDayDateFrom(0) ->setAllDayDateTo(0) @@ -456,27 +445,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $this; } - public function getFrequencyRule() { - return idx($this->recurrenceFrequency, 'rule'); - } - - public function getFrequencyUnit() { - $frequency = $this->getFrequencyRule(); - - switch ($frequency) { - case 'daily': - return 'day'; - case 'weekly': - return 'week'; - case 'monthly': - return 'month'; - case 'yearly': - return 'year'; - default: - return 'day'; - } - } - public function getURI() { if ($this->getIsGhostEvent()) { $base = $this->getParentEvent()->getURI(); @@ -840,20 +808,27 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $datetime->newAbsoluteDateTime()->toDictionary()); } + public function setRecurrenceRule(PhutilCalendarRecurrenceRule $rrule) { + return $this->setParameter( + 'recurrenceRule', + $rrule->toDictionary()); + } public function newRecurrenceRule() { if ($this->isChildEvent()) { return $this->getParentEvent()->newRecurrenceRule(); } - // TODO: This is a little fragile since it relies on the convenient - // definition of FREQUENCY constants here and in RecurrenceRule, but - // should be gone soon. - $map = array( - 'FREQ' => phutil_utf8_strtoupper($this->getFrequencyRule()), - ); + if (!$this->getIsRecurring()) { + return null; + } - $rrule = PhutilCalendarRecurrenceRule::newFromDictionary($map); + $dict = $this->getParameter('recurrenceRule'); + if (!$dict) { + return null; + } + + $rrule = PhutilCalendarRecurrenceRule::newFromDictionary($dict); $start = $this->newStartDateTime(); $rrule->setStartDateTime($start); @@ -869,6 +844,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $set = new PhutilCalendarRecurrenceSet(); $rrule = $this->newRecurrenceRule(); + if (!$rrule) { + return null; + } + $set->addSource($rrule); return $set; diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php index 9690a97dce..2da4f1da62 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php @@ -6,32 +6,66 @@ final class PhabricatorCalendarEventFrequencyTransaction const TRANSACTIONTYPE = 'calendar.frequency'; public function generateOldValue($object) { - return $object->getFrequencyRule(); + $rrule = $object->newRecurrenceRule(); + + if (!$rrule) { + return null; + } + + return $rrule->getFrequency(); } public function applyInternalEffects($object, $value) { - $object->setRecurrenceFrequency( - array( - 'rule' => $value, - )); + $rrule = id(new PhutilCalendarRecurrenceRule()) + ->setFrequency($value); + + $dict = $rrule->toDictionary(); + $object->setRecurrenceRule($dict); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $valid = array( + PhutilCalendarRecurrenceRule::FREQUENCY_DAILY, + PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY, + PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY, + PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY, + ); + $valid = array_fuse($valid); + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + + if (!isset($valid[$value])) { + $errors[] = $this->newInvalidError( + pht( + 'Event frequency "%s" is not valid. Valid frequences are: %s.', + $value, + implode(', ', $valid)), + $xaction); + } + } + + return $errors; } public function getTitle() { - $frequency = $this->getFrequencyRule($this->getNewValue()); + $frequency = $this->getFrequency($this->getNewValue()); switch ($frequency) { - case PhabricatorCalendarEvent::FREQUENCY_DAILY: + case PhutilCalendarRecurrenceRule::FREQUENCY_DAILY: return pht( '%s set this event to repeat daily.', $this->renderAuthor()); - case PhabricatorCalendarEvent::FREQUENCY_WEEKLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY: return pht( '%s set this event to repeat weekly.', $this->renderAuthor()); - case PhabricatorCalendarEvent::FREQUENCY_MONTHLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY: return pht( '%s set this event to repeat monthly.', $this->renderAuthor()); - case PhabricatorCalendarEvent::FREQUENCY_YEARLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY: return pht( '%s set this event to repeat yearly.', $this->renderAuthor()); @@ -39,24 +73,24 @@ final class PhabricatorCalendarEventFrequencyTransaction } public function getTitleForFeed() { - $frequency = $this->getFrequencyRule($this->getNewValue()); + $frequency = $this->getFrequency($this->getNewValue()); switch ($frequency) { - case PhabricatorCalendarEvent::FREQUENCY_DAILY: + case PhutilCalendarRecurrenceRule::FREQUENCY_DAILY: return pht( '%s set %s to repeat daily.', $this->renderAuthor(), $this->renderObject()); - case PhabricatorCalendarEvent::FREQUENCY_WEEKLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY: return pht( '%s set %s to repeat weekly.', $this->renderAuthor(), $this->renderObject()); - case PhabricatorCalendarEvent::FREQUENCY_MONTHLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY: return pht( '%s set %s to repeat monthly.', $this->renderAuthor(), $this->renderObject()); - case PhabricatorCalendarEvent::FREQUENCY_YEARLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY: return pht( '%s set %s to repeat yearly.', $this->renderAuthor(), @@ -64,12 +98,18 @@ final class PhabricatorCalendarEventFrequencyTransaction } } - private function getFrequencyRule($value) { + private function getFrequency($value) { + // NOTE: This is normalizing three generations of these transactions + // to use RRULE constants. It would be vaguely nice to migrate them + // for consistency. + if (is_array($value)) { $value = idx($value, 'rule'); } else { - return $value; + $value = $value; } + + return phutil_utf8_strtoupper($value); } } From c5efa3ecb5d5afa4b6d1c06336993ed84c35bd4b Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 5 Oct 2016 11:30:56 -0700 Subject: [PATCH 29/36] Swap "Description" and "Invitees / Details" on Calendar event views Summary: Ref T11326. This reorders sections: - Description (if present) - Recurring event series info (if recurring) - Invitees (this also has custom stuff, if it exists) Test Plan: Viewed some events, saw more sensible order. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16671 --- .../controller/PhabricatorCalendarEventViewController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 2a1e08ae89..89e8109a4c 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -72,9 +72,9 @@ final class PhabricatorCalendarEventViewController $comment_view, )) ->setCurtain($curtain) - ->addPropertySection($details_header, $details) + ->addPropertySection(pht('Description'), $description) ->addPropertySection($recurring_header, $recurring) - ->addPropertySection(pht('Description'), $description); + ->addPropertySection($details_header, $details); return $this->newPage() ->setTitle($page_title) From 49448a87c17cda66d63017f09315a790f2828849 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 5 Oct 2016 11:55:04 -0700 Subject: [PATCH 30/36] Rough in most of Calendar exports Summary: Ref T10747. Rough flow is: - Run a query. - Select a new "Export Events..." action. - This lets you define an "Export", which has a unique URL you can paste into Google Calendar or Calendar.app or whatever. Most of this does nothing yet but here's the boilerplate. Test Plan: Doesn't do anything yet. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16675 --- .../autopatches/20161005.cal.02.export.sql | 14 ++ .../20161005.cal.03.exportxaction.sql | 19 +++ src/__phutil_library_map__.php | 33 ++++ .../PhabricatorCalendarApplication.php | 10 ++ ...habricatorCalendarExportEditController.php | 12 ++ ...habricatorCalendarExportListController.php | 27 ++++ .../PhabricatorCalendarExportEditEngine.php | 96 ++++++++++++ .../PhabricatorCalendarExportEditor.php | 14 ++ .../PhabricatorCalendarExportPHIDType.php | 50 ++++++ .../query/PhabricatorCalendarExportQuery.php | 81 ++++++++++ .../PhabricatorCalendarExportSearchEngine.php | 109 +++++++++++++ .../storage/PhabricatorCalendarExport.php | 144 ++++++++++++++++++ .../PhabricatorCalendarExportTransaction.php | 18 +++ ...icatorCalendarExportDisableTransaction.php | 28 ++++ ...abricatorCalendarExportModeTransaction.php | 54 +++++++ ...abricatorCalendarExportNameTransaction.php | 35 +++++ ...catorCalendarExportQueryKeyTransaction.php | 51 +++++++ ...abricatorCalendarExportTransactionType.php | 4 + .../query/ManiphestTaskSearchEngine.php | 2 +- .../user/userguide/calendar_exports.diviner | 12 ++ src/infrastructure/storage/lisk/LiskDAO.php | 11 +- 21 files changed, 818 insertions(+), 6 deletions(-) create mode 100644 resources/sql/autopatches/20161005.cal.02.export.sql create mode 100644 resources/sql/autopatches/20161005.cal.03.exportxaction.sql create mode 100644 src/applications/calendar/controller/PhabricatorCalendarExportEditController.php create mode 100644 src/applications/calendar/controller/PhabricatorCalendarExportListController.php create mode 100644 src/applications/calendar/editor/PhabricatorCalendarExportEditEngine.php create mode 100644 src/applications/calendar/editor/PhabricatorCalendarExportEditor.php create mode 100644 src/applications/calendar/phid/PhabricatorCalendarExportPHIDType.php create mode 100644 src/applications/calendar/query/PhabricatorCalendarExportQuery.php create mode 100644 src/applications/calendar/query/PhabricatorCalendarExportSearchEngine.php create mode 100644 src/applications/calendar/storage/PhabricatorCalendarExport.php create mode 100644 src/applications/calendar/storage/PhabricatorCalendarExportTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarExportDisableTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarExportModeTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarExportNameTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php create mode 100644 src/docs/user/userguide/calendar_exports.diviner diff --git a/resources/sql/autopatches/20161005.cal.02.export.sql b/resources/sql/autopatches/20161005.cal.02.export.sql new file mode 100644 index 0000000000..bd1c031165 --- /dev/null +++ b/resources/sql/autopatches/20161005.cal.02.export.sql @@ -0,0 +1,14 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_export ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + authorPHID VARBINARY(64) NOT NULL, + policyMode VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}, + queryKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}, + secretKey BINARY(20) NOT NULL, + isDisabled BOOL NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + KEY `key_author` (authorPHID), + UNIQUE KEY `key_secret` (secretKey) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20161005.cal.03.exportxaction.sql b/resources/sql/autopatches/20161005.cal.03.exportxaction.sql new file mode 100644 index 0000000000..1161534015 --- /dev/null +++ b/resources/sql/autopatches/20161005.cal.03.exportxaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_exporttransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 72e7854799..73787b0dee 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2075,6 +2075,20 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarEventTransactionType.php', 'PhabricatorCalendarEventUntilDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php', 'PhabricatorCalendarEventViewController' => 'applications/calendar/controller/PhabricatorCalendarEventViewController.php', + 'PhabricatorCalendarExport' => 'applications/calendar/storage/PhabricatorCalendarExport.php', + 'PhabricatorCalendarExportDisableTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportDisableTransaction.php', + 'PhabricatorCalendarExportEditController' => 'applications/calendar/controller/PhabricatorCalendarExportEditController.php', + 'PhabricatorCalendarExportEditEngine' => 'applications/calendar/editor/PhabricatorCalendarExportEditEngine.php', + 'PhabricatorCalendarExportEditor' => 'applications/calendar/editor/PhabricatorCalendarExportEditor.php', + 'PhabricatorCalendarExportListController' => 'applications/calendar/controller/PhabricatorCalendarExportListController.php', + 'PhabricatorCalendarExportModeTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportModeTransaction.php', + 'PhabricatorCalendarExportNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportNameTransaction.php', + 'PhabricatorCalendarExportPHIDType' => 'applications/calendar/phid/PhabricatorCalendarExportPHIDType.php', + 'PhabricatorCalendarExportQuery' => 'applications/calendar/query/PhabricatorCalendarExportQuery.php', + 'PhabricatorCalendarExportQueryKeyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php', + 'PhabricatorCalendarExportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarExportSearchEngine.php', + 'PhabricatorCalendarExportTransaction' => 'applications/calendar/storage/PhabricatorCalendarExportTransaction.php', + 'PhabricatorCalendarExportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php', 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', 'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php', @@ -6825,6 +6839,25 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorCalendarEventUntilDateTransaction' => 'PhabricatorCalendarEventDateTransaction', 'PhabricatorCalendarEventViewController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarExport' => array( + 'PhabricatorCalendarDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorDestructibleInterface', + ), + 'PhabricatorCalendarExportDisableTransaction' => 'PhabricatorCalendarExportTransactionType', + 'PhabricatorCalendarExportEditController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarExportEditEngine' => 'PhabricatorEditEngine', + 'PhabricatorCalendarExportEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorCalendarExportListController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarExportModeTransaction' => 'PhabricatorCalendarExportTransactionType', + 'PhabricatorCalendarExportNameTransaction' => 'PhabricatorCalendarExportTransactionType', + 'PhabricatorCalendarExportPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorCalendarExportQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorCalendarExportQueryKeyTransaction' => 'PhabricatorCalendarExportTransactionType', + 'PhabricatorCalendarExportSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorCalendarExportTransaction' => 'PhabricatorModularTransaction', + 'PhabricatorCalendarExportTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', 'PhabricatorCalendarIconSet' => 'PhabricatorIconSet', diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index e948d70198..ba18c13830 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -62,6 +62,16 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { 'export/(?P[1-9]\d*)/(?P[^/]*)' => 'PhabricatorCalendarEventExportController', ), + 'export/' => array( + $this->getQueryRoutePattern() + => 'PhabricatorCalendarExportListController', + $this->getEditRoutePattern('edit/') + => 'PhabricatorCalendarExportEditController', + '(?P[1-9]\d*)/' + => 'PhabricatorCalendarExportViewController', + 'ics/(?P[^/]+)/(?P[^/]*)' + => 'PhabricatorCalendarExportICSController', + ), ), ); } diff --git a/src/applications/calendar/controller/PhabricatorCalendarExportEditController.php b/src/applications/calendar/controller/PhabricatorCalendarExportEditController.php new file mode 100644 index 0000000000..4bd673576d --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarExportEditController.php @@ -0,0 +1,12 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/calendar/controller/PhabricatorCalendarExportListController.php b/src/applications/calendar/controller/PhabricatorCalendarExportListController.php new file mode 100644 index 0000000000..473a6277ba --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarExportListController.php @@ -0,0 +1,27 @@ +setController($this) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $doc_name = 'Calendar User Guide: Exporting Events'; + $doc_href = PhabricatorEnv::getDoclink($doc_name); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Guide: Exporting Events')) + ->setIcon('fa-book') + ->setHref($doc_href)); + + return $crumbs; + } + +} diff --git a/src/applications/calendar/editor/PhabricatorCalendarExportEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarExportEditEngine.php new file mode 100644 index 0000000000..f637200597 --- /dev/null +++ b/src/applications/calendar/editor/PhabricatorCalendarExportEditEngine.php @@ -0,0 +1,96 @@ +getViewer()); + } + + protected function newObjectQuery() { + return new PhabricatorCalendarExportQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Export'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Export: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getMonogram(); + } + + protected function getObjectCreateShortText() { + return pht('Create Export'); + } + + protected function getObjectName() { + return pht('Export'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('export/edit/'); + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + + $fields = array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the export.')) + ->setIsRequired(true) + ->setTransactionType( + PhabricatorCalendarExportNameTransaction::TRANSACTIONTYPE) + ->setConduitDescription(pht('Rename the export.')) + ->setConduitTypeDescription(pht('New export name.')) + ->setValue($object->getName()), + id(new PhabricatorBoolEditField()) + ->setKey('disabled') + ->setOptions(pht('Active'), pht('Disabled')) + ->setLabel(pht('Disabled')) + ->setDescription(pht('Disable the export.')) + ->setTransactionType( + PhabricatorCalendarExportDisableTransaction::TRANSACTIONTYPE) + ->setIsConduitOnly(true) + ->setConduitDescription(pht('Disable or restore the export.')) + ->setConduitTypeDescription(pht('True to cancel the export.')) + ->setValue($object->getIsDisabled()), + ); + + return $fields; + } + + +} diff --git a/src/applications/calendar/editor/PhabricatorCalendarExportEditor.php b/src/applications/calendar/editor/PhabricatorCalendarExportEditor.php new file mode 100644 index 0000000000..efdcb0e522 --- /dev/null +++ b/src/applications/calendar/editor/PhabricatorCalendarExportEditor.php @@ -0,0 +1,14 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $export = $objects[$phid]; + + $id = $export->getID(); + $name = $export->getName(); + $uri = $export->getURI(); + + $handle + ->setName($name) + ->setFullName(pht('Calendar Export %s: %s', $id, $name)) + ->setURI($uri); + + if ($export->getIsDisabled()) { + $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); + } + } + } + +} diff --git a/src/applications/calendar/query/PhabricatorCalendarExportQuery.php b/src/applications/calendar/query/PhabricatorCalendarExportQuery.php new file mode 100644 index 0000000000..ecd3dc8e45 --- /dev/null +++ b/src/applications/calendar/query/PhabricatorCalendarExportQuery.php @@ -0,0 +1,81 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withAuthorPHIDs(array $phids) { + $this->authorPHIDs = $phids; + return $this; + } + + public function withIsDisabled($is_disabled) { + $this->isDisabled = $is_disabled; + return $this; + } + + public function newResultObject() { + return new PhabricatorCalendarExport(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'export.id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'export.phid IN (%Ls)', + $this->phids); + } + + if ($this->authorPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'export.authorPHID IN (%Ls)', + $this->authorPHIDs); + } + + if ($this->isDisabled !== null) { + $where[] = qsprintf( + $conn, + 'export.isDisabled = %d', + (int)$this->isDisabled); + } + + return $where; + } + + protected function getPrimaryTableAlias() { + return 'export'; + } + + public function getQueryApplicationClass() { + return 'PhabricatorCalendarApplication'; + } + +} diff --git a/src/applications/calendar/query/PhabricatorCalendarExportSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarExportSearchEngine.php new file mode 100644 index 0000000000..462bdaeea1 --- /dev/null +++ b/src/applications/calendar/query/PhabricatorCalendarExportSearchEngine.php @@ -0,0 +1,109 @@ +requireViewer(); + + return id(new PhabricatorCalendarExportQuery()) + ->withAuthorPHIDs(array($viewer->getPHID())); + } + + protected function buildCustomSearchFields() { + return array(); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + return $query; + } + + protected function getURI($path) { + return '/calendar/export/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Exports'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $exports, + PhabricatorSavedQuery $query, + array $handles) { + + assert_instances_of($exports, 'PhabricatorCalendarExport'); + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + foreach ($exports as $export) { + $item = id(new PHUIObjectItemView()) + ->setViewer($viewer) + ->setHeader($export->getName()) + ->setHref($export->getURI()); + + if ($export->getIsDisabled()) { + $item->setDisabled(true); + } + + $list->addItem($item); + } + + $result = new PhabricatorApplicationSearchResultView(); + $result->setObjectList($list); + $result->setNoDataString(pht('No exports found.')); + + return $result; + } + + protected function getNewUserBody() { + $doc_name = 'Calendar User Guide: Exporting Events'; + $doc_href = PhabricatorEnv::getDoclink($doc_name); + + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-book white') + ->setText($doc_name) + ->setHref($doc_href) + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon('fa-download') + ->setTitle(pht('No Exports Configured')) + ->setDescription( + pht( + 'You have not set up any events for export from Calendar yet. '. + 'See the documentation for instructions on how to get started.')) + ->addAction($create_button); + + return $view; + } + +} diff --git a/src/applications/calendar/storage/PhabricatorCalendarExport.php b/src/applications/calendar/storage/PhabricatorCalendarExport.php new file mode 100644 index 0000000000..b92b8c4e7d --- /dev/null +++ b/src/applications/calendar/storage/PhabricatorCalendarExport.php @@ -0,0 +1,144 @@ +setAuthorPHID($actor->getPHID()) + ->setPolicyMode(self::MODE_PRIVATE) + ->setIsDisabled(0); + } + + protected function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text', + 'policyMode' => 'text64', + 'queryKey' => 'text64', + 'secretKey' => 'bytes20', + 'isDisabled' => 'bool', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_author' => array( + 'columns' => array('authorPHID'), + ), + 'key_secret' => array( + 'columns' => array('secretKey'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function getPHIDType() { + return PhabricatorCalendarExportPHIDType::TYPECONST; + } + + public function save() { + if (!$this->getSecretKey()) { + $this->setSecretKey(Filesystem::readRandomCharacters(20)); + } + + return parent::save(); + } + + public function getURI() { + $id = $this->getID(); + return "/calendar/export/{$id}/"; + } + + private static function getPolicyModeMap() { + return array( + self::MODE_PUBLIC => array( + 'name' => pht('Public'), + ), + self::MODE_PRIVATE => array( + 'name' => pht('Private'), + ), + ); + } + + private static function getPolicyModeSpec($const) { + return idx(self::getPolicyModeMap(), $const, array()); + } + + public static function getPolicyModeName($const) { + $map = self::getPolicyModeSpec($const); + return idx($map, 'name', $const); + } + + public static function getPolicyModes() { + return array_keys(self::getPolicyModeMap()); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + return $this->getAuthorPHID(); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorCalendarExportEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorCalendarExportTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + + return $timeline; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $this->delete(); + } + +} diff --git a/src/applications/calendar/storage/PhabricatorCalendarExportTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarExportTransaction.php new file mode 100644 index 0000000000..1bd24ef188 --- /dev/null +++ b/src/applications/calendar/storage/PhabricatorCalendarExportTransaction.php @@ -0,0 +1,18 @@ +getIsDisabled(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsDisabled((int)$value); + } + + public function getTitle() { + if ($this->getNewValue()) { + return pht( + '%s disabled this export.', + $this->renderAuthor()); + } else { + return pht( + '%s enabled this export.', + $this->renderAuthor()); + } + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarExportModeTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarExportModeTransaction.php new file mode 100644 index 0000000000..a721428437 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarExportModeTransaction.php @@ -0,0 +1,54 @@ +getPolicyMode(); + } + + public function applyInternalEffects($object, $value) { + $object->setPolicyMode($value); + } + + public function getTitle() { + $old_value = $this->getOldValue(); + $new_value = $this->getNewValue(); + + $old_name = PhabricatorCalendarExport::getPolicyModeName($old_value); + $new_name = PhabricatorCalendarExport::getPolicyModeName($new_value); + + return pht( + '%s changed the policy mode for this export from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $valid = PhabricatorCalendarExport::getPolicyModes(); + $valid = array_fuse($valid); + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + + if (isset($valid[$value])) { + continue; + } + + $errors[] = $this->newInvalidError( + pht( + 'Mode "%s" is not a valid policy mode. Valid modes are: %s.', + $value, + implode(', ', $valid)), + $xaction); + } + + return $errors; + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarExportNameTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarExportNameTransaction.php new file mode 100644 index 0000000000..8d624c4a69 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarExportNameTransaction.php @@ -0,0 +1,35 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this export from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Calendar exports must have a name.')); + } + + return $errors; + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php new file mode 100644 index 0000000000..25b19591e6 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php @@ -0,0 +1,51 @@ +getQueryKey(); + } + + public function applyInternalEffects($object, $value) { + $object->setQueryKey($value); + } + + public function getTitle() { + return pht( + '%s changed the query for this export.', + $this->renderAuthor()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + + $query = id(new PhabricatorSavedQueryQuery()) + ->withEngineClassNames(array('PhabricatorCalendarEventSearchEngine')) + ->withQueryKeys(array($value)) + ->executeOne(); + if ($query) { + continue; + } + + $errors[] = $this->newInvalidError( + pht( + 'Query key "%s" does not identify a valid event query.', + $value), + $xaction); + } + + if ($this->isEmptyTextTransaction($object->getQueryKey(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Calendar exports must have a query key.')); + } + + return $errors; + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php b/src/applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php new file mode 100644 index 0000000000..a5343f7fab --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php @@ -0,0 +1,4 @@ +addAction($create_button); - return $view; + return $view; } } diff --git a/src/docs/user/userguide/calendar_exports.diviner b/src/docs/user/userguide/calendar_exports.diviner new file mode 100644 index 0000000000..a9d4ab301f --- /dev/null +++ b/src/docs/user/userguide/calendar_exports.diviner @@ -0,0 +1,12 @@ +@title Calendar User Guide: Exporting Events +@group userguide + +Exporting events to other calendars. + +Overview +======== + +IMPORTANT: Calendar is a prototype application. See +@{article:User Guide: Prototype Applications}. + +Coming soon! diff --git a/src/infrastructure/storage/lisk/LiskDAO.php b/src/infrastructure/storage/lisk/LiskDAO.php index e795777394..71cfba3d54 100644 --- a/src/infrastructure/storage/lisk/LiskDAO.php +++ b/src/infrastructure/storage/lisk/LiskDAO.php @@ -1339,11 +1339,12 @@ abstract class LiskDAO extends Phobject { * @task hook */ public function generatePHID() { - throw new Exception( - pht( - 'To use %s, you need to overload %s to perform PHID generation.', - 'CONFIG_AUX_PHID', - 'generatePHID()')); + $type = $this->getPHIDType(); + return PhabricatorPHID::generateNewPHID($type); + } + + public function getPHIDType() { + throw new PhutilMethodNotImplementedException(); } From fa6a5a46ba579b5cf0aaf6c556f516790d8597e9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 5 Oct 2016 16:22:54 -0700 Subject: [PATCH 31/36] Make more of the Calendar export workflow work Summary: Ref T10747. - Adds a "Use Results..." dropdown to query result pages, with actions you can take with search results (today: create export; in future: bulk edit, export as excel, make dashboard panel, etc). - Allows you to create an export against a query key. - I'm just using a text edit field for this for now. - Fleshes out export modes. I plan to support: public (as though you were logged out), privileged (as though you were logged in) and availability (event times, but not details). This does not actually export stuff yet. Test Plan: Created some exports. Viewed and listed exports. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16676 --- src/__phutil_library_map__.php | 4 + ...habricatorCalendarExportViewController.php | 154 ++++++++++++++++++ .../PhabricatorCalendarExportEditEngine.php | 39 ++++- .../PhabricatorCalendarExportEditor.php | 4 + .../PhabricatorCalendarEventSearchEngine.php | 26 ++- .../PhabricatorCalendarExportSearchEngine.php | 10 ++ ...bricatorCalendarExportTransactionQuery.php | 10 ++ .../storage/PhabricatorCalendarExport.php | 66 +++++++- ...catorCalendarExportQueryKeyTransaction.php | 10 ++ ...PhabricatorApplicationSearchController.php | 40 ++++- .../PhabricatorApplicationSearchEngine.php | 4 + 11 files changed, 352 insertions(+), 15 deletions(-) create mode 100644 src/applications/calendar/controller/PhabricatorCalendarExportViewController.php create mode 100644 src/applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 73787b0dee..2dab43aa5d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2088,7 +2088,9 @@ phutil_register_library_map(array( 'PhabricatorCalendarExportQueryKeyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php', 'PhabricatorCalendarExportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarExportSearchEngine.php', 'PhabricatorCalendarExportTransaction' => 'applications/calendar/storage/PhabricatorCalendarExportTransaction.php', + 'PhabricatorCalendarExportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php', 'PhabricatorCalendarExportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php', + 'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.php', 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', 'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php', @@ -6857,7 +6859,9 @@ phutil_register_library_map(array( 'PhabricatorCalendarExportQueryKeyTransaction' => 'PhabricatorCalendarExportTransactionType', 'PhabricatorCalendarExportSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorCalendarExportTransaction' => 'PhabricatorModularTransaction', + 'PhabricatorCalendarExportTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorCalendarExportTransactionType' => 'PhabricatorModularTransactionType', + 'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController', 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', 'PhabricatorCalendarIconSet' => 'PhabricatorIconSet', diff --git a/src/applications/calendar/controller/PhabricatorCalendarExportViewController.php b/src/applications/calendar/controller/PhabricatorCalendarExportViewController.php new file mode 100644 index 0000000000..3763fe30ae --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarExportViewController.php @@ -0,0 +1,154 @@ +getViewer(); + + $export = id(new PhabricatorCalendarExportQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); + if (!$export) { + return new Aphront404Response(); + } + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb( + pht('Exports'), + '/calendar/export/'); + $crumbs->addTextCrumb(pht('Export %d', $export->getID())); + $crumbs->setBorder(true); + + $timeline = $this->buildTransactionTimeline( + $export, + new PhabricatorCalendarExportTransactionQuery()); + $timeline->setShouldTerminate(true); + + $header = $this->buildHeaderView($export); + $curtain = $this->buildCurtain($export); + $details = $this->buildPropertySection($export); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn( + array( + $timeline, + )) + ->setCurtain($curtain) + ->addPropertySection(pht('Details'), $details); + + $page_title = pht('Export %d %s', $export->getID(), $export->getName()); + + return $this->newPage() + ->setTitle($page_title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($export->getPHID())) + ->appendChild($view); + } + + private function buildHeaderView( + PhabricatorCalendarExport $export) { + $viewer = $this->getViewer(); + $id = $export->getID(); + + if ($export->getIsDisabled()) { + $icon = 'fa-ban'; + $color = 'grey'; + $status = pht('Disabled'); + } else { + $icon = 'fa-check'; + $color = 'bluegrey'; + $status = pht('Active'); + } + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($export->getName()) + ->setStatus($icon, $color, $status) + ->setPolicyObject($export); + + return $header; + } + + private function buildCurtain(PhabricatorCalendarExport $export) { + $viewer = $this->getRequest()->getUser(); + $id = $export->getID(); + + $curtain = $this->newCurtainView($export); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $export, + PhabricatorPolicyCapability::CAN_EDIT); + + $ics_uri = $export->getICSURI(); + + $edit_uri = "export/edit/{$id}/"; + $edit_uri = $this->getApplicationURI($edit_uri); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Export')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($edit_uri)); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Export as .ics')) + ->setIcon('fa-download') + ->setHref($ics_uri)); + + return $curtain; + } + + private function buildPropertySection( + PhabricatorCalendarExport $export) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $mode = $export->getPolicyMode(); + + $policy_icon = PhabricatorCalendarExport::getPolicyModeIcon($mode); + $policy_name = PhabricatorCalendarExport::getPolicyModeName($mode); + $policy_desc = PhabricatorCalendarExport::getPolicyModeDescription($mode); + $policy_color = PhabricatorCalendarExport::getPolicyModeColor($mode); + + $policy_view = id(new PHUIStatusListView()) + ->addItem( + id(new PHUIStatusItemView()) + ->setIcon($policy_icon, $policy_color) + ->setTarget($policy_name) + ->setNote($policy_desc)); + + $properties->addProperty(pht('Mode'), $policy_view); + + $query_key = $export->getQueryKey(); + $query_link = phutil_tag( + 'a', + array( + 'href' => $this->getApplicationURI("/query/{$query_key}/"), + ), + $query_key); + $properties->addProperty(pht('Query'), $query_link); + + $ics_uri = $export->getICSURI(); + $ics_uri = PhabricatorEnv::getURI($ics_uri); + + $properties->addProperty( + pht('ICS URI'), + phutil_tag( + 'a', + array( + 'href' => $ics_uri, + ), + $ics_uri)); + + return $properties; + } +} diff --git a/src/applications/calendar/editor/PhabricatorCalendarExportEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarExportEditEngine.php index f637200597..6ebbc9e1e0 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarExportEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarExportEditEngine.php @@ -43,7 +43,7 @@ final class PhabricatorCalendarExportEditEngine } protected function getObjectEditShortText($object) { - return $object->getMonogram(); + return pht('Export %d', $object->getID()); } protected function getObjectCreateShortText() { @@ -65,6 +65,22 @@ final class PhabricatorCalendarExportEditEngine protected function buildCustomEditFields($object) { $viewer = $this->getViewer(); + $export_modes = PhabricatorCalendarExport::getAvailablePolicyModes(); + $export_modes = array_fuse($export_modes); + + $current_mode = $object->getPolicyMode(); + if (empty($export_modes[$current_mode])) { + array_shift($export_modes, $current_mode); + } + + $mode_options = array(); + foreach ($export_modes as $export_mode) { + $mode_name = PhabricatorCalendarExport::getPolicyModeName($export_mode); + $mode_summary = PhabricatorCalendarExport::getPolicyModeSummary( + $export_mode); + $mode_options[$export_mode] = pht('%s: %s', $mode_name, $mode_summary); + } + $fields = array( id(new PhabricatorTextEditField()) ->setKey('name') @@ -87,6 +103,27 @@ final class PhabricatorCalendarExportEditEngine ->setConduitDescription(pht('Disable or restore the export.')) ->setConduitTypeDescription(pht('True to cancel the export.')) ->setValue($object->getIsDisabled()), + id(new PhabricatorTextEditField()) + ->setKey('queryKey') + ->setLabel(pht('Query Key')) + ->setDescription(pht('Query to execute.')) + ->setIsRequired(true) + ->setTransactionType( + PhabricatorCalendarExportQueryKeyTransaction::TRANSACTIONTYPE) + ->setConduitDescription(pht('Change the export query key.')) + ->setConduitTypeDescription(pht('New export query key.')) + ->setValue($object->getQueryKey()), + id(new PhabricatorSelectEditField()) + ->setKey('mode') + ->setLabel(pht('Mode')) + ->setTransactionType( + PhabricatorCalendarExportModeTransaction::TRANSACTIONTYPE) + ->setOptions($mode_options) + ->setDescription(pht('Change the policy mode for the export.')) + ->setConduitDescription(pht('Adjust export mode.')) + ->setConduitTypeDescription(pht('New export mode.')) + ->setValue($current_mode), + ); return $fields; diff --git a/src/applications/calendar/editor/PhabricatorCalendarExportEditor.php b/src/applications/calendar/editor/PhabricatorCalendarExportEditor.php index efdcb0e522..6ddd172d58 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarExportEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarExportEditor.php @@ -11,4 +11,8 @@ final class PhabricatorCalendarExportEditor return pht('Calendar Exports'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this export.', $author); + } + } diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 5058457cde..64c0c39aec 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -255,11 +255,20 @@ final class PhabricatorCalendarEventSearchEngine array $handles) { if ($this->isMonthView($query)) { - return $this->buildCalendarMonthView($events, $query); + $result = $this->buildCalendarMonthView($events, $query); } else if ($this->isDayView($query)) { - return $this->buildCalendarDayView($events, $query); + $result = $this->buildCalendarDayView($events, $query); + } else { + $result = $this->buildCalendarListView($events, $query); } + return $result; + } + + private function buildCalendarListView( + array $events, + PhabricatorSavedQuery $query) { + assert_instances_of($events, 'PhabricatorCalendarEvent'); $viewer = $this->requireViewer(); $list = new PHUIObjectItemListView(); @@ -562,4 +571,17 @@ final class PhabricatorCalendarEventSearchEngine return false; } + public function newUseResultsActions(PhabricatorSavedQuery $saved) { + $viewer = $this->requireViewer(); + $can_export = $viewer->isLoggedIn(); + + return array( + id(new PhabricatorActionView()) + ->setIcon('fa-download') + ->setName(pht('Export Query as .ics')) + ->setDisabled(!$can_export) + ->setHref('/calendar/export/edit/?queryKey='.$saved->getQueryKey()), + ); + } + } diff --git a/src/applications/calendar/query/PhabricatorCalendarExportSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarExportSearchEngine.php index 462bdaeea1..4a65bfd099 100644 --- a/src/applications/calendar/query/PhabricatorCalendarExportSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarExportSearchEngine.php @@ -64,6 +64,7 @@ final class PhabricatorCalendarExportSearchEngine foreach ($exports as $export) { $item = id(new PHUIObjectItemView()) ->setViewer($viewer) + ->setObjectName(pht('Export %d', $export->getID())) ->setHeader($export->getName()) ->setHref($export->getURI()); @@ -71,6 +72,15 @@ final class PhabricatorCalendarExportSearchEngine $item->setDisabled(true); } + $mode = $export->getPolicyMode(); + $policy_icon = PhabricatorCalendarExport::getPolicyModeIcon($mode); + $policy_name = PhabricatorCalendarExport::getPolicyModeName($mode); + $policy_color = PhabricatorCalendarExport::getPolicyModeColor($mode); + + $item->addIcon( + "{$policy_icon} {$policy_color}", + $policy_name); + $list->addItem($item); } diff --git a/src/applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php b/src/applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php new file mode 100644 index 0000000000..32b9d71b65 --- /dev/null +++ b/src/applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php @@ -0,0 +1,10 @@ +setAuthorPHID($actor->getPHID()) - ->setPolicyMode(self::MODE_PRIVATE) + ->setPolicyMode(self::MODE_PRIVILEGED) ->setIsDisabled(0); } @@ -65,10 +65,23 @@ final class PhabricatorCalendarExport extends PhabricatorCalendarDAO private static function getPolicyModeMap() { return array( self::MODE_PUBLIC => array( + 'icon' => 'fa-globe', 'name' => pht('Public'), + 'color' => 'bluegrey', + 'summary' => pht( + 'Export only public data.'), + 'description' => pht( + 'Only publicly available data is exported.'), ), - self::MODE_PRIVATE => array( - 'name' => pht('Private'), + self::MODE_PRIVILEGED => array( + 'icon' => 'fa-unlock-alt', + 'name' => pht('Privileged'), + 'color' => 'red', + 'summary' => pht( + 'Export private data.'), + 'description' => pht( + 'Anyone who knows the URI for this export can view all event '. + 'details as though they were logged in with your account.'), ), ); } @@ -78,14 +91,55 @@ final class PhabricatorCalendarExport extends PhabricatorCalendarDAO } public static function getPolicyModeName($const) { - $map = self::getPolicyModeSpec($const); - return idx($map, 'name', $const); + $spec = self::getPolicyModeSpec($const); + return idx($spec, 'name', $const); + } + + public static function getPolicyModeIcon($const) { + $spec = self::getPolicyModeSpec($const); + return idx($spec, 'icon', $const); + } + + public static function getPolicyModeColor($const) { + $spec = self::getPolicyModeSpec($const); + return idx($spec, 'color', $const); + } + + public static function getPolicyModeSummary($const) { + $spec = self::getPolicyModeSpec($const); + return idx($spec, 'summary', $const); + } + + public static function getPolicyModeDescription($const) { + $spec = self::getPolicyModeSpec($const); + return idx($spec, 'description', $const); } public static function getPolicyModes() { return array_keys(self::getPolicyModeMap()); } + public static function getAvailablePolicyModes() { + $modes = array(); + + if (PhabricatorEnv::getEnvConfig('policy.allow-public')) { + $modes[] = self::MODE_PUBLIC; + } + + $modes[] = self::MODE_PRIVILEGED; + + return $modes; + } + + public function getICSFilename() { + return PhabricatorSlug::normalizeProjectSlug($this->getName()).'.ics'; + } + + public function getICSURI() { + $secret_key = $this->getSecretKey(); + $ics_name = $this->getICSFilename(); + return "/calendar/export/ics/{$secret_key}/{$ics_name}"; + } /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php index 25b19591e6..bcdecf09a3 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php @@ -20,12 +20,15 @@ final class PhabricatorCalendarExportQueryKeyTransaction } public function validateTransactions($object, array $xactions) { + $actor = $this->getActor(); + $errors = array(); foreach ($xactions as $xaction) { $value = $xaction->getNewValue(); $query = id(new PhabricatorSavedQueryQuery()) + ->setViewer($actor) ->withEngineClassNames(array('PhabricatorCalendarEventSearchEngine')) ->withQueryKeys(array($value)) ->executeOne(); @@ -33,6 +36,13 @@ final class PhabricatorCalendarExportQueryKeyTransaction continue; } + $builtin = id(new PhabricatorCalendarEventSearchEngine()) + ->setViewer($actor) + ->getBuiltinQueries($actor); + if (isset($builtin[$value])) { + continue; + } + $errors[] = $this->newInvalidError( pht( 'Query key "%s" does not identify a valid event query.', diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index d36d279c55..6350df5e02 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -252,12 +252,6 @@ final class PhabricatorApplicationSearchController get_class($engine))); } - if ($list->getActions()) { - foreach ($list->getActions() as $action) { - $header->addActionLink($action); - } - } - if ($list->getObjectList()) { $box->setObjectList($list->getObjectList()); } @@ -274,6 +268,21 @@ final class PhabricatorApplicationSearchController $result_header = $list->getHeader(); if ($result_header) { $box->setHeader($result_header); + $header = $result_header; + } + + if ($list->getActions()) { + foreach ($list->getActions() as $action) { + $header->addActionLink($action); + } + } + + $use_actions = $engine->newUseResultsActions($saved_query); + if ($use_actions) { + $use_dropdown = $this->newUseResultsDropdown( + $saved_query, + $use_actions); + $header->addActionLink($use_dropdown); } $more_crumbs = $list->getCrumbs(); @@ -496,5 +505,24 @@ final class PhabricatorApplicationSearchController return $nux_view; } + private function newUseResultsDropdown( + PhabricatorSavedQuery $query, + array $dropdown_items) { + + $viewer = $this->getViewer(); + + $action_list = id(new PhabricatorActionListView()) + ->setViewer($viewer); + foreach ($dropdown_items as $dropdown_item) { + $action_list->addAction($dropdown_item); + } + + return id(new PHUIButtonView()) + ->setTag('a') + ->setHref('#') + ->setText(pht('Use Results...')) + ->setIcon('fa-road') + ->setDropdownMenu($action_list); + } } diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 13606c542f..a1279ad8ae 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -1390,4 +1390,8 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return null; } + public function newUseResultsActions(PhabricatorSavedQuery $saved) { + return array(); + } + } From 4819446fe5112d76b478eb6a022543ea3b22154b Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 6 Oct 2016 12:31:57 -0700 Subject: [PATCH 32/36] Export recurring events and build ICS files for configured exports Summary: Ref T10747. This: - Exports recurring events properly, with RRULE + RECURRENCE-ID. - When exporting a part of an event series, export the whole series to ICS so it is represented faithfully. - Make the subscribable URL for "Export" objects work. Test Plan: - Downloaded the ".ics" for a normal event, imported it into Calendar.app and Google Calendar. - Downloaded the ".ics" for a recurring event, imported it into Calendar.app and Google Calendar. - Defined an ".ics" Export of my events, subscribed to them in Calendar.app. - Edited an event in Phabricator. - Hit {key Command R} in Calendar.app, saw changes. (MAGIC!) - This export included recurring events, which appeared the same way in Calendar.app and Phabricator. - Can't import into Google Calendar from my local install easily since Google's servers can't hit my laptop, but I'll test once we deploy. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16679 --- src/__phutil_library_map__.php | 2 + .../PhabricatorCalendarController.php | 39 ++++++++ ...abricatorCalendarEventExportController.php | 24 ++--- ...PhabricatorCalendarEventListController.php | 24 ++++- ...PhabricatorCalendarExportICSController.php | 93 +++++++++++++++++++ .../query/PhabricatorCalendarEventQuery.php | 19 +++- .../PhabricatorCalendarEventSearchEngine.php | 8 +- .../query/PhabricatorCalendarExportQuery.php | 13 +++ .../storage/PhabricatorCalendarEvent.php | 36 ++++++- 9 files changed, 232 insertions(+), 26 deletions(-) create mode 100644 src/applications/calendar/controller/PhabricatorCalendarExportICSController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2dab43aa5d..fb94a74cbf 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2080,6 +2080,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarExportEditController' => 'applications/calendar/controller/PhabricatorCalendarExportEditController.php', 'PhabricatorCalendarExportEditEngine' => 'applications/calendar/editor/PhabricatorCalendarExportEditEngine.php', 'PhabricatorCalendarExportEditor' => 'applications/calendar/editor/PhabricatorCalendarExportEditor.php', + 'PhabricatorCalendarExportICSController' => 'applications/calendar/controller/PhabricatorCalendarExportICSController.php', 'PhabricatorCalendarExportListController' => 'applications/calendar/controller/PhabricatorCalendarExportListController.php', 'PhabricatorCalendarExportModeTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportModeTransaction.php', 'PhabricatorCalendarExportNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportNameTransaction.php', @@ -6851,6 +6852,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarExportEditController' => 'PhabricatorCalendarController', 'PhabricatorCalendarExportEditEngine' => 'PhabricatorEditEngine', 'PhabricatorCalendarExportEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorCalendarExportICSController' => 'PhabricatorCalendarController', 'PhabricatorCalendarExportListController' => 'PhabricatorCalendarController', 'PhabricatorCalendarExportModeTransaction' => 'PhabricatorCalendarExportTransactionType', 'PhabricatorCalendarExportNameTransaction' => 'PhabricatorCalendarExportTransactionType', diff --git a/src/applications/calendar/controller/PhabricatorCalendarController.php b/src/applications/calendar/controller/PhabricatorCalendarController.php index 01ea367aeb..9a811b6a8d 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarController.php @@ -2,4 +2,43 @@ abstract class PhabricatorCalendarController extends PhabricatorController { + protected function newICSResponse( + PhabricatorUser $viewer, + $file_name, + array $events) { + $events = mpull($events, null, 'getPHID'); + + if ($events) { + $child_map = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withParentEventPHIDs(array_keys($events)) + ->execute(); + $child_map = mpull($child_map, null, 'getPHID'); + } else { + $child_map = array(); + } + + $all_events = $events + $child_map; + $child_groups = mgroup($child_map, 'getInstanceOfEventPHID'); + + $document_node = new PhutilCalendarDocumentNode(); + + foreach ($all_events as $event) { + $child_events = idx($child_groups, $event->getPHID(), array()); + $event_node = $event->newIntermediateEventNode($viewer, $child_events); + $document_node->appendChild($event_node); + } + + $root_node = id(new PhutilCalendarRootNode()) + ->appendChild($document_node); + + $ics_data = id(new PhutilICSWriter()) + ->writeICSDocument($root_node); + + return id(new AphrontFileResponse()) + ->setDownload($file_name) + ->setMimeType('text/calendar') + ->setContent($ics_data); + } + } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventExportController.php b/src/applications/calendar/controller/PhabricatorCalendarEventExportController.php index 8163e4abeb..5e29479169 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventExportController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventExportController.php @@ -19,22 +19,16 @@ final class PhabricatorCalendarEventExportController return new Aphront404Response(); } - $file_name = $event->getICSFilename(); - $event_node = $event->newIntermediateEventNode($viewer); + if ($event->isChildEvent()) { + $target = $event->getParentEvent(); + } else { + $target = $event; + } - $document_node = id(new PhutilCalendarDocumentNode()) - ->appendChild($event_node); - - $root_node = id(new PhutilCalendarRootNode()) - ->appendChild($document_node); - - $ics_data = id(new PhutilICSWriter()) - ->writeICSDocument($root_node); - - return id(new AphrontFileResponse()) - ->setDownload($file_name) - ->setMimeType('text/calendar') - ->setContent($ics_data); + return $this->newICSResponse( + $viewer, + $target->getICSFileName(), + array($target)); } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php index a2915b0be9..ac9015fbcf 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php @@ -11,17 +11,19 @@ final class PhabricatorCalendarEventListController $year = $request->getURIData('year'); $month = $request->getURIData('month'); $day = $request->getURIData('day'); + $engine = new PhabricatorCalendarEventSearchEngine(); if ($month && $year) { $engine->setCalendarYearAndMonthAndDay($year, $month, $day); } - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine($engine); + $nav_items = $this->buildNavigationItems(); - return $this->delegateToController($controller); + return $engine + ->setNavigationItems($nav_items) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { @@ -34,4 +36,18 @@ final class PhabricatorCalendarEventListController return $crumbs; } + protected function buildNavigationItems() { + $items = array(); + + $items[] = id(new PHUIListItemView()) + ->setType(PHUIListItemView::TYPE_LABEL) + ->setName(pht('Import/Export')); + + $items[] = id(new PHUIListItemView()) + ->setName('Exports') + ->setHref('/calendar/export/'); + + return $items; + } + } diff --git a/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php b/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php new file mode 100644 index 0000000000..04a116a740 --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php @@ -0,0 +1,93 @@ +setViewer($omnipotent) + ->withSecretKeys(array($request->getURIData('secretKey'))) + ->executeOne(); + if (!$export) { + return new Aphront404Response(); + } + + $author = id(new PhabricatorPeopleQuery()) + ->setViewer($omnipotent) + ->withPHIDs(array($export->getAuthorPHID())) + ->needUserSettings(true) + ->executeOne(); + if (!$author) { + return new Aphront404Response(); + } + + $mode = $export->getPolicyMode(); + switch ($mode) { + case PhabricatorCalendarExport::MODE_PUBLIC: + $viewer = new PhabricatorUser(); + break; + case PhabricatorCalendarExport::MODE_PRIVILEGED: + $viewer = $author; + break; + default: + throw new Exception( + pht( + 'This export has an invalid mode ("%s").', + $mode)); + } + + $engine = id(new PhabricatorCalendarEventSearchEngine()) + ->setViewer($viewer); + + $query_key = $export->getQueryKey(); + $saved = id(new PhabricatorSavedQueryQuery()) + ->setViewer($omnipotent) + ->withEngineClassNames(array(get_class($engine))) + ->withQueryKeys(array($query_key)) + ->executeOne(); + if (!$saved) { + $saved = $engine->buildSavedQueryFromBuiltin($query_key); + } + + if (!$saved) { + return new Aphront404Response(); + } + + $saved = clone $saved; + + // Mark this as a query for export, so we get the correct ghost/recurring + // behaviors. We also want to load all matching events. + $saved->setParameter('export', true); + $saved->setParameter('limit', 0xFFFF); + + // Remove any range constraints. We always export all matching events into + // ICS files. + $saved->setParameter('rangeStart', null); + $saved->setParameter('rangeEnd', null); + $saved->setParameter('upcoming', null); + + $query = $engine->buildQueryFromSavedQuery($saved); + + $events = $query + ->setViewer($viewer) + ->execute(); + + return $this->newICSResponse( + $viewer, + $export->getICSFilename(), + $events); + } + +} diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 06eb50cc54..9b928c9d1a 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -13,6 +13,7 @@ final class PhabricatorCalendarEventQuery private $eventsWithNoParent; private $instanceSequencePairs; private $isStub; + private $parentEventPHIDs; private $generateGhosts = false; @@ -71,6 +72,11 @@ final class PhabricatorCalendarEventQuery return $this; } + public function withParentEventPHIDs(array $parent_phids) { + $this->parentEventPHIDs = $parent_phids; + return $this; + } + protected function getDefaultOrderVector() { return array('start', 'id'); } @@ -315,14 +321,14 @@ final class PhabricatorCalendarEventQuery protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( $conn, 'event.id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( $conn, 'event.phid IN (%Ls)', @@ -354,7 +360,7 @@ final class PhabricatorCalendarEventQuery $this->inviteePHIDs); } - if ($this->hostPHIDs) { + if ($this->hostPHIDs !== null) { $where[] = qsprintf( $conn, 'event.hostPHID IN (%Ls)', @@ -398,6 +404,13 @@ final class PhabricatorCalendarEventQuery (int)$this->isStub); } + if ($this->parentEventPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'event.instanceOfEventPHID IN (%Ls)', + $this->parentEventPHIDs); + } + return $where; } diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 64c0c39aec..a29b7ea37a 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -115,8 +115,12 @@ final class PhabricatorCalendarEventSearchEngine } // Generate ghosts (and ignore stub events) if we aren't querying for - // specific events. - if (!$map['ids'] && !$map['phids']) { + // specific events or exporting. + if (!empty($map['export'])) { + // This is a specific mode enabled by event exports. + $query + ->withIsStub(false); + } else if (!$map['ids'] && !$map['phids']) { $query ->withIsStub(false) ->setGenerateGhosts(true); diff --git a/src/applications/calendar/query/PhabricatorCalendarExportQuery.php b/src/applications/calendar/query/PhabricatorCalendarExportQuery.php index ecd3dc8e45..c51671c806 100644 --- a/src/applications/calendar/query/PhabricatorCalendarExportQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarExportQuery.php @@ -6,6 +6,7 @@ final class PhabricatorCalendarExportQuery private $ids; private $phids; private $authorPHIDs; + private $secretKeys; private $isDisabled; public function withIDs(array $ids) { @@ -28,6 +29,11 @@ final class PhabricatorCalendarExportQuery return $this; } + public function withSecretKeys(array $keys) { + $this->secretKeys = $keys; + return $this; + } + public function newResultObject() { return new PhabricatorCalendarExport(); } @@ -67,6 +73,13 @@ final class PhabricatorCalendarExportQuery (int)$this->isDisabled); } + if ($this->secretKeys !== null) { + $where[] = qsprintf( + $conn, + 'export.secretKey IN (%Ls)', + $this->secretKeys); + } + return $where; } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 676d85b608..67a04d226e 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -601,11 +601,28 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return $this->getMonogram().'.ics'; } - public function newIntermediateEventNode(PhabricatorUser $viewer) { + public function newIntermediateEventNode( + PhabricatorUser $viewer, + array $children) { + $base_uri = new PhutilURI(PhabricatorEnv::getProductionURI('/')); $domain = $base_uri->getDomain(); - $uid = $this->getPHID().'@'.$domain; + // NOTE: For recurring events, all of the events in the series have the + // same UID (the UID of the parent). The child event instances are + // differentiated by the "RECURRENCE-ID" field. + if ($this->isChildEvent()) { + $parent = $this->getParentEvent(); + $instance_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( + $this->getUTCInstanceEpoch()); + $recurrence_id = $instance_datetime->getISO8601(); + $rrule = null; + } else { + $parent = $this; + $recurrence_id = null; + $rrule = $this->newRecurrenceRule(); + } + $uid = $parent->getPHID().'@'.$domain; $created = $this->getDateCreated(); $created = PhutilCalendarAbsoluteDateTime::newFromEpoch($created); @@ -674,6 +691,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setStatus($status); } + // TODO: Use $children to generate EXDATE/RDATE information. + $node = id(new PhutilCalendarEventNode()) ->setUID($uid) ->setName($this->getName()) @@ -685,6 +704,14 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO ->setOrganizer($organizer) ->setAttendees($attendees); + if ($rrule) { + $node->setRecurrenceRule($rrule); + } + + if ($recurrence_id) { + $node->setRecurrenceID($recurrence_id); + } + return $node; } @@ -833,6 +860,11 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO $start = $this->newStartDateTime(); $rrule->setStartDateTime($start); + $until = $this->newUntilDateTime(); + if ($until) { + $rrule->setUntil($until); + } + return $rrule; } From ff97ed21959904cce16a4a071049683d1f9de71b Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 6 Oct 2016 14:46:50 -0700 Subject: [PATCH 33/36] Document how to export Calendar events Summary: Ref T10747. This explains how exports work. Also make mail exports use the same logic as other stuff. Test Plan: Read documentation. Did some exports. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16680 --- src/__phutil_library_map__.php | 2 + .../PhabricatorCalendarController.php | 31 +------ .../editor/PhabricatorCalendarEventEditor.php | 14 +-- .../util/PhabricatorCalendarICSWriter.php | 60 +++++++++++++ .../user/userguide/calendar_exports.diviner | 87 ++++++++++++++++++- 5 files changed, 156 insertions(+), 38 deletions(-) create mode 100644 src/applications/calendar/util/PhabricatorCalendarICSWriter.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fb94a74cbf..e7b7304e18 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2094,6 +2094,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.php', 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', + 'PhabricatorCalendarICSWriter' => 'applications/calendar/util/PhabricatorCalendarICSWriter.php', 'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php', 'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php', 'PhabricatorCalendarReplyHandler' => 'applications/calendar/mail/PhabricatorCalendarReplyHandler.php', @@ -6866,6 +6867,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController', 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', + 'PhabricatorCalendarICSWriter' => 'Phobject', 'PhabricatorCalendarIconSet' => 'PhabricatorIconSet', 'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorCalendarReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', diff --git a/src/applications/calendar/controller/PhabricatorCalendarController.php b/src/applications/calendar/controller/PhabricatorCalendarController.php index 9a811b6a8d..36a9bfbbe9 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarController.php @@ -6,34 +6,11 @@ abstract class PhabricatorCalendarController extends PhabricatorController { PhabricatorUser $viewer, $file_name, array $events) { - $events = mpull($events, null, 'getPHID'); - if ($events) { - $child_map = id(new PhabricatorCalendarEventQuery()) - ->setViewer($viewer) - ->withParentEventPHIDs(array_keys($events)) - ->execute(); - $child_map = mpull($child_map, null, 'getPHID'); - } else { - $child_map = array(); - } - - $all_events = $events + $child_map; - $child_groups = mgroup($child_map, 'getInstanceOfEventPHID'); - - $document_node = new PhutilCalendarDocumentNode(); - - foreach ($all_events as $event) { - $child_events = idx($child_groups, $event->getPHID(), array()); - $event_node = $event->newIntermediateEventNode($viewer, $child_events); - $document_node->appendChild($event_node); - } - - $root_node = id(new PhutilCalendarRootNode()) - ->appendChild($document_node); - - $ics_data = id(new PhutilICSWriter()) - ->writeICSDocument($root_node); + $ics_data = id(new PhabricatorCalendarICSWriter()) + ->setViewer($viewer) + ->setEvents($events) + ->writeICSDocument(); return id(new AphrontFileResponse()) ->setDownload($file_name) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index 5dd9b463c6..33442b6702 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -309,16 +309,10 @@ final class PhabricatorCalendarEventEditor PhabricatorCalendarEvent $event) { $actor = $this->getActor(); - $event_node = $event->newIntermediateEventNode($actor); - - $document_node = id(new PhutilCalendarDocumentNode()) - ->appendChild($event_node); - - $root_node = id(new PhutilCalendarRootNode()) - ->appendChild($document_node); - - $ics_data = id(new PhutilICSWriter()) - ->writeICSDocument($root_node); + $ics_data = id(new PhabricatorCalendarICSWriter()) + ->setViewer($actor) + ->setEvents(array($event)) + ->writeICSDocument(); $ics_attachment = new PhabricatorMetaMTAAttachment( $ics_data, diff --git a/src/applications/calendar/util/PhabricatorCalendarICSWriter.php b/src/applications/calendar/util/PhabricatorCalendarICSWriter.php new file mode 100644 index 0000000000..ff5657ae73 --- /dev/null +++ b/src/applications/calendar/util/PhabricatorCalendarICSWriter.php @@ -0,0 +1,60 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function setEvents(array $events) { + assert_instances_of($events, 'PhabricatorCalendarEvent'); + $this->events = $events; + return $this; + } + + public function getEvents() { + return $this->events; + } + + public function writeICSDocument() { + $viewer = $this->getViewer(); + $events = $this->getEvents(); + + $events = mpull($events, null, 'getPHID'); + + if ($events) { + $child_map = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withParentEventPHIDs(array_keys($events)) + ->execute(); + $child_map = mpull($child_map, null, 'getPHID'); + } else { + $child_map = array(); + } + + $all_events = $events + $child_map; + $child_groups = mgroup($child_map, 'getInstanceOfEventPHID'); + + $document_node = new PhutilCalendarDocumentNode(); + + foreach ($all_events as $event) { + $child_events = idx($child_groups, $event->getPHID(), array()); + $event_node = $event->newIntermediateEventNode($viewer, $child_events); + $document_node->appendChild($event_node); + } + + $root_node = id(new PhutilCalendarRootNode()) + ->appendChild($document_node); + + return id(new PhutilICSWriter()) + ->writeICSDocument($root_node); + } +} diff --git a/src/docs/user/userguide/calendar_exports.diviner b/src/docs/user/userguide/calendar_exports.diviner index a9d4ab301f..340cff863d 100644 --- a/src/docs/user/userguide/calendar_exports.diviner +++ b/src/docs/user/userguide/calendar_exports.diviner @@ -9,4 +9,89 @@ Overview IMPORTANT: Calendar is a prototype application. See @{article:User Guide: Prototype Applications}. -Coming soon! +You can export events from Phabricator to other calendar applications like +**Google Calendar** or **Calendar.app**. This document will guide you through +how to export event data from Phabricator. + +When you export events into another application, they generally will not be +editable from that application. Exporting events allows you to create one +calendar that shows all the events you care about in whatever application you +prefer (so you can keep track of everything you need to do), but does not let +you edit Phabricator events from another application. + +When exporting events, you can either export individual events one at a time +or export an entire group of events (for example, all events you are attending). + + +Exporting a Single Event +======================== + +To export a single event, visit the event detail page and click +{nav Export as .ics}. This will download an `.ics` file which you can import +into most other calendar applications. + +Mail you receive about events also has a copy of this `.ics` file attached to +it. You can import this `.ics` file directly. + +In **Google Calendar**, use {nav Other Calendars > Import Calendar} to import +the `.ics` file. + +In **Calendar.app**, use {nav File > Import...} to import the `.ics` file, or +drag the `.ics` file onto your calendar. + +When you export a recurring event, the `.ics` file will contain information +about the entire event series. + +If you want to update event information later, you can just repeat this +process. Calendar applications will update the existing event if you've +previously imported an older version of it. + + +Exporting a Group of Events +=========================== + +You can export a group of events matching an arbitrary query (like all events +you are attending) to keep different calendars in sync. + +To export a group of events: + + - Run a query in Calendar which selects the events you want to export. + - Example: All events you are attending. + - Example: All events you are invited to. + - Example: All events tagged `#meetup`. + - Select the {nav Use Results... > Export Query as .ics} action to turn + the query into an export. + - Name the export with a descritive name. + - Select a policy mode for the export (see below for discussion). + - Click {nav Create New Export} to finish the process. + +The **policy modes** for exports are: + + - **Public**: Only public information (visible to logged-out users) will + be exported. This mode is not available if your install does not have + public information (per `policy.allow-public` in Config). + - **Privileged**: All event information will be exported. This means that + anyone who knows the export URI can see ALL of the related event + information, as though they were logged in with your account. + +WARNING: Anyone who learns the URI for an export can see the data you choose +to export, even if they don't have a Phabricator account! Be careful about how +much data you export and treat the URI as a secret. If you accidentally share +a URI, you can disable the export. + +After finishing the process, you'll see a screen with some details about the +export and an **ICS URI**. This URI allows you to import the events which match +the query into another calendar application. + +In **Google Calendar**, use {nav Other Calendars > Add by URL} to import the +URI. + +In **Calendar.app**, use {nav File > New Calendar Subscription...} to subscribe +to the URI. + +Next Steps +========== + +Continue by: + + - returning to the @{article:Calendar User Guide}. From bc6e6c0500b7c7c0c7e48f93c05c6952e066c795 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 6 Oct 2016 14:57:43 -0700 Subject: [PATCH 34/36] Allow Calendar exports to be disabled Summary: Ref T10747. This adds disable/enable to exports. Mostly useful if you leak a URI by accident. Test Plan: - Disabled and enabled exports. - Verified that disabled exports don't actually export any data. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16681 --- src/__phutil_library_map__.php | 2 + .../PhabricatorCalendarApplication.php | 3 + ...ricatorCalendarExportDisableController.php | 63 +++++++++++++++++++ ...PhabricatorCalendarExportICSController.php | 4 ++ ...habricatorCalendarExportViewController.php | 32 ++++++++-- 5 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 src/applications/calendar/controller/PhabricatorCalendarExportDisableController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e7b7304e18..085e86c84b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2076,6 +2076,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventUntilDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php', 'PhabricatorCalendarEventViewController' => 'applications/calendar/controller/PhabricatorCalendarEventViewController.php', 'PhabricatorCalendarExport' => 'applications/calendar/storage/PhabricatorCalendarExport.php', + 'PhabricatorCalendarExportDisableController' => 'applications/calendar/controller/PhabricatorCalendarExportDisableController.php', 'PhabricatorCalendarExportDisableTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportDisableTransaction.php', 'PhabricatorCalendarExportEditController' => 'applications/calendar/controller/PhabricatorCalendarExportEditController.php', 'PhabricatorCalendarExportEditEngine' => 'applications/calendar/editor/PhabricatorCalendarExportEditEngine.php', @@ -6849,6 +6850,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', ), + 'PhabricatorCalendarExportDisableController' => 'PhabricatorCalendarController', 'PhabricatorCalendarExportDisableTransaction' => 'PhabricatorCalendarExportTransactionType', 'PhabricatorCalendarExportEditController' => 'PhabricatorCalendarController', 'PhabricatorCalendarExportEditEngine' => 'PhabricatorEditEngine', diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index ba18c13830..208fc83ae9 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -71,6 +71,9 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { => 'PhabricatorCalendarExportViewController', 'ics/(?P[^/]+)/(?P[^/]*)' => 'PhabricatorCalendarExportICSController', + 'disable/(?P[1-9]\d*)/' + => 'PhabricatorCalendarExportDisableController', + ), ), ); diff --git a/src/applications/calendar/controller/PhabricatorCalendarExportDisableController.php b/src/applications/calendar/controller/PhabricatorCalendarExportDisableController.php new file mode 100644 index 0000000000..6be28499c4 --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarExportDisableController.php @@ -0,0 +1,63 @@ +getViewer(); + + $export = id(new PhabricatorCalendarExportQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$export) { + return new Aphront404Response(); + } + + $export_uri = $export->getURI(); + $is_disable = !$export->getIsDisabled(); + + if ($request->isFormPost()) { + $xactions = array(); + $xactions[] = id(new PhabricatorCalendarExportTransaction()) + ->setTransactionType( + PhabricatorCalendarExportDisableTransaction::TRANSACTIONTYPE) + ->setNewValue($is_disable ? 1 : 0); + + $editor = id(new PhabricatorCalendarExportEditor()) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->setContentSourceFromRequest($request); + + $editor->applyTransactions($export, $xactions); + + return id(new AphrontRedirectResponse())->setURI($export_uri); + } + + if ($is_disable) { + $title = pht('Disable Export'); + $body = pht( + 'Disable this export? The export URI will no longer function.'); + $button = pht('Disable Export'); + } else { + $title = pht('Enable Export'); + $body = pht( + 'Enable this export? Anyone who knows the export URI will be able '. + 'to export the data.'); + $button = pht('Enable Export'); + } + + return $this->newDialog() + ->setTitle($title) + ->appendParagraph($body) + ->addCancelButton($export_uri) + ->addSubmitButton($button); + } + +} diff --git a/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php b/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php index 04a116a740..a1264693bd 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php @@ -24,6 +24,10 @@ final class PhabricatorCalendarExportICSController return new Aphront404Response(); } + if ($export->getIsDisabled()) { + return new Aphront404Response(); + } + $author = id(new PhabricatorPeopleQuery()) ->setViewer($omnipotent) ->withPHIDs(array($export->getAuthorPHID())) diff --git a/src/applications/calendar/controller/PhabricatorCalendarExportViewController.php b/src/applications/calendar/controller/PhabricatorCalendarExportViewController.php index 3763fe30ae..1dc2a8d1be 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarExportViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarExportViewController.php @@ -55,7 +55,7 @@ final class PhabricatorCalendarExportViewController if ($export->getIsDisabled()) { $icon = 'fa-ban'; - $color = 'grey'; + $color = 'red'; $status = pht('Disabled'); } else { $icon = 'fa-check'; @@ -102,6 +102,24 @@ final class PhabricatorCalendarExportViewController ->setIcon('fa-download') ->setHref($ics_uri)); + $disable_uri = "export/disable/{$id}/"; + $disable_uri = $this->getApplicationURI($disable_uri); + if ($export->getIsDisabled()) { + $disable_name = pht('Enable Export'); + $disable_icon = 'fa-check'; + } else { + $disable_name = pht('Disable Export'); + $disable_icon = 'fa-ban'; + } + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName($disable_name) + ->setIcon($disable_icon) + ->setDisabled(!$can_edit) + ->setWorkflow(true) + ->setHref($disable_uri)); + return $curtain; } @@ -140,14 +158,18 @@ final class PhabricatorCalendarExportViewController $ics_uri = $export->getICSURI(); $ics_uri = PhabricatorEnv::getURI($ics_uri); - $properties->addProperty( - pht('ICS URI'), - phutil_tag( + if ($export->getIsDisabled()) { + $ics_href = phutil_tag('em', array(), $ics_uri); + } else { + $ics_href = phutil_tag( 'a', array( 'href' => $ics_uri, ), - $ics_uri)); + $ics_uri); + } + + $properties->addProperty(pht('ICS URI'), $ics_href); return $properties; } From 72edd36c7a061701f95f3f5dd0570882cb6a9f10 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 7 Oct 2016 07:57:53 -0700 Subject: [PATCH 35/36] Fix an issue with recurrence rules being set improperly in transaction code Summary: Fixes T11745. I just missed this while juggling some of the internal storage. Test Plan: Created a new event with recurrence behavior. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11745 Differential Revision: https://secure.phabricator.com/D16684 --- .../xaction/PhabricatorCalendarEventFrequencyTransaction.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php index 2da4f1da62..33c954ac9f 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php @@ -19,8 +19,7 @@ final class PhabricatorCalendarEventFrequencyTransaction $rrule = id(new PhutilCalendarRecurrenceRule()) ->setFrequency($value); - $dict = $rrule->toDictionary(); - $object->setRecurrenceRule($dict); + $object->setRecurrenceRule($rrule); } public function validateTransactions($object, array $xactions) { From ce14338081e189cb28b7ac6088dfadbd7429c33b Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 7 Oct 2016 08:07:15 -0700 Subject: [PATCH 36/36] When setup issues raise opcache configuration errors, point at the opcache configuration page Summary: Fixes T11746. The opcache docs are on a different page, so point there if we're raising opcache issues. (It's possible for a setup issue to say "configure X, or configure Y", where X is opcache and Y is non-opcache, so we may want to render both links.) Test Plan: {F1867109} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11746 Differential Revision: https://secure.phabricator.com/D16685 --- .../config/view/PhabricatorSetupIssueView.php | 55 ++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/src/applications/config/view/PhabricatorSetupIssueView.php b/src/applications/config/view/PhabricatorSetupIssueView.php index ca6397133d..d2e5a6c281 100644 --- a/src/applications/config/view/PhabricatorSetupIssueView.php +++ b/src/applications/config/view/PhabricatorSetupIssueView.php @@ -404,19 +404,48 @@ final class PhabricatorSetupIssueView extends AphrontView { implode("\n", $more_loc)); } - $info[] = phutil_tag( - 'p', - array(), - pht( - 'You can find more information about PHP configuration values in the '. - '%s.', - phutil_tag( - 'a', - array( - 'href' => 'http://php.net/manual/ini.list.php', - 'target' => '_blank', - ), - pht('PHP Documentation')))); + $show_standard = false; + $show_opcache = false; + + foreach ($configs as $key) { + if (preg_match('/^opcache\./', $key)) { + $show_opcache = true; + } else { + $show_standard = true; + } + } + + if ($show_standard) { + $info[] = phutil_tag( + 'p', + array(), + pht( + 'You can find more information about PHP configuration values '. + 'in the %s.', + phutil_tag( + 'a', + array( + 'href' => 'http://php.net/manual/ini.list.php', + 'target' => '_blank', + ), + pht('PHP Documentation')))); + } + + if ($show_opcache) { + $info[] = phutil_tag( + 'p', + array(), + pht( + 'You can find more information about configuring OPCache in '. + 'the %s.', + phutil_tag( + 'a', + array( + 'href' => 'http://php.net/manual/opcache.configuration.php', + 'target' => '_blank', + ), + pht('PHP OPCache Documentation')))); + } $info[] = phutil_tag( 'p',