diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2549,6 +2549,7 @@ 'PhabricatorSearchDAO' => 'applications/search/storage/PhabricatorSearchDAO.php', 'PhabricatorSearchDatasource' => 'applications/search/typeahead/PhabricatorSearchDatasource.php', 'PhabricatorSearchDatasourceField' => 'applications/search/field/PhabricatorSearchDatasourceField.php', + 'PhabricatorSearchDateControlField' => 'applications/search/field/PhabricatorSearchDateControlField.php', 'PhabricatorSearchDateField' => 'applications/search/field/PhabricatorSearchDateField.php', 'PhabricatorSearchDeleteController' => 'applications/search/controller/PhabricatorSearchDeleteController.php', 'PhabricatorSearchDocument' => 'applications/search/storage/document/PhabricatorSearchDocument.php', @@ -5086,6 +5087,7 @@ 'PhabricatorCalendarEvent' => array( 'PhabricatorCalendarDAO', 'PhabricatorPolicyInterface', + 'PhabricatorProjectInterface', 'PhabricatorMarkupInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorSubscribableInterface', @@ -6286,6 +6288,7 @@ 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', 'PhabricatorSearchDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorSearchDatasourceField' => 'PhabricatorSearchTokenizerField', + 'PhabricatorSearchDateControlField' => 'PhabricatorSearchField', 'PhabricatorSearchDateField' => 'PhabricatorSearchField', 'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -140,6 +140,15 @@ $cancel_uri = '/'.$event->getMonogram(); } + if ($this->isCreate()) { + $projects = array(); + } else { + $projects = PhabricatorEdgeQuery::loadDestinationPHIDs( + $event->getPHID(), + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); + $projects = array_reverse($projects); + } + $name = $event->getName(); $description = $event->getDescription(); $is_all_day = $event->getIsAllDay(); @@ -167,6 +176,7 @@ $request, 'recurrenceEndDate'); $recurrence_end_date_value->setOptional(true); + $projects = $request->getArr('projects'); $description = $request->getStr('description'); $subscribers = $request->getArr('subscribers'); $edit_policy = $request->getStr('editPolicy'); @@ -262,6 +272,12 @@ ->setContinueOnNoEffect(true); try { + $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $proj_edge_type) + ->setNewValue(array('=' => array_fuse($projects))); + $xactions = $editor->applyTransactions($event, $xactions); $response = id(new AphrontRedirectResponse()); switch ($next_workflow) { @@ -437,6 +453,13 @@ ->setValue($end_disabled); } + $projects = id(new AphrontFormTokenizerControl()) + ->setLabel(pht('Projects')) + ->setName('projects') + ->setValue($projects) + ->setUser($viewer) + ->setDatasource(new PhabricatorProjectDatasource()); + $description = id(new PhabricatorRemarkupControl()) ->setLabel(pht('Description')) ->setName('description') @@ -511,6 +534,7 @@ ->appendControl($edit_policies) ->appendControl($subscribers) ->appendControl($invitees) + ->appendChild($projects) ->appendChild($description) ->appendChild($icon); diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -15,6 +15,10 @@ private $generateGhosts = false; + public function newResultObject() { + return new PhabricatorCalendarEvent(); + } + public function setGenerateGhosts($generate_ghosts) { $this->generateGhosts = $generate_ghosts; return $this; diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -15,66 +15,137 @@ return 'PhabricatorCalendarApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); + public function newQuery() { + return new PhabricatorCalendarEventQuery(); + } + + protected function shouldShowOrderField() { + return false; + } - $saved->setParameter( - 'rangeStart', - $this->readDateFromRequest($request, 'rangeStart')); + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Created By')) + ->setKey('creatorPHIDs') + ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()), + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Invited')) + ->setKey('invitedPHIDs') + ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()), + id(new PhabricatorSearchDateControlField()) + ->setLabel(pht('Occurs After')) + ->setKey('rangeStart'), + id(new PhabricatorSearchDateControlField()) + ->setLabel(pht('Occurs Before')) + ->setKey('rangeEnd') + ->setAliases(array('rangeEnd')), + id(new PhabricatorSearchCheckboxesField()) + ->setKey('upcoming') + ->setOptions(array( + 'upcoming' => pht('Show only upcoming events.'), + )), + id(new PhabricatorSearchSelectField()) + ->setLabel(pht('Cancelled Events')) + ->setKey('isCancelled') + ->setOptions($this->getCancelledOptions()) + ->setDefault('active'), + id(new PhabricatorSearchSelectField()) + ->setLabel(pht('Display Options')) + ->setKey('display') + ->setOptions($this->getViewOptions()) + ->setDefault('month'), + ); + } + + private function getCancelledOptions() { + return array( + 'active' => pht('Active Events Only'), + 'cancelled' => pht('Cancelled Events Only'), + 'both' => pht('Both Cancelled and Active Events'), + ); + } + + private function getViewOptions() { + return array( + 'month' => pht('Month View'), + 'day' => pht('Day View'), + 'list' => pht('List View'), + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + $viewer = $this->requireViewer(); + + if ($map['creatorPHIDs']) { + $query->withCreatorPHIDs($map['creatorPHIDs']); + } - $saved->setParameter( - 'rangeEnd', - $this->readDateFromRequest($request, 'rangeEnd')); + if ($map['invitedPHIDs']) { + $query->withInvitedPHIDs($map['invitedPHIDs']); + } - $saved->setParameter( - 'upcoming', - $this->readBoolFromRequest($request, 'upcoming')); + $range_start = $map['rangeStart']; + $range_end = $map['rangeEnd']; + $display = $map['display']; - $saved->setParameter( - 'invitedPHIDs', - $this->readUsersFromRequest($request, 'invited')); + if ($map['upcoming'] && $map['upcoming'][0] == 'upcoming') { + $upcoming = true; + } else { + $upcoming = false; + } - $saved->setParameter( - 'creatorPHIDs', - $this->readUsersFromRequest($request, 'creators')); + list($range_start, $range_end) = $this->getQueryDateRange( + $range_start, + $range_end, + $display, + $upcoming); - $saved->setParameter( - 'isCancelled', - $request->getStr('isCancelled')); + $query->withDateRange($range_start, $range_end); - $saved->setParameter( - 'display', - $request->getStr('display')); + switch ($map['isCancelled']) { + case 'active': + $query->withIsCancelled(false); + break; + case 'cancelled': + $query->withIsCancelled(true); + break; + } - return $saved; + return $query->setGenerateGhosts(true); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PhabricatorCalendarEventQuery()) - ->setGenerateGhosts(true); + private function getQueryDateRange( + $start_date_wild, + $end_date_wild, + $display, + $upcoming) { + + $start_date_value = $this->getSafeDate($start_date_wild); + $end_date_value = $this->getSafeDate($end_date_wild); + $viewer = $this->requireViewer(); $timezone = new DateTimeZone($viewer->getTimezoneIdentifier()); + $min_range = null; + $max_range = null; - $min_range = $this->getDateFrom($saved)->getEpoch(); - $max_range = $this->getDateTo($saved)->getEpoch(); - - $user_datasource = id(new PhabricatorPeopleUserFunctionDatasource()) - ->setViewer($viewer); + $min_range = $start_date_value->getEpoch(); + $max_range = $end_date_value->getEpoch(); - if ($this->isMonthView($saved) || - $this->isDayView($saved)) { + if ($display == 'month' || $display == 'day') { list($start_year, $start_month, $start_day) = - $this->getDisplayYearAndMonthAndDay($saved); + $this->getDisplayYearAndMonthAndDay($min_range, $max_range, $display); $start_day = new DateTime( "{$start_year}-{$start_month}-{$start_day}", $timezone); $next = clone $start_day; - if ($this->isMonthView($saved)) { + if ($display == 'month') { $next->modify('+1 month'); - } else if ($this->isDayView($saved)) { - $next->modify('+6 day'); + } else if ($display == 'day') { + $next->modify('+7 day'); } $display_start = $start_day->format('U'); @@ -92,7 +163,7 @@ if (!$min_range || ($min_range < $display_start)) { $min_range = $display_start; - if ($this->isMonthView($saved) && + if ($display == 'month' && $first_of_month !== $start_of_week) { $interim_day_num = ($first_of_month + 7 - $start_of_week) % 7; $min_range = id(clone $start_day) @@ -103,18 +174,17 @@ if (!$max_range || ($max_range > $display_end)) { $max_range = $display_end; - if ($this->isMonthView($saved) && + if ($display == 'month' && $last_of_month !== $end_of_week) { $interim_day_num = ($end_of_week + 7 - $last_of_month) % 7; $max_range = id(clone $next) ->modify('+'.$interim_day_num.' days') ->format('U'); } - } } - if ($saved->getParameter('upcoming')) { + if ($upcoming) { if ($min_range) { $min_range = max(time(), $min_range); } else { @@ -122,128 +192,7 @@ } } - if ($min_range || $max_range) { - $query->withDateRange($min_range, $max_range); - } - - $invited_phids = $saved->getParameter('invitedPHIDs', array()); - $invited_phids = $user_datasource->evaluateTokens($invited_phids); - if ($invited_phids) { - $query->withInvitedPHIDs($invited_phids); - } - - $creator_phids = $saved->getParameter('creatorPHIDs', array()); - $creator_phids = $user_datasource->evaluateTokens($creator_phids); - if ($creator_phids) { - $query->withCreatorPHIDs($creator_phids); - } - - $is_cancelled = $saved->getParameter('isCancelled', 'active'); - - switch ($is_cancelled) { - case 'active': - $query->withIsCancelled(false); - break; - case 'cancelled': - $query->withIsCancelled(true); - break; - } - - return $query; - } - - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - - $range_start = $this->getDateFrom($saved); - $e_start = null; - - $range_end = $this->getDateTo($saved); - $e_end = null; - - if (!$range_start->isValid()) { - $this->addError(pht('Start date is not valid.')); - $e_start = pht('Invalid'); - } - - if (!$range_end->isValid()) { - $this->addError(pht('End date is not valid.')); - $e_end = pht('Invalid'); - } - - $start_epoch = $range_start->getEpoch(); - $end_epoch = $range_end->getEpoch(); - - if ($start_epoch && $end_epoch && ($start_epoch > $end_epoch)) { - $this->addError(pht('End date must be after start date.')); - $e_start = pht('Invalid'); - $e_end = pht('Invalid'); - } - - $upcoming = $saved->getParameter('upcoming'); - $is_cancelled = $saved->getParameter('isCancelled', 'active'); - $display = $saved->getParameter('display', 'month'); - - $invited_phids = $saved->getParameter('invitedPHIDs', array()); - $creator_phids = $saved->getParameter('creatorPHIDs', array()); - $resolution_types = array( - 'active' => pht('Active Events Only'), - 'cancelled' => pht('Cancelled Events Only'), - 'both' => pht('Both Cancelled and Active Events'), - ); - $display_options = array( - 'month' => pht('Month View'), - 'day' => pht('Day View (beta)'), - 'list' => pht('List View'), - ); - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()) - ->setName('creators') - ->setLabel(pht('Created By')) - ->setValue($creator_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()) - ->setName('invited') - ->setLabel(pht('Invited')) - ->setValue($invited_phids)) - ->appendChild( - id(new AphrontFormDateControl()) - ->setLabel(pht('Occurs After')) - ->setUser($this->requireViewer()) - ->setName('rangeStart') - ->setError($e_start) - ->setValue($range_start)) - ->appendChild( - id(new AphrontFormDateControl()) - ->setLabel(pht('Occurs Before')) - ->setUser($this->requireViewer()) - ->setName('rangeEnd') - ->setError($e_end) - ->setValue($range_end)) - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->addCheckbox( - 'upcoming', - 1, - pht('Show only upcoming events.'), - $upcoming)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Cancelled Events')) - ->setName('isCancelled') - ->setValue($is_cancelled) - ->setOptions($resolution_types)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Display Options')) - ->setName('display') - ->setValue($display) - ->setOptions($display_options)); + return array($min_range, $max_range); } protected function getURI($path) { @@ -279,7 +228,9 @@ case 'day': return $query->setParameter('display', 'day'); case 'upcoming': - return $query->setParameter('upcoming', true); + return $query->setParameter('upcoming', array( + 0 => 'upcoming', + )); case 'all': return $query; } @@ -311,6 +262,7 @@ assert_instances_of($events, 'PhabricatorCalendarEvent'); $viewer = $this->requireViewer(); $list = new PHUIObjectItemListView(); + foreach ($events as $event) { $from = phabricator_datetime($event->getDateFrom(), $viewer); $duration = ''; @@ -349,11 +301,15 @@ array $statuses, PhabricatorSavedQuery $query, array $handles) { + $viewer = $this->requireViewer(); $now = time(); list($start_year, $start_month) = - $this->getDisplayYearAndMonthAndDay($query); + $this->getDisplayYearAndMonthAndDay( + $this->getQueryDateFrom($query)->getEpoch(), + $this->getQueryDateTo($query)->getEpoch(), + $query->getParameter('display')); $now_year = phabricator_format_local_time($now, $viewer, 'Y'); $now_month = phabricator_format_local_time($now, $viewer, 'm'); @@ -361,15 +317,15 @@ if ($start_month == $now_month && $start_year == $now_year) { $month_view = new PHUICalendarMonthView( - $this->getDateFrom($query), - $this->getDateTo($query), + $this->getQueryDateFrom($query), + $this->getQueryDateTo($query), $start_month, $start_year, $now_day); } else { $month_view = new PHUICalendarMonthView( - $this->getDateFrom($query), - $this->getDateTo($query), + $this->getQueryDateFrom($query), + $this->getQueryDateTo($query), $start_month, $start_year); } @@ -406,13 +362,18 @@ array $statuses, PhabricatorSavedQuery $query, array $handles) { + $viewer = $this->requireViewer(); + list($start_year, $start_month, $start_day) = - $this->getDisplayYearAndMonthAndDay($query); + $this->getDisplayYearAndMonthAndDay( + $this->getQueryDateFrom($query)->getEpoch(), + $this->getQueryDateTo($query)->getEpoch(), + $query->getParameter('display')); $day_view = id(new PHUICalendarDayView( - $this->getDateFrom($query), - $this->getDateTo($query), + $this->getQueryDateFrom($query)->getEpoch(), + $this->getQueryDateTo($query)->getEpoch(), $start_year, $start_month, $start_day)) @@ -454,21 +415,26 @@ } private function getDisplayYearAndMonthAndDay( - PhabricatorSavedQuery $query) { + $range_start, + $range_end, + $display) { + $viewer = $this->requireViewer(); + $epoch = null; + if ($this->calendarYear && $this->calendarMonth) { $start_year = $this->calendarYear; $start_month = $this->calendarMonth; $start_day = $this->calendarDay ? $this->calendarDay : 1; } else { - $epoch = $this->getDateFrom($query)->getEpoch(); - if (!$epoch) { - $epoch = $this->getDateTo($query)->getEpoch(); - if (!$epoch) { - $epoch = time(); - } + if ($range_start) { + $epoch = $range_start; + } else if ($range_end) { + $epoch = $range_end; + } else { + $epoch = time(); } - if ($this->isMonthView($query)) { + if ($display == 'month') { $day = 1; } else { $day = phabricator_format_local_time($epoch, $viewer, 'd'); @@ -488,20 +454,30 @@ } } - private function getDateFrom(PhabricatorSavedQuery $saved) { - return $this->getDate($saved, 'rangeStart'); + private function getQueryDateFrom(PhabricatorSavedQuery $saved) { + return $this->getQueryDate($saved, 'rangeStart'); } - private function getDateTo(PhabricatorSavedQuery $saved) { - return $this->getDate($saved, 'rangeEnd'); + private function getQueryDateTo(PhabricatorSavedQuery $saved) { + return $this->getQueryDate($saved, 'rangeEnd'); } - private function getDate(PhabricatorSavedQuery $saved, $key) { + private function getQueryDate(PhabricatorSavedQuery $saved, $key) { $viewer = $this->requireViewer(); $wild = $saved->getParameter($key); - if ($wild) { - $value = AphrontFormDateControlValue::newFromWild($viewer, $wild); + return $this->getSafeDate($wild); + } + + private function getSafeDate($value) { + $viewer = $this->requireViewer(); + if ($value) { + // ideally this would be consistent and always pass in the same type + if ($value instanceof AphrontFormDateControlValue) { + return $value; + } else { + $value = AphrontFormDateControlValue::newFromWild($viewer, $value); + } } else { $value = AphrontFormDateControlValue::newFromEpoch( $viewer, diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -2,6 +2,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO implements PhabricatorPolicyInterface, + PhabricatorProjectInterface, PhabricatorMarkupInterface, PhabricatorApplicationTransactionInterface, PhabricatorSubscribableInterface, diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -266,7 +266,7 @@ } $query = $this->newQuery(); - if ($query) { + if ($query && $this->shouldShowOrderField()) { $orders = $query->getBuiltinOrders(); $orders = ipull($orders, 'name'); @@ -293,6 +293,10 @@ return $field_map; } + protected function shouldShowOrderField() { + return true; + } + private function adjustFieldsForDisplay(array $field_map) { $order = $this->getDefaultFieldOrder(); diff --git a/src/applications/search/field/PhabricatorSearchDateControlField.php b/src/applications/search/field/PhabricatorSearchDateControlField.php new file mode 100644 --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchDateControlField.php @@ -0,0 +1,39 @@ +getExists($key.'_d'); + } + + protected function getValueFromRequest(AphrontRequest $request, $key) { + $value = AphrontFormDateControlValue::newFromRequest($request, $key); + $value->setOptional(true); + return $value->getDictionary(); + } + + protected function newControl() { + return id(new AphrontFormDateControl()) + ->setAllowNull(true); + } + + protected function didReadValueFromSavedQuery($value) { + if (!$value) { + return null; + } + + if ($value instanceof AphrontFormDateControlValue && $value->getEpoch()) { + return $value->setOptional(true); + } + + $value = AphrontFormDateControlValue::newFromWild( + $this->getViewer(), + $value); + return $value->setOptional(true); + } + +} diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -208,8 +208,15 @@ private function getQueryRangeWarning() { $errors = array(); - $range_start_epoch = $this->rangeStart->getEpoch(); - $range_end_epoch = $this->rangeEnd->getEpoch(); + $range_start_epoch = null; + $range_end_epoch = null; + + if ($this->rangeStart) { + $range_start_epoch = $this->rangeStart->getEpoch(); + } + if ($this->rangeEnd) { + $range_end_epoch = $this->rangeEnd->getEpoch(); + } $day_start = $this->getDateTime(); $day_end = id(clone $day_start)->modify('+1 day'); @@ -226,10 +233,10 @@ $errors[] = pht('Part of the day is out of range'); } - if (($this->rangeEnd->getEpoch() != null && - $this->rangeEnd->getEpoch() < $day_start) || - ($this->rangeStart->getEpoch() != null && - $this->rangeStart->getEpoch() > $day_end)) { + if (($range_end_epoch != null && + $range_end_epoch < $day_start) || + ($range_start_epoch != null && + $range_start_epoch > $day_end)) { $errors[] = pht('Day is out of query range'); } return $errors; diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -434,8 +434,15 @@ private function getQueryRangeWarning() { $errors = array(); - $range_start_epoch = $this->rangeStart->getEpoch(); - $range_end_epoch = $this->rangeEnd->getEpoch(); + $range_start_epoch = null; + $range_end_epoch = null; + + if ($this->rangeStart) { + $range_start_epoch = $this->rangeStart->getEpoch(); + } + if ($this->rangeEnd) { + $range_end_epoch = $this->rangeEnd->getEpoch(); + } $month_start = $this->getDateTime(); $month_end = id(clone $month_start)->modify('+1 month'); @@ -452,10 +459,10 @@ $errors[] = pht('Part of the month is out of range'); } - if (($this->rangeEnd->getEpoch() != null && - $this->rangeEnd->getEpoch() < $month_start) || - ($this->rangeStart->getEpoch() != null && - $this->rangeStart->getEpoch() > $month_end)) { + if (($range_end_epoch != null && + $range_end_epoch < $month_start) || + ($range_start_epoch != null && + $range_start_epoch > $month_end)) { $errors[] = pht('Month is out of query range'); }