Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php
12256 views
1
<?php
2
3
final class PhabricatorCalendarEventEditEngine
4
extends PhabricatorEditEngine {
5
6
const ENGINECONST = 'calendar.event';
7
8
private $rawTransactions;
9
private $seriesEditMode = self::MODE_THIS;
10
11
const MODE_THIS = 'this';
12
const MODE_FUTURE = 'future';
13
14
public function setSeriesEditMode($series_edit_mode) {
15
$this->seriesEditMode = $series_edit_mode;
16
return $this;
17
}
18
19
public function getSeriesEditMode() {
20
return $this->seriesEditMode;
21
}
22
23
public function getEngineName() {
24
return pht('Calendar Events');
25
}
26
27
public function getSummaryHeader() {
28
return pht('Configure Calendar Event Forms');
29
}
30
31
public function getSummaryText() {
32
return pht('Configure how users create and edit events.');
33
}
34
35
public function getEngineApplicationClass() {
36
return 'PhabricatorCalendarApplication';
37
}
38
39
protected function newEditableObject() {
40
return PhabricatorCalendarEvent::initializeNewCalendarEvent(
41
$this->getViewer());
42
}
43
44
protected function newObjectQuery() {
45
return new PhabricatorCalendarEventQuery();
46
}
47
48
protected function getObjectCreateTitleText($object) {
49
return pht('Create New Event');
50
}
51
52
protected function getObjectEditTitleText($object) {
53
return pht('Edit Event: %s', $object->getName());
54
}
55
56
protected function getObjectEditShortText($object) {
57
return $object->getMonogram();
58
}
59
60
protected function getObjectCreateShortText() {
61
return pht('Create Event');
62
}
63
64
protected function getObjectName() {
65
return pht('Event');
66
}
67
68
protected function getObjectViewURI($object) {
69
return $object->getURI();
70
}
71
72
protected function getEditorURI() {
73
return $this->getApplication()->getApplicationURI('event/edit/');
74
}
75
76
protected function buildCustomEditFields($object) {
77
$viewer = $this->getViewer();
78
79
if ($this->getIsCreate()) {
80
$invitee_phids = array($viewer->getPHID());
81
} else {
82
$invitee_phids = $object->getInviteePHIDsForEdit();
83
}
84
85
$frequency_map = PhabricatorCalendarEvent::getFrequencyMap();
86
$frequency_options = ipull($frequency_map, 'label');
87
88
$rrule = $object->newRecurrenceRule();
89
if ($rrule) {
90
$frequency = $rrule->getFrequency();
91
} else {
92
$frequency = null;
93
}
94
95
// At least for now, just hide "Invitees" when editing all future events.
96
// This may eventually deserve a more nuanced approach.
97
$is_future = ($this->getSeriesEditMode() == self::MODE_FUTURE);
98
99
$fields = array(
100
id(new PhabricatorTextEditField())
101
->setKey('name')
102
->setLabel(pht('Name'))
103
->setDescription(pht('Name of the event.'))
104
->setIsRequired(true)
105
->setTransactionType(
106
PhabricatorCalendarEventNameTransaction::TRANSACTIONTYPE)
107
->setConduitDescription(pht('Rename the event.'))
108
->setConduitTypeDescription(pht('New event name.'))
109
->setValue($object->getName()),
110
id(new PhabricatorBoolEditField())
111
->setIsLockable(false)
112
->setIsDefaultable(false)
113
->setKey('isAllDay')
114
->setOptions(pht('Normal Event'), pht('All Day Event'))
115
->setAsCheckbox(true)
116
->setTransactionType(
117
PhabricatorCalendarEventAllDayTransaction::TRANSACTIONTYPE)
118
->setDescription(pht('Marks this as an all day event.'))
119
->setConduitDescription(pht('Make the event an all day event.'))
120
->setConduitTypeDescription(pht('Mark the event as an all day event.'))
121
->setValue($object->getIsAllDay()),
122
id(new PhabricatorEpochEditField())
123
->setKey('start')
124
->setLabel(pht('Start'))
125
->setIsLockable(false)
126
->setIsDefaultable(false)
127
->setTransactionType(
128
PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE)
129
->setDescription(pht('Start time of the event.'))
130
->setConduitDescription(pht('Change the start time of the event.'))
131
->setConduitTypeDescription(pht('New event start time.'))
132
->setValue($object->getStartDateTimeEpoch()),
133
id(new PhabricatorEpochEditField())
134
->setKey('end')
135
->setLabel(pht('End'))
136
->setIsLockable(false)
137
->setIsDefaultable(false)
138
->setTransactionType(
139
PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE)
140
->setDescription(pht('End time of the event.'))
141
->setConduitDescription(pht('Change the end time of the event.'))
142
->setConduitTypeDescription(pht('New event end time.'))
143
->setValue($object->newEndDateTimeForEdit()->getEpoch()),
144
id(new PhabricatorBoolEditField())
145
->setKey('cancelled')
146
->setOptions(pht('Active'), pht('Cancelled'))
147
->setLabel(pht('Cancelled'))
148
->setDescription(pht('Cancel the event.'))
149
->setTransactionType(
150
PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE)
151
->setIsFormField(false)
152
->setConduitDescription(pht('Cancel or restore the event.'))
153
->setConduitTypeDescription(pht('True to cancel the event.'))
154
->setValue($object->getIsCancelled()),
155
id(new PhabricatorUsersEditField())
156
->setIsLockable(false)
157
->setIsDefaultable(false)
158
->setKey('hostPHID')
159
->setAliases(array('host'))
160
->setLabel(pht('Host'))
161
->setDescription(pht('Host of the event.'))
162
->setTransactionType(
163
PhabricatorCalendarEventHostTransaction::TRANSACTIONTYPE)
164
->setIsFormField(!$this->getIsCreate())
165
->setConduitDescription(pht('Change the host of the event.'))
166
->setConduitTypeDescription(pht('New event host.'))
167
->setSingleValue($object->getHostPHID()),
168
id(new PhabricatorDatasourceEditField())
169
->setIsLockable(false)
170
->setIsDefaultable(false)
171
->setIsHidden($is_future)
172
->setKey('inviteePHIDs')
173
->setAliases(array('invite', 'invitee', 'invitees', 'inviteePHID'))
174
->setLabel(pht('Invitees'))
175
->setDatasource(new PhabricatorMetaMTAMailableDatasource())
176
->setTransactionType(
177
PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE)
178
->setDescription(pht('Users invited to the event.'))
179
->setConduitDescription(pht('Change invited users.'))
180
->setConduitTypeDescription(pht('New event invitees.'))
181
->setValue($invitee_phids)
182
->setCommentActionLabel(pht('Change Invitees')),
183
id(new PhabricatorRemarkupEditField())
184
->setKey('description')
185
->setLabel(pht('Description'))
186
->setDescription(pht('Description of the event.'))
187
->setTransactionType(
188
PhabricatorCalendarEventDescriptionTransaction::TRANSACTIONTYPE)
189
->setConduitDescription(pht('Update the event description.'))
190
->setConduitTypeDescription(pht('New event description.'))
191
->setValue($object->getDescription()),
192
id(new PhabricatorIconSetEditField())
193
->setKey('icon')
194
->setLabel(pht('Icon'))
195
->setIconSet(new PhabricatorCalendarIconSet())
196
->setTransactionType(
197
PhabricatorCalendarEventIconTransaction::TRANSACTIONTYPE)
198
->setDescription(pht('Event icon.'))
199
->setConduitDescription(pht('Change the event icon.'))
200
->setConduitTypeDescription(pht('New event icon.'))
201
->setValue($object->getIcon()),
202
203
// NOTE: We're being a little sneaky here. This field is hidden and
204
// always has the value "true", so it makes the event recurring when you
205
// submit a form which contains the field. Then we put the the field on
206
// the "recurring" page in the "Make Recurring" dialog to simplify the
207
// workflow. This is still normal, explicit field from the perspective
208
// of the API.
209
210
id(new PhabricatorBoolEditField())
211
->setIsHidden(true)
212
->setIsLockable(false)
213
->setIsDefaultable(false)
214
->setKey('isRecurring')
215
->setLabel(pht('Recurring'))
216
->setOptions(pht('One-Time Event'), pht('Recurring Event'))
217
->setTransactionType(
218
PhabricatorCalendarEventRecurringTransaction::TRANSACTIONTYPE)
219
->setDescription(pht('One time or recurring event.'))
220
->setConduitDescription(pht('Make the event recurring.'))
221
->setConduitTypeDescription(pht('Mark the event as a recurring event.'))
222
->setValue(true),
223
id(new PhabricatorSelectEditField())
224
->setIsLockable(false)
225
->setIsDefaultable(false)
226
->setKey('frequency')
227
->setLabel(pht('Frequency'))
228
->setOptions($frequency_options)
229
->setTransactionType(
230
PhabricatorCalendarEventFrequencyTransaction::TRANSACTIONTYPE)
231
->setDescription(pht('Recurring event frequency.'))
232
->setConduitDescription(pht('Change the event frequency.'))
233
->setConduitTypeDescription(pht('New event frequency.'))
234
->setValue($frequency),
235
id(new PhabricatorEpochEditField())
236
->setIsLockable(false)
237
->setIsDefaultable(false)
238
->setAllowNull(true)
239
->setHideTime($object->getIsAllDay())
240
->setKey('until')
241
->setLabel(pht('Repeat Until'))
242
->setTransactionType(
243
PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE)
244
->setDescription(pht('Last instance of the event.'))
245
->setConduitDescription(pht('Change when the event repeats until.'))
246
->setConduitTypeDescription(pht('New final event time.'))
247
->setValue($object->getUntilDateTimeEpoch()),
248
);
249
250
return $fields;
251
}
252
253
protected function willBuildEditForm($object, array $fields) {
254
$all_day_field = idx($fields, 'isAllDay');
255
$start_field = idx($fields, 'start');
256
$end_field = idx($fields, 'end');
257
258
if ($all_day_field) {
259
$is_all_day = $all_day_field->getValueForTransaction();
260
261
$control_ids = array();
262
if ($start_field) {
263
$control_ids[] = $start_field->getControlID();
264
}
265
if ($end_field) {
266
$control_ids[] = $end_field->getControlID();
267
}
268
269
Javelin::initBehavior(
270
'event-all-day',
271
array(
272
'allDayID' => $all_day_field->getControlID(),
273
'controlIDs' => $control_ids,
274
));
275
276
} else {
277
$is_all_day = $object->getIsAllDay();
278
}
279
280
if ($is_all_day) {
281
if ($start_field) {
282
$start_field->setHideTime(true);
283
}
284
285
if ($end_field) {
286
$end_field->setHideTime(true);
287
}
288
}
289
290
return $fields;
291
}
292
293
protected function newPages($object) {
294
// Controls for event recurrence behavior go on a separate page which we
295
// put in a dialog. This simplifies event creation in the common case.
296
297
return array(
298
id(new PhabricatorEditPage())
299
->setKey('core')
300
->setLabel(pht('Core'))
301
->setIsDefault(true),
302
id(new PhabricatorEditPage())
303
->setKey('recurring')
304
->setLabel(pht('Recurrence'))
305
->setFieldKeys(
306
array(
307
'isRecurring',
308
'frequency',
309
'until',
310
)),
311
);
312
}
313
314
protected function willApplyTransactions($object, array $xactions) {
315
$viewer = $this->getViewer();
316
317
$is_parent = $object->isParentEvent();
318
$is_child = $object->isChildEvent();
319
$is_future = ($this->getSeriesEditMode() === self::MODE_FUTURE);
320
321
// Figure out which transactions we can apply to the whole series of events.
322
// Some transactions (like comments) can never be bulk applied.
323
$inherited_xactions = array();
324
foreach ($xactions as $xaction) {
325
$modular_type = $xaction->getModularType();
326
if (!($modular_type instanceof PhabricatorCalendarEventTransactionType)) {
327
continue;
328
}
329
330
$inherited_edit = $modular_type->isInheritedEdit();
331
if ($inherited_edit) {
332
$inherited_xactions[] = $xaction;
333
}
334
}
335
$this->rawTransactions = $this->cloneTransactions($inherited_xactions);
336
337
$must_fork = ($is_child && $is_future) ||
338
($is_parent && !$is_future);
339
340
// We don't need to fork when editing a parent event if none of the edits
341
// can transfer to child events. For example, commenting on a parent is
342
// fine.
343
if ($is_parent && !$is_future) {
344
if (!$inherited_xactions) {
345
$must_fork = false;
346
}
347
}
348
349
if ($must_fork) {
350
$fork_target = $object->loadForkTarget($viewer);
351
if ($fork_target) {
352
$fork_xaction = id(new PhabricatorCalendarEventTransaction())
353
->setTransactionType(
354
PhabricatorCalendarEventForkTransaction::TRANSACTIONTYPE)
355
->setNewValue(true);
356
357
if ($fork_target->getPHID() == $object->getPHID()) {
358
// We're forking the object itself, so just slip it into the
359
// transactions we're going to apply.
360
array_unshift($xactions, $fork_xaction);
361
} else {
362
// Otherwise, we're forking a different object, so we have to
363
// apply that separately.
364
$this->applyTransactions($fork_target, array($fork_xaction));
365
}
366
}
367
}
368
369
return $xactions;
370
}
371
372
protected function didApplyTransactions($object, array $xactions) {
373
$viewer = $this->getViewer();
374
375
if ($this->getSeriesEditMode() !== self::MODE_FUTURE) {
376
return;
377
}
378
379
$targets = $object->loadFutureEvents($viewer);
380
if (!$targets) {
381
return;
382
}
383
384
foreach ($targets as $target) {
385
$apply = $this->cloneTransactions($this->rawTransactions);
386
$this->applyTransactions($target, $apply);
387
}
388
}
389
390
private function applyTransactions($target, array $xactions) {
391
$viewer = $this->getViewer();
392
393
// TODO: This isn't the most accurate source we could use, but this mode
394
// is web-only for now.
395
$content_source = PhabricatorContentSource::newForSource(
396
PhabricatorWebContentSource::SOURCECONST);
397
398
$editor = id(new PhabricatorCalendarEventEditor())
399
->setActor($viewer)
400
->setContentSource($content_source)
401
->setContinueOnNoEffect(true)
402
->setContinueOnMissingFields(true);
403
404
try {
405
$editor->applyTransactions($target, $xactions);
406
} catch (PhabricatorApplicationTransactionValidationException $ex) {
407
// Just ignore any issues we run into.
408
}
409
}
410
411
private function cloneTransactions(array $xactions) {
412
$result = array();
413
foreach ($xactions as $xaction) {
414
$result[] = clone $xaction;
415
}
416
return $result;
417
}
418
419
}
420
421