diff --git a/resources/sql/autopatches/20161005.cal.01.rrules.php b/resources/sql/autopatches/20161005.cal.01.rrules.php new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20161005.cal.01.rrules.php @@ -0,0 +1,44 @@ +establishConnection('w'); +$table_name = 'calendar_event'; + +foreach (new LiskRawMigrationIterator($conn, $table_name) as $row) { + $parameters = phutil_json_decode($row['parameters']); + if (isset($parameters['recurrenceRule'])) { + // This event has already been migrated. + continue; + } + + if (!$row['isRecurring']) { + continue; + } + + $old_rule = $row['recurrenceFrequency']; + if (!$old_rule) { + continue; + } + + try { + $frequency = phutil_json_decode($old_rule); + if ($frequency) { + $frequency_rule = $frequency['rule']; + $frequency_rule = phutil_utf8_strtoupper($frequency_rule); + + $rrule = id(new PhutilCalendarRecurrenceRule()) + ->setFrequency($frequency_rule); + } + } catch (Exception $ex) { + continue; + } + + $parameters['recurrenceRule'] = $rrule->toDictionary(); + + queryfx( + $conn, + 'UPDATE %T SET parameters = %s WHERE id = %d', + $table_name, + phutil_json_encode($parameters), + $row['id']); +} diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -348,9 +348,16 @@ ->render(); } - $rule = $event->getFrequencyRule(); - switch ($rule) { - case PhabricatorCalendarEvent::FREQUENCY_DAILY: + $rrule = $event->newRecurrenceRule(); + + if ($rrule) { + $frequency = $rrule->getFrequency(); + } else { + $frequency = null; + } + + switch ($frequency) { + case PhutilCalendarRecurrenceRule::FREQUENCY_DAILY: if ($is_parent) { $message = pht('This event repeats every day.'); } else { @@ -359,7 +366,7 @@ $parent_link); } break; - case PhabricatorCalendarEvent::FREQUENCY_WEEKLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY: if ($is_parent) { $message = pht('This event repeats every week.'); } else { @@ -368,7 +375,7 @@ $parent_link); } break; - case PhabricatorCalendarEvent::FREQUENCY_MONTHLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY: if ($is_parent) { $message = pht('This event repeats every month.'); } else { @@ -377,7 +384,7 @@ $parent_link); } break; - case PhabricatorCalendarEvent::FREQUENCY_YEARLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY: if ($is_parent) { $message = pht('This event repeats every year.'); } else { diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php @@ -68,10 +68,10 @@ } $frequency_options = array( - PhabricatorCalendarEvent::FREQUENCY_DAILY => pht('Daily'), - PhabricatorCalendarEvent::FREQUENCY_WEEKLY => pht('Weekly'), - PhabricatorCalendarEvent::FREQUENCY_MONTHLY => pht('Monthly'), - PhabricatorCalendarEvent::FREQUENCY_YEARLY => pht('Yearly'), + PhutilCalendarRecurrenceRule::FREQUENCY_DAILY => pht('Daily'), + PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY => pht('Weekly'), + PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY => pht('Monthly'), + PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY => pht('Yearly'), ); $fields = array( @@ -142,6 +142,14 @@ ->setConduitTypeDescription(pht('Mark the event as a recurring event.')) ->setValue($object->getIsRecurring()); + + $rrule = $object->newRecurrenceRule(); + if ($rrule) { + $frequency = $rrule->getFrequency(); + } else { + $frequency = null; + } + $fields[] = id(new PhabricatorSelectEditField()) ->setKey('frequency') ->setLabel(pht('Frequency')) @@ -151,7 +159,7 @@ ->setDescription(pht('Recurring event frequency.')) ->setConduitDescription(pht('Change the event frequency.')) ->setConduitTypeDescription(pht('New event frequency.')) - ->setValue($object->getFrequencyRule()); + ->setValue($frequency); } if ($this->getIsCreate() || $object->getIsRecurring()) { diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -25,7 +25,6 @@ protected $isStub; protected $isRecurring = 0; - protected $recurrenceFrequency = array(); private $isGhostEvent = false; protected $instanceOfEventPHID; @@ -52,12 +51,7 @@ protected $dateFrom; protected $dateTo; protected $recurrenceEndDate; - - // Frequency Constants - const FREQUENCY_DAILY = 'daily'; - const FREQUENCY_WEEKLY = 'weekly'; - const FREQUENCY_MONTHLY = 'monthly'; - const FREQUENCY_YEARLY = 'yearly'; + protected $recurrenceFrequency = array(); public static function initializeNewCalendarEvent(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) @@ -85,10 +79,6 @@ ->setIsAllDay(0) ->setIsStub(0) ->setIsRecurring(0) - ->setRecurrenceFrequency( - array( - 'rule' => self::FREQUENCY_WEEKLY, - )) ->setIcon($default_icon) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy) @@ -120,7 +110,6 @@ ->setInstanceOfEventPHID($this->getPHID()) ->setSequenceIndex($sequence) ->setIsRecurring(true) - ->setRecurrenceFrequency($this->getRecurrenceFrequency()) ->attachParentEvent($this) ->setAllDayDateFrom(0) ->setAllDayDateTo(0) @@ -456,27 +445,6 @@ return $this; } - public function getFrequencyRule() { - return idx($this->recurrenceFrequency, 'rule'); - } - - public function getFrequencyUnit() { - $frequency = $this->getFrequencyRule(); - - switch ($frequency) { - case 'daily': - return 'day'; - case 'weekly': - return 'week'; - case 'monthly': - return 'month'; - case 'yearly': - return 'year'; - default: - return 'day'; - } - } - public function getURI() { if ($this->getIsGhostEvent()) { $base = $this->getParentEvent()->getURI(); @@ -840,20 +808,27 @@ $datetime->newAbsoluteDateTime()->toDictionary()); } + public function setRecurrenceRule(PhutilCalendarRecurrenceRule $rrule) { + return $this->setParameter( + 'recurrenceRule', + $rrule->toDictionary()); + } public function newRecurrenceRule() { if ($this->isChildEvent()) { return $this->getParentEvent()->newRecurrenceRule(); } - // TODO: This is a little fragile since it relies on the convenient - // definition of FREQUENCY constants here and in RecurrenceRule, but - // should be gone soon. - $map = array( - 'FREQ' => phutil_utf8_strtoupper($this->getFrequencyRule()), - ); + if (!$this->getIsRecurring()) { + return null; + } + + $dict = $this->getParameter('recurrenceRule'); + if (!$dict) { + return null; + } - $rrule = PhutilCalendarRecurrenceRule::newFromDictionary($map); + $rrule = PhutilCalendarRecurrenceRule::newFromDictionary($dict); $start = $this->newStartDateTime(); $rrule->setStartDateTime($start); @@ -869,6 +844,10 @@ $set = new PhutilCalendarRecurrenceSet(); $rrule = $this->newRecurrenceRule(); + if (!$rrule) { + return null; + } + $set->addSource($rrule); return $set; diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php --- a/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php @@ -6,32 +6,66 @@ const TRANSACTIONTYPE = 'calendar.frequency'; public function generateOldValue($object) { - return $object->getFrequencyRule(); + $rrule = $object->newRecurrenceRule(); + + if (!$rrule) { + return null; + } + + return $rrule->getFrequency(); } public function applyInternalEffects($object, $value) { - $object->setRecurrenceFrequency( - array( - 'rule' => $value, - )); + $rrule = id(new PhutilCalendarRecurrenceRule()) + ->setFrequency($value); + + $dict = $rrule->toDictionary(); + $object->setRecurrenceRule($dict); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $valid = array( + PhutilCalendarRecurrenceRule::FREQUENCY_DAILY, + PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY, + PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY, + PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY, + ); + $valid = array_fuse($valid); + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + + if (!isset($valid[$value])) { + $errors[] = $this->newInvalidError( + pht( + 'Event frequency "%s" is not valid. Valid frequences are: %s.', + $value, + implode(', ', $valid)), + $xaction); + } + } + + return $errors; } public function getTitle() { - $frequency = $this->getFrequencyRule($this->getNewValue()); + $frequency = $this->getFrequency($this->getNewValue()); switch ($frequency) { - case PhabricatorCalendarEvent::FREQUENCY_DAILY: + case PhutilCalendarRecurrenceRule::FREQUENCY_DAILY: return pht( '%s set this event to repeat daily.', $this->renderAuthor()); - case PhabricatorCalendarEvent::FREQUENCY_WEEKLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY: return pht( '%s set this event to repeat weekly.', $this->renderAuthor()); - case PhabricatorCalendarEvent::FREQUENCY_MONTHLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY: return pht( '%s set this event to repeat monthly.', $this->renderAuthor()); - case PhabricatorCalendarEvent::FREQUENCY_YEARLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY: return pht( '%s set this event to repeat yearly.', $this->renderAuthor()); @@ -39,24 +73,24 @@ } public function getTitleForFeed() { - $frequency = $this->getFrequencyRule($this->getNewValue()); + $frequency = $this->getFrequency($this->getNewValue()); switch ($frequency) { - case PhabricatorCalendarEvent::FREQUENCY_DAILY: + case PhutilCalendarRecurrenceRule::FREQUENCY_DAILY: return pht( '%s set %s to repeat daily.', $this->renderAuthor(), $this->renderObject()); - case PhabricatorCalendarEvent::FREQUENCY_WEEKLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY: return pht( '%s set %s to repeat weekly.', $this->renderAuthor(), $this->renderObject()); - case PhabricatorCalendarEvent::FREQUENCY_MONTHLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY: return pht( '%s set %s to repeat monthly.', $this->renderAuthor(), $this->renderObject()); - case PhabricatorCalendarEvent::FREQUENCY_YEARLY: + case PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY: return pht( '%s set %s to repeat yearly.', $this->renderAuthor(), @@ -64,12 +98,18 @@ } } - private function getFrequencyRule($value) { + private function getFrequency($value) { + // NOTE: This is normalizing three generations of these transactions + // to use RRULE constants. It would be vaguely nice to migrate them + // for consistency. + if (is_array($value)) { $value = idx($value, 'rule'); } else { - return $value; + $value = $value; } + + return phutil_utf8_strtoupper($value); } }