Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2893177
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
35 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
index bf63d8e8e7..12f7b8ba8d 100644
--- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
+++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
@@ -1,341 +1,367 @@
<?php
final class PhabricatorCalendarEventEditor
extends PhabricatorApplicationTransactionEditor {
+ private $oldIsAllDay;
+ private $newIsAllDay;
+
public function getEditorApplicationClass() {
return 'PhabricatorCalendarApplication';
}
public function getEditorObjectsDescription() {
return pht('Calendar');
}
public function getCreateObjectTitle($author, $object) {
return pht('%s created this event.', $author);
}
public function getCreateObjectTitleForFeed($author, $object) {
return pht('%s created %s.', $author, $object);
}
-
protected function shouldApplyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
+ public function getOldIsAllDay() {
+ return $this->oldIsAllDay;
+ }
+
+ public function getNewIsAllDay() {
+ return $this->newIsAllDay;
+ }
+
protected function applyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
$actor = $this->requireActor();
if ($object->getIsStub()) {
$this->materializeStub($object);
}
+
+ // Before doing anything, figure out if the event will be an all day event
+ // or not after the edit. This affects how we store datetime values, and
+ // whether we render times or not.
+ $old_allday = $object->getIsAllDay();
+ $new_allday = $old_allday;
+ $type_allday = PhabricatorCalendarEventAllDayTransaction::TRANSACTIONTYPE;
+ foreach ($xactions as $xaction) {
+ if ($xaction->getTransactionType() != $type_allday) {
+ continue;
+ }
+ $target_alllday = (bool)$xaction->getNewValue();
+ }
+
+ $this->oldIsAllDay = $old_allday;
+ $this->newIsAllDay = $new_allday;
}
private function materializeStub(PhabricatorCalendarEvent $event) {
if (!$event->getIsStub()) {
throw new Exception(
pht('Can not materialize an event stub: this event is not a stub.'));
}
$actor = $this->getActor();
$invitees = $event->getInvitees();
$event->copyFromParent($actor);
$event->setIsStub(0);
$event->openTransaction();
$event->save();
foreach ($invitees as $invitee) {
$invitee
->setEventPHID($event->getPHID())
->save();
}
$event->saveTransaction();
$event->attachInvitees($invitees);
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorTransactions::TYPE_COMMENT;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
protected function adjustObjectForPolicyChecks(
PhabricatorLiskDAO $object,
array $xactions) {
$copy = parent::adjustObjectForPolicyChecks($object, $xactions);
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorCalendarEventHostTransaction::TRANSACTIONTYPE:
$copy->setHostPHID($xaction->getNewValue());
break;
case PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE:
PhabricatorPolicyRule::passTransactionHintToRule(
$copy,
new PhabricatorCalendarEventInviteesPolicyRule(),
array_fuse($xaction->getNewValue()));
break;
}
}
return $copy;
}
protected function applyFinalEffects(
PhabricatorLiskDAO $object,
array $xactions) {
// Clear the availability caches for users whose availability is affected
// by this edit.
$invalidate_all = false;
$invalidate_phids = array();
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventAllDayTransaction::TRANSACTIONTYPE:
// For these kinds of changes, we need to invalidate the availabilty
// caches for all attendees.
$invalidate_all = true;
break;
case PhabricatorCalendarEventAcceptTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventDeclineTransaction::TRANSACTIONTYPE:
$acting_phid = $this->getActingAsPHID();
$invalidate_phids[$acting_phid] = $acting_phid;
break;
case PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE:
foreach ($xaction->getNewValue() as $phid => $ignored) {
$invalidate_phids[$phid] = $phid;
}
break;
}
}
$phids = mpull($object->getInvitees(), 'getInviteePHID');
$phids = array_fuse($phids);
if (!$invalidate_all) {
$phids = array_select_keys($phids, $invalidate_phids);
}
if ($phids) {
$object->applyViewerTimezone($this->getActor());
$user = new PhabricatorUser();
$conn_w = $user->establishConnection('w');
queryfx(
$conn_w,
'UPDATE %T SET availabilityCacheTTL = NULL
WHERE phid IN (%Ls) AND availabilityCacheTTL >= %d',
$user->getTableName(),
$phids,
$object->getStartDateTimeEpochForCache());
}
return $xactions;
}
protected function validateAllTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
$start_date_xaction =
PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE;
$end_date_xaction =
PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE;
$is_recurrence_xaction =
PhabricatorCalendarEventRecurringTransaction::TRANSACTIONTYPE;
$recurrence_end_xaction =
PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE;
$start_date = $object->getStartDateTimeEpoch();
$end_date = $object->getEndDateTimeEpoch();
$recurrence_end = $object->getUntilDateTimeEpoch();
$is_recurring = $object->getIsRecurring();
$errors = array();
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() == $start_date_xaction) {
$start_date = $xaction->getNewValue()->getEpoch();
} else if ($xaction->getTransactionType() == $end_date_xaction) {
$end_date = $xaction->getNewValue()->getEpoch();
} else if ($xaction->getTransactionType() == $recurrence_end_xaction) {
$recurrence_end = $xaction->getNewValue()->getEpoch();
} else if ($xaction->getTransactionType() == $is_recurrence_xaction) {
$is_recurring = $xaction->getNewValue();
}
}
if ($start_date > $end_date) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$end_date_xaction,
pht('Invalid'),
pht('End date must be after start date.'),
null);
}
if ($recurrence_end && !$is_recurring) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$recurrence_end_xaction,
pht('Invalid'),
pht('Event must be recurring to have a recurrence end date.').
null);
}
return $errors;
}
protected function shouldPublishFeedStory(
PhabricatorLiskDAO $object,
array $xactions) {
if ($object->isImportedEvent()) {
return false;
}
return true;
}
protected function supportsSearch() {
return true;
}
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
if ($object->isImportedEvent()) {
return false;
}
return true;
}
protected function getMailSubjectPrefix() {
return pht('[Calendar]');
}
protected function getMailTo(PhabricatorLiskDAO $object) {
$phids = array();
if ($object->getHostPHID()) {
$phids[] = $object->getHostPHID();
}
$phids[] = $this->getActingAsPHID();
$invitees = $object->getInvitees();
foreach ($invitees as $invitee) {
$status = $invitee->getStatus();
if ($status === PhabricatorCalendarEventInvitee::STATUS_ATTENDING
|| $status === PhabricatorCalendarEventInvitee::STATUS_INVITED) {
$phids[] = $invitee->getInviteePHID();
}
}
$phids = array_unique($phids);
return $phids;
}
public function getMailTagsMap() {
return array(
PhabricatorCalendarEventTransaction::MAILTAG_CONTENT =>
pht(
"An event's name, status, invite list, ".
"icon, and description changes."),
PhabricatorCalendarEventTransaction::MAILTAG_RESCHEDULE =>
pht(
"An event's start and end date ".
"and cancellation status changes."),
PhabricatorCalendarEventTransaction::MAILTAG_OTHER =>
pht('Other event activity not listed above occurs.'),
);
}
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
return id(new PhabricatorCalendarReplyHandler())
->setMailReceiver($object);
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$name = $object->getName();
return id(new PhabricatorMetaMTAMail())
->setSubject("E{$id}: {$name}")
->addHeader('Thread-Topic', "E{$id}: ".$object->getName());
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$description = $object->getDescription();
$body = parent::buildMailBody($object, $xactions);
if (strlen($description)) {
$body->addRemarkupSection(
pht('EVENT DESCRIPTION'),
$description);
}
$body->addLinkSection(
pht('EVENT DETAIL'),
PhabricatorEnv::getProductionURI('/E'.$object->getID()));
$ics_attachment = $this->newICSAttachment($object);
$body->addAttachment($ics_attachment);
return $body;
}
protected function shouldApplyHeraldRules(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function buildHeraldAdapter(
PhabricatorLiskDAO $object,
array $xactions) {
return id(new PhabricatorCalendarEventHeraldAdapter())
->setObject($object);
}
private function newICSAttachment(
PhabricatorCalendarEvent $event) {
$actor = $this->getActor();
$ics_data = id(new PhabricatorCalendarICSWriter())
->setViewer($actor)
->setEvents(array($event))
->writeICSDocument();
$ics_attachment = new PhabricatorMetaMTAAttachment(
$ics_data,
$event->getICSFilename(),
'text/calendar');
return $ics_attachment;
}
}
diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php
index 5a4958ba4a..cc2cb59da9 100644
--- a/src/applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php
+++ b/src/applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php
@@ -1,27 +1,31 @@
<?php
abstract class PhabricatorCalendarEventDateTransaction
extends PhabricatorCalendarEventTransactionType {
abstract protected function getInvalidDateMessage();
public function generateNewValue($object, $value) {
- return $value->getEpoch();
+ $editor = $this->getEditor();
+ return $value->newPhutilDateTime()
+ ->setIsAllDay($editor->getNewIsAllDay())
+ ->newAbsoluteDateTime()
+ ->toDictionary();
}
public function validateTransactions($object, array $xactions) {
$errors = array();
foreach ($xactions as $xaction) {
if ($xaction->getNewValue()->isValid()) {
continue;
}
$message = $this->getInvalidDateMessage();
$errors[] = $this->newInvalidError($message, $xaction);
}
return $errors;
}
}
diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php
index 8d66176ea8..4c8bb0fb74 100644
--- a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php
+++ b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php
@@ -1,44 +1,56 @@
<?php
final class PhabricatorCalendarEventEndDateTransaction
extends PhabricatorCalendarEventDateTransaction {
const TRANSACTIONTYPE = 'calendar.enddate';
public function generateOldValue($object) {
- // TODO: Upgrade this.
- return $object->newEndDateTimeForEdit()->getEpoch();
+ $editor = $this->getEditor();
+
+ return $object->newEndDateTimeForEdit()
+ ->newAbsoluteDateTime()
+ ->setIsAllDay($editor->getOldIsAllDay())
+ ->toDictionary();
}
public function applyInternalEffects($object, $value) {
$actor = $this->getActor();
+ $editor = $this->getEditor();
+
+ $datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($value);
+ $datetime->setIsAllDay($editor->getNewIsAllDay());
- $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
- $value,
- $actor->getTimezoneIdentifier());
- $datetime->setIsAllDay($object->getIsAllDay());
$object->setEndDateTime($datetime);
}
+ public function shouldHide() {
+ if ($this->isCreateTransaction()) {
+ return true;
+ }
+
+ return false;
+ }
+
public function getTitle() {
return pht(
'%s changed the end date for this event from %s to %s.',
$this->renderAuthor(),
$this->renderOldDate(),
$this->renderNewDate());
}
public function getTitleForFeed() {
return pht(
'%s changed the end date for %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderOldDate(),
$this->renderNewDate());
}
protected function getInvalidDateMessage() {
return pht('End date is invalid.');
}
}
diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php
index e08bbac780..484591fa5f 100644
--- a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php
+++ b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php
@@ -1,44 +1,56 @@
<?php
final class PhabricatorCalendarEventStartDateTransaction
extends PhabricatorCalendarEventDateTransaction {
const TRANSACTIONTYPE = 'calendar.startdate';
public function generateOldValue($object) {
- // TODO: Upgrade this.
- return $object->getStartDateTimeEpoch();
+ $editor = $this->getEditor();
+
+ return $object->newStartDateTime()
+ ->newAbsoluteDateTime()
+ ->setIsAllDay($editor->getOldIsAllDay())
+ ->toDictionary();
}
public function applyInternalEffects($object, $value) {
$actor = $this->getActor();
+ $editor = $this->getEditor();
+
+ $datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($value);
+ $datetime->setIsAllDay($editor->getNewIsAllDay());
- $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
- $value,
- $actor->getTimezoneIdentifier());
- $datetime->setIsAllDay($object->getIsAllDay());
$object->setStartDateTime($datetime);
}
+ public function shouldHide() {
+ if ($this->isCreateTransaction()) {
+ return true;
+ }
+
+ return false;
+ }
+
public function getTitle() {
return pht(
'%s changed the start date for this event from %s to %s.',
$this->renderAuthor(),
$this->renderOldDate(),
$this->renderNewDate());
}
public function getTitleForFeed() {
return pht(
'%s changed the start date for %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderOldDate(),
$this->renderNewDate());
}
protected function getInvalidDateMessage() {
return pht('Start date is invalid.');
}
}
diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php
index 736ed13704..a841d4d09c 100644
--- a/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php
+++ b/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php
@@ -1,45 +1,49 @@
<?php
final class PhabricatorCalendarEventUntilDateTransaction
extends PhabricatorCalendarEventDateTransaction {
const TRANSACTIONTYPE = 'calendar.recurrenceenddate';
public function generateOldValue($object) {
- // TODO: Upgrade this.
- return $object->getUntilDateTimeEpoch();
+ $editor = $this->getEditor();
+
+ return $object->newUntilDateTime()
+ ->newAbsoluteDateTime()
+ ->setIsAllDay($editor->getOldIsAllDay())
+ ->toDictionary();
}
public function applyInternalEffects($object, $value) {
$actor = $this->getActor();
+ $editor = $this->getEditor();
// TODO: DEPRECATED.
$object->setRecurrenceEndDate($value);
- $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
- $value,
- $actor->getTimezoneIdentifier());
- $datetime->setIsAllDay($object->getIsAllDay());
+ $datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($value);
+ $datetime->setIsAllDay($editor->getNewIsAllDay());
+
$object->setUntilDateTime($datetime);
}
public function getTitle() {
return pht(
'%s changed this event to repeat until %s.',
$this->renderAuthor(),
$this->renderNewDate());
}
public function getTitleForFeed() {
return pht(
'%s changed %s to repeat until %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderNewDate());
}
protected function getInvalidDateMessage() {
return pht('Repeat until date is invalid.');
}
}
diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php
index a5ddcc3e94..01a02f7fd3 100644
--- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php
+++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php
@@ -1,265 +1,286 @@
<?php
abstract class PhabricatorModularTransactionType
extends Phobject {
private $storage;
private $viewer;
private $editor;
final public function getTransactionTypeConstant() {
return $this->getPhobjectClassConstant('TRANSACTIONTYPE');
}
public function generateOldValue($object) {
throw new PhutilMethodNotImplementedException();
}
public function generateNewValue($object, $value) {
return $value;
}
public function validateTransactions($object, array $xactions) {
return array();
}
public function willApplyTransactions($object, array $xactions) {
return;
}
public function applyInternalEffects($object, $value) {
return;
}
public function applyExternalEffects($object, $value) {
return;
}
public function extractFilePHIDs($object, $value) {
return array();
}
public function shouldHide() {
return false;
}
public function getIcon() {
return null;
}
public function getTitle() {
return null;
}
public function getTitleForFeed() {
return null;
}
public function getColor() {
return null;
}
public function hasChangeDetailView() {
return false;
}
public function newChangeDetailView() {
throw new PhutilMethodNotImplementedException();
}
public function newRemarkupChanges() {
return array();
}
final public function setStorage(
PhabricatorApplicationTransaction $xaction) {
$this->storage = $xaction;
return $this;
}
private function getStorage() {
return $this->storage;
}
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
final protected function getViewer() {
return $this->viewer;
}
final public function getActor() {
return $this->getEditor()->getActor();
}
final public function getActingAsPHID() {
return $this->getEditor()->getActingAsPHID();
}
final public function setEditor(
PhabricatorApplicationTransactionEditor $editor) {
$this->editor = $editor;
return $this;
}
final protected function getEditor() {
if (!$this->editor) {
throw new PhutilInvalidStateException('setEditor');
}
return $this->editor;
}
final protected function getAuthorPHID() {
return $this->getStorage()->getAuthorPHID();
}
final protected function getObjectPHID() {
return $this->getStorage()->getObjectPHID();
}
final protected function getObject() {
return $this->getStorage()->getObject();
}
final protected function getOldValue() {
return $this->getStorage()->getOldValue();
}
final protected function getNewValue() {
return $this->getStorage()->getNewValue();
}
final protected function renderAuthor() {
$author_phid = $this->getAuthorPHID();
return $this->getStorage()->renderHandleLink($author_phid);
}
final protected function renderObject() {
$object_phid = $this->getObjectPHID();
return $this->getStorage()->renderHandleLink($object_phid);
}
final protected function renderHandle($phid) {
$viewer = $this->getViewer();
$display = $viewer->renderHandle($phid);
if ($this->isTextMode()) {
$display->setAsText(true);
}
return $display;
}
final protected function renderOldHandle() {
return $this->renderHandle($this->getOldValue());
}
final protected function renderNewHandle() {
return $this->renderHandle($this->getNewValue());
}
final protected function renderHandleList(array $phids) {
$viewer = $this->getViewer();
$display = $viewer->renderHandleList($phids)
->setAsInline(true);
if ($this->isTextMode()) {
$display->setAsText(true);
}
return $display;
}
final protected function renderValue($value) {
if ($this->isTextMode()) {
return sprintf('"%s"', $value);
}
return phutil_tag(
'span',
array(
'class' => 'phui-timeline-value',
),
$value);
}
final protected function renderOldValue() {
return $this->renderValue($this->getOldValue());
}
final protected function renderNewValue() {
return $this->renderValue($this->getNewValue());
}
final protected function renderDate($epoch) {
$viewer = $this->getViewer();
- $display = phabricator_datetime($epoch, $viewer);
+ // We accept either epoch timestamps or dictionaries describing a
+ // PhutilCalendarDateTime.
+ if (is_array($epoch)) {
+ $datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($epoch)
+ ->setViewerTimezone($viewer->getTimezoneIdentifier());
- // When rendering to text, we explicitly render the offset from UTC to
- // provide context to the date: the mail may be generating with the
- // server's settings, or the user may later refer back to it after changing
- // timezones.
+ $all_day = $datetime->getIsAllDay();
- if ($this->isTextMode()) {
- $offset = $viewer->getTimeZoneOffsetInHours();
- if ($offset >= 0) {
- $display = pht('%s (UTC+%d)', $display, $offset);
- } else {
- $display = pht('%s (UTC-%d)', $display, abs($offset));
+ $epoch = $datetime->getEpoch();
+ } else {
+ $all_day = false;
+ }
+
+ if ($all_day) {
+ $display = phabricator_date($epoch, $viewer);
+ } else {
+ $display = phabricator_datetime($epoch, $viewer);
+
+ // When rendering to text, we explicitly render the offset from UTC to
+ // provide context to the date: the mail may be generating with the
+ // server's settings, or the user may later refer back to it after
+ // changing timezones.
+
+ if ($this->isTextMode()) {
+ $offset = $viewer->getTimeZoneOffsetInHours();
+ if ($offset >= 0) {
+ $display = pht('%s (UTC+%d)', $display, $offset);
+ } else {
+ $display = pht('%s (UTC-%d)', $display, abs($offset));
+ }
}
}
return $this->renderValue($display);
}
final protected function renderOldDate() {
return $this->renderDate($this->getOldValue());
}
final protected function renderNewDate() {
return $this->renderDate($this->getNewValue());
}
final protected function newError($title, $message, $xaction = null) {
return new PhabricatorApplicationTransactionValidationError(
$this->getTransactionTypeConstant(),
$title,
$message,
$xaction);
}
final protected function newRequiredError($message, $xaction = null) {
return $this->newError(pht('Required'), $message, $xaction)
->setIsMissingFieldError(true);
}
final protected function newInvalidError($message, $xaction = null) {
return $this->newError(pht('Invalid'), $message, $xaction);
}
final protected function isNewObject() {
return $this->getEditor()->getIsNewObject();
}
final protected function isEmptyTextTransaction($value, array $xactions) {
foreach ($xactions as $xaction) {
$value = $xaction->getNewValue();
}
return !strlen($value);
}
private function isTextMode() {
$target = $this->getStorage()->getRenderingTarget();
return ($target == PhabricatorApplicationTransaction::TARGET_TEXT);
}
final protected function newRemarkupChange() {
return id(new PhabricatorTransactionRemarkupChange())
->setTransaction($this->getStorage());
}
+ final protected function isCreateTransaction() {
+ return $this->getStorage()->getIsCreateTransaction();
+ }
+
}
diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php
index 51942c20e7..873ad44fe4 100644
--- a/src/view/form/control/AphrontFormDateControlValue.php
+++ b/src/view/form/control/AphrontFormDateControlValue.php
@@ -1,332 +1,358 @@
<?php
final class AphrontFormDateControlValue extends Phobject {
private $valueDate;
private $valueTime;
private $valueEnabled;
private $viewer;
private $zone;
private $optional;
public function getValueDate() {
return $this->valueDate;
}
public function getValueTime() {
return $this->valueTime;
}
public function isValid() {
if ($this->isDisabled()) {
return true;
}
return ($this->getEpoch() !== null);
}
public function isEmpty() {
if ($this->valueDate) {
return false;
}
if ($this->valueTime) {
return false;
}
return true;
}
public function isDisabled() {
return ($this->optional && !$this->valueEnabled);
}
public function setEnabled($enabled) {
$this->valueEnabled = $enabled;
return $this;
}
public function setOptional($optional) {
$this->optional = $optional;
return $this;
}
public function getOptional() {
return $this->optional;
}
public function getViewer() {
return $this->viewer;
}
public static function newFromRequest(AphrontRequest $request, $key) {
$value = new AphrontFormDateControlValue();
$value->viewer = $request->getViewer();
$date = $request->getStr($key.'_d');
$time = $request->getStr($key.'_t');
// If we have the individual parts, we read them preferentially. If we do
// not, try to read the key as a raw value. This makes it so that HTTP
// prefilling is overwritten by the control value if the user changes it.
if (!strlen($date) && !strlen($time)) {
$date = $request->getStr($key);
$time = null;
}
$value->valueDate = $date;
$value->valueTime = $time;
$formatted = $value->getFormattedDateFromDate(
$value->valueDate,
$value->valueTime);
if ($formatted) {
list($value->valueDate, $value->valueTime) = $formatted;
}
$value->valueEnabled = $request->getStr($key.'_e');
return $value;
}
public static function newFromEpoch(PhabricatorUser $viewer, $epoch) {
$value = new AphrontFormDateControlValue();
$value->viewer = $viewer;
if (!$epoch) {
return $value;
}
$readable = $value->formatTime($epoch, 'Y!m!d!g:i A');
$readable = explode('!', $readable, 4);
$year = $readable[0];
$month = $readable[1];
$day = $readable[2];
$time = $readable[3];
list($value->valueDate, $value->valueTime) =
$value->getFormattedDateFromParts(
$year,
$month,
$day,
$time);
return $value;
}
public static function newFromDictionary(
PhabricatorUser $viewer,
array $dictionary) {
$value = new AphrontFormDateControlValue();
$value->viewer = $viewer;
$value->valueDate = idx($dictionary, 'd');
$value->valueTime = idx($dictionary, 't');
$formatted = $value->getFormattedDateFromDate(
$value->valueDate,
$value->valueTime);
if ($formatted) {
list($value->valueDate, $value->valueTime) = $formatted;
}
$value->valueEnabled = idx($dictionary, 'e');
return $value;
}
public static function newFromWild(PhabricatorUser $viewer, $wild) {
if (is_array($wild)) {
return self::newFromDictionary($viewer, $wild);
} else if (is_numeric($wild)) {
return self::newFromEpoch($viewer, $wild);
} else {
throw new Exception(
pht(
'Unable to construct a date value from value of type "%s".',
gettype($wild)));
}
}
public function getDictionary() {
return array(
'd' => $this->valueDate,
't' => $this->valueTime,
'e' => $this->valueEnabled,
);
}
public function getValueAsFormat($format) {
return phabricator_format_local_time(
$this->getEpoch(),
$this->viewer,
$format);
}
private function formatTime($epoch, $format) {
return phabricator_format_local_time(
$epoch,
$this->viewer,
$format);
}
public function getEpoch() {
if ($this->isDisabled()) {
return null;
}
$datetime = $this->newDateTime($this->valueDate, $this->valueTime);
if (!$datetime) {
return null;
}
return (int)$datetime->format('U');
}
private function getTimeFormat() {
$viewer = $this->getViewer();
$time_key = PhabricatorTimeFormatSetting::SETTINGKEY;
return $viewer->getUserSetting($time_key);
}
private function getDateFormat() {
$viewer = $this->getViewer();
$date_key = PhabricatorDateFormatSetting::SETTINGKEY;
return $viewer->getUserSetting($date_key);
}
private function getFormattedDateFromDate($date, $time) {
$datetime = $this->newDateTime($date, $time);
if (!$datetime) {
return null;
}
return array(
$datetime->format($this->getDateFormat()),
$datetime->format($this->getTimeFormat()),
);
return array($date, $time);
}
private function newDateTime($date, $time) {
$date = $this->getStandardDateFormat($date);
$time = $this->getStandardTimeFormat($time);
try {
// We need to provide the timezone in the constructor, and also set it
// explicitly. If the date is an epoch timestamp, the timezone in the
// constructor is ignored. If the date is not an epoch timestamp, it is
// used to parse the date.
$zone = $this->getTimezone();
$datetime = new DateTime("{$date} {$time}", $zone);
$datetime->setTimezone($zone);
} catch (Exception $ex) {
return null;
}
return $datetime;
}
+ public function newPhutilDateTime() {
+ $datetime = $this->getDateTime();
+ if (!$datetime) {
+ return null;
+ }
+
+ $all_day = !strlen($this->valueTime);
+ $zone_identifier = $this->viewer->getTimezoneIdentifier();
+
+ $result = id(new PhutilCalendarAbsoluteDateTime())
+ ->setYear((int)$datetime->format('Y'))
+ ->setMonth((int)$datetime->format('m'))
+ ->setDay((int)$datetime->format('d'))
+ ->setHour((int)$datetime->format('G'))
+ ->setMinute((int)$datetime->format('i'))
+ ->setSecond((int)$datetime->format('s'))
+ ->setTimezone($zone_identifier);
+
+ if ($all_day) {
+ $result->setIsAllDay(true);
+ }
+
+ return $result;
+ }
+
+
private function getFormattedDateFromParts(
$year,
$month,
$day,
$time) {
$zone = $this->getTimezone();
$date_time = id(new DateTime("{$year}-{$month}-{$day} {$time}", $zone));
return array(
$date_time->format($this->getDateFormat()),
$date_time->format($this->getTimeFormat()),
);
}
private function getFormatSeparator() {
$format = $this->getDateFormat();
switch ($format) {
case 'n/j/Y':
return '/';
default:
return '-';
}
}
public function getDateTime() {
return $this->newDateTime($this->valueDate, $this->valueTime);
}
private function getTimezone() {
if ($this->zone) {
return $this->zone;
}
$viewer_zone = $this->viewer->getTimezoneIdentifier();
$this->zone = new DateTimeZone($viewer_zone);
return $this->zone;
}
private function getStandardDateFormat($date) {
$colloquial = array(
'newyear' => 'January 1',
'valentine' => 'February 14',
'pi' => 'March 14',
'christma' => 'December 25',
);
// Lowercase the input, then remove punctuation, a "day" suffix, and an
// "s" if one is present. This allows all of these to match. This allows
// variations like "New Year's Day" and "New Year" to both match.
$normalized = phutil_utf8_strtolower($date);
$normalized = preg_replace('/[^a-z]/', '', $normalized);
$normalized = preg_replace('/day\z/', '', $normalized);
$normalized = preg_replace('/s\z/', '', $normalized);
if (isset($colloquial[$normalized])) {
return $colloquial[$normalized];
}
// If this looks like an epoch timestamp, prefix it with "@" so that
// DateTime() reads it as one. Assume small numbers are a "Ymd" digit
// string instead of an epoch timestamp for a time in 1970.
if (ctype_digit($date) && ($date > 30000000)) {
$date = '@'.$date;
}
$separator = $this->getFormatSeparator();
$parts = preg_split('@[,./:-]@', $date);
return implode($separator, $parts);
}
private function getStandardTimeFormat($time) {
$colloquial = array(
'crack of dawn' => '5:00 AM',
'dawn' => '6:00 AM',
'early' => '7:00 AM',
'morning' => '8:00 AM',
'elevenses' => '11:00 AM',
'morning tea' => '11:00 AM',
'noon' => '12:00 PM',
'high noon' => '12:00 PM',
'lunch' => '12:00 PM',
'afternoon' => '2:00 PM',
'tea time' => '3:00 PM',
'evening' => '7:00 PM',
'late' => '11:00 PM',
'witching hour' => '12:00 AM',
'midnight' => '12:00 AM',
);
$normalized = phutil_utf8_strtolower($time);
if (isset($colloquial[$normalized])) {
$time = $colloquial[$normalized];
}
return $time;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 18:01 (2 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127209
Default Alt Text
(35 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment