@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'master' into redesign-2015

+963 -235
+35 -26
resources/celerity/map.php
··· 8 8 return array( 9 9 'names' => array( 10 10 'core.pkg.css' => '61e69662', 11 - 'core.pkg.js' => 'f1e8abd7', 11 + 'core.pkg.js' => 'a590b451', 12 12 'darkconsole.pkg.js' => 'e7393ebb', 13 13 'differential.pkg.css' => 'fe951924', 14 14 'differential.pkg.js' => 'ebef29b1', ··· 327 327 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 328 328 'rsrc/js/application/aphlict/Aphlict.js' => '5359e785', 329 329 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '031cee25', 330 - 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 330 + 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'fb20ac8d', 331 331 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 332 + 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'edd1ba66', 332 333 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 333 334 'rsrc/js/application/calendar/behavior-day-view.js' => '5c46cff2', 334 335 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', ··· 428 429 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', 429 430 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', 430 431 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 431 - 'rsrc/js/core/Notification.js' => '0c6946e7', 432 + 'rsrc/js/core/Notification.js' => 'ccf1cbf8', 432 433 'rsrc/js/core/Prefab.js' => '6920d200', 433 434 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 434 435 'rsrc/js/core/TextAreaUtils.js' => '5c93c52c', ··· 532 533 'javelin-aphlict' => '5359e785', 533 534 'javelin-behavior' => '61cbc29a', 534 535 'javelin-behavior-aphlict-dropdown' => '031cee25', 535 - 'javelin-behavior-aphlict-listen' => 'b1a59974', 536 + 'javelin-behavior-aphlict-listen' => 'fb20ac8d', 536 537 'javelin-behavior-aphlict-status' => 'ea681761', 537 538 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 538 539 'javelin-behavior-aphront-crop' => 'fa0f4fc2', ··· 554 555 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 555 556 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', 556 557 'javelin-behavior-day-view' => '5c46cff2', 558 + 'javelin-behavior-desktop-notifications-control' => 'edd1ba66', 557 559 'javelin-behavior-device' => 'a205cf28', 558 560 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 559 561 'javelin-behavior-differential-comment-jump' => '4fdb476d', ··· 724 726 'phabricator-keyboard-shortcut-manager' => 'c1700f6f', 725 727 'phabricator-main-menu-view' => '3cd48671', 726 728 'phabricator-nav-view-css' => '0ecd30a1', 727 - 'phabricator-notification' => '0c6946e7', 729 + 'phabricator-notification' => 'ccf1cbf8', 728 730 'phabricator-notification-css' => '9c279160', 729 731 'phabricator-notification-menu-css' => 'f31c0bde', 730 732 'phabricator-object-selector-css' => '029a133d', ··· 903 905 'javelin-dom', 904 906 'javelin-router', 905 907 ), 906 - '0c6946e7' => array( 907 - 'javelin-install', 908 - 'javelin-dom', 909 - 'javelin-stratcom', 910 - 'javelin-util', 911 - 'phabricator-notification-css', 912 - ), 913 908 '0f764c35' => array( 914 909 'javelin-install', 915 910 'javelin-util', ··· 1650 1645 'javelin-util', 1651 1646 'phabricator-shaped-request', 1652 1647 ), 1653 - 'b1a59974' => array( 1654 - 'javelin-behavior', 1655 - 'javelin-aphlict', 1656 - 'javelin-stratcom', 1657 - 'javelin-request', 1658 - 'javelin-uri', 1659 - 'javelin-dom', 1660 - 'javelin-json', 1661 - 'javelin-router', 1662 - 'javelin-util', 1663 - 'javelin-leader', 1664 - 'javelin-sound', 1665 - 'phabricator-notification', 1666 - ), 1667 1648 'b1f0ccee' => array( 1668 1649 'javelin-install', 1669 1650 'javelin-dom', ··· 1798 1779 'javelin-stratcom', 1799 1780 'phabricator-phtize', 1800 1781 ), 1782 + 'ccf1cbf8' => array( 1783 + 'javelin-install', 1784 + 'javelin-dom', 1785 + 'javelin-stratcom', 1786 + 'javelin-util', 1787 + 'phabricator-notification-css', 1788 + ), 1801 1789 'cf86d16a' => array( 1802 1790 'javelin-behavior', 1803 1791 'javelin-dom', ··· 1942 1930 'phabricator-phtize', 1943 1931 'javelin-dom', 1944 1932 ), 1933 + 'edd1ba66' => array( 1934 + 'javelin-behavior', 1935 + 'javelin-stratcom', 1936 + 'javelin-dom', 1937 + 'javelin-uri', 1938 + 'phabricator-notification', 1939 + ), 1945 1940 'eeaa9e5a' => array( 1946 1941 'javelin-behavior', 1947 1942 'javelin-stratcom', ··· 2016 2011 'javelin-dom', 2017 2012 'javelin-vector', 2018 2013 'javelin-magical-init', 2014 + ), 2015 + 'fb20ac8d' => array( 2016 + 'javelin-behavior', 2017 + 'javelin-aphlict', 2018 + 'javelin-stratcom', 2019 + 'javelin-request', 2020 + 'javelin-uri', 2021 + 'javelin-dom', 2022 + 'javelin-json', 2023 + 'javelin-router', 2024 + 'javelin-util', 2025 + 'javelin-leader', 2026 + 'javelin-sound', 2027 + 'phabricator-notification', 2019 2028 ), 2020 2029 'fbe497e7' => array( 2021 2030 'javelin-behavior',
+2
resources/sql/autopatches/20150622.metamta.1.phid-col.sql
··· 1 + ALTER TABLE {$NAMESPACE}_metamta.metamta_mail 2 + ADD phid VARBINARY(64) NOT NULL AFTER id;
+22
resources/sql/autopatches/20150622.metamta.2.phid-mig.php
··· 1 + <?php 2 + 3 + $table = new PhabricatorMetaMTAMail(); 4 + $conn_w = $table->establishConnection('w'); 5 + 6 + echo pht('Assigning PHIDs to mails...')."\n"; 7 + foreach (new LiskMigrationIterator($table) as $mail) { 8 + $id = $mail->getID(); 9 + 10 + echo pht('Updating mail %d...', $id)."\n"; 11 + if ($mail->getPHID()) { 12 + continue; 13 + } 14 + 15 + queryfx( 16 + $conn_w, 17 + 'UPDATE %T SET phid = %s WHERE id = %d', 18 + $table->getTableName(), 19 + $table->generatePHID(), 20 + $id); 21 + } 22 + echo pht('Done.')."\n";
+2
resources/sql/autopatches/20150622.metamta.3.phid-key.sql
··· 1 + ALTER TABLE {$NAMESPACE}_metamta.metamta_mail 2 + ADD UNIQUE KEY `key_phid` (phid);
+2
resources/sql/autopatches/20150622.metamta.4.actor-phid-col.sql
··· 1 + ALTER TABLE {$NAMESPACE}_metamta.metamta_mail 2 + ADD actorPHID VARBINARY(64) AFTER phid;
+27
resources/sql/autopatches/20150622.metamta.5.actor-phid-mig.php
··· 1 + <?php 2 + 3 + $table = new PhabricatorMetaMTAMail(); 4 + $conn_w = $table->establishConnection('w'); 5 + 6 + echo pht('Assigning actorPHIDs to mails...')."\n"; 7 + foreach (new LiskMigrationIterator($table) as $mail) { 8 + $id = $mail->getID(); 9 + 10 + echo pht('Updating mail %d...', $id)."\n"; 11 + if ($mail->getActorPHID()) { 12 + continue; 13 + } 14 + 15 + $actor_phid = $mail->getFrom(); 16 + if ($actor_phid === null) { 17 + continue; 18 + } 19 + 20 + queryfx( 21 + $conn_w, 22 + 'UPDATE %T SET actorPHID = %s WHERE id = %d', 23 + $table->getTableName(), 24 + $actor_phid, 25 + $id); 26 + } 27 + echo pht('Done.')."\n";
+2
resources/sql/autopatches/20150622.metamta.6.actor-phid-key.sql
··· 1 + ALTER TABLE {$NAMESPACE}_metamta.metamta_mail 2 + ADD KEY `key_actorPHID` (actorPHID);
+15 -3
src/__phutil_library_map__.php
··· 1792 1792 'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php', 1793 1793 'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php', 1794 1794 'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php', 1795 + 'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php', 1795 1796 'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php', 1796 1797 'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php', 1797 1798 'PhabricatorDeveloperConfigOptions' => 'applications/config/option/PhabricatorDeveloperConfigOptions.php', ··· 2109 2110 'PhabricatorMetaMTAMail' => 'applications/metamta/storage/PhabricatorMetaMTAMail.php', 2110 2111 'PhabricatorMetaMTAMailBody' => 'applications/metamta/view/PhabricatorMetaMTAMailBody.php', 2111 2112 'PhabricatorMetaMTAMailBodyTestCase' => 'applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php', 2113 + 'PhabricatorMetaMTAMailPHIDType' => 'applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php', 2114 + 'PhabricatorMetaMTAMailQuery' => 'applications/metamta/query/PhabricatorMetaMTAMailQuery.php', 2112 2115 'PhabricatorMetaMTAMailSection' => 'applications/metamta/view/PhabricatorMetaMTAMailSection.php', 2113 2116 'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php', 2114 2117 'PhabricatorMetaMTAMailableDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableDatasource.php', ··· 2136 2139 'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php', 2137 2140 'PhabricatorNavigationRemarkupRule' => 'infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php', 2138 2141 'PhabricatorNeverTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorNeverTriggerClock.php', 2139 - 'PhabricatorNotificationAdHocFeedStory' => 'applications/notification/feed/PhabricatorNotificationAdHocFeedStory.php', 2140 2142 'PhabricatorNotificationBuilder' => 'applications/notification/builder/PhabricatorNotificationBuilder.php', 2141 2143 'PhabricatorNotificationClearController' => 'applications/notification/controller/PhabricatorNotificationClearController.php', 2142 2144 'PhabricatorNotificationClient' => 'applications/notification/client/PhabricatorNotificationClient.php', ··· 2150 2152 'PhabricatorNotificationStatusController' => 'applications/notification/controller/PhabricatorNotificationStatusController.php', 2151 2153 'PhabricatorNotificationStatusView' => 'applications/notification/view/PhabricatorNotificationStatusView.php', 2152 2154 'PhabricatorNotificationTestController' => 'applications/notification/controller/PhabricatorNotificationTestController.php', 2155 + 'PhabricatorNotificationTestFeedStory' => 'applications/notification/feed/PhabricatorNotificationTestFeedStory.php', 2153 2156 'PhabricatorNotificationUIExample' => 'applications/uiexample/examples/PhabricatorNotificationUIExample.php', 2154 2157 'PhabricatorNotificationsApplication' => 'applications/notification/application/PhabricatorNotificationsApplication.php', 2155 2158 'PhabricatorNuanceApplication' => 'applications/nuance/application/PhabricatorNuanceApplication.php', ··· 2550 2553 'PhabricatorSearchDAO' => 'applications/search/storage/PhabricatorSearchDAO.php', 2551 2554 'PhabricatorSearchDatasource' => 'applications/search/typeahead/PhabricatorSearchDatasource.php', 2552 2555 'PhabricatorSearchDatasourceField' => 'applications/search/field/PhabricatorSearchDatasourceField.php', 2556 + 'PhabricatorSearchDateControlField' => 'applications/search/field/PhabricatorSearchDateControlField.php', 2553 2557 'PhabricatorSearchDateField' => 'applications/search/field/PhabricatorSearchDateField.php', 2554 2558 'PhabricatorSearchDeleteController' => 'applications/search/controller/PhabricatorSearchDeleteController.php', 2555 2559 'PhabricatorSearchDocument' => 'applications/search/storage/document/PhabricatorSearchDocument.php', ··· 5090 5094 'PhabricatorCalendarEvent' => array( 5091 5095 'PhabricatorCalendarDAO', 5092 5096 'PhabricatorPolicyInterface', 5097 + 'PhabricatorProjectInterface', 5093 5098 'PhabricatorMarkupInterface', 5094 5099 'PhabricatorApplicationTransactionInterface', 5095 5100 'PhabricatorSubscribableInterface', ··· 5390 5395 'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck', 5391 5396 'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel', 5392 5397 'PhabricatorDebugController' => 'PhabricatorController', 5398 + 'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel', 5393 5399 'PhabricatorDestructionEngine' => 'Phobject', 5394 5400 'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions', 5395 5401 'PhabricatorDeveloperPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', ··· 5745 5751 'PhabricatorMetaMTAEmailBodyParser' => 'Phobject', 5746 5752 'PhabricatorMetaMTAEmailBodyParserTestCase' => 'PhabricatorTestCase', 5747 5753 'PhabricatorMetaMTAErrorMailAction' => 'PhabricatorSystemAction', 5748 - 'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO', 5754 + 'PhabricatorMetaMTAMail' => array( 5755 + 'PhabricatorMetaMTADAO', 5756 + 'PhabricatorPolicyInterface', 5757 + ), 5749 5758 'PhabricatorMetaMTAMailBody' => 'Phobject', 5750 5759 'PhabricatorMetaMTAMailBodyTestCase' => 'PhabricatorTestCase', 5760 + 'PhabricatorMetaMTAMailPHIDType' => 'PhabricatorPHIDType', 5761 + 'PhabricatorMetaMTAMailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5751 5762 'PhabricatorMetaMTAMailSection' => 'Phobject', 5752 5763 'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase', 5753 5764 'PhabricatorMetaMTAMailableDatasource' => 'PhabricatorTypeaheadCompositeDatasource', ··· 5778 5789 'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5779 5790 'PhabricatorNavigationRemarkupRule' => 'PhutilRemarkupRule', 5780 5791 'PhabricatorNeverTriggerClock' => 'PhabricatorTriggerClock', 5781 - 'PhabricatorNotificationAdHocFeedStory' => 'PhabricatorFeedStory', 5782 5792 'PhabricatorNotificationBuilder' => 'Phobject', 5783 5793 'PhabricatorNotificationClearController' => 'PhabricatorNotificationController', 5784 5794 'PhabricatorNotificationClient' => 'Phobject', ··· 5792 5802 'PhabricatorNotificationStatusController' => 'PhabricatorNotificationController', 5793 5803 'PhabricatorNotificationStatusView' => 'AphrontTagView', 5794 5804 'PhabricatorNotificationTestController' => 'PhabricatorNotificationController', 5805 + 'PhabricatorNotificationTestFeedStory' => 'PhabricatorFeedStory', 5795 5806 'PhabricatorNotificationUIExample' => 'PhabricatorUIExample', 5796 5807 'PhabricatorNotificationsApplication' => 'PhabricatorApplication', 5797 5808 'PhabricatorNuanceApplication' => 'PhabricatorApplication', ··· 6288 6299 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', 6289 6300 'PhabricatorSearchDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 6290 6301 'PhabricatorSearchDatasourceField' => 'PhabricatorSearchTokenizerField', 6302 + 'PhabricatorSearchDateControlField' => 'PhabricatorSearchField', 6291 6303 'PhabricatorSearchDateField' => 'PhabricatorSearchField', 6292 6304 'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController', 6293 6305 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO',
+24
src/applications/calendar/controller/PhabricatorCalendarEventEditController.php
··· 140 140 $cancel_uri = '/'.$event->getMonogram(); 141 141 } 142 142 143 + if ($this->isCreate()) { 144 + $projects = array(); 145 + } else { 146 + $projects = PhabricatorEdgeQuery::loadDestinationPHIDs( 147 + $event->getPHID(), 148 + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); 149 + $projects = array_reverse($projects); 150 + } 151 + 143 152 $name = $event->getName(); 144 153 $description = $event->getDescription(); 145 154 $is_all_day = $event->getIsAllDay(); ··· 167 176 $request, 168 177 'recurrenceEndDate'); 169 178 $recurrence_end_date_value->setOptional(true); 179 + $projects = $request->getArr('projects'); 170 180 $description = $request->getStr('description'); 171 181 $subscribers = $request->getArr('subscribers'); 172 182 $edit_policy = $request->getStr('editPolicy'); ··· 262 272 ->setContinueOnNoEffect(true); 263 273 264 274 try { 275 + $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 276 + $xactions[] = id(new PhabricatorCalendarEventTransaction()) 277 + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 278 + ->setMetadataValue('edge:type', $proj_edge_type) 279 + ->setNewValue(array('=' => array_fuse($projects))); 280 + 265 281 $xactions = $editor->applyTransactions($event, $xactions); 266 282 $response = id(new AphrontRedirectResponse()); 267 283 switch ($next_workflow) { ··· 437 453 ->setValue($end_disabled); 438 454 } 439 455 456 + $projects = id(new AphrontFormTokenizerControl()) 457 + ->setLabel(pht('Projects')) 458 + ->setName('projects') 459 + ->setValue($projects) 460 + ->setUser($viewer) 461 + ->setDatasource(new PhabricatorProjectDatasource()); 462 + 440 463 $description = id(new PhabricatorRemarkupControl()) 441 464 ->setLabel(pht('Description')) 442 465 ->setName('description') ··· 511 534 ->appendControl($edit_policies) 512 535 ->appendControl($subscribers) 513 536 ->appendControl($invitees) 537 + ->appendChild($projects) 514 538 ->appendChild($description) 515 539 ->appendChild($icon); 516 540
+4
src/applications/calendar/query/PhabricatorCalendarEventQuery.php
··· 15 15 16 16 private $generateGhosts = false; 17 17 18 + public function newResultObject() { 19 + return new PhabricatorCalendarEvent(); 20 + } 21 + 18 22 public function setGenerateGhosts($generate_ghosts) { 19 23 $this->generateGhosts = $generate_ghosts; 20 24 return $this;
+164 -188
src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
··· 15 15 return 'PhabricatorCalendarApplication'; 16 16 } 17 17 18 - public function buildSavedQueryFromRequest(AphrontRequest $request) { 19 - $saved = new PhabricatorSavedQuery(); 18 + public function newQuery() { 19 + return new PhabricatorCalendarEventQuery(); 20 + } 20 21 21 - $saved->setParameter( 22 - 'rangeStart', 23 - $this->readDateFromRequest($request, 'rangeStart')); 22 + protected function shouldShowOrderField() { 23 + return false; 24 + } 24 25 25 - $saved->setParameter( 26 - 'rangeEnd', 27 - $this->readDateFromRequest($request, 'rangeEnd')); 26 + protected function buildCustomSearchFields() { 27 + return array( 28 + id(new PhabricatorSearchDatasourceField()) 29 + ->setLabel(pht('Created By')) 30 + ->setKey('creatorPHIDs') 31 + ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()), 32 + id(new PhabricatorSearchDatasourceField()) 33 + ->setLabel(pht('Invited')) 34 + ->setKey('invitedPHIDs') 35 + ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()), 36 + id(new PhabricatorSearchDateControlField()) 37 + ->setLabel(pht('Occurs After')) 38 + ->setKey('rangeStart'), 39 + id(new PhabricatorSearchDateControlField()) 40 + ->setLabel(pht('Occurs Before')) 41 + ->setKey('rangeEnd') 42 + ->setAliases(array('rangeEnd')), 43 + id(new PhabricatorSearchCheckboxesField()) 44 + ->setKey('upcoming') 45 + ->setOptions(array( 46 + 'upcoming' => pht('Show only upcoming events.'), 47 + )), 48 + id(new PhabricatorSearchSelectField()) 49 + ->setLabel(pht('Cancelled Events')) 50 + ->setKey('isCancelled') 51 + ->setOptions($this->getCancelledOptions()) 52 + ->setDefault('active'), 53 + id(new PhabricatorSearchSelectField()) 54 + ->setLabel(pht('Display Options')) 55 + ->setKey('display') 56 + ->setOptions($this->getViewOptions()) 57 + ->setDefault('month'), 58 + ); 59 + } 28 60 29 - $saved->setParameter( 30 - 'upcoming', 31 - $this->readBoolFromRequest($request, 'upcoming')); 61 + private function getCancelledOptions() { 62 + return array( 63 + 'active' => pht('Active Events Only'), 64 + 'cancelled' => pht('Cancelled Events Only'), 65 + 'both' => pht('Both Cancelled and Active Events'), 66 + ); 67 + } 32 68 33 - $saved->setParameter( 34 - 'invitedPHIDs', 35 - $this->readUsersFromRequest($request, 'invited')); 69 + private function getViewOptions() { 70 + return array( 71 + 'month' => pht('Month View'), 72 + 'day' => pht('Day View'), 73 + 'list' => pht('List View'), 74 + ); 75 + } 36 76 37 - $saved->setParameter( 38 - 'creatorPHIDs', 39 - $this->readUsersFromRequest($request, 'creators')); 77 + protected function buildQueryFromParameters(array $map) { 78 + $query = $this->newQuery(); 79 + $viewer = $this->requireViewer(); 40 80 41 - $saved->setParameter( 42 - 'isCancelled', 43 - $request->getStr('isCancelled')); 81 + if ($map['creatorPHIDs']) { 82 + $query->withCreatorPHIDs($map['creatorPHIDs']); 83 + } 44 84 45 - $saved->setParameter( 46 - 'display', 47 - $request->getStr('display')); 85 + if ($map['invitedPHIDs']) { 86 + $query->withInvitedPHIDs($map['invitedPHIDs']); 87 + } 48 88 49 - return $saved; 89 + $range_start = $map['rangeStart']; 90 + $range_end = $map['rangeEnd']; 91 + $display = $map['display']; 92 + 93 + if ($map['upcoming'] && $map['upcoming'][0] == 'upcoming') { 94 + $upcoming = true; 95 + } else { 96 + $upcoming = false; 97 + } 98 + 99 + list($range_start, $range_end) = $this->getQueryDateRange( 100 + $range_start, 101 + $range_end, 102 + $display, 103 + $upcoming); 104 + 105 + $query->withDateRange($range_start, $range_end); 106 + 107 + switch ($map['isCancelled']) { 108 + case 'active': 109 + $query->withIsCancelled(false); 110 + break; 111 + case 'cancelled': 112 + $query->withIsCancelled(true); 113 + break; 114 + } 115 + 116 + return $query->setGenerateGhosts(true); 50 117 } 51 118 52 - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 53 - $query = id(new PhabricatorCalendarEventQuery()) 54 - ->setGenerateGhosts(true); 119 + private function getQueryDateRange( 120 + $start_date_wild, 121 + $end_date_wild, 122 + $display, 123 + $upcoming) { 124 + 125 + $start_date_value = $this->getSafeDate($start_date_wild); 126 + $end_date_value = $this->getSafeDate($end_date_wild); 127 + 55 128 $viewer = $this->requireViewer(); 56 129 $timezone = new DateTimeZone($viewer->getTimezoneIdentifier()); 57 - 58 - $min_range = $this->getDateFrom($saved)->getEpoch(); 59 - $max_range = $this->getDateTo($saved)->getEpoch(); 130 + $min_range = null; 131 + $max_range = null; 60 132 61 - $user_datasource = id(new PhabricatorPeopleUserFunctionDatasource()) 62 - ->setViewer($viewer); 133 + $min_range = $start_date_value->getEpoch(); 134 + $max_range = $end_date_value->getEpoch(); 63 135 64 - if ($this->isMonthView($saved) || 65 - $this->isDayView($saved)) { 136 + if ($display == 'month' || $display == 'day') { 66 137 list($start_year, $start_month, $start_day) = 67 - $this->getDisplayYearAndMonthAndDay($saved); 138 + $this->getDisplayYearAndMonthAndDay($min_range, $max_range, $display); 68 139 69 140 $start_day = new DateTime( 70 141 "{$start_year}-{$start_month}-{$start_day}", 71 142 $timezone); 72 143 $next = clone $start_day; 73 144 74 - if ($this->isMonthView($saved)) { 145 + if ($display == 'month') { 75 146 $next->modify('+1 month'); 76 - } else if ($this->isDayView($saved)) { 77 - $next->modify('+6 day'); 147 + } else if ($display == 'day') { 148 + $next->modify('+7 day'); 78 149 } 79 150 80 151 $display_start = $start_day->format('U'); ··· 92 163 if (!$min_range || ($min_range < $display_start)) { 93 164 $min_range = $display_start; 94 165 95 - if ($this->isMonthView($saved) && 166 + if ($display == 'month' && 96 167 $first_of_month !== $start_of_week) { 97 168 $interim_day_num = ($first_of_month + 7 - $start_of_week) % 7; 98 169 $min_range = id(clone $start_day) ··· 103 174 if (!$max_range || ($max_range > $display_end)) { 104 175 $max_range = $display_end; 105 176 106 - if ($this->isMonthView($saved) && 177 + if ($display == 'month' && 107 178 $last_of_month !== $end_of_week) { 108 179 $interim_day_num = ($end_of_week + 7 - $last_of_month) % 7; 109 180 $max_range = id(clone $next) 110 181 ->modify('+'.$interim_day_num.' days') 111 182 ->format('U'); 112 183 } 113 - 114 184 } 115 185 } 116 186 117 - if ($saved->getParameter('upcoming')) { 187 + if ($upcoming) { 118 188 if ($min_range) { 119 189 $min_range = max(time(), $min_range); 120 190 } else { ··· 122 192 } 123 193 } 124 194 125 - if ($min_range || $max_range) { 126 - $query->withDateRange($min_range, $max_range); 127 - } 128 - 129 - $invited_phids = $saved->getParameter('invitedPHIDs', array()); 130 - $invited_phids = $user_datasource->evaluateTokens($invited_phids); 131 - if ($invited_phids) { 132 - $query->withInvitedPHIDs($invited_phids); 133 - } 134 - 135 - $creator_phids = $saved->getParameter('creatorPHIDs', array()); 136 - $creator_phids = $user_datasource->evaluateTokens($creator_phids); 137 - if ($creator_phids) { 138 - $query->withCreatorPHIDs($creator_phids); 139 - } 140 - 141 - $is_cancelled = $saved->getParameter('isCancelled', 'active'); 142 - 143 - switch ($is_cancelled) { 144 - case 'active': 145 - $query->withIsCancelled(false); 146 - break; 147 - case 'cancelled': 148 - $query->withIsCancelled(true); 149 - break; 150 - } 151 - 152 - return $query; 153 - } 154 - 155 - public function buildSearchForm( 156 - AphrontFormView $form, 157 - PhabricatorSavedQuery $saved) { 158 - 159 - $range_start = $this->getDateFrom($saved); 160 - $e_start = null; 161 - 162 - $range_end = $this->getDateTo($saved); 163 - $e_end = null; 164 - 165 - if (!$range_start->isValid()) { 166 - $this->addError(pht('Start date is not valid.')); 167 - $e_start = pht('Invalid'); 168 - } 169 - 170 - if (!$range_end->isValid()) { 171 - $this->addError(pht('End date is not valid.')); 172 - $e_end = pht('Invalid'); 173 - } 174 - 175 - $start_epoch = $range_start->getEpoch(); 176 - $end_epoch = $range_end->getEpoch(); 177 - 178 - if ($start_epoch && $end_epoch && ($start_epoch > $end_epoch)) { 179 - $this->addError(pht('End date must be after start date.')); 180 - $e_start = pht('Invalid'); 181 - $e_end = pht('Invalid'); 182 - } 183 - 184 - $upcoming = $saved->getParameter('upcoming'); 185 - $is_cancelled = $saved->getParameter('isCancelled', 'active'); 186 - $display = $saved->getParameter('display', 'month'); 187 - 188 - $invited_phids = $saved->getParameter('invitedPHIDs', array()); 189 - $creator_phids = $saved->getParameter('creatorPHIDs', array()); 190 - $resolution_types = array( 191 - 'active' => pht('Active Events Only'), 192 - 'cancelled' => pht('Cancelled Events Only'), 193 - 'both' => pht('Both Cancelled and Active Events'), 194 - ); 195 - $display_options = array( 196 - 'month' => pht('Month View'), 197 - 'day' => pht('Day View (beta)'), 198 - 'list' => pht('List View'), 199 - ); 200 - 201 - $form 202 - ->appendControl( 203 - id(new AphrontFormTokenizerControl()) 204 - ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()) 205 - ->setName('creators') 206 - ->setLabel(pht('Created By')) 207 - ->setValue($creator_phids)) 208 - ->appendControl( 209 - id(new AphrontFormTokenizerControl()) 210 - ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()) 211 - ->setName('invited') 212 - ->setLabel(pht('Invited')) 213 - ->setValue($invited_phids)) 214 - ->appendChild( 215 - id(new AphrontFormDateControl()) 216 - ->setLabel(pht('Occurs After')) 217 - ->setUser($this->requireViewer()) 218 - ->setName('rangeStart') 219 - ->setError($e_start) 220 - ->setValue($range_start)) 221 - ->appendChild( 222 - id(new AphrontFormDateControl()) 223 - ->setLabel(pht('Occurs Before')) 224 - ->setUser($this->requireViewer()) 225 - ->setName('rangeEnd') 226 - ->setError($e_end) 227 - ->setValue($range_end)) 228 - ->appendChild( 229 - id(new AphrontFormCheckboxControl()) 230 - ->addCheckbox( 231 - 'upcoming', 232 - 1, 233 - pht('Show only upcoming events.'), 234 - $upcoming)) 235 - ->appendChild( 236 - id(new AphrontFormSelectControl()) 237 - ->setLabel(pht('Cancelled Events')) 238 - ->setName('isCancelled') 239 - ->setValue($is_cancelled) 240 - ->setOptions($resolution_types)) 241 - ->appendChild( 242 - id(new AphrontFormSelectControl()) 243 - ->setLabel(pht('Display Options')) 244 - ->setName('display') 245 - ->setValue($display) 246 - ->setOptions($display_options)); 195 + return array($min_range, $max_range); 247 196 } 248 197 249 198 protected function getURI($path) { ··· 279 228 case 'day': 280 229 return $query->setParameter('display', 'day'); 281 230 case 'upcoming': 282 - return $query->setParameter('upcoming', true); 231 + return $query->setParameter('upcoming', array( 232 + 0 => 'upcoming', 233 + )); 283 234 case 'all': 284 235 return $query; 285 236 } ··· 311 262 assert_instances_of($events, 'PhabricatorCalendarEvent'); 312 263 $viewer = $this->requireViewer(); 313 264 $list = new PHUIObjectItemListView(); 265 + 314 266 foreach ($events as $event) { 315 267 $from = phabricator_datetime($event->getDateFrom(), $viewer); 316 268 $duration = ''; ··· 353 305 array $statuses, 354 306 PhabricatorSavedQuery $query, 355 307 array $handles) { 308 + 356 309 $viewer = $this->requireViewer(); 357 310 $now = time(); 358 311 359 312 list($start_year, $start_month) = 360 - $this->getDisplayYearAndMonthAndDay($query); 313 + $this->getDisplayYearAndMonthAndDay( 314 + $this->getQueryDateFrom($query)->getEpoch(), 315 + $this->getQueryDateTo($query)->getEpoch(), 316 + $query->getParameter('display')); 361 317 362 318 $now_year = phabricator_format_local_time($now, $viewer, 'Y'); 363 319 $now_month = phabricator_format_local_time($now, $viewer, 'm'); ··· 365 321 366 322 if ($start_month == $now_month && $start_year == $now_year) { 367 323 $month_view = new PHUICalendarMonthView( 368 - $this->getDateFrom($query), 369 - $this->getDateTo($query), 324 + $this->getQueryDateFrom($query), 325 + $this->getQueryDateTo($query), 370 326 $start_month, 371 327 $start_year, 372 328 $now_day); 373 329 } else { 374 330 $month_view = new PHUICalendarMonthView( 375 - $this->getDateFrom($query), 376 - $this->getDateTo($query), 331 + $this->getQueryDateFrom($query), 332 + $this->getQueryDateTo($query), 377 333 $start_month, 378 334 $start_year); 379 335 } ··· 414 370 array $statuses, 415 371 PhabricatorSavedQuery $query, 416 372 array $handles) { 373 + 417 374 $viewer = $this->requireViewer(); 375 + 418 376 list($start_year, $start_month, $start_day) = 419 - $this->getDisplayYearAndMonthAndDay($query); 377 + $this->getDisplayYearAndMonthAndDay( 378 + $this->getQueryDateFrom($query)->getEpoch(), 379 + $this->getQueryDateTo($query)->getEpoch(), 380 + $query->getParameter('display')); 420 381 421 382 $day_view = id(new PHUICalendarDayView( 422 - $this->getDateFrom($query), 423 - $this->getDateTo($query), 383 + $this->getQueryDateFrom($query)->getEpoch(), 384 + $this->getQueryDateTo($query)->getEpoch(), 424 385 $start_year, 425 386 $start_month, 426 387 $start_day)) ··· 465 426 } 466 427 467 428 private function getDisplayYearAndMonthAndDay( 468 - PhabricatorSavedQuery $query) { 429 + $range_start, 430 + $range_end, 431 + $display) { 432 + 469 433 $viewer = $this->requireViewer(); 434 + $epoch = null; 435 + 470 436 if ($this->calendarYear && $this->calendarMonth) { 471 437 $start_year = $this->calendarYear; 472 438 $start_month = $this->calendarMonth; 473 439 $start_day = $this->calendarDay ? $this->calendarDay : 1; 474 440 } else { 475 - $epoch = $this->getDateFrom($query)->getEpoch(); 476 - if (!$epoch) { 477 - $epoch = $this->getDateTo($query)->getEpoch(); 478 - if (!$epoch) { 479 - $epoch = time(); 480 - } 441 + if ($range_start) { 442 + $epoch = $range_start; 443 + } else if ($range_end) { 444 + $epoch = $range_end; 445 + } else { 446 + $epoch = time(); 481 447 } 482 - if ($this->isMonthView($query)) { 448 + if ($display == 'month') { 483 449 $day = 1; 484 450 } else { 485 451 $day = phabricator_format_local_time($epoch, $viewer, 'd'); ··· 499 465 } 500 466 } 501 467 502 - private function getDateFrom(PhabricatorSavedQuery $saved) { 503 - return $this->getDate($saved, 'rangeStart'); 468 + private function getQueryDateFrom(PhabricatorSavedQuery $saved) { 469 + return $this->getQueryDate($saved, 'rangeStart'); 504 470 } 505 471 506 - private function getDateTo(PhabricatorSavedQuery $saved) { 507 - return $this->getDate($saved, 'rangeEnd'); 472 + private function getQueryDateTo(PhabricatorSavedQuery $saved) { 473 + return $this->getQueryDate($saved, 'rangeEnd'); 508 474 } 509 475 510 - private function getDate(PhabricatorSavedQuery $saved, $key) { 476 + private function getQueryDate(PhabricatorSavedQuery $saved, $key) { 511 477 $viewer = $this->requireViewer(); 512 478 513 479 $wild = $saved->getParameter($key); 514 - if ($wild) { 515 - $value = AphrontFormDateControlValue::newFromWild($viewer, $wild); 480 + return $this->getSafeDate($wild); 481 + } 482 + 483 + private function getSafeDate($value) { 484 + $viewer = $this->requireViewer(); 485 + if ($value) { 486 + // ideally this would be consistent and always pass in the same type 487 + if ($value instanceof AphrontFormDateControlValue) { 488 + return $value; 489 + } else { 490 + $value = AphrontFormDateControlValue::newFromWild($viewer, $value); 491 + } 516 492 } else { 517 493 $value = AphrontFormDateControlValue::newFromEpoch( 518 494 $viewer,
+1
src/applications/calendar/storage/PhabricatorCalendarEvent.php
··· 2 2 3 3 final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO 4 4 implements PhabricatorPolicyInterface, 5 + PhabricatorProjectInterface, 5 6 PhabricatorMarkupInterface, 6 7 PhabricatorApplicationTransactionInterface, 7 8 PhabricatorSubscribableInterface,
+43
src/applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php
··· 1 + <?php 2 + 3 + final class PhabricatorMetaMTAMailPHIDType extends PhabricatorPHIDType { 4 + 5 + const TYPECONST = 'MTAM'; 6 + 7 + public function getTypeName() { 8 + return pht('MetaMTA Mail'); 9 + } 10 + 11 + public function getPHIDTypeApplicationClass() { 12 + return 'PhabricatorMetaMTAApplication'; 13 + } 14 + 15 + public function newObject() { 16 + return new PhabricatorMetaMTAMail(); 17 + } 18 + 19 + protected function buildQueryForObjects( 20 + PhabricatorObjectQuery $query, 21 + array $phids) { 22 + 23 + return id(new PhabricatorMetaMTAMailQuery()) 24 + ->withPHIDs($phids); 25 + } 26 + 27 + public function loadHandles( 28 + PhabricatorHandleQuery $query, 29 + array $handles, 30 + array $objects) { 31 + 32 + foreach ($handles as $phid => $handle) { 33 + $mail = $objects[$phid]; 34 + 35 + $id = $mail->getID(); 36 + $name = pht('Mail %d', $id); 37 + 38 + $handle 39 + ->setName($name) 40 + ->setFullName($name); 41 + } 42 + } 43 + }
+57
src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorMetaMTAMailQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + 9 + public function withIDs(array $ids) { 10 + $this->ids = $ids; 11 + return $this; 12 + } 13 + 14 + public function withPHIDs(array $phids) { 15 + $this->phids = $phids; 16 + return $this; 17 + } 18 + 19 + protected function loadPage() { 20 + return $this->loadStandardPage($this->newResultObject()); 21 + } 22 + 23 + protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { 24 + $where = array(); 25 + 26 + if ($this->ids !== null) { 27 + $where[] = qsprintf( 28 + $conn_r, 29 + 'mail.id IN (%Ld)', 30 + $this->ids); 31 + } 32 + 33 + if ($this->phids !== null) { 34 + $where[] = qsprintf( 35 + $conn_r, 36 + 'mail.phid IN (%Ls)', 37 + $this->phids); 38 + } 39 + 40 + $where[] = $this->buildPagingClause($conn_r); 41 + 42 + return $this->formatWhereClause($where); 43 + } 44 + 45 + protected function getPrimaryTableAlias() { 46 + return 'mail'; 47 + } 48 + 49 + public function newResultObject() { 50 + return new PhabricatorMetaMTAMail(); 51 + } 52 + 53 + public function getQueryApplicationClass() { 54 + return 'PhabricatorMetaMTAApplication'; 55 + } 56 + 57 + }
+44 -1
src/applications/metamta/storage/PhabricatorMetaMTAMail.php
··· 3 3 /** 4 4 * @task recipients Managing Recipients 5 5 */ 6 - final class PhabricatorMetaMTAMail extends PhabricatorMetaMTADAO { 6 + final class PhabricatorMetaMTAMail 7 + extends PhabricatorMetaMTADAO 8 + implements PhabricatorPolicyInterface { 7 9 8 10 const STATUS_QUEUE = 'queued'; 9 11 const STATUS_SENT = 'sent'; ··· 12 14 13 15 const RETRY_DELAY = 5; 14 16 17 + protected $actorPHID; 15 18 protected $parameters; 16 19 protected $status; 17 20 protected $message; ··· 29 32 30 33 protected function getConfiguration() { 31 34 return array( 35 + self::CONFIG_AUX_PHID => true, 32 36 self::CONFIG_SERIALIZATION => array( 33 37 'parameters' => self::SERIALIZATION_JSON, 34 38 ), 35 39 self::CONFIG_COLUMN_SCHEMA => array( 40 + 'actorPHID' => 'phid?', 36 41 'status' => 'text32', 37 42 'relatedPHID' => 'phid?', 38 43 ··· 44 49 'status' => array( 45 50 'columns' => array('status'), 46 51 ), 52 + 'key_actorPHID' => array( 53 + 'columns' => array('actorPHID'), 54 + ), 47 55 'relatedPHID' => array( 48 56 'columns' => array('relatedPHID'), 49 57 ), ··· 52 60 ), 53 61 ), 54 62 ) + parent::getConfiguration(); 63 + } 64 + 65 + public function generatePHID() { 66 + return PhabricatorPHID::generateNewPHID( 67 + PhabricatorMetaMTAMailPHIDType::TYPECONST); 55 68 } 56 69 57 70 protected function setParam($param, $value) { ··· 211 224 212 225 public function setFrom($from) { 213 226 $this->setParam('from', $from); 227 + $this->setActorPHID($from); 214 228 return $this; 229 + } 230 + 231 + public function getFrom() { 232 + return $this->getParam('from'); 215 233 } 216 234 217 235 public function setRawFrom($raw_email, $raw_name) { ··· 990 1008 } catch (PhabricatorSystemActionRateLimitException $ex) { 991 1009 return true; 992 1010 } 1011 + } 1012 + 1013 + 1014 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 1015 + 1016 + 1017 + public function getCapabilities() { 1018 + return array( 1019 + PhabricatorPolicyCapability::CAN_VIEW, 1020 + ); 1021 + } 1022 + 1023 + public function getPolicy($capability) { 1024 + return PhabricatorPolicies::POLICY_NOONE; 1025 + } 1026 + 1027 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 1028 + $actor_phids = $this->getAllActorPHIDs(); 1029 + $actor_phids = $this->expandRecipients($actor_phids); 1030 + return in_array($viewer->getPHID(), $actor_phids); 1031 + } 1032 + 1033 + public function describeAutomaticCapability($capability) { 1034 + return pht( 1035 + 'The mail sender and message recipients can always see the mail.'); 993 1036 } 994 1037 995 1038
+48 -1
src/applications/notification/builder/PhabricatorNotificationBuilder.php
··· 3 3 final class PhabricatorNotificationBuilder extends Phobject { 4 4 5 5 private $stories; 6 + private $parsedStories; 6 7 private $user = null; 7 8 8 9 public function __construct(array $stories) { 10 + assert_instances_of($stories, 'PhabricatorFeedStory'); 9 11 $this->stories = $stories; 10 12 } 11 13 ··· 14 16 return $this; 15 17 } 16 18 17 - public function buildView() { 19 + private function parseStories() { 20 + 21 + if ($this->parsedStories) { 22 + return $this->parsedStories; 23 + } 18 24 19 25 $stories = $this->stories; 20 26 $stories = mpull($stories, null, 'getChronologicalKey'); ··· 100 106 $stories = mpull($stories, null, 'getChronologicalKey'); 101 107 krsort($stories); 102 108 109 + $this->parsedStories = $stories; 110 + return $stories; 111 + } 112 + 113 + public function buildView() { 114 + $stories = $this->parseStories(); 103 115 $null_view = new AphrontNullView(); 104 116 105 117 foreach ($stories as $story) { ··· 113 125 } 114 126 115 127 return $null_view; 128 + } 129 + 130 + public function buildDict() { 131 + $stories = $this->parseStories(); 132 + $dict = array(); 133 + 134 + foreach ($stories as $story) { 135 + if ($story instanceof PhabricatorApplicationTransactionFeedStory) { 136 + $dict[] = array( 137 + 'desktopReady' => true, 138 + 'title' => $story->renderText(), 139 + 'body' => $story->renderTextBody(), 140 + 'href' => $story->getURI(), 141 + 'icon' => $story->getImageURI(), 142 + ); 143 + } else if ($story instanceof PhabricatorNotificationTestFeedStory) { 144 + $dict[] = array( 145 + 'desktopReady' => true, 146 + 'title' => pht('Test Notification'), 147 + 'body' => $story->renderText(), 148 + 'href' => null, 149 + 'icon' => PhabricatorUser::getDefaultProfileImageURI(), 150 + ); 151 + } else { 152 + $dict[] = array( 153 + 'desktopReady' => false, 154 + 'title' => null, 155 + 'body' => null, 156 + 'href' => null, 157 + 'icon' => null, 158 + ); 159 + } 160 + } 161 + 162 + return $dict; 116 163 } 117 164 }
+7
src/applications/notification/controller/PhabricatorNotificationIndividualController.php
··· 33 33 34 34 $builder = new PhabricatorNotificationBuilder(array($story)); 35 35 $content = $builder->buildView()->render(); 36 + $dict = $builder->buildDict(); 37 + $data = $dict[0]; 36 38 37 39 $response = array( 38 40 'pertinent' => true, 39 41 'primaryObjectPHID' => $story->getPrimaryObjectPHID(), 42 + 'desktopReady' => $data['desktopReady'], 43 + 'href' => $data['href'], 44 + 'icon' => $data['icon'], 45 + 'title' => $data['title'], 46 + 'body' => $data['body'], 40 47 'content' => hsprintf('%s', $content), 41 48 ); 42 49
+1 -1
src/applications/notification/controller/PhabricatorNotificationTestController.php
··· 7 7 $request = $this->getRequest(); 8 8 $viewer = $request->getUser(); 9 9 10 - $story_type = 'PhabricatorNotificationAdHocFeedStory'; 10 + $story_type = 'PhabricatorNotificationTestFeedStory'; 11 11 $story_data = array( 12 12 'title' => pht( 13 13 'This is a test notification, sent at %s.',
+1 -1
src/applications/notification/feed/PhabricatorNotificationAdHocFeedStory.php src/applications/notification/feed/PhabricatorNotificationTestFeedStory.php
··· 1 1 <?php 2 2 3 - final class PhabricatorNotificationAdHocFeedStory extends PhabricatorFeedStory { 3 + final class PhabricatorNotificationTestFeedStory extends PhabricatorFeedStory { 4 4 5 5 public function getPrimaryObjectPHID() { 6 6 return $this->getAuthorPHID();
+5 -1
src/applications/search/engine/PhabricatorApplicationSearchEngine.php
··· 266 266 } 267 267 268 268 $query = $this->newQuery(); 269 - if ($query) { 269 + if ($query && $this->shouldShowOrderField()) { 270 270 $orders = $query->getBuiltinOrders(); 271 271 $orders = ipull($orders, 'name'); 272 272 ··· 291 291 } 292 292 293 293 return $field_map; 294 + } 295 + 296 + protected function shouldShowOrderField() { 297 + return true; 294 298 } 295 299 296 300 private function adjustFieldsForDisplay(array $field_map) {
+39
src/applications/search/field/PhabricatorSearchDateControlField.php
··· 1 + <?php 2 + 3 + final class PhabricatorSearchDateControlField 4 + extends PhabricatorSearchField { 5 + 6 + protected function getValueExistsInRequest(AphrontRequest $request, $key) { 7 + // The control doesn't actually submit a value with the same name as the 8 + // key, so look for the "_d" value instead, which has the date part of the 9 + // control value. 10 + return $request->getExists($key.'_d'); 11 + } 12 + 13 + protected function getValueFromRequest(AphrontRequest $request, $key) { 14 + $value = AphrontFormDateControlValue::newFromRequest($request, $key); 15 + $value->setOptional(true); 16 + return $value->getDictionary(); 17 + } 18 + 19 + protected function newControl() { 20 + return id(new AphrontFormDateControl()) 21 + ->setAllowNull(true); 22 + } 23 + 24 + protected function didReadValueFromSavedQuery($value) { 25 + if (!$value) { 26 + return null; 27 + } 28 + 29 + if ($value instanceof AphrontFormDateControlValue && $value->getEpoch()) { 30 + return $value->setOptional(true); 31 + } 32 + 33 + $value = AphrontFormDateControlValue::newFromWild( 34 + $this->getViewer(), 35 + $value); 36 + return $value->setOptional(true); 37 + } 38 + 39 + }
+159
src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php
··· 1 + <?php 2 + 3 + final class PhabricatorDesktopNotificationsSettingsPanel 4 + extends PhabricatorSettingsPanel { 5 + 6 + public function isEnabled() { 7 + return PhabricatorEnv::getEnvConfig('notification.enabled') && 8 + PhabricatorApplication::isClassInstalled( 9 + 'PhabricatorNotificationsApplication'); 10 + } 11 + 12 + public function getPanelKey() { 13 + return 'desktopnotifications'; 14 + } 15 + 16 + public function getPanelName() { 17 + return pht('Desktop Notifications'); 18 + } 19 + 20 + public function getPanelGroup() { 21 + return pht('Application Settings'); 22 + } 23 + 24 + public function processRequest(AphrontRequest $request) { 25 + $user = $request->getUser(); 26 + $preferences = $user->loadPreferences(); 27 + 28 + $pref = PhabricatorUserPreferences::PREFERENCE_DESKTOP_NOTIFICATIONS; 29 + 30 + if ($request->isFormPost()) { 31 + $notifications = $request->getInt($pref); 32 + $preferences->setPreference($pref, $notifications); 33 + $preferences->save(); 34 + return id(new AphrontRedirectResponse()) 35 + ->setURI($this->getPanelURI('?saved=true')); 36 + } 37 + 38 + $title = pht('Desktop Notifications'); 39 + $control_id = celerity_generate_unique_node_id(); 40 + $status_id = celerity_generate_unique_node_id(); 41 + $browser_status_id = celerity_generate_unique_node_id(); 42 + $cancel_ask = pht( 43 + 'The dialog asking for permission to send desktop notifications was '. 44 + 'closed without granting permission. Only application notifications '. 45 + 'will be sent.'); 46 + $accept_ask = pht( 47 + 'Click "Save Preference" to persist these changes.'); 48 + $reject_ask = pht( 49 + 'Permission for desktop notifications was denied. Only application '. 50 + 'notifications will be sent.'); 51 + $no_support = pht( 52 + 'This web browser does not support desktop notifications. Only '. 53 + 'application notifications will be sent for this browser regardless of '. 54 + 'this preference.'); 55 + $default_status = phutil_tag( 56 + 'span', 57 + array(), 58 + array( 59 + pht('This browser has not yet granted permission to send desktop '. 60 + 'notifications for this Phabricator instance.'), 61 + phutil_tag('br'), 62 + phutil_tag('br'), 63 + javelin_tag( 64 + 'button', 65 + array( 66 + 'sigil' => 'desktop-notifications-permission-button', 67 + 'class' => 'green', 68 + ), 69 + pht('Grant Permission')), 70 + )); 71 + $granted_status = phutil_tag( 72 + 'span', 73 + array(), 74 + pht('This browser has been granted permission to send desktop '. 75 + 'notifications for this Phabricator instance.')); 76 + $denied_status = phutil_tag( 77 + 'span', 78 + array(), 79 + pht('This browser has denied permission to send desktop notifications '. 80 + 'for this Phabricator instance. Consult your browser settings / '. 81 + 'documentation to figure out how to clear this setting, do so, '. 82 + 'and then re-visit this page to grant permission.')); 83 + $status_box = id(new PHUIInfoView()) 84 + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) 85 + ->setID($status_id) 86 + ->setIsHidden(true) 87 + ->appendChild($accept_ask); 88 + 89 + $control_config = array( 90 + 'controlID' => $control_id, 91 + 'statusID' => $status_id, 92 + 'browserStatusID' => $browser_status_id, 93 + 'defaultMode' => 0, 94 + 'desktopMode' => 1, 95 + 'cancelAsk' => $cancel_ask, 96 + 'grantedAsk' => $accept_ask, 97 + 'deniedAsk' => $reject_ask, 98 + 'defaultStatus' => $default_status, 99 + 'deniedStatus' => $denied_status, 100 + 'grantedStatus' => $granted_status, 101 + 'noSupport' => $no_support, 102 + ); 103 + 104 + $form = id(new AphrontFormView()) 105 + ->setUser($user) 106 + ->appendChild( 107 + id(new AphrontFormSelectControl()) 108 + ->setLabel($title) 109 + ->setControlID($control_id) 110 + ->setName($pref) 111 + ->setValue($preferences->getPreference($pref)) 112 + ->setOptions( 113 + array( 114 + 1 => pht('Send Desktop Notifications Too'), 115 + 0 => pht('Send Application Notifications Only'), 116 + )) 117 + ->setCaption( 118 + pht( 119 + 'Should Phabricator send desktop notifications? These are sent '. 120 + 'in addition to the notifications within the Phabricator '. 121 + 'application.')) 122 + ->initBehavior( 123 + 'desktop-notifications-control', 124 + $control_config)) 125 + ->appendChild( 126 + id(new AphrontFormSubmitControl()) 127 + ->setValue(pht('Save Preference'))); 128 + 129 + $test_icon = id(new PHUIIconView()) 130 + ->setIconFont('fa-exclamation-triangle'); 131 + $test_button = id(new PHUIButtonView()) 132 + ->setTag('a') 133 + ->setWorkflow(true) 134 + ->setText(pht('Send Test Notification')) 135 + ->setHref('/notification/test/') 136 + ->setIcon($test_icon); 137 + 138 + $form_box = id(new PHUIObjectBoxView()) 139 + ->setHeader( 140 + id(new PHUIHeaderView()) 141 + ->setHeader(pht('Desktop Notifications')) 142 + ->addActionLink($test_button)) 143 + ->setForm($form) 144 + ->setInfoView($status_box) 145 + ->setFormSaved($request->getBool('saved')); 146 + 147 + $browser_status_box = id(new PHUIInfoView()) 148 + ->setID($browser_status_id) 149 + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) 150 + ->setIsHidden(true) 151 + ->appendChild($default_status); 152 + 153 + return array( 154 + $form_box, 155 + $browser_status_box, 156 + ); 157 + } 158 + 159 + }
+1
src/applications/settings/storage/PhabricatorUserPreferences.php
··· 39 39 const PREFERENCE_CONPHERENCE_COLUMN = 'conpherence-column'; 40 40 41 41 const PREFERENCE_RESOURCE_POSTPROCESSOR = 'resource-postprocessor'; 42 + const PREFERENCE_DESKTOP_NOTIFICATIONS = 'desktop-notifications'; 42 43 43 44 // These are in an unusual order for historic reasons. 44 45 const MAILTAG_PREFERENCE_NOTIFY = 0;
+29
src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php
··· 116 116 return $text; 117 117 } 118 118 119 + public function renderTextBody() { 120 + $all_bodies = ''; 121 + $new_target = PhabricatorApplicationTransaction::TARGET_TEXT; 122 + $xaction_phids = $this->getValue('transactionPHIDs'); 123 + foreach ($xaction_phids as $xaction_phid) { 124 + $secondary_xaction = $this->getObject($xaction_phid); 125 + $old_target = $secondary_xaction->getRenderingTarget(); 126 + $secondary_xaction->setRenderingTarget($new_target); 127 + $secondary_xaction->setHandles($this->getHandles()); 128 + 129 + $body = $secondary_xaction->getBodyForMail(); 130 + if (nonempty($body)) { 131 + $all_bodies .= $body."\n"; 132 + } 133 + $secondary_xaction->setRenderingTarget($old_target); 134 + } 135 + return trim($all_bodies); 136 + } 137 + 138 + public function getImageURI() { 139 + $author_phid = $this->getPrimaryTransaction()->getAuthorPHID(); 140 + return $this->getHandle($author_phid)->getImageURI(); 141 + } 142 + 143 + public function getURI() { 144 + $handle = $this->getHandle($this->getPrimaryObjectPHID()); 145 + return PhabricatorEnv::getProductionURI($handle->getURI()); 146 + } 147 + 119 148 public function renderAsTextForDoorkeeper( 120 149 DoorkeeperFeedStoryPublisher $publisher) { 121 150
+1
src/view/AphrontView.php
··· 143 143 $name, 144 144 $config, 145 145 $this->getDefaultResourceSource()); 146 + return $this; 146 147 } 147 148 148 149
+7
src/view/form/PHUIInfoView.php
··· 13 13 private $severity; 14 14 private $id; 15 15 private $buttons = array(); 16 + private $isHidden; 16 17 17 18 public function setTitle($title) { 18 19 $this->title = $title; ··· 31 32 32 33 public function setID($id) { 33 34 $this->id = $id; 35 + return $this; 36 + } 37 + 38 + public function setIsHidden($bool) { 39 + $this->isHidden = $bool; 34 40 return $this; 35 41 } 36 42 ··· 112 118 array( 113 119 'id' => $this->id, 114 120 'class' => $classes, 121 + 'style' => $this->isHidden ? 'display: none;' : null, 115 122 ), 116 123 array( 117 124 $buttons,
+4
src/view/phui/PHUIFeedStoryView.php
··· 54 54 return $this; 55 55 } 56 56 57 + public function getImage() { 58 + return $this->image; 59 + } 60 + 57 61 public function setImageHref($image_href) { 58 62 $this->imageHref = $image_href; 59 63 return $this;
+13 -6
src/view/phui/calendar/PHUICalendarDayView.php
··· 208 208 private function getQueryRangeWarning() { 209 209 $errors = array(); 210 210 211 - $range_start_epoch = $this->rangeStart->getEpoch(); 212 - $range_end_epoch = $this->rangeEnd->getEpoch(); 211 + $range_start_epoch = null; 212 + $range_end_epoch = null; 213 + 214 + if ($this->rangeStart) { 215 + $range_start_epoch = $this->rangeStart->getEpoch(); 216 + } 217 + if ($this->rangeEnd) { 218 + $range_end_epoch = $this->rangeEnd->getEpoch(); 219 + } 213 220 214 221 $day_start = $this->getDateTime(); 215 222 $day_end = id(clone $day_start)->modify('+1 day'); ··· 226 233 $errors[] = pht('Part of the day is out of range'); 227 234 } 228 235 229 - if (($this->rangeEnd->getEpoch() != null && 230 - $this->rangeEnd->getEpoch() < $day_start) || 231 - ($this->rangeStart->getEpoch() != null && 232 - $this->rangeStart->getEpoch() > $day_end)) { 236 + if (($range_end_epoch != null && 237 + $range_end_epoch < $day_start) || 238 + ($range_start_epoch != null && 239 + $range_start_epoch > $day_end)) { 233 240 $errors[] = pht('Day is out of query range'); 234 241 } 235 242 return $errors;
+13 -6
src/view/phui/calendar/PHUICalendarMonthView.php
··· 434 434 private function getQueryRangeWarning() { 435 435 $errors = array(); 436 436 437 - $range_start_epoch = $this->rangeStart->getEpoch(); 438 - $range_end_epoch = $this->rangeEnd->getEpoch(); 437 + $range_start_epoch = null; 438 + $range_end_epoch = null; 439 + 440 + if ($this->rangeStart) { 441 + $range_start_epoch = $this->rangeStart->getEpoch(); 442 + } 443 + if ($this->rangeEnd) { 444 + $range_end_epoch = $this->rangeEnd->getEpoch(); 445 + } 439 446 440 447 $month_start = $this->getDateTime(); 441 448 $month_end = id(clone $month_start)->modify('+1 month'); ··· 452 459 $errors[] = pht('Part of the month is out of range'); 453 460 } 454 461 455 - if (($this->rangeEnd->getEpoch() != null && 456 - $this->rangeEnd->getEpoch() < $month_start) || 457 - ($this->rangeStart->getEpoch() != null && 458 - $this->rangeStart->getEpoch() > $month_end)) { 462 + if (($range_end_epoch != null && 463 + $range_end_epoch < $month_start) || 464 + ($range_start_epoch != null && 465 + $range_start_epoch > $month_end)) { 459 466 $errors[] = pht('Month is out of query range'); 460 467 } 461 468
+6
webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js
··· 75 75 // Show the notification itself. 76 76 new JX.Notification() 77 77 .setContent(JX.$H(response.content)) 78 + .setDesktopReady(response.desktopReady) 79 + .setKey(response.primaryObjectPHID) 80 + .setTitle(response.title) 81 + .setBody(response.body) 82 + .setHref(response.href) 83 + .setIcon(response.icon) 78 84 .show(); 79 85 80 86 // If the notification affected an object on this page, show a
+120
webroot/rsrc/js/application/aphlict/behavior-desktop-notifications-control.js
··· 1 + /** 2 + * @provides javelin-behavior-desktop-notifications-control 3 + * @requires javelin-behavior 4 + * javelin-stratcom 5 + * javelin-dom 6 + * javelin-uri 7 + * phabricator-notification 8 + */ 9 + 10 + JX.behavior('desktop-notifications-control', function(config, statics) { 11 + 12 + function findEl(id) { 13 + var el = null; 14 + try { 15 + el = JX.$(id); 16 + } catch (e) { 17 + // not found 18 + } 19 + return el; 20 + } 21 + function updateFormStatus(permission) { 22 + var statusEl = findEl(config.statusID); 23 + if (!statusEl) { 24 + return; 25 + } 26 + switch (permission) { 27 + case 'default': 28 + JX.DOM.setContent(statusEl.firstChild, config.cancelAsk); 29 + break; 30 + case 'granted': 31 + JX.DOM.setContent(statusEl.firstChild, config.grantedAsk); 32 + break; 33 + case 'denied': 34 + JX.DOM.setContent(statusEl.firstChild, config.deniedAsk); 35 + break; 36 + } 37 + JX.DOM.show(statusEl); 38 + } 39 + 40 + function updateBrowserStatus(permission) { 41 + var browserStatusEl = findEl(config.browserStatusID); 42 + if (!browserStatusEl) { 43 + return; 44 + } 45 + switch (permission) { 46 + case 'default': 47 + JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-notice', true); 48 + JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-success', false); 49 + JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-error', false); 50 + JX.DOM.setContent(browserStatusEl, JX.$H(config.defaultStatus)); 51 + break; 52 + case 'granted': 53 + JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-success', true); 54 + JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-notice', false); 55 + JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-error', false); 56 + JX.DOM.setContent(browserStatusEl, JX.$H(config.grantedStatus)); 57 + break; 58 + case 'denied': 59 + JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-error', true); 60 + JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-notice', false); 61 + JX.DOM.alterClass(browserStatusEl, 'phui-info-severity-success', false); 62 + JX.DOM.setContent(browserStatusEl, JX.$H(config.deniedStatus)); 63 + break; 64 + } 65 + JX.DOM.show(browserStatusEl); 66 + } 67 + 68 + function installSelectListener() { 69 + var controlEl = findEl(config.controlID); 70 + if (!controlEl) { 71 + return; 72 + } 73 + var select = JX.DOM.find(controlEl, 'select'); 74 + JX.DOM.listen( 75 + select, 76 + 'change', 77 + null, 78 + function (e) { 79 + if (!JX.Notification.supportsDesktopNotifications()) { 80 + return; 81 + } 82 + var value = e.getTarget().value; 83 + if (value == config.desktopMode) { 84 + window.Notification.requestPermission( 85 + function (permission) { 86 + updateFormStatus(permission); 87 + updateBrowserStatus(permission); 88 + }); 89 + } else { 90 + var statusEl = JX.$(config.statusID); 91 + JX.DOM.hide(statusEl); 92 + } 93 + }); 94 + } 95 + 96 + function install() { 97 + JX.Stratcom.listen( 98 + 'click', 99 + 'desktop-notifications-permission-button', 100 + function () { 101 + window.Notification.requestPermission( 102 + function (permission) { 103 + updateFormStatus(permission); 104 + updateBrowserStatus(permission); 105 + }); 106 + }); 107 + 108 + return true; 109 + } 110 + 111 + statics.installed = statics.installed || install(); 112 + if (!JX.Notification.supportsDesktopNotifications()) { 113 + var statusEl = JX.$(config.statusID); 114 + JX.DOM.setContent(statusEl.firstChild, config.noSupport); 115 + JX.DOM.show(statusEl); 116 + } else { 117 + updateBrowserStatus(window.Notification.permission); 118 + } 119 + installSelectListener(); 120 + });
+65 -1
webroot/rsrc/js/core/Notification.js
··· 26 26 _visible : false, 27 27 _hideTimer : null, 28 28 _duration : 12000, 29 + _desktopReady : false, 30 + _key : null, 31 + _title : null, 32 + _body : null, 33 + _href : null, 34 + _icon : null, 29 35 30 36 show : function() { 37 + var self = JX.Notification; 31 38 if (!this._visible) { 32 39 this._visible = true; 33 40 34 - var self = JX.Notification; 35 41 self._show(this); 36 42 this._updateTimer(); 37 43 } 44 + 45 + if (self.supportsDesktopNotifications() && 46 + self.desktopNotificationsEnabled() && 47 + this._desktopReady) { 48 + // Note: specifying "tag" means that notifications with matching 49 + // keys will aggregate. 50 + var n = new window.Notification(this._title, { 51 + icon: this._icon, 52 + body: this._body, 53 + tag: this._key, 54 + }); 55 + n.onclick = JX.bind(n, function (href) { 56 + this.close(); 57 + window.focus(); 58 + if (href) { 59 + JX.$U(href).go(); 60 + } 61 + }, this._href); 62 + // Note: some OS / browsers do this automagically; make the behavior 63 + // happen everywhere. 64 + setTimeout(n.close.bind(n), this._duration); 65 + } 38 66 return this; 39 67 }, 40 68 ··· 59 87 return this; 60 88 }, 61 89 90 + setDesktopReady : function(ready) { 91 + this._desktopReady = ready; 92 + return this; 93 + }, 94 + 95 + setTitle : function(title) { 96 + this._title = title; 97 + return this; 98 + }, 99 + 100 + setBody : function(body) { 101 + this._body = body; 102 + return this; 103 + }, 104 + 105 + setHref : function(href) { 106 + this._href = href; 107 + return this; 108 + }, 109 + 110 + setKey : function(key) { 111 + this._key = key; 112 + return this; 113 + }, 114 + 115 + setIcon : function(icon) { 116 + this._icon = icon; 117 + return this; 118 + }, 119 + 62 120 /** 63 121 * Set duration before the notification fades away, in milliseconds. If set 64 122 * to 0, the notification persists until dismissed. ··· 97 155 }, 98 156 99 157 statics : { 158 + supportsDesktopNotifications : function () { 159 + return 'Notification' in window; 160 + }, 161 + desktopNotificationsEnabled : function () { 162 + return window.Notification.permission === 'granted'; 163 + }, 100 164 _container : null, 101 165 _listening : false, 102 166 _active : [],