Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14908165
D13039.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
28 KB
Referenced Files
None
Subscribers
None
D13039.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D13039: DRAFT Add db columns for recurring events
Attached
Detach File
Event Timeline
Log In to Comment