Path: blob/master/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php
12256 views
<?php12final class PhabricatorCalendarEventEditEngine3extends PhabricatorEditEngine {45const ENGINECONST = 'calendar.event';67private $rawTransactions;8private $seriesEditMode = self::MODE_THIS;910const MODE_THIS = 'this';11const MODE_FUTURE = 'future';1213public function setSeriesEditMode($series_edit_mode) {14$this->seriesEditMode = $series_edit_mode;15return $this;16}1718public function getSeriesEditMode() {19return $this->seriesEditMode;20}2122public function getEngineName() {23return pht('Calendar Events');24}2526public function getSummaryHeader() {27return pht('Configure Calendar Event Forms');28}2930public function getSummaryText() {31return pht('Configure how users create and edit events.');32}3334public function getEngineApplicationClass() {35return 'PhabricatorCalendarApplication';36}3738protected function newEditableObject() {39return PhabricatorCalendarEvent::initializeNewCalendarEvent(40$this->getViewer());41}4243protected function newObjectQuery() {44return new PhabricatorCalendarEventQuery();45}4647protected function getObjectCreateTitleText($object) {48return pht('Create New Event');49}5051protected function getObjectEditTitleText($object) {52return pht('Edit Event: %s', $object->getName());53}5455protected function getObjectEditShortText($object) {56return $object->getMonogram();57}5859protected function getObjectCreateShortText() {60return pht('Create Event');61}6263protected function getObjectName() {64return pht('Event');65}6667protected function getObjectViewURI($object) {68return $object->getURI();69}7071protected function getEditorURI() {72return $this->getApplication()->getApplicationURI('event/edit/');73}7475protected function buildCustomEditFields($object) {76$viewer = $this->getViewer();7778if ($this->getIsCreate()) {79$invitee_phids = array($viewer->getPHID());80} else {81$invitee_phids = $object->getInviteePHIDsForEdit();82}8384$frequency_map = PhabricatorCalendarEvent::getFrequencyMap();85$frequency_options = ipull($frequency_map, 'label');8687$rrule = $object->newRecurrenceRule();88if ($rrule) {89$frequency = $rrule->getFrequency();90} else {91$frequency = null;92}9394// At least for now, just hide "Invitees" when editing all future events.95// This may eventually deserve a more nuanced approach.96$is_future = ($this->getSeriesEditMode() == self::MODE_FUTURE);9798$fields = array(99id(new PhabricatorTextEditField())100->setKey('name')101->setLabel(pht('Name'))102->setDescription(pht('Name of the event.'))103->setIsRequired(true)104->setTransactionType(105PhabricatorCalendarEventNameTransaction::TRANSACTIONTYPE)106->setConduitDescription(pht('Rename the event.'))107->setConduitTypeDescription(pht('New event name.'))108->setValue($object->getName()),109id(new PhabricatorBoolEditField())110->setIsLockable(false)111->setIsDefaultable(false)112->setKey('isAllDay')113->setOptions(pht('Normal Event'), pht('All Day Event'))114->setAsCheckbox(true)115->setTransactionType(116PhabricatorCalendarEventAllDayTransaction::TRANSACTIONTYPE)117->setDescription(pht('Marks this as an all day event.'))118->setConduitDescription(pht('Make the event an all day event.'))119->setConduitTypeDescription(pht('Mark the event as an all day event.'))120->setValue($object->getIsAllDay()),121id(new PhabricatorEpochEditField())122->setKey('start')123->setLabel(pht('Start'))124->setIsLockable(false)125->setIsDefaultable(false)126->setTransactionType(127PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE)128->setDescription(pht('Start time of the event.'))129->setConduitDescription(pht('Change the start time of the event.'))130->setConduitTypeDescription(pht('New event start time.'))131->setValue($object->getStartDateTimeEpoch()),132id(new PhabricatorEpochEditField())133->setKey('end')134->setLabel(pht('End'))135->setIsLockable(false)136->setIsDefaultable(false)137->setTransactionType(138PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE)139->setDescription(pht('End time of the event.'))140->setConduitDescription(pht('Change the end time of the event.'))141->setConduitTypeDescription(pht('New event end time.'))142->setValue($object->newEndDateTimeForEdit()->getEpoch()),143id(new PhabricatorBoolEditField())144->setKey('cancelled')145->setOptions(pht('Active'), pht('Cancelled'))146->setLabel(pht('Cancelled'))147->setDescription(pht('Cancel the event.'))148->setTransactionType(149PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE)150->setIsFormField(false)151->setConduitDescription(pht('Cancel or restore the event.'))152->setConduitTypeDescription(pht('True to cancel the event.'))153->setValue($object->getIsCancelled()),154id(new PhabricatorUsersEditField())155->setIsLockable(false)156->setIsDefaultable(false)157->setKey('hostPHID')158->setAliases(array('host'))159->setLabel(pht('Host'))160->setDescription(pht('Host of the event.'))161->setTransactionType(162PhabricatorCalendarEventHostTransaction::TRANSACTIONTYPE)163->setIsFormField(!$this->getIsCreate())164->setConduitDescription(pht('Change the host of the event.'))165->setConduitTypeDescription(pht('New event host.'))166->setSingleValue($object->getHostPHID()),167id(new PhabricatorDatasourceEditField())168->setIsLockable(false)169->setIsDefaultable(false)170->setIsHidden($is_future)171->setKey('inviteePHIDs')172->setAliases(array('invite', 'invitee', 'invitees', 'inviteePHID'))173->setLabel(pht('Invitees'))174->setDatasource(new PhabricatorMetaMTAMailableDatasource())175->setTransactionType(176PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE)177->setDescription(pht('Users invited to the event.'))178->setConduitDescription(pht('Change invited users.'))179->setConduitTypeDescription(pht('New event invitees.'))180->setValue($invitee_phids)181->setCommentActionLabel(pht('Change Invitees')),182id(new PhabricatorRemarkupEditField())183->setKey('description')184->setLabel(pht('Description'))185->setDescription(pht('Description of the event.'))186->setTransactionType(187PhabricatorCalendarEventDescriptionTransaction::TRANSACTIONTYPE)188->setConduitDescription(pht('Update the event description.'))189->setConduitTypeDescription(pht('New event description.'))190->setValue($object->getDescription()),191id(new PhabricatorIconSetEditField())192->setKey('icon')193->setLabel(pht('Icon'))194->setIconSet(new PhabricatorCalendarIconSet())195->setTransactionType(196PhabricatorCalendarEventIconTransaction::TRANSACTIONTYPE)197->setDescription(pht('Event icon.'))198->setConduitDescription(pht('Change the event icon.'))199->setConduitTypeDescription(pht('New event icon.'))200->setValue($object->getIcon()),201202// NOTE: We're being a little sneaky here. This field is hidden and203// always has the value "true", so it makes the event recurring when you204// submit a form which contains the field. Then we put the the field on205// the "recurring" page in the "Make Recurring" dialog to simplify the206// workflow. This is still normal, explicit field from the perspective207// of the API.208209id(new PhabricatorBoolEditField())210->setIsHidden(true)211->setIsLockable(false)212->setIsDefaultable(false)213->setKey('isRecurring')214->setLabel(pht('Recurring'))215->setOptions(pht('One-Time Event'), pht('Recurring Event'))216->setTransactionType(217PhabricatorCalendarEventRecurringTransaction::TRANSACTIONTYPE)218->setDescription(pht('One time or recurring event.'))219->setConduitDescription(pht('Make the event recurring.'))220->setConduitTypeDescription(pht('Mark the event as a recurring event.'))221->setValue(true),222id(new PhabricatorSelectEditField())223->setIsLockable(false)224->setIsDefaultable(false)225->setKey('frequency')226->setLabel(pht('Frequency'))227->setOptions($frequency_options)228->setTransactionType(229PhabricatorCalendarEventFrequencyTransaction::TRANSACTIONTYPE)230->setDescription(pht('Recurring event frequency.'))231->setConduitDescription(pht('Change the event frequency.'))232->setConduitTypeDescription(pht('New event frequency.'))233->setValue($frequency),234id(new PhabricatorEpochEditField())235->setIsLockable(false)236->setIsDefaultable(false)237->setAllowNull(true)238->setHideTime($object->getIsAllDay())239->setKey('until')240->setLabel(pht('Repeat Until'))241->setTransactionType(242PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE)243->setDescription(pht('Last instance of the event.'))244->setConduitDescription(pht('Change when the event repeats until.'))245->setConduitTypeDescription(pht('New final event time.'))246->setValue($object->getUntilDateTimeEpoch()),247);248249return $fields;250}251252protected function willBuildEditForm($object, array $fields) {253$all_day_field = idx($fields, 'isAllDay');254$start_field = idx($fields, 'start');255$end_field = idx($fields, 'end');256257if ($all_day_field) {258$is_all_day = $all_day_field->getValueForTransaction();259260$control_ids = array();261if ($start_field) {262$control_ids[] = $start_field->getControlID();263}264if ($end_field) {265$control_ids[] = $end_field->getControlID();266}267268Javelin::initBehavior(269'event-all-day',270array(271'allDayID' => $all_day_field->getControlID(),272'controlIDs' => $control_ids,273));274275} else {276$is_all_day = $object->getIsAllDay();277}278279if ($is_all_day) {280if ($start_field) {281$start_field->setHideTime(true);282}283284if ($end_field) {285$end_field->setHideTime(true);286}287}288289return $fields;290}291292protected function newPages($object) {293// Controls for event recurrence behavior go on a separate page which we294// put in a dialog. This simplifies event creation in the common case.295296return array(297id(new PhabricatorEditPage())298->setKey('core')299->setLabel(pht('Core'))300->setIsDefault(true),301id(new PhabricatorEditPage())302->setKey('recurring')303->setLabel(pht('Recurrence'))304->setFieldKeys(305array(306'isRecurring',307'frequency',308'until',309)),310);311}312313protected function willApplyTransactions($object, array $xactions) {314$viewer = $this->getViewer();315316$is_parent = $object->isParentEvent();317$is_child = $object->isChildEvent();318$is_future = ($this->getSeriesEditMode() === self::MODE_FUTURE);319320// Figure out which transactions we can apply to the whole series of events.321// Some transactions (like comments) can never be bulk applied.322$inherited_xactions = array();323foreach ($xactions as $xaction) {324$modular_type = $xaction->getModularType();325if (!($modular_type instanceof PhabricatorCalendarEventTransactionType)) {326continue;327}328329$inherited_edit = $modular_type->isInheritedEdit();330if ($inherited_edit) {331$inherited_xactions[] = $xaction;332}333}334$this->rawTransactions = $this->cloneTransactions($inherited_xactions);335336$must_fork = ($is_child && $is_future) ||337($is_parent && !$is_future);338339// We don't need to fork when editing a parent event if none of the edits340// can transfer to child events. For example, commenting on a parent is341// fine.342if ($is_parent && !$is_future) {343if (!$inherited_xactions) {344$must_fork = false;345}346}347348if ($must_fork) {349$fork_target = $object->loadForkTarget($viewer);350if ($fork_target) {351$fork_xaction = id(new PhabricatorCalendarEventTransaction())352->setTransactionType(353PhabricatorCalendarEventForkTransaction::TRANSACTIONTYPE)354->setNewValue(true);355356if ($fork_target->getPHID() == $object->getPHID()) {357// We're forking the object itself, so just slip it into the358// transactions we're going to apply.359array_unshift($xactions, $fork_xaction);360} else {361// Otherwise, we're forking a different object, so we have to362// apply that separately.363$this->applyTransactions($fork_target, array($fork_xaction));364}365}366}367368return $xactions;369}370371protected function didApplyTransactions($object, array $xactions) {372$viewer = $this->getViewer();373374if ($this->getSeriesEditMode() !== self::MODE_FUTURE) {375return;376}377378$targets = $object->loadFutureEvents($viewer);379if (!$targets) {380return;381}382383foreach ($targets as $target) {384$apply = $this->cloneTransactions($this->rawTransactions);385$this->applyTransactions($target, $apply);386}387}388389private function applyTransactions($target, array $xactions) {390$viewer = $this->getViewer();391392// TODO: This isn't the most accurate source we could use, but this mode393// is web-only for now.394$content_source = PhabricatorContentSource::newForSource(395PhabricatorWebContentSource::SOURCECONST);396397$editor = id(new PhabricatorCalendarEventEditor())398->setActor($viewer)399->setContentSource($content_source)400->setContinueOnNoEffect(true)401->setContinueOnMissingFields(true);402403try {404$editor->applyTransactions($target, $xactions);405} catch (PhabricatorApplicationTransactionValidationException $ex) {406// Just ignore any issues we run into.407}408}409410private function cloneTransactions(array $xactions) {411$result = array();412foreach ($xactions as $xaction) {413$result[] = clone $xaction;414}415return $result;416}417418}419420421