diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index bb5812a09a..72422e03e2 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -1,537 +1,537 @@ id = idx($data, 'id'); } public function isCreate() { return !$this->id; } public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $user_phid = $viewer->getPHID(); $error_name = true; $error_recurrence_end_date = true; $error_start_date = true; $error_end_date = true; $validation_exception = null; $is_recurring_id = celerity_generate_unique_node_id(); $recurrence_end_date_id = celerity_generate_unique_node_id(); $frequency_id = celerity_generate_unique_node_id(); $all_day_id = celerity_generate_unique_node_id(); $start_date_id = celerity_generate_unique_node_id(); $end_date_id = celerity_generate_unique_node_id(); $next_workflow = $request->getStr('next'); $uri_query = $request->getStr('query'); if ($this->isCreate()) { $mode = $request->getStr('mode'); $event = PhabricatorCalendarEvent::initializeNewCalendarEvent( $viewer, $mode); $create_start_year = $request->getInt('year'); $create_start_month = $request->getInt('month'); $create_start_day = $request->getInt('day'); $create_start_time = $request->getStr('time'); if ($create_start_year) { $start = AphrontFormDateControlValue::newFromParts( $viewer, $create_start_year, $create_start_month, $create_start_day, $create_start_time); if (!$start->isValid()) { return new Aphront400Response(); } $start_value = AphrontFormDateControlValue::newFromEpoch( $viewer, $start->getEpoch()); $end = clone $start_value->getDateTime(); $end->modify('+1 hour'); $end_value = AphrontFormDateControlValue::newFromEpoch( $viewer, $end->format('U')); } else { list($start_value, $end_value) = $this->getDefaultTimeValues($viewer); } $recurrence_end_date_value = clone $end_value; $recurrence_end_date_value->setOptional(true); $submit_label = pht('Create'); $page_title = pht('Create Event'); $redirect = 'created'; $subscribers = array(); $invitees = array($user_phid); $cancel_uri = $this->getApplicationURI(); } else { $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$event) { return new Aphront404Response(); } if ($request->getURIData('sequence')) { $index = $request->getURIData('sequence'); $result = $this->getEventAtIndexForGhostPHID( $viewer, $event->getPHID(), $index); if ($result) { return id(new AphrontRedirectResponse()) ->setURI('/calendar/event/edit/'.$result->getID().'/'); } $event = $this->createEventFromGhost( $viewer, $event, $index); return id(new AphrontRedirectResponse()) ->setURI('/calendar/event/edit/'.$event->getID().'/'); } $end_value = AphrontFormDateControlValue::newFromEpoch( $viewer, $event->getDateTo()); $start_value = AphrontFormDateControlValue::newFromEpoch( $viewer, $event->getDateFrom()); $recurrence_end_date_value = id(clone $end_value) ->setOptional(true); $submit_label = pht('Update'); $page_title = pht('Update Event'); $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( $event->getPHID()); $invitees = array(); foreach ($event->getInvitees() as $invitee) { if ($invitee->isUninvited()) { continue; } else { $invitees[] = $invitee->getInviteePHID(); } } $cancel_uri = '/'.$event->getMonogram(); } $name = $event->getName(); $description = $event->getDescription(); $is_all_day = $event->getIsAllDay(); $is_recurring = $event->getIsRecurring(); $frequency = idx($event->getRecurrenceFrequency(), 'rule'); $icon = $event->getIcon(); $current_policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($event) ->execute(); if ($request->isFormPost()) { $xactions = array(); $name = $request->getStr('name'); $start_value = AphrontFormDateControlValue::newFromRequest( $request, 'start'); $end_value = AphrontFormDateControlValue::newFromRequest( $request, 'end'); $recurrence_end_date_value = AphrontFormDateControlValue::newFromRequest( $request, 'recurrenceEndDate'); $recurrence_end_date_value->setOptional(true); $description = $request->getStr('description'); $subscribers = $request->getArr('subscribers'); $edit_policy = $request->getStr('editPolicy'); $view_policy = $request->getStr('viewPolicy'); $is_recurring = $request->getStr('isRecurring') ? 1 : 0; $frequency = $request->getStr('frequency'); $is_all_day = $request->getStr('isAllDay'); $icon = $request->getStr('icon'); $invitees = $request->getArr('invitees'); $new_invitees = $this->getNewInviteeList($invitees, $event); $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; if ($this->isCreate()) { $status = idx($new_invitees, $viewer->getPHID()); if ($status) { $new_invitees[$viewer->getPHID()] = $status_attending; } } $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_NAME) ->setNewValue($name); if ($this->isCreate()) { $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_RECURRING) ->setNewValue($is_recurring); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_FREQUENCY) ->setNewValue(array('rule' => $frequency)); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE) ->setNewValue($recurrence_end_date_value); } $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_ALL_DAY) ->setNewValue($is_all_day); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_ICON) ->setNewValue($icon); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_START_DATE) ->setNewValue($start_value); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_END_DATE) ->setNewValue($end_value); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorTransactions::TYPE_SUBSCRIBERS) ->setNewValue(array('=' => array_fuse($subscribers))); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_INVITE) ->setNewValue($new_invitees); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION) ->setNewValue($description); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) ->setNewValue($request->getStr('viewPolicy')); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) ->setNewValue($request->getStr('editPolicy')); $editor = id(new PhabricatorCalendarEventEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $xactions = $editor->applyTransactions($event, $xactions); $response = id(new AphrontRedirectResponse()); switch ($next_workflow) { case 'day': if (!$uri_query) { $uri_query = 'month'; } $year = $start_value->getDateTime()->format('Y'); $month = $start_value->getDateTime()->format('m'); $day = $start_value->getDateTime()->format('d'); $response->setURI( '/calendar/query/'.$uri_query.'/'.$year.'/'.$month.'/'.$day.'/'); break; default: $response->setURI('/E'.$event->getID()); break; } return $response; } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $error_name = $ex->getShortMessage( PhabricatorCalendarEventTransaction::TYPE_NAME); $error_start_date = $ex->getShortMessage( PhabricatorCalendarEventTransaction::TYPE_START_DATE); $error_end_date = $ex->getShortMessage( PhabricatorCalendarEventTransaction::TYPE_END_DATE); $error_recurrence_end_date = $ex->getShortMessage( PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE); $event->setViewPolicy($view_policy); $event->setEditPolicy($edit_policy); } } $is_recurring_checkbox = null; $recurrence_end_date_control = null; $recurrence_frequency_select = null; $name = id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') ->setValue($name) ->setError($error_name); if ($this->isCreate()) { Javelin::initBehavior('recurring-edit', array( 'isRecurring' => $is_recurring_id, 'frequency' => $frequency_id, 'recurrenceEndDate' => $recurrence_end_date_id, )); $is_recurring_checkbox = id(new AphrontFormCheckboxControl()) ->addCheckbox( 'isRecurring', 1, pht('Recurring Event'), $is_recurring, $is_recurring_id); $recurrence_end_date_control = id(new AphrontFormDateControl()) ->setUser($viewer) ->setName('recurrenceEndDate') ->setLabel(pht('Recurrence End Date')) ->setError($error_recurrence_end_date) ->setValue($recurrence_end_date_value) ->setID($recurrence_end_date_id) ->setIsTimeDisabled(true) ->setAllowNull(true) - ->setIsDisabled(!$is_recurring); + ->setIsDisabled($recurrence_end_date_value->isDisabled()); $recurrence_frequency_select = id(new AphrontFormSelectControl()) ->setName('frequency') ->setOptions(array( 'daily' => pht('Daily'), 'weekly' => pht('Weekly'), 'monthly' => pht('Monthly'), 'yearly' => pht('Yearly'), )) ->setValue($frequency) ->setLabel(pht('Recurring Event Frequency')) ->setID($frequency_id) ->setDisabled(!$is_recurring); } Javelin::initBehavior('event-all-day', array( 'allDayID' => $all_day_id, 'startDateID' => $start_date_id, 'endDateID' => $end_date_id, )); $all_day_checkbox = id(new AphrontFormCheckboxControl()) ->addCheckbox( 'isAllDay', 1, pht('All Day Event'), $is_all_day, $all_day_id); $start_control = id(new AphrontFormDateControl()) ->setUser($viewer) ->setName('start') ->setLabel(pht('Start')) ->setError($error_start_date) ->setValue($start_value) ->setID($start_date_id) ->setIsTimeDisabled($is_all_day) ->setEndDateID($end_date_id); $end_control = id(new AphrontFormDateControl()) ->setUser($viewer) ->setName('end') ->setLabel(pht('End')) ->setError($error_end_date) ->setValue($end_value) ->setID($end_date_id) ->setIsTimeDisabled($is_all_day); $description = id(new AphrontFormTextAreaControl()) ->setLabel(pht('Description')) ->setName('description') ->setValue($description); $view_policies = id(new AphrontFormPolicyControl()) ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($event) ->setPolicies($current_policies) ->setName('viewPolicy'); $edit_policies = id(new AphrontFormPolicyControl()) ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($event) ->setPolicies($current_policies) ->setName('editPolicy'); $subscribers = id(new AphrontFormTokenizerControl()) ->setLabel(pht('Subscribers')) ->setName('subscribers') ->setValue($subscribers) ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); $invitees = id(new AphrontFormTokenizerControl()) ->setLabel(pht('Invitees')) ->setName('invitees') ->setValue($invitees) ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); if ($this->isCreate()) { $icon_uri = $this->getApplicationURI('icon/'); } else { $icon_uri = $this->getApplicationURI('icon/'.$event->getID().'/'); } $icon_display = PhabricatorCalendarIcon::renderIconForChooser($icon); $icon = id(new AphrontFormChooseButtonControl()) ->setLabel(pht('Icon')) ->setName('icon') ->setDisplayValue($icon_display) ->setButtonText(pht('Choose Icon...')) ->setChooseURI($icon_uri) ->setValue($icon); $form = id(new AphrontFormView()) ->addHiddenInput('next', $next_workflow) ->addHiddenInput('query', $uri_query) ->setUser($viewer) ->appendChild($name); if ($is_recurring_checkbox) { $form->appendChild($is_recurring_checkbox); } if ($recurrence_end_date_control) { $form->appendChild($recurrence_end_date_control); } if ($recurrence_frequency_select) { $form->appendControl($recurrence_frequency_select); } $form ->appendChild($all_day_checkbox) ->appendChild($start_control) ->appendChild($end_control) ->appendControl($view_policies) ->appendControl($edit_policies) ->appendControl($subscribers) ->appendControl($invitees) ->appendChild($description) ->appendChild($icon); if ($request->isAjax()) { return $this->newDialog() ->setTitle($page_title) ->setWidth(AphrontDialogView::WIDTH_FULL) ->appendForm($form) ->addCancelButton($cancel_uri) ->addSubmitButton($submit_label); } $submit = id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($submit_label); $form->appendChild($submit); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); if (!$this->isCreate()) { $crumbs->addTextCrumb('E'.$event->getId(), '/E'.$event->getId()); } $crumbs->addTextCrumb($page_title); $object_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) ->setValidationException($validation_exception) ->appendChild($form); return $this->buildApplicationPage( array( $crumbs, $object_box, ), array( 'title' => $page_title, )); } public function getNewInviteeList(array $phids, $event) { $invitees = $event->getInvitees(); $invitees = mpull($invitees, null, 'getInviteePHID'); $invited_status = PhabricatorCalendarEventInvitee::STATUS_INVITED; $uninvited_status = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; $phids = array_fuse($phids); $new = array(); foreach ($phids as $phid) { $old_status = $event->getUserInviteStatus($phid); if ($old_status != $uninvited_status) { continue; } $new[$phid] = $invited_status; } foreach ($invitees as $invitee) { $deleted_invitee = !idx($phids, $invitee->getInviteePHID()); if ($deleted_invitee) { $new[$invitee->getInviteePHID()] = $uninvited_status; } } return $new; } private function getDefaultTimeValues($viewer) { $start = new DateTime('@'.time()); $start->setTimeZone($viewer->getTimeZone()); $start->setTime($start->format('H'), 0, 0); $start->modify('+1 hour'); $end = id(clone $start)->modify('+1 hour'); $start_value = AphrontFormDateControlValue::newFromEpoch( $viewer, $start->format('U')); $end_value = AphrontFormDateControlValue::newFromEpoch( $viewer, $end->format('U')); return array($start_value, $end_value); } } diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index 712dd8945d..79b5946f04 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -1,431 +1,455 @@ getTransactionType()) { case PhabricatorCalendarEventTransaction::TYPE_RECURRING: return $object->getIsRecurring(); case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: return $object->getRecurrenceFrequency(); case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: return $object->getRecurrenceEndDate(); case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: return $object->getInstanceOfEventPHID(); case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: return $object->getSequenceIndex(); case PhabricatorCalendarEventTransaction::TYPE_NAME: return $object->getName(); case PhabricatorCalendarEventTransaction::TYPE_START_DATE: return $object->getDateFrom(); case PhabricatorCalendarEventTransaction::TYPE_END_DATE: return $object->getDateTo(); case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: return $object->getDescription(); case PhabricatorCalendarEventTransaction::TYPE_CANCEL: return $object->getIsCancelled(); case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: return (int)$object->getIsAllDay(); case PhabricatorCalendarEventTransaction::TYPE_ICON: return $object->getIcon(); case PhabricatorCalendarEventTransaction::TYPE_INVITE: $map = $xaction->getNewValue(); $phids = array_keys($map); $invitees = mpull($object->getInvitees(), null, 'getInviteePHID'); $old = array(); foreach ($phids as $phid) { $invitee = idx($invitees, $phid); if ($invitee) { $old[$phid] = $invitee->getStatus(); } else { $old[$phid] = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; } } return $old; } return parent::getCustomTransactionOldValue($object, $xaction); } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorCalendarEventTransaction::TYPE_RECURRING: case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: case PhabricatorCalendarEventTransaction::TYPE_NAME: case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: case PhabricatorCalendarEventTransaction::TYPE_CANCEL: case PhabricatorCalendarEventTransaction::TYPE_INVITE: case PhabricatorCalendarEventTransaction::TYPE_ICON: return $xaction->getNewValue(); case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: return (int)$xaction->getNewValue(); case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: return $xaction->getNewValue()->getEpoch(); } return parent::getCustomTransactionNewValue($object, $xaction); } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorCalendarEventTransaction::TYPE_RECURRING: return $object->setIsRecurring($xaction->getNewValue()); case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: return $object->setRecurrenceFrequency($xaction->getNewValue()); case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: return $object->setInstanceOfEventPHID($xaction->getNewValue()); case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: return $object->setSequenceIndex($xaction->getNewValue()); case PhabricatorCalendarEventTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); return; case PhabricatorCalendarEventTransaction::TYPE_START_DATE: $object->setDateFrom($xaction->getNewValue()); return; case PhabricatorCalendarEventTransaction::TYPE_END_DATE: $object->setDateTo($xaction->getNewValue()); return; case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: $object->setRecurrenceEndDate($xaction->getNewValue()); return; case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: $object->setDescription($xaction->getNewValue()); return; case PhabricatorCalendarEventTransaction::TYPE_CANCEL: $object->setIsCancelled((int)$xaction->getNewValue()); return; case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: $object->setIsAllDay((int)$xaction->getNewValue()); return; case PhabricatorCalendarEventTransaction::TYPE_ICON: $object->setIcon($xaction->getNewValue()); return; case PhabricatorCalendarEventTransaction::TYPE_INVITE: return; } return parent::applyCustomInternalTransaction($object, $xaction); } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorCalendarEventTransaction::TYPE_RECURRING: case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: case PhabricatorCalendarEventTransaction::TYPE_NAME: case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: case PhabricatorCalendarEventTransaction::TYPE_CANCEL: case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: case PhabricatorCalendarEventTransaction::TYPE_ICON: return; case PhabricatorCalendarEventTransaction::TYPE_INVITE: $map = $xaction->getNewValue(); $phids = array_keys($map); $invitees = $object->getInvitees(); $invitees = mpull($invitees, null, 'getInviteePHID'); foreach ($phids as $phid) { $invitee = idx($invitees, $phid); if (!$invitee) { $invitee = id(new PhabricatorCalendarEventInvitee()) ->setEventPHID($object->getPHID()) ->setInviteePHID($phid) ->setInviterPHID($this->getActingAsPHID()); $invitees[] = $invitee; } $invitee->setStatus($map[$phid]) ->save(); } $object->attachInvitees($invitees); return; } return parent::applyCustomExternalTransaction($object, $xaction); } protected function didApplyInternalEffects( PhabricatorLiskDAO $object, array $xactions) { $object->removeViewerTimezone($this->requireActor()); return $xactions; } protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { // Clear the availability caches for users whose availability is affected // by this edit. $invalidate_all = false; $invalidate_phids = array(); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorCalendarEventTransaction::TYPE_ICON: break; case PhabricatorCalendarEventTransaction::TYPE_RECURRING: case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY: case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT: case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX: case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: case PhabricatorCalendarEventTransaction::TYPE_CANCEL: case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: // For these kinds of changes, we need to invalidate the availabilty // caches for all attendees. $invalidate_all = true; break; case PhabricatorCalendarEventTransaction::TYPE_INVITE: foreach ($xaction->getNewValue() as $phid => $ignored) { $invalidate_phids[$phid] = $phid; } break; } } $phids = mpull($object->getInvitees(), 'getInviteePHID'); $phids = array_fuse($phids); if (!$invalidate_all) { $phids = array_select_keys($phids, $invalidate_phids); } if ($phids) { $user = new PhabricatorUser(); $conn_w = $user->establishConnection('w'); queryfx( $conn_w, 'UPDATE %T SET availabilityCacheTTL = NULL WHERE phid IN (%Ls) AND availabilityCacheTTL >= %d', $user->getTableName(), $phids, $object->getDateFromForCache()); } return $xactions; } protected function validateAllTransactions( PhabricatorLiskDAO $object, array $xactions) { - $start_date_xaction = PhabricatorCalendarEventTransaction::TYPE_START_DATE; - $end_date_xaction = PhabricatorCalendarEventTransaction::TYPE_END_DATE; + $start_date_xaction = + PhabricatorCalendarEventTransaction::TYPE_START_DATE; + $end_date_xaction = + PhabricatorCalendarEventTransaction::TYPE_END_DATE; + $is_recurrence_xaction = + PhabricatorCalendarEventTransaction::TYPE_RECURRING; + $recurrence_end_xaction = + PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE; + $start_date = $object->getDateFrom(); $end_date = $object->getDateTo(); + $recurrence_end = $object->getRecurrenceEndDate(); + $is_recurring = $object->getIsRecurring(); + $errors = array(); foreach ($xactions as $xaction) { if ($xaction->getTransactionType() == $start_date_xaction) { $start_date = $xaction->getNewValue()->getEpoch(); } else if ($xaction->getTransactionType() == $end_date_xaction) { $end_date = $xaction->getNewValue()->getEpoch(); + } else if ($xaction->getTransactionType() == $recurrence_end_xaction) { + $recurrence_end = $xaction->getNewValue(); + } else if ($xaction->getTransactionType() == $is_recurrence_xaction) { + $is_recurring = $xaction->getNewValue(); } } if ($start_date > $end_date) { $type = PhabricatorCalendarEventTransaction::TYPE_END_DATE; $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht('End date must be after start date.'), null); } + if ($recurrence_end && !$is_recurring) { + $type = + PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE; + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('Event must be recurring to have a recurrence end date.'). + null); + } + return $errors; } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhabricatorCalendarEventTransaction::TYPE_NAME: $missing = $this->validateIsEmptyTextField( $object->getName(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('Event name is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } break; case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: foreach ($xactions as $xaction) { $date_value = $xaction->getNewValue(); if (!$date_value->isValid()) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht('Invalid date.'), $xaction); } } break; } return $errors; } protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function supportsSearch() { return true; } protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { $xactions = mfilter($xactions, 'shouldHide', true); return $xactions; } protected function getMailSubjectPrefix() { return pht('[Calendar]'); } protected function getMailTo(PhabricatorLiskDAO $object) { $phids = array(); if ($object->getUserPHID()) { $phids[] = $object->getUserPHID(); } $phids[] = $this->getActingAsPHID(); $invitees = $object->getInvitees(); foreach ($invitees as $invitee) { $status = $invitee->getStatus(); if ($status === PhabricatorCalendarEventInvitee::STATUS_ATTENDING || $status === PhabricatorCalendarEventInvitee::STATUS_INVITED) { $phids[] = $invitee->getInviteePHID(); } } $phids = array_unique($phids); return $phids; } public function getMailTagsMap() { return array( PhabricatorCalendarEventTransaction::MAILTAG_CONTENT => pht( "An event's name, status, invite list, ". "icon, and description changes."), PhabricatorCalendarEventTransaction::MAILTAG_RESCHEDULE => pht( "An event's start and end date ". "and cancellation status changes."), PhabricatorCalendarEventTransaction::MAILTAG_OTHER => pht('Other event activity not listed above occurs.'), ); } protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new PhabricatorCalendarReplyHandler()) ->setMailReceiver($object); } protected function buildMailTemplate(PhabricatorLiskDAO $object) { $id = $object->getID(); $name = $object->getName(); return id(new PhabricatorMetaMTAMail()) ->setSubject("E{$id}: {$name}") ->addHeader('Thread-Topic', "E{$id}: ".$object->getName()); } protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $description = $object->getDescription(); $body = parent::buildMailBody($object, $xactions); if (strlen($description)) { $body->addTextSection( pht('EVENT DESCRIPTION'), $object->getDescription()); } $body->addLinkSection( pht('EVENT DETAIL'), PhabricatorEnv::getProductionURI('/E'.$object->getID())); return $body; } } diff --git a/webroot/rsrc/js/application/calendar/behavior-recurring-edit.js b/webroot/rsrc/js/application/calendar/behavior-recurring-edit.js index 6ce29bdf6b..7e75e3b838 100644 --- a/webroot/rsrc/js/application/calendar/behavior-recurring-edit.js +++ b/webroot/rsrc/js/application/calendar/behavior-recurring-edit.js @@ -1,21 +1,41 @@ /** * @provides javelin-behavior-recurring-edit */ JX.behavior('recurring-edit', function(config) { var checkbox = JX.$(config.isRecurring); + var frequency = JX.$(config.frequency); + var end_date = JX.$(config.recurrenceEndDate); - JX.DOM.listen(checkbox, 'change', null, function() { - var frequency = JX.$(config.frequency); - var end_date = JX.$(config.recurrenceEndDate); + var end_date_checkbox = JX.DOM.find(end_date, 'input', 'calendar-enable'); - frequency.disabled = checkbox.checked ? false : true; - end_date.disabled = checkbox.checked ? false : true; + JX.DOM.listen(checkbox, 'change', null, function() { + if (checkbox.checked) { + enableRecurring(); + } else { + disableRecurring(); + } + }); - if (end_date.disabled) { - JX.DOM.alterClass(end_date, 'datepicker-disabled', !checkbox.checked); + JX.DOM.listen(end_date, 'change', null, function() { + if (end_date_checkbox.checked) { + enableRecurring(); } }); + function enableRecurring() { + checkbox.checked = true; + frequency.disabled = false; + end_date.disabled = false; + } + + function disableRecurring() { + checkbox.checked = false; + frequency.disabled = true; + end_date.disabled = true; + end_date_checkbox.checked = false; + + JX.DOM.alterClass(end_date, 'datepicker-disabled', true); + } });