mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-28 16:30:59 +01:00
DRAFT Add db columns for recurring events
Summary: Ref T2896, DRAFT Add db columns for recurring events Test Plan: Open event, confirm it still works. Reviewers: chad, #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: btrahan, Korvin, epriestley Maniphest Tasks: T2896 Differential Revision: https://secure.phabricator.com/D13039
This commit is contained in:
parent
1aa8bc319b
commit
59f0e8f950
13 changed files with 354 additions and 30 deletions
|
@ -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 @@ return array(
|
|||
'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 @@ return array(
|
|||
'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 @@ return array(
|
|||
'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 @@ return array(
|
|||
'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',
|
||||
|
|
|
@ -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 = '';
|
|
@ -40,7 +40,8 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication {
|
|||
|
||||
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+)/)?)?'
|
||||
|
|
|
@ -21,9 +21,11 @@ final class PhabricatorCalendarEventEditController
|
|||
$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 @@ final class PhabricatorCalendarEventEditController
|
|||
$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 @@ final class PhabricatorCalendarEventEditController
|
|||
$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 @@ final class PhabricatorCalendarEventEditController
|
|||
$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');
|
||||
|
||||
|
@ -152,6 +157,16 @@ final class PhabricatorCalendarEventEditController
|
|||
PhabricatorCalendarEventTransaction::TYPE_NAME)
|
||||
->setNewValue($name);
|
||||
|
||||
$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)
|
||||
|
@ -233,18 +248,44 @@ final class PhabricatorCalendarEventEditController
|
|||
}
|
||||
}
|
||||
|
||||
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 @@ final class PhabricatorCalendarEventEditController
|
|||
->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)
|
||||
|
|
|
@ -17,6 +17,8 @@ final class PhabricatorCalendarEventViewController
|
|||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$sequence = $request->getURIData('sequence');
|
||||
|
||||
$event = id(new PhabricatorCalendarEventQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
|
@ -25,6 +27,12 @@ final class PhabricatorCalendarEventViewController
|
|||
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 @@ final class PhabricatorCalendarEventViewController
|
|||
$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 @@ final class PhabricatorCalendarEventViewController
|
|||
phabricator_datetime($event->getDateTo(), $viewer));
|
||||
}
|
||||
|
||||
if ($event->getIsRecurring()) {
|
||||
$properties->addProperty(
|
||||
pht('Recurs'),
|
||||
idx($event->getRecurrenceFrequency(), 'rule'));
|
||||
}
|
||||
|
||||
$properties->addProperty(
|
||||
pht('Host'),
|
||||
$viewer->renderHandle($event->getUserPHID()));
|
||||
|
|
|
@ -23,6 +23,12 @@ final class PhabricatorCalendarEventEditor
|
|||
$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 @@ final class PhabricatorCalendarEventEditor
|
|||
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 @@ final class PhabricatorCalendarEventEditor
|
|||
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 @@ final class PhabricatorCalendarEventEditor
|
|||
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 @@ final class PhabricatorCalendarEventEditor
|
|||
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 @@ final class PhabricatorCalendarEventEditor
|
|||
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:
|
||||
|
|
|
@ -11,6 +11,13 @@ final class PhabricatorCalendarEventQuery
|
|||
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 @@ final class PhabricatorCalendarEventQuery
|
|||
protected function loadPage() {
|
||||
$table = new PhabricatorCalendarEvent();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$data = queryfx_all(
|
||||
$conn_r,
|
||||
|
@ -86,6 +94,63 @@ final class PhabricatorCalendarEventQuery
|
|||
$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 @@ final class PhabricatorCalendarEventQuery
|
|||
if ($this->rangeBegin) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'event.dateTo >= %d',
|
||||
'event.dateTo >= %d OR event.isRecurring = 1',
|
||||
$this->rangeBegin);
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,8 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
}
|
||||
|
||||
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 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
$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 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
$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 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
$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 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
$event->setViewerIsInvited($viewer_is_invited);
|
||||
|
||||
$event->setName($status->getName());
|
||||
$event->setURI('/'.$status->getMonogram());
|
||||
$event->setURI($status->getURI());
|
||||
$day_view->addEvent($event);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,14 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
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 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
->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 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
->setUserPHID($actor->getPHID())
|
||||
->setIsCancelled(0)
|
||||
->setIsAllDay(0)
|
||||
->setIsRecurring($is_recurring)
|
||||
->setIcon(self::DEFAULT_ICON)
|
||||
->setViewPolicy($view_policy)
|
||||
->setEditPolicy($actor->getPHID())
|
||||
|
@ -180,12 +194,19 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
'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 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
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 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
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 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
|
||||
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 )------------------------- */
|
||||
|
|
|
@ -12,6 +12,13 @@ final class PhabricatorCalendarEventTransaction
|
|||
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 @@ final class PhabricatorCalendarEventTransaction
|
|||
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 @@ final class PhabricatorCalendarEventTransaction
|
|||
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 @@ final class PhabricatorCalendarEventTransaction
|
|||
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 @@ final class PhabricatorCalendarEventTransaction
|
|||
}
|
||||
}
|
||||
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 @@ final class PhabricatorCalendarEventTransaction
|
|||
}
|
||||
}
|
||||
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();
|
||||
|
|
|
@ -89,7 +89,7 @@ final class PHUICalendarListView extends AphrontTagView {
|
|||
$content = javelin_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/E'.$event->getEventID(),
|
||||
'href' => $event->getURI(),
|
||||
'sigil' => 'has-tooltip',
|
||||
'meta' => array(
|
||||
'tip' => $tip,
|
||||
|
|
|
@ -140,7 +140,7 @@ textarea::-webkit-input-placeholder {
|
|||
color: {$lightgreytext};
|
||||
}
|
||||
|
||||
select[disabled="disabled"],
|
||||
input[disabled="disabled"] {
|
||||
select[disabled],
|
||||
input[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in a new issue