@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.

Allow users to drop .ics files on calendar views to import them

Summary:
Ref T10747. When a user drops a ".ics" file or a bunch of ".ics" files into a calendar view, import the events.

(Possibly we should just do this if you drop ".ics" files into any application, but we can look at that later.)

Test Plan: Dropped some .ics files into calendar views, got imports.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10747

Differential Revision: https://secure.phabricator.com/D16722

+198 -31
+10 -10
resources/celerity/map.php
··· 10 10 'conpherence.pkg.css' => '6412a825', 11 11 'conpherence.pkg.js' => 'cbe4d9be', 12 12 'core.pkg.css' => 'b99bbf5e', 13 - 'core.pkg.js' => '3eb7abf7', 13 + 'core.pkg.js' => '2d9fc958', 14 14 'darkconsole.pkg.js' => 'e7393ebb', 15 15 'differential.pkg.css' => 'e1d704ce', 16 16 'differential.pkg.js' => '634399e9', ··· 555 555 'rsrc/js/core/behavior-file-tree.js' => '88236f00', 556 556 'rsrc/js/core/behavior-form.js' => '5c54cbf3', 557 557 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', 558 - 'rsrc/js/core/behavior-global-drag-and-drop.js' => 'c8e57404', 558 + 'rsrc/js/core/behavior-global-drag-and-drop.js' => '960f6a39', 559 559 'rsrc/js/core/behavior-high-security-warning.js' => 'a464fe03', 560 560 'rsrc/js/core/behavior-history-install.js' => '7ee2b591', 561 561 'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64', ··· 701 701 'javelin-behavior-error-log' => '6882e80a', 702 702 'javelin-behavior-event-all-day' => '937bb700', 703 703 'javelin-behavior-fancy-datepicker' => '568931f3', 704 - 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 704 + 'javelin-behavior-global-drag-and-drop' => '960f6a39', 705 705 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 706 706 'javelin-behavior-high-security-warning' => 'a464fe03', 707 707 'javelin-behavior-history-install' => '7ee2b591', ··· 1735 1735 'javelin-dom', 1736 1736 'phabricator-busy', 1737 1737 ), 1738 + '960f6a39' => array( 1739 + 'javelin-behavior', 1740 + 'javelin-dom', 1741 + 'javelin-uri', 1742 + 'javelin-mask', 1743 + 'phabricator-drag-and-drop-file-upload', 1744 + ), 1738 1745 '988040b4' => array( 1739 1746 'javelin-install', 1740 1747 'javelin-dom', ··· 1981 1988 ), 1982 1989 'c7ccd872' => array( 1983 1990 'phui-fontkit-css', 1984 - ), 1985 - 'c8e57404' => array( 1986 - 'javelin-behavior', 1987 - 'javelin-dom', 1988 - 'javelin-uri', 1989 - 'javelin-mask', 1990 - 'phabricator-drag-and-drop-file-upload', 1991 1991 ), 1992 1992 'c90a04fc' => array( 1993 1993 'javelin-dom',
+2
src/__phutil_library_map__.php
··· 2109 2109 'PhabricatorCalendarImportDeleteTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportDeleteTransaction.php', 2110 2110 'PhabricatorCalendarImportDisableController' => 'applications/calendar/controller/PhabricatorCalendarImportDisableController.php', 2111 2111 'PhabricatorCalendarImportDisableTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportDisableTransaction.php', 2112 + 'PhabricatorCalendarImportDropController' => 'applications/calendar/controller/PhabricatorCalendarImportDropController.php', 2112 2113 'PhabricatorCalendarImportDuplicateLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportDuplicateLogType.php', 2113 2114 'PhabricatorCalendarImportEditController' => 'applications/calendar/controller/PhabricatorCalendarImportEditController.php', 2114 2115 'PhabricatorCalendarImportEditEngine' => 'applications/calendar/editor/PhabricatorCalendarImportEditEngine.php', ··· 6944 6945 'PhabricatorCalendarImportDeleteTransaction' => 'PhabricatorCalendarImportTransactionType', 6945 6946 'PhabricatorCalendarImportDisableController' => 'PhabricatorCalendarController', 6946 6947 'PhabricatorCalendarImportDisableTransaction' => 'PhabricatorCalendarImportTransactionType', 6948 + 'PhabricatorCalendarImportDropController' => 'PhabricatorCalendarController', 6947 6949 'PhabricatorCalendarImportDuplicateLogType' => 'PhabricatorCalendarImportLogType', 6948 6950 'PhabricatorCalendarImportEditController' => 'PhabricatorCalendarController', 6949 6951 'PhabricatorCalendarImportEditEngine' => 'PhabricatorEditEngine',
+2
src/applications/calendar/application/PhabricatorCalendarApplication.php
··· 85 85 => 'PhabricatorCalendarImportDisableController', 86 86 'delete/(?P<id>[1-9]\d*)/' 87 87 => 'PhabricatorCalendarImportDeleteController', 88 + 'drop/' 89 + => 'PhabricatorCalendarImportDropController', 88 90 'log/' => array( 89 91 $this->getQueryRoutePattern() 90 92 => 'PhabricatorCalendarImportLogListController',
+4
src/applications/calendar/controller/PhabricatorCalendarEventListController.php
··· 7 7 return true; 8 8 } 9 9 10 + public function isGlobalDragAndDropUploadEnabled() { 11 + return true; 12 + } 13 + 10 14 public function handleRequest(AphrontRequest $request) { 11 15 $year = $request->getURIData('year'); 12 16 $month = $request->getURIData('month');
+86
src/applications/calendar/controller/PhabricatorCalendarImportDropController.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarImportDropController 4 + extends PhabricatorCalendarController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + 9 + if (!$request->validateCSRF()) { 10 + return new Aphront400Response(); 11 + } 12 + 13 + $cancel_uri = $this->getApplicationURI(); 14 + 15 + $ids = $request->getStrList('h'); 16 + if ($ids) { 17 + $files = id(new PhabricatorFileQuery()) 18 + ->setViewer($viewer) 19 + ->withIDs($ids) 20 + ->setRaisePolicyExceptions(true) 21 + ->execute(); 22 + } else { 23 + $files = array(); 24 + } 25 + 26 + if (!$files) { 27 + return $this->newDialog() 28 + ->setTitle(pht('Nothing Uploaded')) 29 + ->appendParagraph( 30 + pht( 31 + 'Drag and drop .ics files to upload them and import them into '. 32 + 'Calendar.')) 33 + ->addCancelButton($cancel_uri, pht('Done')); 34 + } 35 + 36 + $engine = new PhabricatorCalendarICSImportEngine(); 37 + $imports = array(); 38 + foreach ($files as $file) { 39 + $import = PhabricatorCalendarImport::initializeNewCalendarImport( 40 + $viewer, 41 + clone $engine); 42 + 43 + $xactions = array(); 44 + $xactions[] = id(new PhabricatorCalendarImportTransaction()) 45 + ->setTransactionType( 46 + PhabricatorCalendarImportICSFileTransaction::TRANSACTIONTYPE) 47 + ->setNewValue($file->getPHID()); 48 + 49 + $editor = id(new PhabricatorCalendarImportEditor()) 50 + ->setActor($viewer) 51 + ->setContinueOnNoEffect(true) 52 + ->setContinueOnMissingFields(true) 53 + ->setContentSourceFromRequest($request); 54 + 55 + $editor->applyTransactions($import, $xactions); 56 + 57 + $imports[] = $import; 58 + } 59 + 60 + $import_phids = mpull($imports, 'getPHID'); 61 + $events = id(new PhabricatorCalendarEventQuery()) 62 + ->setViewer($viewer) 63 + ->withImportSourcePHIDs($import_phids) 64 + ->execute(); 65 + 66 + if (count($events) == 1) { 67 + // The user imported exactly one event. This is consistent with dropping 68 + // a .ics file from an email; just take them to the event. 69 + $event = head($events); 70 + $next_uri = $event->getURI(); 71 + } else if (count($imports) > 1) { 72 + // The user imported multiple different files. Take them to a summary 73 + // list of generated import activity. 74 + $source_phids = implode(',', $import_phids); 75 + $next_uri = '/calendar/import/log/?importSourcePHIDs='.$source_phids; 76 + } else { 77 + // The user imported one file, which had zero or more than one event. 78 + // Take them to the import detail page. 79 + $import = head($imports); 80 + $next_uri = $import->getURI(); 81 + } 82 + 83 + return id(new AphrontRedirectResponse())->setURI($next_uri); 84 + } 85 + 86 + }
+2 -2
src/applications/calendar/import/PhabricatorCalendarImportEngine.php
··· 390 390 if ($rrule) { 391 391 $event->setRecurrenceRule($rrule); 392 392 393 - $until_datetime = $rrule->getUntil() 394 - ->setViewerTimezone($timezone); 393 + $until_datetime = $rrule->getUntil(); 395 394 if ($until_datetime) { 395 + $until_datetime->setViewerTimezone($timezone); 396 396 $event->setUntilDateTime($until_datetime); 397 397 } 398 398 }
+29 -11
src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
··· 321 321 $list->addItem($item); 322 322 } 323 323 324 - $result = new PhabricatorApplicationSearchResultView(); 325 - $result->setObjectList($list); 326 - $result->setNoDataString(pht('No events found.')); 327 - 328 - return $result; 324 + return $this->newResultView() 325 + ->setObjectList($list) 326 + ->setNoDataString(pht('No events found.')); 329 327 } 330 328 331 329 private function buildCalendarMonthView( ··· 393 391 ->setProfileHeader(true) 394 392 ->setHeader($from->format('F Y')); 395 393 396 - return id(new PhabricatorApplicationSearchResultView()) 394 + return $this->newResultView($month_view) 397 395 ->setCrumbs($crumbs) 398 - ->setHeader($header) 399 - ->setContent($month_view); 396 + ->setHeader($header); 400 397 } 401 398 402 399 private function buildCalendarDayView( ··· 467 464 ->setProfileHeader(true) 468 465 ->setHeader($from->format('D, F jS')); 469 466 470 - return id(new PhabricatorApplicationSearchResultView()) 467 + return $this->newResultView($day_view) 471 468 ->setCrumbs($crumbs) 472 - ->setHeader($header) 473 - ->setContent($day_view); 469 + ->setHeader($header); 474 470 } 475 471 476 472 private function getDisplayYearAndMonthAndDay( ··· 594 590 ->setDisabled(!$can_export) 595 591 ->setHref('/calendar/export/edit/?queryKey='.$saved->getQueryKey()), 596 592 ); 593 + } 594 + 595 + 596 + private function newResultView($content = null) { 597 + // If we aren't rendering a dashboard panel, activate global drag-and-drop 598 + // so you can import ".ics" files by dropping them directly onto the 599 + // calendar. 600 + if (!$this->isPanelContext()) { 601 + $drop_upload = id(new PhabricatorGlobalUploadTargetView()) 602 + ->setViewer($this->requireViewer()) 603 + ->setHintText("\xE2\x87\xAA ".pht('Drop .ics Files to Import')) 604 + ->setSubmitURI('/calendar/import/drop/') 605 + ->setViewPolicy(PhabricatorPolicies::POLICY_NOONE); 606 + 607 + $content = array( 608 + $drop_upload, 609 + $content, 610 + ); 611 + } 612 + 613 + return id(new PhabricatorApplicationSearchResultView()) 614 + ->setContent($content); 597 615 } 598 616 599 617 }
+1
src/applications/calendar/storage/PhabricatorCalendarImport.php
··· 21 21 PhabricatorUser $actor, 22 22 PhabricatorCalendarImportEngine $engine) { 23 23 return id(new self()) 24 + ->setName('') 24 25 ->setAuthorPHID($actor->getPHID()) 25 26 ->setViewPolicy($actor->getPHID()) 26 27 ->setEditPolicy($actor->getPHID())
+48 -4
src/applications/files/view/PhabricatorGlobalUploadTargetView.php
··· 14 14 final class PhabricatorGlobalUploadTargetView extends AphrontView { 15 15 16 16 private $showIfSupportedID; 17 + private $hintText; 18 + private $viewPolicy; 19 + private $submitURI; 17 20 18 21 public function setShowIfSupportedID($show_if_supported_id) { 19 22 $this->showIfSupportedID = $show_if_supported_id; ··· 24 27 return $this->showIfSupportedID; 25 28 } 26 29 30 + public function setHintText($hint_text) { 31 + $this->hintText = $hint_text; 32 + return $this; 33 + } 34 + 35 + public function getHintText() { 36 + return $this->hintText; 37 + } 38 + 39 + public function setViewPolicy($view_policy) { 40 + $this->viewPolicy = $view_policy; 41 + return $this; 42 + } 43 + 44 + public function getViewPolicy() { 45 + return $this->viewPolicy; 46 + } 47 + 48 + public function setSubmitURI($submit_uri) { 49 + $this->submitURI = $submit_uri; 50 + return $this; 51 + } 52 + 53 + public function getSubmitURI() { 54 + return $this->submitURI; 55 + } 56 + 57 + 58 + 27 59 public function render() { 28 - $viewer = $this->getUser(); 60 + $viewer = $this->getViewer(); 29 61 if (!$viewer->isLoggedIn()) { 30 62 return null; 31 63 } ··· 33 65 $instructions_id = 'phabricator-global-drag-and-drop-upload-instructions'; 34 66 35 67 require_celerity_resource('global-drag-and-drop-css'); 68 + 69 + $hint_text = $this->getHintText(); 70 + if (!strlen($hint_text)) { 71 + $hint_text = "\xE2\x87\xAA ".pht('Drop Files to Upload'); 72 + } 36 73 37 74 // Use the configured default view policy. Drag and drop uploads use 38 75 // a more restrictive view policy if we don't specify a policy explicitly, 39 76 // as the more restrictive policy is correct for most drop targets (like 40 77 // Pholio uploads and Remarkup text areas). 41 78 42 - $view_policy = PhabricatorFile::initializeNewFile()->getViewPolicy(); 79 + $view_policy = $this->getViewPolicy(); 80 + if ($view_policy === null) { 81 + $view_policy = PhabricatorFile::initializeNewFile()->getViewPolicy(); 82 + } 83 + 84 + $submit_uri = $this->getSubmitURI(); 85 + $done_uri = '/file/query/authored/'; 43 86 44 87 Javelin::initBehavior('global-drag-and-drop', array( 45 88 'ifSupported' => $this->showIfSupportedID, 46 89 'instructions' => $instructions_id, 47 90 'uploadURI' => '/file/dropupload/', 48 - 'browseURI' => '/file/query/authored/', 91 + 'submitURI' => $submit_uri, 92 + 'browseURI' => $done_uri, 49 93 'viewPolicy' => $view_policy, 50 94 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(), 51 95 )); ··· 57 101 'class' => 'phabricator-global-upload-instructions', 58 102 'style' => 'display: none;', 59 103 ), 60 - "\xE2\x87\xAA ".pht('Drop Files to Upload')); 104 + $hint_text); 61 105 } 62 106 }
-2
src/applications/search/view/PhabricatorApplicationSearchResultView.php
··· 94 94 return $this->header; 95 95 } 96 96 97 - 98 - 99 97 }
+14 -2
webroot/rsrc/js/core/behavior-global-drag-and-drop.js
··· 70 70 // If whatever the user dropped in has finished uploading, send them to 71 71 // their uploads. 72 72 var uri; 73 - uri = JX.$U(config.browseURI); 73 + var is_submit = !!config.submitURI; 74 + 75 + if (is_submit) { 76 + uri = JX.$U(config.submitURI); 77 + } else { 78 + uri = JX.$U(config.browseURI); 79 + } 80 + 74 81 var ids = []; 75 82 for (var ii = 0; ii < statics.files.length; ii++) { 76 83 ids.push(statics.files[ii].getID()); ··· 79 86 80 87 statics.files = []; 81 88 82 - uri.go(); 89 + if (is_submit) { 90 + new JX.Workflow(uri) 91 + .start(); 92 + } else { 93 + uri.go(); 94 + } 83 95 } 84 96 }); 85 97