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

Respect "End Date" for recurring Calendar events in detail UI

Summary:
Fixes T11396. Currently, you can keep clicking "Next >" forever to generate infinite instances of an event, even if it has a set end date.

Likewise, you can visit `/E123/999999` or whatever to stub out the 999999th instance of an event.

Instead:

- Before creating a new stub, make sure it happens before any end date.
- 404 stubs if we can't create them.
- Disable the "Next >" button if it isn't valid.

Test Plan:
- Visited `/E123/9999` for an event with a recurrence end date, got 404.
- Clicked "Next >" on an event with an end date, got new events until I hit the end date.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T11396

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

+62 -18
+13 -3
src/applications/calendar/controller/PhabricatorCalendarEventViewController.php
··· 278 278 $parent = $event->getParentEvent(); 279 279 } 280 280 281 - $next_uri = $parent->getURI().'/'.($sequence + 1); 281 + if ($parent->isValidSequenceIndex($viewer, $sequence + 1)) { 282 + $next_uri = $parent->getURI().'/'.($sequence + 1); 283 + $has_next = true; 284 + } else { 285 + $next_uri = null; 286 + $has_next = false; 287 + } 288 + 282 289 if ($sequence) { 283 290 if ($sequence > 1) { 284 291 $previous_uri = $parent->getURI().'/'.($sequence - 1); ··· 302 309 ->setTag('a') 303 310 ->setIcon('fa-chevron-right') 304 311 ->setHref($next_uri) 312 + ->setDisabled(!$has_next) 305 313 ->setText(pht('Next')); 306 314 307 315 $header ··· 450 458 'it.')); 451 459 } 452 460 453 - $instance = $event->newStub($viewer, $sequence); 461 + if (!$event->isValidSequenceIndex($viewer, $sequence)) { 462 + return null; 463 + } 454 464 455 - return $instance; 465 + return $event->newStub($viewer, $sequence); 456 466 } 457 467 458 468 private function buildSubheaderView(PhabricatorCalendarEvent $event) {
+49 -15
src/applications/calendar/storage/PhabricatorCalendarEvent.php
··· 137 137 ); 138 138 139 139 // Read these fields from the parent event instead of this event. For 140 - // example, we want any changes to the parent event's name to 140 + // example, we want any changes to the parent event's name to apply to 141 + // the child. 141 142 if (isset($inherit[$field])) { 142 143 if ($this->getIsStub()) { 143 144 // TODO: This should be unconditional, but the execution order of ··· 171 172 ->setName($parent->getName()) 172 173 ->setDescription($parent->getDescription()); 173 174 174 - $frequency = $parent->getFrequencyUnit(); 175 - $modify_key = '+'.$this->getSequenceIndex().' '.$frequency; 175 + $sequence = $this->getSequenceIndex(); 176 + $duration = $this->getDuration(); 177 + $epochs = $parent->getSequenceIndexEpochs($actor, $sequence, $duration); 178 + 179 + $this 180 + ->setDateFrom($epochs['dateFrom']) 181 + ->setDateTo($epochs['dateTo']) 182 + ->setAllDayDateFrom($epochs['allDayDateFrom']) 183 + ->setAllDayDateTo($epochs['allDayDateTo']); 184 + 185 + return $this; 186 + } 187 + 188 + public function isValidSequenceIndex(PhabricatorUser $viewer, $sequence) { 189 + try { 190 + $this->getSequenceIndexEpochs($viewer, $sequence, $this->getDuration()); 191 + return true; 192 + } catch (Exception $ex) { 193 + return false; 194 + } 195 + } 176 196 177 - $date = $parent->getDateFrom(); 178 - $date_time = PhabricatorTime::getDateTimeFromEpoch($date, $actor); 197 + private function getSequenceIndexEpochs( 198 + PhabricatorUser $viewer, 199 + $sequence, 200 + $duration) { 201 + 202 + $frequency = $this->getFrequencyUnit(); 203 + $modify_key = '+'.$sequence.' '.$frequency; 204 + 205 + $date = $this->getDateFrom(); 206 + $date_time = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); 179 207 $date_time->modify($modify_key); 180 208 $date = $date_time->format('U'); 181 209 182 - $duration = $this->getDuration(); 210 + $end_date = $this->getRecurrenceEndDate(); 211 + if ($end_date && $date > $end_date) { 212 + throw new Exception( 213 + pht( 214 + 'Sequence "%s" is invalid for this event: it would occur after '. 215 + 'the event stops repeating.', 216 + $sequence)); 217 + } 183 218 184 219 $utc = new DateTimeZone('UTC'); 185 220 186 - $allday_from = $parent->getAllDayDateFrom(); 221 + $allday_from = $this->getAllDayDateFrom(); 187 222 $allday_date = new DateTime('@'.$allday_from, $utc); 188 223 $allday_date->setTimeZone($utc); 189 224 $allday_date->modify($modify_key); 190 225 191 226 $allday_min = $allday_date->format('U'); 192 - $allday_duration = ($parent->getAllDayDateTo() - $allday_from); 193 - 194 - $this 195 - ->setDateFrom($date) 196 - ->setDateTo($date + $duration) 197 - ->setAllDayDateFrom($allday_min) 198 - ->setAllDayDateTo($allday_min + $allday_duration); 227 + $allday_duration = ($this->getAllDayDateTo() - $allday_from); 199 228 200 - return $this; 229 + return array( 230 + 'dateFrom' => $date, 231 + 'dateTo' => $date + $duration, 232 + 'allDayDateFrom' => $allday_min, 233 + 'allDayDateTo' => $allday_min + $allday_duration, 234 + ); 201 235 } 202 236 203 237 public function newStub(PhabricatorUser $actor, $sequence) {