Page MenuHomePhabricator

D13039.diff
No OneTemporary

D13039.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -7,7 +7,7 @@
*/
return array(
'names' => array(
- 'core.pkg.css' => '68d4f4fb',
+ 'core.pkg.css' => 'e2460e8f',
'core.pkg.js' => '3bbe23c6',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '30602b8c',
@@ -135,7 +135,7 @@
'rsrc/css/phui/phui-feed-story.css' => 'c9f3a0b5',
'rsrc/css/phui/phui-fontkit.css' => 'dd8ddf27',
'rsrc/css/phui/phui-form-view.css' => '808329f2',
- 'rsrc/css/phui/phui-form.css' => 'f535f938',
+ 'rsrc/css/phui/phui-form.css' => '25876baf',
'rsrc/css/phui/phui-header-view.css' => '75aaf372',
'rsrc/css/phui/phui-icon.css' => 'bc766998',
'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8',
@@ -333,6 +333,7 @@
'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18',
'rsrc/js/application/calendar/behavior-day-view.js' => '5c46cff2',
'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8',
+ 'rsrc/js/application/calendar/behavior-recurring-edit.js' => '85c73ceb',
'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408',
'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2',
'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a',
@@ -629,6 +630,7 @@
'javelin-behavior-project-boards' => 'ba4fa35c',
'javelin-behavior-project-create' => '065227cc',
'javelin-behavior-quicksand-blacklist' => '7927a7d3',
+ 'javelin-behavior-recurring-edit' => '85c73ceb',
'javelin-behavior-refresh-csrf' => '7814b593',
'javelin-behavior-releeph-preview-branch' => 'b2b4fbaf',
'javelin-behavior-releeph-request-state-change' => 'a0b57eb8',
@@ -775,7 +777,7 @@
'phui-feed-story-css' => 'c9f3a0b5',
'phui-font-icon-base-css' => '3dad2ae3',
'phui-fontkit-css' => 'dd8ddf27',
- 'phui-form-css' => 'f535f938',
+ 'phui-form-css' => '25876baf',
'phui-form-view-css' => '808329f2',
'phui-header-view-css' => '75aaf372',
'phui-icon-view-css' => 'bc766998',
diff --git a/resources/sql/autopatches/20150527.calendar.recurringevents.sql b/resources/sql/autopatches/20150527.calendar.recurringevents.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150527.calendar.recurringevents.sql
@@ -0,0 +1,17 @@
+ALTER TABLE {$NAMESPACE}_calendar.calendar_event
+ ADD isRecurring BOOL NOT NULL;
+
+ALTER TABLE {$NAMESPACE}_calendar.calendar_event
+ ADD recurrenceFrequency LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL;
+
+ALTER TABLE {$NAMESPACE}_calendar.calendar_event
+ ADD recurrenceEndDate INT UNSIGNED;
+
+ALTER TABLE {$NAMESPACE}_calendar.calendar_event
+ ADD instanceOfEventPHID varbinary(64);
+
+ALTER TABLE {$NAMESPACE}_calendar.calendar_event
+ ADD sequenceIndex INT UNSIGNED;
+
+UPDATE {$NAMESPACE}_calendar.calendar_event
+ SET recurrenceFrequency = '[]' WHERE recurrenceFrequency = '';
diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php
--- a/src/applications/calendar/application/PhabricatorCalendarApplication.php
+++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php
@@ -40,7 +40,8 @@
public function getRoutes() {
return array(
- '/E(?P<id>[1-9]\d*)' => 'PhabricatorCalendarEventViewController',
+ '/E(?P<id>[1-9]\d*)(?:/(?P<sequence>\d+))?'
+ => 'PhabricatorCalendarEventViewController',
'/calendar/' => array(
'(?:query/(?P<queryKey>[^/]+)/(?:(?P<year>\d+)/'.
'(?P<month>\d+)/)?(?:(?P<day>\d+)/)?)?'
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
@@ -21,9 +21,11 @@
$error_end_date = true;
$validation_exception = null;
+ $is_recurring_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 = null;
+ $end_date_id = celerity_generate_unique_node_id();
$next_workflow = $request->getStr('next');
$uri_query = $request->getStr('query');
@@ -70,7 +72,6 @@
$subscribers = array();
$invitees = array($user_phid);
$cancel_uri = $this->getApplicationURI();
- $end_date_id = celerity_generate_unique_node_id();
} else {
$event = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
@@ -113,6 +114,8 @@
$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())
@@ -134,6 +137,8 @@
$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');
@@ -154,6 +159,16 @@
$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_ALL_DAY)
->setNewValue($is_all_day);
@@ -233,18 +248,44 @@
}
}
- Javelin::initBehavior('event-all-day', array(
- 'allDayID' => $all_day_id,
- 'startDateID' => $start_date_id,
- 'endDateID' => $end_date_id,
- ));
-
$name = id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($name)
->setError($error_name);
+ Javelin::initBehavior('recurring-edit', array(
+ 'isRecurring' => $is_recurring_id,
+ 'frequency' => $frequency_id,
+ ));
+
+ $is_recurring_checkbox = id(new AphrontFormCheckboxControl())
+ ->addCheckbox(
+ 'isRecurring',
+ 1,
+ pht('Recurring Event'),
+ $is_recurring,
+ $is_recurring_id);
+
+ $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',
@@ -323,6 +364,8 @@
->addHiddenInput('query', $uri_query)
->setUser($viewer)
->appendChild($name)
+ ->appendChild($is_recurring_checkbox)
+ ->appendChild($recurrence_frequency_select)
->appendChild($all_day_checkbox)
->appendChild($start_control)
->appendChild($end_control)
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
@@ -17,6 +17,8 @@
$request = $this->getRequest();
$viewer = $request->getUser();
+ $sequence = $request->getURIData('sequence');
+
$event = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
->withIDs(array($this->id))
@@ -25,6 +27,12 @@
return new Aphront404Response();
}
+ if ($sequence && $event->getIsRecurring()) {
+ $event = $event->generateNthGhost($sequence, $viewer);
+ } else if ($sequence) {
+ return new Aphront404Response();
+ }
+
$title = 'E'.$event->getID();
$page_title = $title.' '.$event->getName();
$crumbs = $this->buildApplicationCrumbs();
@@ -127,13 +135,15 @@
$event,
PhabricatorPolicyCapability::CAN_EDIT);
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Edit Event'))
- ->setIcon('fa-pencil')
- ->setHref($this->getApplicationURI("event/edit/{$id}/"))
- ->setDisabled(!$can_edit)
- ->setWorkflow(!$can_edit));
+ if (!$event->getIsGhostEvent()) {
+ $actions->addAction(
+ id(new PhabricatorActionView())
+ ->setName(pht('Edit Event'))
+ ->setIcon('fa-pencil')
+ ->setHref($this->getApplicationURI("event/edit/{$id}/"))
+ ->setDisabled(!$can_edit)
+ ->setWorkflow(!$can_edit));
+ }
if ($is_attending) {
$actions->addAction(
@@ -205,6 +215,12 @@
phabricator_datetime($event->getDateTo(), $viewer));
}
+ if ($event->getIsRecurring()) {
+ $properties->addProperty(
+ pht('Recurs'),
+ idx($event->getRecurrenceFrequency(), 'rule'));
+ }
+
$properties->addProperty(
pht('Host'),
$viewer->renderHandle($event->getUserPHID()));
diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
--- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
+++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
@@ -23,6 +23,12 @@
$types[] = PhabricatorCalendarEventTransaction::TYPE_ALL_DAY;
$types[] = PhabricatorCalendarEventTransaction::TYPE_ICON;
+ $types[] = PhabricatorCalendarEventTransaction::TYPE_RECURRING;
+ $types[] = PhabricatorCalendarEventTransaction::TYPE_FREQUENCY;
+ $types[] = PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE;
+ $types[] = PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT;
+ $types[] = PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX;
+
$types[] = PhabricatorTransactions::TYPE_COMMENT;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
@@ -34,6 +40,16 @@
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->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:
@@ -72,6 +88,11 @@
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_DESCRIPTION:
case PhabricatorCalendarEventTransaction::TYPE_CANCEL:
@@ -93,6 +114,16 @@
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_RECURRENCE_END_DATE:
+ return $object->setRecurrenceEndDate($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;
@@ -126,6 +157,11 @@
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:
@@ -181,6 +217,11 @@
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:
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
@@ -11,6 +11,13 @@
private $creatorPHIDs;
private $isCancelled;
+ private $generateGhosts = false;
+
+ public function setGenerateGhosts($generate_ghosts) {
+ $this->generateGhosts = $generate_ghosts;
+ return $this;
+ }
+
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
@@ -69,6 +76,7 @@
protected function loadPage() {
$table = new PhabricatorCalendarEvent();
$conn_r = $table->establishConnection('r');
+ $viewer = $this->getViewer();
$data = queryfx_all(
$conn_r,
@@ -86,6 +94,63 @@
$event->applyViewerTimezone($this->getViewer());
}
+ if (!$this->generateGhosts) {
+ return $events;
+ }
+
+ foreach ($events as $event) {
+ $sequence_start = 0;
+ $instance_count = null;
+
+ if ($event->getIsRecurring()) {
+ $frequency = $event->getFrequencyUnit();
+ $modify_key = '+1 '.$frequency;
+
+ if ($this->rangeBegin && $this->rangeBegin > $event->getDateFrom()) {
+ $max_date = $this->rangeBegin;
+ $date = $event->getDateFrom();
+ $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
+
+ while ($date < $max_date) {
+ // TODO: optimize this to not loop through all off-screen events
+ $sequence_start++;
+ $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
+ $date = $datetime->modify($modify_key)->format('U');
+ }
+
+ $start = $this->rangeBegin;
+ } else {
+ $start = $event->getDateFrom();
+ }
+
+ $date = $start;
+ $start_datetime = PhabricatorTime::getDateTimeFromEpoch(
+ $start,
+ $viewer);
+
+ if ($this->rangeEnd) {
+ $end = $this->rangeEnd;
+ $instance_count = $sequence_start;
+
+ while ($date < $end) {
+ $instance_count++;
+ $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
+ $datetime->modify($modify_key);
+ $date = $datetime->format('U');
+ }
+ } else {
+ $instance_count = $this->getRawResultLimit();
+ }
+
+ $sequence_start = max(1, $sequence_start);
+
+ $max_sequence = $sequence_start + $instance_count;
+ for ($index = $sequence_start; $index < $max_sequence; $index++) {
+ $events[] = $event->generateNthGhost($index, $viewer);
+ }
+ }
+ }
+
return $events;
}
@@ -122,7 +187,7 @@
if ($this->rangeBegin) {
$where[] = qsprintf(
$conn_r,
- 'event.dateTo >= %d',
+ 'event.dateTo >= %d OR event.isRecurring = 1',
$this->rangeBegin);
}
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
@@ -50,7 +50,8 @@
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
- $query = id(new PhabricatorCalendarEventQuery());
+ $query = id(new PhabricatorCalendarEventQuery())
+ ->setGenerateGhosts(true);
$viewer = $this->requireViewer();
$timezone = new DateTimeZone($viewer->getTimezoneIdentifier());
@@ -132,7 +133,8 @@
$query->withCreatorPHIDs($creator_phids);
}
- $is_cancelled = $saved->getParameter('isCancelled');
+ $is_cancelled = $saved->getParameter('isCancelled', 'active');
+
switch ($is_cancelled) {
case 'active':
$query->withIsCancelled(false);
@@ -305,14 +307,14 @@
$viewer = $this->requireViewer();
$list = new PHUIObjectItemListView();
foreach ($events as $event) {
- $href = '/E'.$event->getID();
+ // $href = '/E'.$event->getID();
$from = phabricator_datetime($event->getDateFrom(), $viewer);
$to = phabricator_datetime($event->getDateTo(), $viewer);
$creator_handle = $handles[$event->getUserPHID()];
$item = id(new PHUIObjectItemView())
->setHeader($event->getName())
- ->setHref($href)
+ ->setHref($event->getURI())
->addByline(pht('Creator: %s', $creator_handle->renderLink()))
->addAttribute(pht('From %s to %s', $from, $to))
->addAttribute(id(new PhutilUTF8StringTruncator())
@@ -371,7 +373,8 @@
$event->setUserPHID($status->getUserPHID());
$event->setDescription(pht('%s (%s)', $name_text, $status_text));
$event->setName($status_text);
- $event->setEventID($status->getID());
+ $event->setURI($status->getURI());
+ // $event->setEventID($status->getID());
$event->setViewerIsInvited($viewer_is_invited);
$month_view->addEvent($event);
}
@@ -423,7 +426,7 @@
$event->setViewerIsInvited($viewer_is_invited);
$event->setName($status->getName());
- $event->setURI('/'.$status->getMonogram());
+ $event->setURI($status->getURI());
$day_view->addEvent($event);
}
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
@@ -20,6 +20,14 @@
protected $icon;
protected $mailKey;
+ protected $isRecurring = 0;
+ protected $recurrenceFrequency = array();
+ protected $recurrenceEndDate;
+
+ private $isGhostEvent = false;
+ protected $instanceOfEventPHID;
+ protected $sequenceIndex;
+
protected $viewPolicy;
protected $editPolicy;
@@ -36,8 +44,13 @@
->withClasses(array('PhabricatorCalendarApplication'))
->executeOne();
+ $view_policy = null;
+ $is_recurring = 0;
+
if ($mode == 'public') {
$view_policy = PhabricatorPolicies::getMostOpenPolicy();
+ } else if ($mode == 'recurring') {
+ $is_recurring = true;
} else {
$view_policy = $actor->getPHID();
}
@@ -46,6 +59,7 @@
->setUserPHID($actor->getPHID())
->setIsCancelled(0)
->setIsAllDay(0)
+ ->setIsRecurring($is_recurring)
->setIcon(self::DEFAULT_ICON)
->setViewPolicy($view_policy)
->setEditPolicy($actor->getPHID())
@@ -180,12 +194,19 @@
'isAllDay' => 'bool',
'icon' => 'text32',
'mailKey' => 'bytes20',
+ 'isRecurring' => 'bool',
+ 'recurrenceEndDate' => 'epoch?',
+ 'instanceOfEventPHID' => 'phid?',
+ 'sequenceIndex' => 'uint32?',
),
self::CONFIG_KEY_SCHEMA => array(
'userPHID_dateFrom' => array(
'columns' => array('userPHID', 'dateTo'),
),
),
+ self::CONFIG_SERIALIZATION => array(
+ 'recurrenceFrequency' => self::SERIALIZATION_JSON,
+ ),
) + parent::getConfiguration();
}
@@ -238,6 +259,69 @@
return true;
}
+ public function getIsGhostEvent() {
+ return $this->isGhostEvent;
+ }
+
+ public function setIsGhostEvent($is_ghost_event) {
+ $this->isGhostEvent = $is_ghost_event;
+ return $this;
+ }
+
+ public function generateNthGhost(
+ $sequence_index,
+ PhabricatorUser $actor) {
+
+ $frequency = $this->getFrequencyUnit();
+ $modify_key = '+'.$sequence_index.' '.$frequency;
+
+ $date = $this->dateFrom;
+ $date_time = PhabricatorTime::getDateTimeFromEpoch($date, $actor);
+ $date_time->modify($modify_key);
+ $date = $date_time->format('U');
+
+ $duration = $this->dateTo - $this->dateFrom;
+
+ $edit_policy = PhabricatorPolicies::POLICY_NOONE;
+
+ $ghost_event = id(clone $this)
+ ->setIsGhostEvent(true)
+ ->setDateFrom($date)
+ ->setDateTo($date + $duration)
+ ->setIsRecurring(false)
+ ->setRecurrenceFrequency(null)
+ ->setInstanceOfEventPHID($this->getPHID())
+ ->setSequenceIndex($sequence_index)
+ ->setEditPolicy($edit_policy);
+
+ return $ghost_event;
+ }
+
+ public function getFrequencyUnit() {
+ $frequency = idx($this->recurrenceFrequency, 'rule');
+
+ switch ($frequency) {
+ case 'daily':
+ return 'day';
+ case 'weekly':
+ return 'week';
+ case 'monthly':
+ return 'month';
+ case 'yearly':
+ return 'yearly';
+ default:
+ return 'day';
+ }
+ }
+
+ public function getURI() {
+ $uri = '/'.$this->getMonogram();
+ if ($this->isGhostEvent) {
+ $uri = $uri.'/'.$this->sequenceIndex;
+ }
+ return $uri;
+ }
+
/* -( Markup Interface )--------------------------------------------------- */
@@ -307,6 +391,9 @@
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
// The owner of a task can always view and edit it.
$user_phid = $this->getUserPHID();
+ if ($this->isGhostEvent) {
+ return false;
+ }
if ($user_phid) {
$viewer_phid = $viewer->getPHID();
if ($viewer_phid == $user_phid) {
@@ -328,7 +415,8 @@
public function describeAutomaticCapability($capability) {
return pht('The owner of an event can always view and edit it,
- and invitees can always view it.');
+ and invitees can always view it, except if the event is an
+ instance of a recurring event.');
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php
--- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php
+++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php
@@ -12,6 +12,13 @@
const TYPE_ICON = 'calendar.icon';
const TYPE_INVITE = 'calendar.invite';
+ const TYPE_RECURRING = 'calendar.recurring';
+ const TYPE_FREQUENCY = 'calendar.frequency';
+ const TYPE_RECURRENCE_END_DATE = 'calendar.recurrenceenddate';
+
+ const TYPE_INSTANCE_OF_EVENT = 'calendar.instanceofevent';
+ const TYPE_SEQUENCE_INDEX = 'calendar.sequenceindex';
+
const MAILTAG_RESCHEDULE = 'calendar-reschedule';
const MAILTAG_CONTENT = 'calendar-content';
const MAILTAG_OTHER = 'calendar-other';
@@ -38,6 +45,11 @@
case self::TYPE_DESCRIPTION:
case self::TYPE_CANCEL:
case self::TYPE_ALL_DAY:
+ case self::TYPE_RECURRING:
+ case self::TYPE_FREQUENCY:
+ case self::TYPE_RECURRENCE_END_DATE:
+ case self::TYPE_INSTANCE_OF_EVENT:
+ case self::TYPE_SEQUENCE_INDEX:
$phids[] = $this->getObjectPHID();
break;
case self::TYPE_INVITE:
@@ -60,6 +72,11 @@
case self::TYPE_CANCEL:
case self::TYPE_ALL_DAY:
case self::TYPE_INVITE:
+ case self::TYPE_RECURRING:
+ case self::TYPE_FREQUENCY:
+ case self::TYPE_RECURRENCE_END_DATE:
+ case self::TYPE_INSTANCE_OF_EVENT:
+ case self::TYPE_SEQUENCE_INDEX:
return ($old === null);
}
return parent::shouldHide();
@@ -75,6 +92,11 @@
case self::TYPE_DESCRIPTION:
case self::TYPE_ALL_DAY:
case self::TYPE_CANCEL:
+ case self::TYPE_RECURRING:
+ case self::TYPE_FREQUENCY:
+ case self::TYPE_RECURRENCE_END_DATE:
+ case self::TYPE_INSTANCE_OF_EVENT:
+ case self::TYPE_SEQUENCE_INDEX:
return 'fa-pencil';
break;
case self::TYPE_INVITE:
@@ -231,6 +253,12 @@
}
}
return $text;
+ case self::TYPE_RECURRING:
+ case self::TYPE_FREQUENCY:
+ case self::TYPE_RECURRENCE_END_DATE:
+ case self::TYPE_INSTANCE_OF_EVENT:
+ case self::TYPE_SEQUENCE_INDEX:
+ return pht('Recurring event has been updated');
}
return parent::getTitle();
}
@@ -411,6 +439,12 @@
}
}
return $text;
+ case self::TYPE_RECURRING:
+ case self::TYPE_FREQUENCY:
+ case self::TYPE_RECURRENCE_END_DATE:
+ case self::TYPE_INSTANCE_OF_EVENT:
+ case self::TYPE_SEQUENCE_INDEX:
+ return pht('Recurring event has been updated');
}
return parent::getTitleForFeed();
diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php
--- a/src/view/phui/calendar/PHUICalendarListView.php
+++ b/src/view/phui/calendar/PHUICalendarListView.php
@@ -89,7 +89,7 @@
$content = javelin_tag(
'a',
array(
- 'href' => '/E'.$event->getEventID(),
+ 'href' => $event->getURI(),
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $tip,
diff --git a/webroot/rsrc/css/phui/phui-form.css b/webroot/rsrc/css/phui/phui-form.css
--- a/webroot/rsrc/css/phui/phui-form.css
+++ b/webroot/rsrc/css/phui/phui-form.css
@@ -140,7 +140,7 @@
color: {$lightgreytext};
}
-select[disabled="disabled"],
-input[disabled="disabled"] {
+select[disabled],
+input[disabled] {
opacity: 0.5;
}
diff --git a/webroot/rsrc/js/application/calendar/behavior-recurring-edit.js b/webroot/rsrc/js/application/calendar/behavior-recurring-edit.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/application/calendar/behavior-recurring-edit.js
@@ -0,0 +1,14 @@
+/**
+ * @provides javelin-behavior-recurring-edit
+ */
+
+
+JX.behavior('recurring-edit', function(config) {
+ var checkbox = JX.$(config.isRecurring);
+ JX.DOM.listen(checkbox, 'change', null, function() {
+ var frequency = JX.$(config.frequency);
+
+ frequency.disabled = checkbox.checked ? false : true;
+ });
+
+});

File Metadata

Mime Type
text/plain
Expires
Tue, Feb 11, 11:11 AM (11 h, 59 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7122651
Default Alt Text
D13039.diff (28 KB)

Event Timeline