mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-05 12:21:02 +01:00
(stable) Promote 2016 Week 41
This commit is contained in:
commit
c9cc383291
83 changed files with 3070 additions and 1066 deletions
BIN
resources/builtin/conpherence.png
Normal file
BIN
resources/builtin/conpherence.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
|
@ -7,10 +7,10 @@
|
|||
*/
|
||||
return array(
|
||||
'names' => array(
|
||||
'conpherence.pkg.css' => 'b1547973',
|
||||
'conpherence.pkg.css' => '4601645d',
|
||||
'conpherence.pkg.js' => '11f3e07e',
|
||||
'core.pkg.css' => 'cfc3eabe',
|
||||
'core.pkg.js' => '975d6a27',
|
||||
'core.pkg.css' => 'de918edf',
|
||||
'core.pkg.js' => '30185d95',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => 'e1d704ce',
|
||||
'differential.pkg.js' => '634399e9',
|
||||
|
@ -35,9 +35,9 @@ return array(
|
|||
'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af',
|
||||
'rsrc/css/application/auth/auth.css' => '0877ed6e',
|
||||
'rsrc/css/application/base/main-menu-view.css' => 'f03e17be',
|
||||
'rsrc/css/application/base/notification-menu.css' => 'b3ab500d',
|
||||
'rsrc/css/application/base/notification-menu.css' => '1e055865',
|
||||
'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601',
|
||||
'rsrc/css/application/base/phui-theme.css' => '027ba77e',
|
||||
'rsrc/css/application/base/phui-theme.css' => '798c69b8',
|
||||
'rsrc/css/application/base/standard-page-view.css' => '79176f5a',
|
||||
'rsrc/css/application/chatlog/chatlog.css' => 'd295b020',
|
||||
'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4',
|
||||
|
@ -46,11 +46,11 @@ return array(
|
|||
'rsrc/css/application/config/config-template.css' => '8f18fa41',
|
||||
'rsrc/css/application/config/setup-issue.css' => 'f794cfc3',
|
||||
'rsrc/css/application/config/unhandled-exception.css' => '4c96257a',
|
||||
'rsrc/css/application/conpherence/durable-column.css' => 'af11a2a7',
|
||||
'rsrc/css/application/conpherence/header-pane.css' => '517de9fe',
|
||||
'rsrc/css/application/conpherence/menu.css' => '78c7b811',
|
||||
'rsrc/css/application/conpherence/durable-column.css' => '44bcaa19',
|
||||
'rsrc/css/application/conpherence/header-pane.css' => '20a7028c',
|
||||
'rsrc/css/application/conpherence/menu.css' => '4f51db5a',
|
||||
'rsrc/css/application/conpherence/message-pane.css' => '0d7dff02',
|
||||
'rsrc/css/application/conpherence/notification.css' => '6cdcc253',
|
||||
'rsrc/css/application/conpherence/notification.css' => '965db05b',
|
||||
'rsrc/css/application/conpherence/participant-pane.css' => '7bba0b56',
|
||||
'rsrc/css/application/conpherence/transaction.css' => '46253e19',
|
||||
'rsrc/css/application/conpherence/update.css' => '53bc527a',
|
||||
|
@ -154,7 +154,7 @@ return array(
|
|||
'rsrc/css/phui/phui-object-item-list-view.css' => '87278fa0',
|
||||
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
|
||||
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
|
||||
'rsrc/css/phui/phui-profile-menu.css' => '8a3fc181',
|
||||
'rsrc/css/phui/phui-profile-menu.css' => '4768721a',
|
||||
'rsrc/css/phui/phui-property-list-view.css' => '6d8e58ac',
|
||||
'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591',
|
||||
'rsrc/css/phui/phui-segment-bar-view.css' => '46342871',
|
||||
|
@ -428,7 +428,7 @@ return array(
|
|||
'rsrc/js/application/aphlict/Aphlict.js' => '5359e785',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '49e20786',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'fb20ac8d',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9',
|
||||
'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'edd1ba66',
|
||||
'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18',
|
||||
'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443',
|
||||
|
@ -438,7 +438,7 @@ return array(
|
|||
'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',
|
||||
'rsrc/js/application/conpherence/behavior-durable-column.js' => 'e287689f',
|
||||
'rsrc/js/application/conpherence/behavior-durable-column.js' => 'c5238acb',
|
||||
'rsrc/js/application/conpherence/behavior-menu.js' => '9eb55204',
|
||||
'rsrc/js/application/conpherence/behavior-participant-pane.js' => '8604caa8',
|
||||
'rsrc/js/application/conpherence/behavior-pontificate.js' => 'f2e58483',
|
||||
|
@ -547,7 +547,6 @@ return array(
|
|||
'rsrc/js/core/behavior-autofocus.js' => '7319e029',
|
||||
'rsrc/js/core/behavior-badge-view.js' => '8ff5e24c',
|
||||
'rsrc/js/core/behavior-choose-control.js' => '327a00d1',
|
||||
'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2',
|
||||
'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae',
|
||||
'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96',
|
||||
'rsrc/js/core/behavior-device.js' => 'bb1dd507',
|
||||
|
@ -618,11 +617,11 @@ return array(
|
|||
'conduit-api-css' => '7bc725c4',
|
||||
'config-options-css' => '0ede4c9b',
|
||||
'config-page-css' => '8798e14f',
|
||||
'conpherence-durable-column-view' => 'af11a2a7',
|
||||
'conpherence-header-pane-css' => '517de9fe',
|
||||
'conpherence-menu-css' => '78c7b811',
|
||||
'conpherence-durable-column-view' => '44bcaa19',
|
||||
'conpherence-header-pane-css' => '20a7028c',
|
||||
'conpherence-menu-css' => '4f51db5a',
|
||||
'conpherence-message-pane-css' => '0d7dff02',
|
||||
'conpherence-notification-css' => '6cdcc253',
|
||||
'conpherence-notification-css' => '965db05b',
|
||||
'conpherence-participant-pane-css' => '7bba0b56',
|
||||
'conpherence-thread-manager' => '01774ab2',
|
||||
'conpherence-transaction-css' => '46253e19',
|
||||
|
@ -653,9 +652,8 @@ return array(
|
|||
'javelin-behavior' => '61cbc29a',
|
||||
'javelin-behavior-aphlict-dropdown' => '49e20786',
|
||||
'javelin-behavior-aphlict-listen' => 'fb20ac8d',
|
||||
'javelin-behavior-aphlict-status' => 'ea681761',
|
||||
'javelin-behavior-aphlict-status' => '5e2634b9',
|
||||
'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884',
|
||||
'javelin-behavior-aphront-crop' => 'fa0f4fc2',
|
||||
'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22',
|
||||
'javelin-behavior-aphront-form-disable-on-submit' => '5c54cbf3',
|
||||
'javelin-behavior-aphront-more' => 'a80d0378',
|
||||
|
@ -699,7 +697,7 @@ return array(
|
|||
'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc',
|
||||
'javelin-behavior-doorkeeper-tag' => 'e5822781',
|
||||
'javelin-behavior-drydock-live-operation-status' => '901935ef',
|
||||
'javelin-behavior-durable-column' => 'e287689f',
|
||||
'javelin-behavior-durable-column' => 'c5238acb',
|
||||
'javelin-behavior-editengine-reorder-configs' => 'd7a74243',
|
||||
'javelin-behavior-editengine-reorder-fields' => 'b59e1e96',
|
||||
'javelin-behavior-error-log' => '6882e80a',
|
||||
|
@ -860,7 +858,7 @@ return array(
|
|||
'phabricator-nav-view-css' => 'b29426e9',
|
||||
'phabricator-notification' => 'ccf1cbf8',
|
||||
'phabricator-notification-css' => '3f6c89c9',
|
||||
'phabricator-notification-menu-css' => 'b3ab500d',
|
||||
'phabricator-notification-menu-css' => '1e055865',
|
||||
'phabricator-object-selector-css' => '85ee8ce6',
|
||||
'phabricator-phtize' => 'd254d646',
|
||||
'phabricator-prefab' => 'cfd23f37',
|
||||
|
@ -932,14 +930,14 @@ return array(
|
|||
'phui-object-item-list-view-css' => '87278fa0',
|
||||
'phui-pager-css' => 'bea33d23',
|
||||
'phui-pinboard-view-css' => '2495140e',
|
||||
'phui-profile-menu-css' => '8a3fc181',
|
||||
'phui-profile-menu-css' => '4768721a',
|
||||
'phui-property-list-view-css' => '6d8e58ac',
|
||||
'phui-remarkup-preview-css' => '1a8f2591',
|
||||
'phui-segment-bar-view-css' => '46342871',
|
||||
'phui-spacing-css' => '042804d6',
|
||||
'phui-status-list-view-css' => 'd5263e49',
|
||||
'phui-tag-view-css' => '6bbd83e2',
|
||||
'phui-theme-css' => '027ba77e',
|
||||
'phui-theme-css' => '798c69b8',
|
||||
'phui-timeline-view-css' => 'bc523970',
|
||||
'phui-two-column-view-css' => 'fcfbe347',
|
||||
'phui-workboard-color-css' => 'ac6fe6a7',
|
||||
|
@ -1424,6 +1422,12 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
),
|
||||
'5e2634b9' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-aphlict',
|
||||
'phabricator-phtize',
|
||||
'javelin-dom',
|
||||
),
|
||||
'5e9f347c' => array(
|
||||
'javelin-behavior',
|
||||
'multirow-row-manager',
|
||||
|
@ -1957,6 +1961,16 @@ return array(
|
|||
'javelin-install',
|
||||
'javelin-dom',
|
||||
),
|
||||
'c5238acb' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-behavior-device',
|
||||
'javelin-scrollbar',
|
||||
'javelin-quicksand',
|
||||
'phabricator-keyboard-shortcut',
|
||||
'conpherence-thread-manager',
|
||||
),
|
||||
'c587b80f' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
|
@ -2109,16 +2123,6 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
),
|
||||
'e287689f' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-behavior-device',
|
||||
'javelin-scrollbar',
|
||||
'javelin-quicksand',
|
||||
'phabricator-keyboard-shortcut',
|
||||
'conpherence-thread-manager',
|
||||
),
|
||||
'e292eaf4' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
|
@ -2169,12 +2173,6 @@ return array(
|
|||
'javelin-dom',
|
||||
'phabricator-draggable-list',
|
||||
),
|
||||
'ea681761' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-aphlict',
|
||||
'phabricator-phtize',
|
||||
'javelin-dom',
|
||||
),
|
||||
'edd1ba66' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -2243,12 +2241,6 @@ return array(
|
|||
'javelin-util',
|
||||
'phabricator-busy',
|
||||
),
|
||||
'fa0f4fc2' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-vector',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'fb20ac8d' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-aphlict',
|
||||
|
|
|
@ -1,52 +1,3 @@
|
|||
<?php
|
||||
|
||||
$table = new PhabricatorCalendarEvent();
|
||||
$conn = $table->establishConnection('w');
|
||||
|
||||
// Previously, "All Day" events were stored with a start and end date set to
|
||||
// the earliest possible start and end seconds for the corresponding days. We
|
||||
// now store all day events with their "date" epochs as UTC, separate from
|
||||
// individual event times.
|
||||
$zone_min = new DateTimeZone('Pacific/Midway');
|
||||
$zone_max = new DateTimeZone('Pacific/Kiritimati');
|
||||
$zone_utc = new DateTimeZone('UTC');
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $event) {
|
||||
// If this event has already migrated, skip it.
|
||||
if ($event->getAllDayDateFrom()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_all_day = $event->getIsAllDay();
|
||||
|
||||
$epoch_min = $event->getDateFrom();
|
||||
$epoch_max = $event->getDateTo();
|
||||
|
||||
$date_min = new DateTime('@'.$epoch_min);
|
||||
$date_max = new DateTime('@'.$epoch_max);
|
||||
|
||||
if ($is_all_day) {
|
||||
$date_min->setTimeZone($zone_min);
|
||||
$date_min->modify('+2 days');
|
||||
$date_max->setTimeZone($zone_max);
|
||||
$date_max->modify('-2 days');
|
||||
} else {
|
||||
$date_min->setTimeZone($zone_utc);
|
||||
$date_max->setTimeZone($zone_utc);
|
||||
}
|
||||
|
||||
$string_min = $date_min->format('Y-m-d');
|
||||
$string_max = $date_max->format('Y-m-d 23:59:00');
|
||||
|
||||
$allday_min = id(new DateTime($string_min, $zone_utc))->format('U');
|
||||
$allday_max = id(new DateTime($string_max, $zone_utc))->format('U');
|
||||
|
||||
queryfx(
|
||||
$conn,
|
||||
'UPDATE %T SET allDayDateFrom = %d, allDayDateTo = %d
|
||||
WHERE id = %d',
|
||||
$table->getTableName(),
|
||||
$allday_min,
|
||||
$allday_max,
|
||||
$event->getID());
|
||||
}
|
||||
// This migration was replaced by "20161004.cal.01.noepoch.php".
|
||||
|
|
8
resources/sql/autopatches/20161003.cal.01.utcepoch.sql
Normal file
8
resources/sql/autopatches/20161003.cal.01.utcepoch.sql
Normal file
|
@ -0,0 +1,8 @@
|
|||
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
|
||||
ADD utcInitialEpoch INT UNSIGNED NOT NULL;
|
||||
|
||||
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
|
||||
ADD utcUntilEpoch INT UNSIGNED;
|
||||
|
||||
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
|
||||
ADD utcInstanceEpoch INT UNSIGNED;
|
5
resources/sql/autopatches/20161003.cal.02.parameters.sql
Normal file
5
resources/sql/autopatches/20161003.cal.02.parameters.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
|
||||
ADD parameters LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};
|
||||
|
||||
UPDATE {$NAMESPACE}_calendar.calendar_event
|
||||
SET parameters = '{}' WHERE parameters = '';
|
125
resources/sql/autopatches/20161004.cal.01.noepoch.php
Normal file
125
resources/sql/autopatches/20161004.cal.01.noepoch.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
$table = new PhabricatorCalendarEvent();
|
||||
$conn = $table->establishConnection('w');
|
||||
$table_name = 'calendar_event';
|
||||
|
||||
// Long ago, "All Day" events were stored with a start and end date set to
|
||||
// the earliest possible start and end seconds for the corresponding days. We
|
||||
// then moved to store all day events with their "date" epochs as UTC, separate
|
||||
// from individual event times. Both systems were later replaced with use of
|
||||
// CalendarDateTime.
|
||||
$zone_min = new DateTimeZone('Pacific/Midway');
|
||||
$zone_max = new DateTimeZone('Pacific/Kiritimati');
|
||||
$zone_utc = new DateTimeZone('UTC');
|
||||
|
||||
foreach (new LiskRawMigrationIterator($conn, $table_name) as $row) {
|
||||
$parameters = phutil_json_decode($row['parameters']);
|
||||
if (isset($parameters['startDateTime'])) {
|
||||
// This event has already been migrated.
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_all_day = $row['isAllDay'];
|
||||
|
||||
if (empty($row['allDayDateFrom'])) {
|
||||
// No "allDayDateFrom" means this is an old event which was never migrated
|
||||
// by the earlier "20160715.event.03.allday.php" migration. The dateFrom
|
||||
// and dateTo will be minimum and maximum earthly seconds for the event. We
|
||||
// convert them to UTC if they were in extreme timezones.
|
||||
$epoch_min = $row['dateFrom'];
|
||||
$epoch_max = $row['dateTo'];
|
||||
|
||||
if ($is_all_day) {
|
||||
$date_min = new DateTime('@'.$epoch_min);
|
||||
$date_max = new DateTime('@'.$epoch_max);
|
||||
|
||||
$date_min->setTimeZone($zone_min);
|
||||
$date_min->modify('+2 days');
|
||||
$date_max->setTimeZone($zone_max);
|
||||
$date_max->modify('-2 days');
|
||||
|
||||
$string_min = $date_min->format('Y-m-d');
|
||||
$string_max = $date_max->format('Y-m-d 23:59:00');
|
||||
|
||||
$utc_min = id(new DateTime($string_min, $zone_utc))->format('U');
|
||||
$utc_max = id(new DateTime($string_max, $zone_utc))->format('U');
|
||||
} else {
|
||||
$utc_min = $epoch_min;
|
||||
$utc_max = $epoch_max;
|
||||
}
|
||||
} else {
|
||||
// This is an event which was migrated already. We can pick the correct
|
||||
// epoch timestamps based on the "isAllDay" flag.
|
||||
if ($is_all_day) {
|
||||
$utc_min = $row['allDayDateFrom'];
|
||||
$utc_max = $row['allDayDateTo'];
|
||||
} else {
|
||||
$utc_min = $row['dateFrom'];
|
||||
$utc_max = $row['dateTo'];
|
||||
}
|
||||
}
|
||||
|
||||
$utc_until = $row['recurrenceEndDate'];
|
||||
|
||||
$start_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($utc_min);
|
||||
if ($is_all_day) {
|
||||
$start_datetime->setIsAllDay(true);
|
||||
}
|
||||
|
||||
$end_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($utc_max);
|
||||
if ($is_all_day) {
|
||||
$end_datetime->setIsAllDay(true);
|
||||
}
|
||||
|
||||
if ($utc_until) {
|
||||
$until_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($utc_until);
|
||||
} else {
|
||||
$until_datetime = null;
|
||||
}
|
||||
|
||||
$parameters['startDateTime'] = $start_datetime->toDictionary();
|
||||
$parameters['endDateTime'] = $end_datetime->toDictionary();
|
||||
if ($until_datetime) {
|
||||
$parameters['untilDateTime'] = $until_datetime->toDictionary();
|
||||
}
|
||||
|
||||
queryfx(
|
||||
$conn,
|
||||
'UPDATE %T SET parameters = %s WHERE id = %d',
|
||||
$table_name,
|
||||
phutil_json_encode($parameters),
|
||||
$row['id']);
|
||||
}
|
||||
|
||||
// Generate UTC epochs for all events. We can't readily do this one at a
|
||||
// time because instance UTC epochs rely on having the parent event.
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
|
||||
$all_events = id(new PhabricatorCalendarEventQuery())
|
||||
->setViewer($viewer)
|
||||
->execute();
|
||||
foreach ($all_events as $event) {
|
||||
if ($event->getUTCInitialEpoch()) {
|
||||
// Already migrated.
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$event->updateUTCEpochs();
|
||||
} catch (Exception $ex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
queryfx(
|
||||
$conn,
|
||||
'UPDATE %T SET
|
||||
utcInitialEpoch = %d,
|
||||
utcUntilEpoch = %nd,
|
||||
utcInstanceEpoch = %nd WHERE id = %d',
|
||||
$table_name,
|
||||
$event->getUTCInitialEpoch(),
|
||||
$event->getUTCUntilEpoch(),
|
||||
$event->getUTCInstanceEpoch(),
|
||||
$event->getID());
|
||||
}
|
44
resources/sql/autopatches/20161005.cal.01.rrules.php
Normal file
44
resources/sql/autopatches/20161005.cal.01.rrules.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
$table = new PhabricatorCalendarEvent();
|
||||
$conn = $table->establishConnection('w');
|
||||
$table_name = 'calendar_event';
|
||||
|
||||
foreach (new LiskRawMigrationIterator($conn, $table_name) as $row) {
|
||||
$parameters = phutil_json_decode($row['parameters']);
|
||||
if (isset($parameters['recurrenceRule'])) {
|
||||
// This event has already been migrated.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$row['isRecurring']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$old_rule = $row['recurrenceFrequency'];
|
||||
if (!$old_rule) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$frequency = phutil_json_decode($old_rule);
|
||||
if ($frequency) {
|
||||
$frequency_rule = $frequency['rule'];
|
||||
$frequency_rule = phutil_utf8_strtoupper($frequency_rule);
|
||||
|
||||
$rrule = id(new PhutilCalendarRecurrenceRule())
|
||||
->setFrequency($frequency_rule);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parameters['recurrenceRule'] = $rrule->toDictionary();
|
||||
|
||||
queryfx(
|
||||
$conn,
|
||||
'UPDATE %T SET parameters = %s WHERE id = %d',
|
||||
$table_name,
|
||||
phutil_json_encode($parameters),
|
||||
$row['id']);
|
||||
}
|
14
resources/sql/autopatches/20161005.cal.02.export.sql
Normal file
14
resources/sql/autopatches/20161005.cal.02.export.sql
Normal file
|
@ -0,0 +1,14 @@
|
|||
CREATE TABLE {$NAMESPACE}_calendar.calendar_export (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
name LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
authorPHID VARBINARY(64) NOT NULL,
|
||||
policyMode VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
queryKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
secretKey BINARY(20) NOT NULL,
|
||||
isDisabled BOOL NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
KEY `key_author` (authorPHID),
|
||||
UNIQUE KEY `key_secret` (secretKey)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
19
resources/sql/autopatches/20161005.cal.03.exportxaction.sql
Normal file
19
resources/sql/autopatches/20161005.cal.03.exportxaction.sql
Normal file
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE {$NAMESPACE}_calendar.calendar_exporttransaction (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
authorPHID VARBINARY(64) NOT NULL,
|
||||
objectPHID VARBINARY(64) NOT NULL,
|
||||
viewPolicy VARBINARY(64) NOT NULL,
|
||||
editPolicy VARBINARY(64) NOT NULL,
|
||||
commentPHID VARBINARY(64) DEFAULT NULL,
|
||||
commentVersion INT UNSIGNED NOT NULL,
|
||||
transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_phid` (`phid`),
|
||||
KEY `key_object` (`objectPHID`)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_conpherence.conpherence_thread
|
||||
ADD profileImagePHID VARBINARY(64);
|
34
resources/sql/autopatches/20161005.conpherence.image.2.php
Normal file
34
resources/sql/autopatches/20161005.conpherence.image.2.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
// Rebuild all Conpherence Room images to profile standards
|
||||
//
|
||||
$table = new ConpherenceThread();
|
||||
$conn = $table->establishConnection('w');
|
||||
$table_name = 'conpherence_thread';
|
||||
|
||||
foreach (new LiskRawMigrationIterator($conn, $table_name) as $row) {
|
||||
|
||||
$images = phutil_json_decode($row['imagePHIDs']);
|
||||
if (!$images) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file_phid = idx($images, 'original');
|
||||
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withPHIDs(array($file_phid))
|
||||
->executeOne();
|
||||
|
||||
$xform = PhabricatorFileTransform::getTransformByKey(
|
||||
PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE);
|
||||
$xformed = $xform->executeTransform($file);
|
||||
$new_phid = $xformed->getPHID();
|
||||
|
||||
queryfx(
|
||||
$conn,
|
||||
'UPDATE %T SET profileImagePHID = %s WHERE id = %d',
|
||||
$table->getTableName(),
|
||||
$new_phid,
|
||||
$row['id']);
|
||||
}
|
|
@ -292,7 +292,6 @@ phutil_register_library_map(array(
|
|||
'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php',
|
||||
'ConpherenceFormDragAndDropUploadControl' => 'applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php',
|
||||
'ConpherenceFulltextQuery' => 'applications/conpherence/query/ConpherenceFulltextQuery.php',
|
||||
'ConpherenceImageData' => 'applications/conpherence/constants/ConpherenceImageData.php',
|
||||
'ConpherenceIndex' => 'applications/conpherence/storage/ConpherenceIndex.php',
|
||||
'ConpherenceLayoutView' => 'applications/conpherence/view/ConpherenceLayoutView.php',
|
||||
'ConpherenceListController' => 'applications/conpherence/controller/ConpherenceListController.php',
|
||||
|
@ -305,11 +304,11 @@ phutil_register_library_map(array(
|
|||
'ConpherenceParticipantQuery' => 'applications/conpherence/query/ConpherenceParticipantQuery.php',
|
||||
'ConpherenceParticipantView' => 'applications/conpherence/view/ConpherenceParticipantView.php',
|
||||
'ConpherenceParticipationStatus' => 'applications/conpherence/constants/ConpherenceParticipationStatus.php',
|
||||
'ConpherencePicCropControl' => 'applications/conpherence/view/ConpherencePicCropControl.php',
|
||||
'ConpherenceQueryThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php',
|
||||
'ConpherenceQueryTransactionConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryTransactionConduitAPIMethod.php',
|
||||
'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php',
|
||||
'ConpherenceRoomListController' => 'applications/conpherence/controller/ConpherenceRoomListController.php',
|
||||
'ConpherenceRoomPictureController' => 'applications/conpherence/controller/ConpherenceRoomPictureController.php',
|
||||
'ConpherenceRoomTestCase' => 'applications/conpherence/__tests__/ConpherenceRoomTestCase.php',
|
||||
'ConpherenceSchemaSpec' => 'applications/conpherence/storage/ConpherenceSchemaSpec.php',
|
||||
'ConpherenceTestCase' => 'applications/conpherence/__tests__/ConpherenceTestCase.php',
|
||||
|
@ -2076,8 +2075,27 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCalendarEventTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarEventTransactionType.php',
|
||||
'PhabricatorCalendarEventUntilDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php',
|
||||
'PhabricatorCalendarEventViewController' => 'applications/calendar/controller/PhabricatorCalendarEventViewController.php',
|
||||
'PhabricatorCalendarExport' => 'applications/calendar/storage/PhabricatorCalendarExport.php',
|
||||
'PhabricatorCalendarExportDisableController' => 'applications/calendar/controller/PhabricatorCalendarExportDisableController.php',
|
||||
'PhabricatorCalendarExportDisableTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportDisableTransaction.php',
|
||||
'PhabricatorCalendarExportEditController' => 'applications/calendar/controller/PhabricatorCalendarExportEditController.php',
|
||||
'PhabricatorCalendarExportEditEngine' => 'applications/calendar/editor/PhabricatorCalendarExportEditEngine.php',
|
||||
'PhabricatorCalendarExportEditor' => 'applications/calendar/editor/PhabricatorCalendarExportEditor.php',
|
||||
'PhabricatorCalendarExportICSController' => 'applications/calendar/controller/PhabricatorCalendarExportICSController.php',
|
||||
'PhabricatorCalendarExportListController' => 'applications/calendar/controller/PhabricatorCalendarExportListController.php',
|
||||
'PhabricatorCalendarExportModeTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportModeTransaction.php',
|
||||
'PhabricatorCalendarExportNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportNameTransaction.php',
|
||||
'PhabricatorCalendarExportPHIDType' => 'applications/calendar/phid/PhabricatorCalendarExportPHIDType.php',
|
||||
'PhabricatorCalendarExportQuery' => 'applications/calendar/query/PhabricatorCalendarExportQuery.php',
|
||||
'PhabricatorCalendarExportQueryKeyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php',
|
||||
'PhabricatorCalendarExportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarExportSearchEngine.php',
|
||||
'PhabricatorCalendarExportTransaction' => 'applications/calendar/storage/PhabricatorCalendarExportTransaction.php',
|
||||
'PhabricatorCalendarExportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php',
|
||||
'PhabricatorCalendarExportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php',
|
||||
'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.php',
|
||||
'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php',
|
||||
'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php',
|
||||
'PhabricatorCalendarICSWriter' => 'applications/calendar/util/PhabricatorCalendarICSWriter.php',
|
||||
'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php',
|
||||
'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php',
|
||||
'PhabricatorCalendarReplyHandler' => 'applications/calendar/mail/PhabricatorCalendarReplyHandler.php',
|
||||
|
@ -4769,7 +4787,6 @@ phutil_register_library_map(array(
|
|||
'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'ConpherenceFormDragAndDropUploadControl' => 'AphrontFormControl',
|
||||
'ConpherenceFulltextQuery' => 'PhabricatorOffsetPagedQuery',
|
||||
'ConpherenceImageData' => 'ConpherenceConstants',
|
||||
'ConpherenceIndex' => 'ConpherenceDAO',
|
||||
'ConpherenceLayoutView' => 'AphrontTagView',
|
||||
'ConpherenceListController' => 'ConpherenceController',
|
||||
|
@ -4782,11 +4799,11 @@ phutil_register_library_map(array(
|
|||
'ConpherenceParticipantQuery' => 'PhabricatorOffsetPagedQuery',
|
||||
'ConpherenceParticipantView' => 'AphrontView',
|
||||
'ConpherenceParticipationStatus' => 'ConpherenceConstants',
|
||||
'ConpherencePicCropControl' => 'AphrontFormControl',
|
||||
'ConpherenceQueryThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod',
|
||||
'ConpherenceQueryTransactionConduitAPIMethod' => 'ConpherenceConduitAPIMethod',
|
||||
'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||
'ConpherenceRoomListController' => 'ConpherenceController',
|
||||
'ConpherenceRoomPictureController' => 'ConpherenceController',
|
||||
'ConpherenceRoomTestCase' => 'ConpherenceTestCase',
|
||||
'ConpherenceSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||
'ConpherenceTestCase' => 'PhabricatorTestCase',
|
||||
|
@ -6827,8 +6844,32 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCalendarEventTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'PhabricatorCalendarEventUntilDateTransaction' => 'PhabricatorCalendarEventDateTransaction',
|
||||
'PhabricatorCalendarEventViewController' => 'PhabricatorCalendarController',
|
||||
'PhabricatorCalendarExport' => array(
|
||||
'PhabricatorCalendarDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
),
|
||||
'PhabricatorCalendarExportDisableController' => 'PhabricatorCalendarController',
|
||||
'PhabricatorCalendarExportDisableTransaction' => 'PhabricatorCalendarExportTransactionType',
|
||||
'PhabricatorCalendarExportEditController' => 'PhabricatorCalendarController',
|
||||
'PhabricatorCalendarExportEditEngine' => 'PhabricatorEditEngine',
|
||||
'PhabricatorCalendarExportEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhabricatorCalendarExportICSController' => 'PhabricatorCalendarController',
|
||||
'PhabricatorCalendarExportListController' => 'PhabricatorCalendarController',
|
||||
'PhabricatorCalendarExportModeTransaction' => 'PhabricatorCalendarExportTransactionType',
|
||||
'PhabricatorCalendarExportNameTransaction' => 'PhabricatorCalendarExportTransactionType',
|
||||
'PhabricatorCalendarExportPHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorCalendarExportQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorCalendarExportQueryKeyTransaction' => 'PhabricatorCalendarExportTransactionType',
|
||||
'PhabricatorCalendarExportSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorCalendarExportTransaction' => 'PhabricatorModularTransaction',
|
||||
'PhabricatorCalendarExportTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorCalendarExportTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController',
|
||||
'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO',
|
||||
'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorCalendarICSWriter' => 'Phobject',
|
||||
'PhabricatorCalendarIconSet' => 'PhabricatorIconSet',
|
||||
'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||
'PhabricatorCalendarReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
|
||||
|
|
|
@ -62,6 +62,19 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication {
|
|||
'export/(?P<id>[1-9]\d*)/(?P<filename>[^/]*)'
|
||||
=> 'PhabricatorCalendarEventExportController',
|
||||
),
|
||||
'export/' => array(
|
||||
$this->getQueryRoutePattern()
|
||||
=> 'PhabricatorCalendarExportListController',
|
||||
$this->getEditRoutePattern('edit/')
|
||||
=> 'PhabricatorCalendarExportEditController',
|
||||
'(?P<id>[1-9]\d*)/'
|
||||
=> 'PhabricatorCalendarExportViewController',
|
||||
'ics/(?P<secretKey>[^/]+)/(?P<filename>[^/]*)'
|
||||
=> 'PhabricatorCalendarExportICSController',
|
||||
'disable/(?P<id>[1-9]\d*)/'
|
||||
=> 'PhabricatorCalendarExportDisableController',
|
||||
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,4 +2,20 @@
|
|||
|
||||
abstract class PhabricatorCalendarController extends PhabricatorController {
|
||||
|
||||
protected function newICSResponse(
|
||||
PhabricatorUser $viewer,
|
||||
$file_name,
|
||||
array $events) {
|
||||
|
||||
$ics_data = id(new PhabricatorCalendarICSWriter())
|
||||
->setViewer($viewer)
|
||||
->setEvents($events)
|
||||
->writeICSDocument();
|
||||
|
||||
return id(new AphrontFileResponse())
|
||||
->setDownload($file_name)
|
||||
->setMimeType('text/calendar')
|
||||
->setContent($ics_data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,22 +19,16 @@ final class PhabricatorCalendarEventExportController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$file_name = $event->getICSFilename();
|
||||
$event_node = $event->newIntermediateEventNode($viewer);
|
||||
if ($event->isChildEvent()) {
|
||||
$target = $event->getParentEvent();
|
||||
} else {
|
||||
$target = $event;
|
||||
}
|
||||
|
||||
$document_node = id(new PhutilCalendarDocumentNode())
|
||||
->appendChild($event_node);
|
||||
|
||||
$root_node = id(new PhutilCalendarRootNode())
|
||||
->appendChild($document_node);
|
||||
|
||||
$ics_data = id(new PhutilICSWriter())
|
||||
->writeICSDocument($root_node);
|
||||
|
||||
return id(new AphrontFileResponse())
|
||||
->setDownload($file_name)
|
||||
->setMimeType('text/calendar')
|
||||
->setContent($ics_data);
|
||||
return $this->newICSResponse(
|
||||
$viewer,
|
||||
$target->getICSFileName(),
|
||||
array($target));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,17 +11,19 @@ final class PhabricatorCalendarEventListController
|
|||
$year = $request->getURIData('year');
|
||||
$month = $request->getURIData('month');
|
||||
$day = $request->getURIData('day');
|
||||
|
||||
$engine = new PhabricatorCalendarEventSearchEngine();
|
||||
|
||||
if ($month && $year) {
|
||||
$engine->setCalendarYearAndMonthAndDay($year, $month, $day);
|
||||
}
|
||||
|
||||
$controller = id(new PhabricatorApplicationSearchController())
|
||||
->setQueryKey($request->getURIData('queryKey'))
|
||||
->setSearchEngine($engine);
|
||||
$nav_items = $this->buildNavigationItems();
|
||||
|
||||
return $this->delegateToController($controller);
|
||||
return $engine
|
||||
->setNavigationItems($nav_items)
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
|
@ -34,4 +36,18 @@ final class PhabricatorCalendarEventListController
|
|||
return $crumbs;
|
||||
}
|
||||
|
||||
protected function buildNavigationItems() {
|
||||
$items = array();
|
||||
|
||||
$items[] = id(new PHUIListItemView())
|
||||
->setType(PHUIListItemView::TYPE_LABEL)
|
||||
->setName(pht('Import/Export'));
|
||||
|
||||
$items[] = id(new PHUIListItemView())
|
||||
->setName('Exports')
|
||||
->setHref('/calendar/export/');
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,8 +27,8 @@ final class PhabricatorCalendarEventViewController
|
|||
$page_title = $monogram.' '.$event->getName();
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
|
||||
$start = new DateTime('@'.$event->getViewerDateFrom());
|
||||
$start->setTimeZone($viewer->getTimeZone());
|
||||
$start = $event->newStartDateTime()
|
||||
->newPHPDateTime();
|
||||
|
||||
$crumbs->addTextCrumb(
|
||||
$start->format('F Y'),
|
||||
|
@ -72,9 +72,9 @@ final class PhabricatorCalendarEventViewController
|
|||
$comment_view,
|
||||
))
|
||||
->setCurtain($curtain)
|
||||
->addPropertySection($details_header, $details)
|
||||
->addPropertySection(pht('Description'), $description)
|
||||
->addPropertySection($recurring_header, $recurring)
|
||||
->addPropertySection(pht('Description'), $description);
|
||||
->addPropertySection($details_header, $details);
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($page_title)
|
||||
|
@ -348,9 +348,16 @@ final class PhabricatorCalendarEventViewController
|
|||
->render();
|
||||
}
|
||||
|
||||
$rule = $event->getFrequencyRule();
|
||||
switch ($rule) {
|
||||
case PhabricatorCalendarEvent::FREQUENCY_DAILY:
|
||||
$rrule = $event->newRecurrenceRule();
|
||||
|
||||
if ($rrule) {
|
||||
$frequency = $rrule->getFrequency();
|
||||
} else {
|
||||
$frequency = null;
|
||||
}
|
||||
|
||||
switch ($frequency) {
|
||||
case PhutilCalendarRecurrenceRule::FREQUENCY_DAILY:
|
||||
if ($is_parent) {
|
||||
$message = pht('This event repeats every day.');
|
||||
} else {
|
||||
|
@ -359,7 +366,7 @@ final class PhabricatorCalendarEventViewController
|
|||
$parent_link);
|
||||
}
|
||||
break;
|
||||
case PhabricatorCalendarEvent::FREQUENCY_WEEKLY:
|
||||
case PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY:
|
||||
if ($is_parent) {
|
||||
$message = pht('This event repeats every week.');
|
||||
} else {
|
||||
|
@ -368,7 +375,7 @@ final class PhabricatorCalendarEventViewController
|
|||
$parent_link);
|
||||
}
|
||||
break;
|
||||
case PhabricatorCalendarEvent::FREQUENCY_MONTHLY:
|
||||
case PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY:
|
||||
if ($is_parent) {
|
||||
$message = pht('This event repeats every month.');
|
||||
} else {
|
||||
|
@ -377,7 +384,7 @@ final class PhabricatorCalendarEventViewController
|
|||
$parent_link);
|
||||
}
|
||||
break;
|
||||
case PhabricatorCalendarEvent::FREQUENCY_YEARLY:
|
||||
case PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY:
|
||||
if ($is_parent) {
|
||||
$message = pht('This event repeats every year.');
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportDisableController
|
||||
extends PhabricatorCalendarController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$export = id(new PhabricatorCalendarExportQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($request->getURIData('id')))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$export) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$export_uri = $export->getURI();
|
||||
$is_disable = !$export->getIsDisabled();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PhabricatorCalendarExportTransaction())
|
||||
->setTransactionType(
|
||||
PhabricatorCalendarExportDisableTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue($is_disable ? 1 : 0);
|
||||
|
||||
$editor = id(new PhabricatorCalendarExportEditor())
|
||||
->setActor($viewer)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContentSourceFromRequest($request);
|
||||
|
||||
$editor->applyTransactions($export, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($export_uri);
|
||||
}
|
||||
|
||||
if ($is_disable) {
|
||||
$title = pht('Disable Export');
|
||||
$body = pht(
|
||||
'Disable this export? The export URI will no longer function.');
|
||||
$button = pht('Disable Export');
|
||||
} else {
|
||||
$title = pht('Enable Export');
|
||||
$body = pht(
|
||||
'Enable this export? Anyone who knows the export URI will be able '.
|
||||
'to export the data.');
|
||||
$button = pht('Enable Export');
|
||||
}
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle($title)
|
||||
->appendParagraph($body)
|
||||
->addCancelButton($export_uri)
|
||||
->addSubmitButton($button);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportEditController
|
||||
extends PhabricatorCalendarController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
return id(new PhabricatorCalendarExportEditEngine())
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportICSController
|
||||
extends PhabricatorCalendarController {
|
||||
|
||||
public function shouldRequireLogin() {
|
||||
// Export URIs are available if you know the secret key. We can't do any
|
||||
// other kind of authentication because third-party applications like
|
||||
// Google Calendar and Calendar.app need to be able to fetch these URIs.
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$omnipotent = PhabricatorUser::getOmnipotentUser();
|
||||
|
||||
// NOTE: We're using the omnipotent viewer to fetch the export, but the
|
||||
// URI must contain the secret key. Once we load the export we'll figure
|
||||
// out who the effective viewer is.
|
||||
$export = id(new PhabricatorCalendarExportQuery())
|
||||
->setViewer($omnipotent)
|
||||
->withSecretKeys(array($request->getURIData('secretKey')))
|
||||
->executeOne();
|
||||
if (!$export) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
if ($export->getIsDisabled()) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$author = id(new PhabricatorPeopleQuery())
|
||||
->setViewer($omnipotent)
|
||||
->withPHIDs(array($export->getAuthorPHID()))
|
||||
->needUserSettings(true)
|
||||
->executeOne();
|
||||
if (!$author) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$mode = $export->getPolicyMode();
|
||||
switch ($mode) {
|
||||
case PhabricatorCalendarExport::MODE_PUBLIC:
|
||||
$viewer = new PhabricatorUser();
|
||||
break;
|
||||
case PhabricatorCalendarExport::MODE_PRIVILEGED:
|
||||
$viewer = $author;
|
||||
break;
|
||||
default:
|
||||
throw new Exception(
|
||||
pht(
|
||||
'This export has an invalid mode ("%s").',
|
||||
$mode));
|
||||
}
|
||||
|
||||
$engine = id(new PhabricatorCalendarEventSearchEngine())
|
||||
->setViewer($viewer);
|
||||
|
||||
$query_key = $export->getQueryKey();
|
||||
$saved = id(new PhabricatorSavedQueryQuery())
|
||||
->setViewer($omnipotent)
|
||||
->withEngineClassNames(array(get_class($engine)))
|
||||
->withQueryKeys(array($query_key))
|
||||
->executeOne();
|
||||
if (!$saved) {
|
||||
$saved = $engine->buildSavedQueryFromBuiltin($query_key);
|
||||
}
|
||||
|
||||
if (!$saved) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$saved = clone $saved;
|
||||
|
||||
// Mark this as a query for export, so we get the correct ghost/recurring
|
||||
// behaviors. We also want to load all matching events.
|
||||
$saved->setParameter('export', true);
|
||||
$saved->setParameter('limit', 0xFFFF);
|
||||
|
||||
// Remove any range constraints. We always export all matching events into
|
||||
// ICS files.
|
||||
$saved->setParameter('rangeStart', null);
|
||||
$saved->setParameter('rangeEnd', null);
|
||||
$saved->setParameter('upcoming', null);
|
||||
|
||||
$query = $engine->buildQueryFromSavedQuery($saved);
|
||||
|
||||
$events = $query
|
||||
->setViewer($viewer)
|
||||
->execute();
|
||||
|
||||
return $this->newICSResponse(
|
||||
$viewer,
|
||||
$export->getICSFilename(),
|
||||
$events);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportListController
|
||||
extends PhabricatorCalendarController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
return id(new PhabricatorCalendarExportSearchEngine())
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
$crumbs = parent::buildApplicationCrumbs();
|
||||
|
||||
$doc_name = 'Calendar User Guide: Exporting Events';
|
||||
$doc_href = PhabricatorEnv::getDoclink($doc_name);
|
||||
|
||||
$crumbs->addAction(
|
||||
id(new PHUIListItemView())
|
||||
->setName(pht('Guide: Exporting Events'))
|
||||
->setIcon('fa-book')
|
||||
->setHref($doc_href));
|
||||
|
||||
return $crumbs;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportViewController
|
||||
extends PhabricatorCalendarController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$export = id(new PhabricatorCalendarExportQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($request->getURIData('id')))
|
||||
->executeOne();
|
||||
if (!$export) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(
|
||||
pht('Exports'),
|
||||
'/calendar/export/');
|
||||
$crumbs->addTextCrumb(pht('Export %d', $export->getID()));
|
||||
$crumbs->setBorder(true);
|
||||
|
||||
$timeline = $this->buildTransactionTimeline(
|
||||
$export,
|
||||
new PhabricatorCalendarExportTransactionQuery());
|
||||
$timeline->setShouldTerminate(true);
|
||||
|
||||
$header = $this->buildHeaderView($export);
|
||||
$curtain = $this->buildCurtain($export);
|
||||
$details = $this->buildPropertySection($export);
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setMainColumn(
|
||||
array(
|
||||
$timeline,
|
||||
))
|
||||
->setCurtain($curtain)
|
||||
->addPropertySection(pht('Details'), $details);
|
||||
|
||||
$page_title = pht('Export %d %s', $export->getID(), $export->getName());
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($page_title)
|
||||
->setCrumbs($crumbs)
|
||||
->setPageObjectPHIDs(array($export->getPHID()))
|
||||
->appendChild($view);
|
||||
}
|
||||
|
||||
private function buildHeaderView(
|
||||
PhabricatorCalendarExport $export) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $export->getID();
|
||||
|
||||
if ($export->getIsDisabled()) {
|
||||
$icon = 'fa-ban';
|
||||
$color = 'red';
|
||||
$status = pht('Disabled');
|
||||
} else {
|
||||
$icon = 'fa-check';
|
||||
$color = 'bluegrey';
|
||||
$status = pht('Active');
|
||||
}
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setUser($viewer)
|
||||
->setHeader($export->getName())
|
||||
->setStatus($icon, $color, $status)
|
||||
->setPolicyObject($export);
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
private function buildCurtain(PhabricatorCalendarExport $export) {
|
||||
$viewer = $this->getRequest()->getUser();
|
||||
$id = $export->getID();
|
||||
|
||||
$curtain = $this->newCurtainView($export);
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$export,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$ics_uri = $export->getICSURI();
|
||||
|
||||
$edit_uri = "export/edit/{$id}/";
|
||||
$edit_uri = $this->getApplicationURI($edit_uri);
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Edit Export'))
|
||||
->setIcon('fa-pencil')
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(!$can_edit)
|
||||
->setHref($edit_uri));
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Export as .ics'))
|
||||
->setIcon('fa-download')
|
||||
->setHref($ics_uri));
|
||||
|
||||
$disable_uri = "export/disable/{$id}/";
|
||||
$disable_uri = $this->getApplicationURI($disable_uri);
|
||||
if ($export->getIsDisabled()) {
|
||||
$disable_name = pht('Enable Export');
|
||||
$disable_icon = 'fa-check';
|
||||
} else {
|
||||
$disable_name = pht('Disable Export');
|
||||
$disable_icon = 'fa-ban';
|
||||
}
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName($disable_name)
|
||||
->setIcon($disable_icon)
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true)
|
||||
->setHref($disable_uri));
|
||||
|
||||
return $curtain;
|
||||
}
|
||||
|
||||
private function buildPropertySection(
|
||||
PhabricatorCalendarExport $export) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$properties = id(new PHUIPropertyListView())
|
||||
->setUser($viewer);
|
||||
|
||||
$mode = $export->getPolicyMode();
|
||||
|
||||
$policy_icon = PhabricatorCalendarExport::getPolicyModeIcon($mode);
|
||||
$policy_name = PhabricatorCalendarExport::getPolicyModeName($mode);
|
||||
$policy_desc = PhabricatorCalendarExport::getPolicyModeDescription($mode);
|
||||
$policy_color = PhabricatorCalendarExport::getPolicyModeColor($mode);
|
||||
|
||||
$policy_view = id(new PHUIStatusListView())
|
||||
->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon($policy_icon, $policy_color)
|
||||
->setTarget($policy_name)
|
||||
->setNote($policy_desc));
|
||||
|
||||
$properties->addProperty(pht('Mode'), $policy_view);
|
||||
|
||||
$query_key = $export->getQueryKey();
|
||||
$query_link = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $this->getApplicationURI("/query/{$query_key}/"),
|
||||
),
|
||||
$query_key);
|
||||
$properties->addProperty(pht('Query'), $query_link);
|
||||
|
||||
$ics_uri = $export->getICSURI();
|
||||
$ics_uri = PhabricatorEnv::getURI($ics_uri);
|
||||
|
||||
if ($export->getIsDisabled()) {
|
||||
$ics_href = phutil_tag('em', array(), $ics_uri);
|
||||
} else {
|
||||
$ics_href = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $ics_uri,
|
||||
),
|
||||
$ics_uri);
|
||||
}
|
||||
|
||||
$properties->addProperty(pht('ICS URI'), $ics_href);
|
||||
|
||||
return $properties;
|
||||
}
|
||||
}
|
|
@ -68,10 +68,10 @@ final class PhabricatorCalendarEventEditEngine
|
|||
}
|
||||
|
||||
$frequency_options = array(
|
||||
PhabricatorCalendarEvent::FREQUENCY_DAILY => pht('Daily'),
|
||||
PhabricatorCalendarEvent::FREQUENCY_WEEKLY => pht('Weekly'),
|
||||
PhabricatorCalendarEvent::FREQUENCY_MONTHLY => pht('Monthly'),
|
||||
PhabricatorCalendarEvent::FREQUENCY_YEARLY => pht('Yearly'),
|
||||
PhutilCalendarRecurrenceRule::FREQUENCY_DAILY => pht('Daily'),
|
||||
PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY => pht('Weekly'),
|
||||
PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY => pht('Monthly'),
|
||||
PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY => pht('Yearly'),
|
||||
);
|
||||
|
||||
$fields = array(
|
||||
|
@ -142,6 +142,14 @@ final class PhabricatorCalendarEventEditEngine
|
|||
->setConduitTypeDescription(pht('Mark the event as a recurring event.'))
|
||||
->setValue($object->getIsRecurring());
|
||||
|
||||
|
||||
$rrule = $object->newRecurrenceRule();
|
||||
if ($rrule) {
|
||||
$frequency = $rrule->getFrequency();
|
||||
} else {
|
||||
$frequency = null;
|
||||
}
|
||||
|
||||
$fields[] = id(new PhabricatorSelectEditField())
|
||||
->setKey('frequency')
|
||||
->setLabel(pht('Frequency'))
|
||||
|
@ -151,7 +159,7 @@ final class PhabricatorCalendarEventEditEngine
|
|||
->setDescription(pht('Recurring event frequency.'))
|
||||
->setConduitDescription(pht('Change the event frequency.'))
|
||||
->setConduitTypeDescription(pht('New event frequency.'))
|
||||
->setValue($object->getFrequencyRule());
|
||||
->setValue($frequency);
|
||||
}
|
||||
|
||||
if ($this->getIsCreate() || $object->getIsRecurring()) {
|
||||
|
@ -164,50 +172,50 @@ final class PhabricatorCalendarEventEditEngine
|
|||
->setDescription(pht('Last instance of the event.'))
|
||||
->setConduitDescription(pht('Change when the event repeats until.'))
|
||||
->setConduitTypeDescription(pht('New final event time.'))
|
||||
->setValue($object->getRecurrenceEndDate());
|
||||
->setValue($object->getUntilDateTimeEpoch());
|
||||
}
|
||||
|
||||
$fields[] = id(new PhabricatorBoolEditField())
|
||||
->setKey('isAllDay')
|
||||
->setLabel(pht('All Day'))
|
||||
->setOptions(pht('Normal Event'), pht('All Day Event'))
|
||||
->setTransactionType(
|
||||
PhabricatorCalendarEventAllDayTransaction::TRANSACTIONTYPE)
|
||||
->setDescription(pht('Marks this as an all day event.'))
|
||||
->setConduitDescription(pht('Make the event an all day event.'))
|
||||
->setConduitTypeDescription(pht('Mark the event as an all day event.'))
|
||||
->setValue($object->getIsAllDay());
|
||||
->setKey('isAllDay')
|
||||
->setLabel(pht('All Day'))
|
||||
->setOptions(pht('Normal Event'), pht('All Day Event'))
|
||||
->setTransactionType(
|
||||
PhabricatorCalendarEventAllDayTransaction::TRANSACTIONTYPE)
|
||||
->setDescription(pht('Marks this as an all day event.'))
|
||||
->setConduitDescription(pht('Make the event an all day event.'))
|
||||
->setConduitTypeDescription(pht('Mark the event as an all day event.'))
|
||||
->setValue($object->getIsAllDay());
|
||||
|
||||
$fields[] = id(new PhabricatorEpochEditField())
|
||||
->setKey('start')
|
||||
->setLabel(pht('Start'))
|
||||
->setTransactionType(
|
||||
PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE)
|
||||
->setDescription(pht('Start time of the event.'))
|
||||
->setConduitDescription(pht('Change the start time of the event.'))
|
||||
->setConduitTypeDescription(pht('New event start time.'))
|
||||
->setValue($object->getViewerDateFrom());
|
||||
->setKey('start')
|
||||
->setLabel(pht('Start'))
|
||||
->setTransactionType(
|
||||
PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE)
|
||||
->setDescription(pht('Start time of the event.'))
|
||||
->setConduitDescription(pht('Change the start time of the event.'))
|
||||
->setConduitTypeDescription(pht('New event start time.'))
|
||||
->setValue($object->getStartDateTimeEpoch());
|
||||
|
||||
$fields[] = id(new PhabricatorEpochEditField())
|
||||
->setKey('end')
|
||||
->setLabel(pht('End'))
|
||||
->setTransactionType(
|
||||
PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE)
|
||||
->setDescription(pht('End time of the event.'))
|
||||
->setConduitDescription(pht('Change the end time of the event.'))
|
||||
->setConduitTypeDescription(pht('New event end time.'))
|
||||
->setValue($object->getViewerDateTo());
|
||||
->setKey('end')
|
||||
->setLabel(pht('End'))
|
||||
->setTransactionType(
|
||||
PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE)
|
||||
->setDescription(pht('End time of the event.'))
|
||||
->setConduitDescription(pht('Change the end time of the event.'))
|
||||
->setConduitTypeDescription(pht('New event end time.'))
|
||||
->setValue($object->getEndDateTimeEpoch());
|
||||
|
||||
$fields[] = id(new PhabricatorIconSetEditField())
|
||||
->setKey('icon')
|
||||
->setLabel(pht('Icon'))
|
||||
->setIconSet(new PhabricatorCalendarIconSet())
|
||||
->setTransactionType(
|
||||
PhabricatorCalendarEventIconTransaction::TRANSACTIONTYPE)
|
||||
->setDescription(pht('Event icon.'))
|
||||
->setConduitDescription(pht('Change the event icon.'))
|
||||
->setConduitTypeDescription(pht('New event icon.'))
|
||||
->setValue($object->getIcon());
|
||||
->setKey('icon')
|
||||
->setLabel(pht('Icon'))
|
||||
->setIconSet(new PhabricatorCalendarIconSet())
|
||||
->setTransactionType(
|
||||
PhabricatorCalendarEventIconTransaction::TRANSACTIONTYPE)
|
||||
->setDescription(pht('Event icon.'))
|
||||
->setConduitDescription(pht('Change the event icon.'))
|
||||
->setConduitTypeDescription(pht('New event icon.'))
|
||||
->setValue($object->getIcon());
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
|
|
@ -139,7 +139,7 @@ final class PhabricatorCalendarEventEditor
|
|||
WHERE phid IN (%Ls) AND availabilityCacheTTL >= %d',
|
||||
$user->getTableName(),
|
||||
$phids,
|
||||
$object->getDateFromForCache());
|
||||
$object->getStartDateTimeEpochForCache());
|
||||
}
|
||||
|
||||
return $xactions;
|
||||
|
@ -159,9 +159,9 @@ final class PhabricatorCalendarEventEditor
|
|||
$recurrence_end_xaction =
|
||||
PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE;
|
||||
|
||||
$start_date = $object->getDateFrom();
|
||||
$end_date = $object->getDateTo();
|
||||
$recurrence_end = $object->getRecurrenceEndDate();
|
||||
$start_date = $object->getStartDateTimeEpoch();
|
||||
$end_date = $object->getEndDateTimeEpoch();
|
||||
$recurrence_end = $object->getUntilDateTimeEpoch();
|
||||
$is_recurring = $object->getIsRecurring();
|
||||
|
||||
$errors = array();
|
||||
|
@ -309,16 +309,10 @@ final class PhabricatorCalendarEventEditor
|
|||
PhabricatorCalendarEvent $event) {
|
||||
$actor = $this->getActor();
|
||||
|
||||
$event_node = $event->newIntermediateEventNode($actor);
|
||||
|
||||
$document_node = id(new PhutilCalendarDocumentNode())
|
||||
->appendChild($event_node);
|
||||
|
||||
$root_node = id(new PhutilCalendarRootNode())
|
||||
->appendChild($document_node);
|
||||
|
||||
$ics_data = id(new PhutilICSWriter())
|
||||
->writeICSDocument($root_node);
|
||||
$ics_data = id(new PhabricatorCalendarICSWriter())
|
||||
->setViewer($actor)
|
||||
->setEvents(array($event))
|
||||
->writeICSDocument();
|
||||
|
||||
$ics_attachment = new PhabricatorMetaMTAAttachment(
|
||||
$ics_data,
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportEditEngine
|
||||
extends PhabricatorEditEngine {
|
||||
|
||||
const ENGINECONST = 'calendar.export';
|
||||
|
||||
public function getEngineName() {
|
||||
return pht('Calendar Exports');
|
||||
}
|
||||
|
||||
public function isEngineConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getSummaryHeader() {
|
||||
return pht('Configure Calendar Export Forms');
|
||||
}
|
||||
|
||||
public function getSummaryText() {
|
||||
return pht('Configure how users create and edit exports.');
|
||||
}
|
||||
|
||||
public function getEngineApplicationClass() {
|
||||
return 'PhabricatorCalendarApplication';
|
||||
}
|
||||
|
||||
protected function newEditableObject() {
|
||||
return PhabricatorCalendarExport::initializeNewCalendarExport(
|
||||
$this->getViewer());
|
||||
}
|
||||
|
||||
protected function newObjectQuery() {
|
||||
return new PhabricatorCalendarExportQuery();
|
||||
}
|
||||
|
||||
protected function getObjectCreateTitleText($object) {
|
||||
return pht('Create New Export');
|
||||
}
|
||||
|
||||
protected function getObjectEditTitleText($object) {
|
||||
return pht('Edit Export: %s', $object->getName());
|
||||
}
|
||||
|
||||
protected function getObjectEditShortText($object) {
|
||||
return pht('Export %d', $object->getID());
|
||||
}
|
||||
|
||||
protected function getObjectCreateShortText() {
|
||||
return pht('Create Export');
|
||||
}
|
||||
|
||||
protected function getObjectName() {
|
||||
return pht('Export');
|
||||
}
|
||||
|
||||
protected function getObjectViewURI($object) {
|
||||
return $object->getURI();
|
||||
}
|
||||
|
||||
protected function getEditorURI() {
|
||||
return $this->getApplication()->getApplicationURI('export/edit/');
|
||||
}
|
||||
|
||||
protected function buildCustomEditFields($object) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$export_modes = PhabricatorCalendarExport::getAvailablePolicyModes();
|
||||
$export_modes = array_fuse($export_modes);
|
||||
|
||||
$current_mode = $object->getPolicyMode();
|
||||
if (empty($export_modes[$current_mode])) {
|
||||
array_shift($export_modes, $current_mode);
|
||||
}
|
||||
|
||||
$mode_options = array();
|
||||
foreach ($export_modes as $export_mode) {
|
||||
$mode_name = PhabricatorCalendarExport::getPolicyModeName($export_mode);
|
||||
$mode_summary = PhabricatorCalendarExport::getPolicyModeSummary(
|
||||
$export_mode);
|
||||
$mode_options[$export_mode] = pht('%s: %s', $mode_name, $mode_summary);
|
||||
}
|
||||
|
||||
$fields = array(
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('name')
|
||||
->setLabel(pht('Name'))
|
||||
->setDescription(pht('Name of the export.'))
|
||||
->setIsRequired(true)
|
||||
->setTransactionType(
|
||||
PhabricatorCalendarExportNameTransaction::TRANSACTIONTYPE)
|
||||
->setConduitDescription(pht('Rename the export.'))
|
||||
->setConduitTypeDescription(pht('New export name.'))
|
||||
->setValue($object->getName()),
|
||||
id(new PhabricatorBoolEditField())
|
||||
->setKey('disabled')
|
||||
->setOptions(pht('Active'), pht('Disabled'))
|
||||
->setLabel(pht('Disabled'))
|
||||
->setDescription(pht('Disable the export.'))
|
||||
->setTransactionType(
|
||||
PhabricatorCalendarExportDisableTransaction::TRANSACTIONTYPE)
|
||||
->setIsConduitOnly(true)
|
||||
->setConduitDescription(pht('Disable or restore the export.'))
|
||||
->setConduitTypeDescription(pht('True to cancel the export.'))
|
||||
->setValue($object->getIsDisabled()),
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('queryKey')
|
||||
->setLabel(pht('Query Key'))
|
||||
->setDescription(pht('Query to execute.'))
|
||||
->setIsRequired(true)
|
||||
->setTransactionType(
|
||||
PhabricatorCalendarExportQueryKeyTransaction::TRANSACTIONTYPE)
|
||||
->setConduitDescription(pht('Change the export query key.'))
|
||||
->setConduitTypeDescription(pht('New export query key.'))
|
||||
->setValue($object->getQueryKey()),
|
||||
id(new PhabricatorSelectEditField())
|
||||
->setKey('mode')
|
||||
->setLabel(pht('Mode'))
|
||||
->setTransactionType(
|
||||
PhabricatorCalendarExportModeTransaction::TRANSACTIONTYPE)
|
||||
->setOptions($mode_options)
|
||||
->setDescription(pht('Change the policy mode for the export.'))
|
||||
->setConduitDescription(pht('Adjust export mode.'))
|
||||
->setConduitTypeDescription(pht('New export mode.'))
|
||||
->setValue($current_mode),
|
||||
|
||||
);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorCalendarApplication';
|
||||
}
|
||||
|
||||
public function getEditorObjectsDescription() {
|
||||
return pht('Calendar Exports');
|
||||
}
|
||||
|
||||
public function getCreateObjectTitle($author, $object) {
|
||||
return pht('%s created this export.', $author);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportPHIDType extends PhabricatorPHIDType {
|
||||
|
||||
const TYPECONST = 'CEXP';
|
||||
|
||||
public function getTypeName() {
|
||||
return pht('Calendar Export');
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new PhabricatorCalendarExport();
|
||||
}
|
||||
|
||||
public function getPHIDTypeApplicationClass() {
|
||||
return 'PhabricatorCalendarApplication';
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
||||
return id(new PhabricatorCalendarExportQuery())
|
||||
->withPHIDs($phids);
|
||||
}
|
||||
|
||||
public function loadHandles(
|
||||
PhabricatorHandleQuery $query,
|
||||
array $handles,
|
||||
array $objects) {
|
||||
|
||||
foreach ($handles as $phid => $handle) {
|
||||
$export = $objects[$phid];
|
||||
|
||||
$id = $export->getID();
|
||||
$name = $export->getName();
|
||||
$uri = $export->getURI();
|
||||
|
||||
$handle
|
||||
->setName($name)
|
||||
->setFullName(pht('Calendar Export %s: %s', $id, $name))
|
||||
->setURI($uri);
|
||||
|
||||
if ($export->getIsDisabled()) {
|
||||
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -13,6 +13,7 @@ final class PhabricatorCalendarEventQuery
|
|||
private $eventsWithNoParent;
|
||||
private $instanceSequencePairs;
|
||||
private $isStub;
|
||||
private $parentEventPHIDs;
|
||||
|
||||
private $generateGhosts = false;
|
||||
|
||||
|
@ -71,6 +72,11 @@ final class PhabricatorCalendarEventQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withParentEventPHIDs(array $parent_phids) {
|
||||
$this->parentEventPHIDs = $parent_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getDefaultOrderVector() {
|
||||
return array('start', 'id');
|
||||
}
|
||||
|
@ -99,7 +105,7 @@ final class PhabricatorCalendarEventQuery
|
|||
protected function getPagingValueMap($cursor, array $keys) {
|
||||
$event = $this->loadCursorObject($cursor);
|
||||
return array(
|
||||
'start' => $event->getViewerDateFrom(),
|
||||
'start' => $event->getStartDateTimeEpoch(),
|
||||
'id' => $event->getID(),
|
||||
);
|
||||
}
|
||||
|
@ -165,70 +171,54 @@ final class PhabricatorCalendarEventQuery
|
|||
// discard anything outside of the time window.
|
||||
$events = $this->getEventsInRange($events);
|
||||
|
||||
$enforced_end = null;
|
||||
$generate_from = $this->rangeBegin;
|
||||
$generate_until = $this->rangeEnd;
|
||||
foreach ($parents as $key => $event) {
|
||||
$sequence_start = 0;
|
||||
$sequence_end = null;
|
||||
$start = null;
|
||||
|
||||
$duration = $event->getDuration();
|
||||
|
||||
$frequency = $event->getFrequencyUnit();
|
||||
$modify_key = '+1 '.$frequency;
|
||||
$start_date = $this->getRecurrenceWindowStart(
|
||||
$event,
|
||||
$generate_from - $duration);
|
||||
|
||||
if (($this->rangeBegin !== null) &&
|
||||
($this->rangeBegin > $event->getViewerDateFrom())) {
|
||||
$max_date = $this->rangeBegin - $duration;
|
||||
$date = $event->getViewerDateFrom();
|
||||
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
|
||||
$end_date = $this->getRecurrenceWindowEnd(
|
||||
$event,
|
||||
$generate_until);
|
||||
|
||||
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');
|
||||
$limit = $this->getRecurrenceLimit($event, $raw_limit);
|
||||
|
||||
$set = $event->newRecurrenceSet();
|
||||
|
||||
$recurrences = $set->getEventsBetween(
|
||||
null,
|
||||
$end_date,
|
||||
$limit + 1);
|
||||
|
||||
// We're generating events from the beginning and then filtering them
|
||||
// here (instead of only generating events starting at the start date)
|
||||
// because we need to know the proper sequence indexes to generate ghost
|
||||
// events. This may change after RDATE support.
|
||||
if ($start_date) {
|
||||
$start_epoch = $start_date->getEpoch();
|
||||
} else {
|
||||
$start_epoch = null;
|
||||
}
|
||||
|
||||
foreach ($recurrences as $sequence_index => $sequence_datetime) {
|
||||
if (!$sequence_index) {
|
||||
// This is the parent event, which we already have.
|
||||
continue;
|
||||
}
|
||||
|
||||
$start = $this->rangeBegin;
|
||||
} else {
|
||||
$start = $event->getViewerDateFrom() - $duration;
|
||||
}
|
||||
|
||||
$date = $start;
|
||||
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
|
||||
|
||||
// Select the minimum end time we need to generate events until.
|
||||
$end_times = array();
|
||||
if ($this->rangeEnd) {
|
||||
$end_times[] = $this->rangeEnd;
|
||||
}
|
||||
|
||||
if ($event->getRecurrenceEndDate()) {
|
||||
$end_times[] = $event->getRecurrenceEndDate();
|
||||
}
|
||||
|
||||
if ($enforced_end) {
|
||||
$end_times[] = $enforced_end;
|
||||
}
|
||||
|
||||
if ($end_times) {
|
||||
$end = min($end_times);
|
||||
$sequence_end = $sequence_start;
|
||||
while ($date < $end) {
|
||||
$sequence_end++;
|
||||
$datetime->modify($modify_key);
|
||||
$date = $datetime->format('U');
|
||||
if ($sequence_end > $raw_limit + $sequence_start) {
|
||||
break;
|
||||
if ($start_epoch) {
|
||||
if ($sequence_datetime->getEpoch() < $start_epoch) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$sequence_end = $raw_limit + $sequence_start;
|
||||
}
|
||||
|
||||
$sequence_start = max(1, $sequence_start);
|
||||
for ($index = $sequence_start; $index < $sequence_end; $index++) {
|
||||
$events[] = $event->newGhost($viewer, $index);
|
||||
$events[] = $event->newGhost(
|
||||
$viewer,
|
||||
$sequence_index,
|
||||
$sequence_datetime);
|
||||
}
|
||||
|
||||
// NOTE: We're slicing results every time because this makes it cheaper
|
||||
|
@ -238,9 +228,9 @@ final class PhabricatorCalendarEventQuery
|
|||
|
||||
if ($raw_limit) {
|
||||
if (count($events) > $raw_limit) {
|
||||
$events = msort($events, 'getViewerDateFrom');
|
||||
$events = msort($events, 'getStartDateTimeEpoch');
|
||||
$events = array_slice($events, 0, $raw_limit, true);
|
||||
$enforced_end = last($events)->getViewerDateFrom();
|
||||
$generate_until = last($events)->getEndDateTimeEpoch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -308,7 +298,7 @@ final class PhabricatorCalendarEventQuery
|
|||
}
|
||||
}
|
||||
|
||||
$events = msort($events, 'getViewerDateFrom');
|
||||
$events = msort($events, 'getStartDateTimeEpoch');
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
@ -331,14 +321,14 @@ final class PhabricatorCalendarEventQuery
|
|||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids) {
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'event.id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids) {
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'event.phid IN (%Ls)',
|
||||
|
@ -352,14 +342,14 @@ final class PhabricatorCalendarEventQuery
|
|||
if ($this->rangeBegin) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'event.dateTo >= %d OR event.isRecurring = 1',
|
||||
'(event.utcUntilEpoch >= %d) OR (event.utcUntilEpoch IS NULL)',
|
||||
$this->rangeBegin - phutil_units('16 hours in seconds'));
|
||||
}
|
||||
|
||||
if ($this->rangeEnd) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'event.dateFrom <= %d',
|
||||
'event.utcInitialEpoch <= %d',
|
||||
$this->rangeEnd + phutil_units('16 hours in seconds'));
|
||||
}
|
||||
|
||||
|
@ -370,7 +360,7 @@ final class PhabricatorCalendarEventQuery
|
|||
$this->inviteePHIDs);
|
||||
}
|
||||
|
||||
if ($this->hostPHIDs) {
|
||||
if ($this->hostPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'event.hostPHID IN (%Ls)',
|
||||
|
@ -414,6 +404,13 @@ final class PhabricatorCalendarEventQuery
|
|||
(int)$this->isStub);
|
||||
}
|
||||
|
||||
if ($this->parentEventPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'event.instanceOfEventPHID IN (%Ls)',
|
||||
$this->parentEventPHIDs);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
@ -500,7 +497,7 @@ final class PhabricatorCalendarEventQuery
|
|||
}
|
||||
}
|
||||
|
||||
$events = msort($events, 'getViewerDateFrom');
|
||||
$events = msort($events, 'getStartDateTimeEpoch');
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
@ -510,8 +507,8 @@ final class PhabricatorCalendarEventQuery
|
|||
$range_end = $this->rangeEnd;
|
||||
|
||||
foreach ($events as $key => $event) {
|
||||
$event_start = $event->getViewerDateFrom();
|
||||
$event_end = $event->getViewerDateTo();
|
||||
$event_start = $event->getStartDateTimeEpoch();
|
||||
$event_end = $event->getEndDateTimeEpoch();
|
||||
|
||||
if ($range_start && $event_end < $range_start) {
|
||||
unset($events[$key]);
|
||||
|
@ -525,4 +522,44 @@ final class PhabricatorCalendarEventQuery
|
|||
return $events;
|
||||
}
|
||||
|
||||
private function getRecurrenceWindowStart(
|
||||
PhabricatorCalendarEvent $event,
|
||||
$generate_from) {
|
||||
|
||||
if (!$generate_from) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return PhutilCalendarAbsoluteDateTime::newFromEpoch($generate_from);
|
||||
}
|
||||
|
||||
private function getRecurrenceWindowEnd(
|
||||
PhabricatorCalendarEvent $event,
|
||||
$generate_until) {
|
||||
|
||||
$end_epochs = array();
|
||||
if ($generate_until) {
|
||||
$end_epochs[] = $generate_until;
|
||||
}
|
||||
|
||||
$until_epoch = $event->getUntilDateTimeEpoch();
|
||||
if ($until_epoch) {
|
||||
$end_epochs[] = $until_epoch;
|
||||
}
|
||||
|
||||
if (!$end_epochs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return PhutilCalendarAbsoluteDateTime::newFromEpoch(min($end_epochs));
|
||||
}
|
||||
|
||||
private function getRecurrenceLimit(
|
||||
PhabricatorCalendarEvent $event,
|
||||
$raw_limit) {
|
||||
|
||||
return $raw_limit;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -115,8 +115,12 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
}
|
||||
|
||||
// Generate ghosts (and ignore stub events) if we aren't querying for
|
||||
// specific events.
|
||||
if (!$map['ids'] && !$map['phids']) {
|
||||
// specific events or exporting.
|
||||
if (!empty($map['export'])) {
|
||||
// This is a specific mode enabled by event exports.
|
||||
$query
|
||||
->withIsStub(false);
|
||||
} else if (!$map['ids'] && !$map['phids']) {
|
||||
$query
|
||||
->withIsStub(false)
|
||||
->setGenerateGhosts(true);
|
||||
|
@ -255,11 +259,20 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
array $handles) {
|
||||
|
||||
if ($this->isMonthView($query)) {
|
||||
return $this->buildCalendarMonthView($events, $query);
|
||||
$result = $this->buildCalendarMonthView($events, $query);
|
||||
} else if ($this->isDayView($query)) {
|
||||
return $this->buildCalendarDayView($events, $query);
|
||||
$result = $this->buildCalendarDayView($events, $query);
|
||||
} else {
|
||||
$result = $this->buildCalendarListView($events, $query);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function buildCalendarListView(
|
||||
array $events,
|
||||
PhabricatorSavedQuery $query) {
|
||||
|
||||
assert_instances_of($events, 'PhabricatorCalendarEvent');
|
||||
$viewer = $this->requireViewer();
|
||||
$list = new PHUIObjectItemListView();
|
||||
|
@ -343,8 +356,8 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
$month_view->setUser($viewer);
|
||||
|
||||
foreach ($events as $event) {
|
||||
$epoch_min = $event->getViewerDateFrom();
|
||||
$epoch_max = $event->getViewerDateTo();
|
||||
$epoch_min = $event->getStartDateTimeEpoch();
|
||||
$epoch_max = $event->getEndDateTimeEpoch();
|
||||
|
||||
$event_view = id(new AphrontCalendarEventView())
|
||||
->setHostPHID($event->getHostPHID())
|
||||
|
@ -408,8 +421,8 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
$event,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$epoch_min = $event->getViewerDateFrom();
|
||||
$epoch_max = $event->getViewerDateTo();
|
||||
$epoch_min = $event->getStartDateTimeEpoch();
|
||||
$epoch_max = $event->getEndDateTimeEpoch();
|
||||
|
||||
$status_icon = $event->getDisplayIcon($viewer);
|
||||
$status_color = $event->getDisplayIconColor($viewer);
|
||||
|
@ -562,4 +575,17 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
return false;
|
||||
}
|
||||
|
||||
public function newUseResultsActions(PhabricatorSavedQuery $saved) {
|
||||
$viewer = $this->requireViewer();
|
||||
$can_export = $viewer->isLoggedIn();
|
||||
|
||||
return array(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-download')
|
||||
->setName(pht('Export Query as .ics'))
|
||||
->setDisabled(!$can_export)
|
||||
->setHref('/calendar/export/edit/?queryKey='.$saved->getQueryKey()),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportQuery
|
||||
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $authorPHIDs;
|
||||
private $secretKeys;
|
||||
private $isDisabled;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withAuthorPHIDs(array $phids) {
|
||||
$this->authorPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withIsDisabled($is_disabled) {
|
||||
$this->isDisabled = $is_disabled;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withSecretKeys(array $keys) {
|
||||
$this->secretKeys = $keys;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new PhabricatorCalendarExport();
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'export.id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'export.phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->authorPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'export.authorPHID IN (%Ls)',
|
||||
$this->authorPHIDs);
|
||||
}
|
||||
|
||||
if ($this->isDisabled !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'export.isDisabled = %d',
|
||||
(int)$this->isDisabled);
|
||||
}
|
||||
|
||||
if ($this->secretKeys !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'export.secretKey IN (%Ls)',
|
||||
$this->secretKeys);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
protected function getPrimaryTableAlias() {
|
||||
return 'export';
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorCalendarApplication';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportSearchEngine
|
||||
extends PhabricatorApplicationSearchEngine {
|
||||
|
||||
public function getResultTypeDescription() {
|
||||
return pht('Calendar Exports');
|
||||
}
|
||||
|
||||
public function getApplicationClassName() {
|
||||
return 'PhabricatorCalendarApplication';
|
||||
}
|
||||
|
||||
public function newQuery() {
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
return id(new PhabricatorCalendarExportQuery())
|
||||
->withAuthorPHIDs(array($viewer->getPHID()));
|
||||
}
|
||||
|
||||
protected function buildCustomSearchFields() {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
return '/calendar/export/'.$path;
|
||||
}
|
||||
|
||||
protected function getBuiltinQueryNames() {
|
||||
$names = array(
|
||||
'all' => pht('All Exports'),
|
||||
);
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
public function buildSavedQueryFromBuiltin($query_key) {
|
||||
$query = $this->newSavedQuery();
|
||||
$query->setQueryKey($query_key);
|
||||
|
||||
switch ($query_key) {
|
||||
case 'all':
|
||||
return $query;
|
||||
}
|
||||
|
||||
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||
}
|
||||
|
||||
protected function renderResultList(
|
||||
array $exports,
|
||||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
|
||||
assert_instances_of($exports, 'PhabricatorCalendarExport');
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$list = new PHUIObjectItemListView();
|
||||
foreach ($exports as $export) {
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setViewer($viewer)
|
||||
->setObjectName(pht('Export %d', $export->getID()))
|
||||
->setHeader($export->getName())
|
||||
->setHref($export->getURI());
|
||||
|
||||
if ($export->getIsDisabled()) {
|
||||
$item->setDisabled(true);
|
||||
}
|
||||
|
||||
$mode = $export->getPolicyMode();
|
||||
$policy_icon = PhabricatorCalendarExport::getPolicyModeIcon($mode);
|
||||
$policy_name = PhabricatorCalendarExport::getPolicyModeName($mode);
|
||||
$policy_color = PhabricatorCalendarExport::getPolicyModeColor($mode);
|
||||
|
||||
$item->addIcon(
|
||||
"{$policy_icon} {$policy_color}",
|
||||
$policy_name);
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
$result = new PhabricatorApplicationSearchResultView();
|
||||
$result->setObjectList($list);
|
||||
$result->setNoDataString(pht('No exports found.'));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getNewUserBody() {
|
||||
$doc_name = 'Calendar User Guide: Exporting Events';
|
||||
$doc_href = PhabricatorEnv::getDoclink($doc_name);
|
||||
|
||||
$create_button = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setIcon('fa-book white')
|
||||
->setText($doc_name)
|
||||
->setHref($doc_href)
|
||||
->setColor(PHUIButtonView::GREEN);
|
||||
|
||||
$icon = $this->getApplication()->getIcon();
|
||||
$app_name = $this->getApplication()->getName();
|
||||
$view = id(new PHUIBigInfoView())
|
||||
->setIcon('fa-download')
|
||||
->setTitle(pht('No Exports Configured'))
|
||||
->setDescription(
|
||||
pht(
|
||||
'You have not set up any events for export from Calendar yet. '.
|
||||
'See the documentation for instructions on how to get started.'))
|
||||
->addAction($create_button);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
public function getTemplateApplicationTransaction() {
|
||||
return new PhabricatorCalendarExportTransaction();
|
||||
}
|
||||
|
||||
}
|
|
@ -17,10 +17,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
|
||||
protected $name;
|
||||
protected $hostPHID;
|
||||
protected $dateFrom;
|
||||
protected $dateTo;
|
||||
protected $allDayDateFrom;
|
||||
protected $allDayDateTo;
|
||||
protected $description;
|
||||
protected $isCancelled;
|
||||
protected $isAllDay;
|
||||
|
@ -29,8 +25,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
protected $isStub;
|
||||
|
||||
protected $isRecurring = 0;
|
||||
protected $recurrenceFrequency = array();
|
||||
protected $recurrenceEndDate;
|
||||
|
||||
private $isGhostEvent = false;
|
||||
protected $instanceOfEventPHID;
|
||||
|
@ -41,17 +35,23 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
|
||||
protected $spacePHID;
|
||||
|
||||
protected $utcInitialEpoch;
|
||||
protected $utcUntilEpoch;
|
||||
protected $utcInstanceEpoch;
|
||||
protected $parameters = array();
|
||||
|
||||
private $parentEvent = self::ATTACHABLE;
|
||||
private $invitees = self::ATTACHABLE;
|
||||
|
||||
private $viewerDateFrom;
|
||||
private $viewerDateTo;
|
||||
private $viewerTimezone;
|
||||
|
||||
// Frequency Constants
|
||||
const FREQUENCY_DAILY = 'daily';
|
||||
const FREQUENCY_WEEKLY = 'weekly';
|
||||
const FREQUENCY_MONTHLY = 'monthly';
|
||||
const FREQUENCY_YEARLY = 'yearly';
|
||||
// TODO: DEPRECATED. Remove once we're sure the migrations worked.
|
||||
protected $allDayDateFrom;
|
||||
protected $allDayDateTo;
|
||||
protected $dateFrom;
|
||||
protected $dateTo;
|
||||
protected $recurrenceEndDate;
|
||||
protected $recurrenceFrequency = array();
|
||||
|
||||
public static function initializeNewCalendarEvent(PhabricatorUser $actor) {
|
||||
$app = id(new PhabricatorApplicationQuery())
|
||||
|
@ -66,45 +66,37 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
|
||||
$now = PhabricatorTime::getNow();
|
||||
|
||||
$start = new DateTime('@'.$now);
|
||||
$start->setTimeZone($actor->getTimeZone());
|
||||
|
||||
$start->setTime($start->format('H'), 0, 0);
|
||||
$start->modify('+1 hour');
|
||||
$end = id(clone $start)->modify('+1 hour');
|
||||
|
||||
$epoch_min = $start->format('U');
|
||||
$epoch_max = $end->format('U');
|
||||
|
||||
$now_date = new DateTime('@'.$now);
|
||||
$now_min = id(clone $now_date)->setTime(0, 0)->format('U');
|
||||
$now_max = id(clone $now_date)->setTime(23, 59)->format('U');
|
||||
|
||||
$default_icon = 'fa-calendar';
|
||||
|
||||
$datetime_start = PhutilCalendarAbsoluteDateTime::newFromEpoch(
|
||||
$now,
|
||||
$actor->getTimezoneIdentifier());
|
||||
$datetime_end = $datetime_start->newRelativeDateTime('PT1H');
|
||||
|
||||
return id(new PhabricatorCalendarEvent())
|
||||
->setHostPHID($actor->getPHID())
|
||||
->setIsCancelled(0)
|
||||
->setIsAllDay(0)
|
||||
->setIsStub(0)
|
||||
->setIsRecurring(0)
|
||||
->setRecurrenceFrequency(
|
||||
array(
|
||||
'rule' => self::FREQUENCY_WEEKLY,
|
||||
))
|
||||
->setIcon($default_icon)
|
||||
->setViewPolicy($view_policy)
|
||||
->setEditPolicy($edit_policy)
|
||||
->setSpacePHID($actor->getDefaultSpacePHID())
|
||||
->attachInvitees(array())
|
||||
->setDateFrom($epoch_min)
|
||||
->setDateTo($epoch_max)
|
||||
->setAllDayDateFrom($now_min)
|
||||
->setAllDayDateTo($now_max)
|
||||
->setDateFrom(0)
|
||||
->setDateTo(0)
|
||||
->setAllDayDateFrom(0)
|
||||
->setAllDayDateTo(0)
|
||||
->setStartDateTime($datetime_start)
|
||||
->setEndDateTime($datetime_end)
|
||||
->applyViewerTimezone($actor);
|
||||
}
|
||||
|
||||
private function newChild(PhabricatorUser $actor, $sequence) {
|
||||
private function newChild(
|
||||
PhabricatorUser $actor,
|
||||
$sequence,
|
||||
PhutilCalendarDateTime $start = null) {
|
||||
if (!$this->isParentEvent()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
@ -118,10 +110,13 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
->setInstanceOfEventPHID($this->getPHID())
|
||||
->setSequenceIndex($sequence)
|
||||
->setIsRecurring(true)
|
||||
->setRecurrenceFrequency($this->getRecurrenceFrequency())
|
||||
->attachParentEvent($this);
|
||||
->attachParentEvent($this)
|
||||
->setAllDayDateFrom(0)
|
||||
->setAllDayDateTo(0)
|
||||
->setDateFrom(0)
|
||||
->setDateTo(0);
|
||||
|
||||
return $child->copyFromParent($actor);
|
||||
return $child->copyFromParent($actor, $start);
|
||||
}
|
||||
|
||||
protected function readField($field) {
|
||||
|
@ -153,7 +148,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
}
|
||||
|
||||
|
||||
public function copyFromParent(PhabricatorUser $actor) {
|
||||
public function copyFromParent(
|
||||
PhabricatorUser $actor,
|
||||
PhutilCalendarDateTime $start = null) {
|
||||
|
||||
if (!$this->isChildEvent()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
@ -173,65 +171,46 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
->setDescription($parent->getDescription());
|
||||
|
||||
$sequence = $this->getSequenceIndex();
|
||||
$duration = $this->getDuration();
|
||||
$epochs = $parent->getSequenceIndexEpochs($actor, $sequence, $duration);
|
||||
|
||||
if ($start) {
|
||||
$start_datetime = $start;
|
||||
} else {
|
||||
$start_datetime = $parent->newSequenceIndexDateTime($sequence);
|
||||
|
||||
if (!$start_datetime) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Sequence "%s" is not valid for event!',
|
||||
$sequence));
|
||||
}
|
||||
}
|
||||
|
||||
$duration = $parent->newDuration();
|
||||
$end_datetime = $start_datetime->newRelativeDateTime($duration);
|
||||
|
||||
$this
|
||||
->setDateFrom($epochs['dateFrom'])
|
||||
->setDateTo($epochs['dateTo'])
|
||||
->setAllDayDateFrom($epochs['allDayDateFrom'])
|
||||
->setAllDayDateTo($epochs['allDayDateTo']);
|
||||
->setStartDateTime($start_datetime)
|
||||
->setEndDateTime($end_datetime);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isValidSequenceIndex(PhabricatorUser $viewer, $sequence) {
|
||||
try {
|
||||
$this->getSequenceIndexEpochs($viewer, $sequence, $this->getDuration());
|
||||
return true;
|
||||
} catch (Exception $ex) {
|
||||
return false;
|
||||
}
|
||||
return (bool)$this->newSequenceIndexDateTime($sequence);
|
||||
}
|
||||
|
||||
private function getSequenceIndexEpochs(
|
||||
PhabricatorUser $viewer,
|
||||
$sequence,
|
||||
$duration) {
|
||||
|
||||
$frequency = $this->getFrequencyUnit();
|
||||
$modify_key = '+'.$sequence.' '.$frequency;
|
||||
|
||||
$date = $this->getDateFrom();
|
||||
$date_time = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
|
||||
$date_time->modify($modify_key);
|
||||
$date = $date_time->format('U');
|
||||
|
||||
$end_date = $this->getRecurrenceEndDate();
|
||||
if ($end_date && $date > $end_date) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Sequence "%s" is invalid for this event: it would occur after '.
|
||||
'the event stops repeating.',
|
||||
$sequence));
|
||||
public function newSequenceIndexDateTime($sequence) {
|
||||
$set = $this->newRecurrenceSet();
|
||||
if (!$set) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$utc = new DateTimeZone('UTC');
|
||||
$instances = $set->getEventsBetween(
|
||||
null,
|
||||
$this->newUntilDateTime(),
|
||||
$sequence + 1);
|
||||
|
||||
$allday_from = $this->getAllDayDateFrom();
|
||||
$allday_date = new DateTime('@'.$allday_from, $utc);
|
||||
$allday_date->setTimeZone($utc);
|
||||
$allday_date->modify($modify_key);
|
||||
|
||||
$allday_min = $allday_date->format('U');
|
||||
$allday_duration = ($this->getAllDayDateTo() - $allday_from);
|
||||
|
||||
return array(
|
||||
'dateFrom' => $date,
|
||||
'dateTo' => $date + $duration,
|
||||
'allDayDateFrom' => $allday_min,
|
||||
'allDayDateTo' => $allday_min + $allday_duration,
|
||||
);
|
||||
return idx($instances, $sequence, null);
|
||||
}
|
||||
|
||||
public function newStub(PhabricatorUser $actor, $sequence) {
|
||||
|
@ -248,8 +227,12 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
return $stub;
|
||||
}
|
||||
|
||||
public function newGhost(PhabricatorUser $actor, $sequence) {
|
||||
$ghost = $this->newChild($actor, $sequence);
|
||||
public function newGhost(
|
||||
PhabricatorUser $actor,
|
||||
$sequence,
|
||||
PhutilCalendarDateTime $start = null) {
|
||||
|
||||
$ghost = $this->newChild($actor, $sequence, $start);
|
||||
|
||||
$ghost
|
||||
->setIsGhostEvent(true)
|
||||
|
@ -260,67 +243,62 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
return $ghost;
|
||||
}
|
||||
|
||||
public function getViewerDateFrom() {
|
||||
if ($this->viewerDateFrom === null) {
|
||||
throw new PhutilInvalidStateException('applyViewerTimezone');
|
||||
}
|
||||
|
||||
return $this->viewerDateFrom;
|
||||
}
|
||||
|
||||
public function getViewerDateTo() {
|
||||
if ($this->viewerDateTo === null) {
|
||||
throw new PhutilInvalidStateException('applyViewerTimezone');
|
||||
}
|
||||
|
||||
return $this->viewerDateTo;
|
||||
}
|
||||
|
||||
public function applyViewerTimezone(PhabricatorUser $viewer) {
|
||||
if (!$this->getIsAllDay()) {
|
||||
$this->viewerDateFrom = $this->getDateFrom();
|
||||
$this->viewerDateTo = $this->getDateTo();
|
||||
} else {
|
||||
$zone = $viewer->getTimeZone();
|
||||
|
||||
$this->viewerDateFrom = $this->getDateEpochForTimezone(
|
||||
$this->getAllDayDateFrom(),
|
||||
new DateTimeZone('UTC'),
|
||||
'Y-m-d',
|
||||
null,
|
||||
$zone);
|
||||
|
||||
$this->viewerDateTo = $this->getDateEpochForTimezone(
|
||||
$this->getAllDayDateTo(),
|
||||
new DateTimeZone('UTC'),
|
||||
'Y-m-d 23:59:00',
|
||||
null,
|
||||
$zone);
|
||||
}
|
||||
|
||||
$this->viewerTimezone = $viewer->getTimezoneIdentifier();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDuration() {
|
||||
return $this->getDateTo() - $this->getDateFrom();
|
||||
return ($this->getEndDateTimeEpoch() - $this->getStartDateTimeEpoch());
|
||||
}
|
||||
|
||||
public function getDateEpochForTimezone(
|
||||
$epoch,
|
||||
$src_zone,
|
||||
$format,
|
||||
$adjust,
|
||||
$dst_zone) {
|
||||
public function updateUTCEpochs() {
|
||||
// The "intitial" epoch is the start time of the event, in UTC.
|
||||
$start_date = $this->newStartDateTime()
|
||||
->setViewerTimezone('UTC');
|
||||
$start_epoch = $start_date->getEpoch();
|
||||
$this->setUTCInitialEpoch($start_epoch);
|
||||
|
||||
$src = new DateTime('@'.$epoch);
|
||||
$src->setTimeZone($src_zone);
|
||||
// The "until" epoch is the last UTC epoch on which any instance of this
|
||||
// event occurs. For infinitely recurring events, it is `null`.
|
||||
|
||||
if (strlen($adjust)) {
|
||||
$adjust = ' '.$adjust;
|
||||
if (!$this->getIsRecurring()) {
|
||||
$end_date = $this->newEndDateTime()
|
||||
->setViewerTimezone('UTC');
|
||||
$until_epoch = $end_date->getEpoch();
|
||||
} else {
|
||||
$until_epoch = null;
|
||||
$until_date = $this->newUntilDateTime();
|
||||
if ($until_date) {
|
||||
$until_date->setViewerTimezone('UTC');
|
||||
$duration = $this->newDuration();
|
||||
$until_epoch = id(new PhutilCalendarRelativeDateTime())
|
||||
->setOrigin($until_date)
|
||||
->setDuration($duration)
|
||||
->getEpoch();
|
||||
}
|
||||
}
|
||||
$this->setUTCUntilEpoch($until_epoch);
|
||||
|
||||
$dst = new DateTime($src->format($format).$adjust, $dst_zone);
|
||||
return $dst->format('U');
|
||||
// The "instance" epoch is a property of instances of recurring events.
|
||||
// It's the original UTC epoch on which the instance started. Usually that
|
||||
// is the same as the start date, but they may be different if the instance
|
||||
// has been edited.
|
||||
|
||||
// The ICS format uses this value (original start time) to identify event
|
||||
// instances, and must do so because it allows additional arbitrary
|
||||
// instances to be added (with "RDATE").
|
||||
|
||||
$instance_epoch = null;
|
||||
$instance_date = $this->newInstanceDateTime();
|
||||
if ($instance_date) {
|
||||
$instance_epoch = $instance_date
|
||||
->setViewerTimezone('UTC')
|
||||
->getEpoch();
|
||||
}
|
||||
$this->setUTCInstanceEpoch($instance_epoch);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function save() {
|
||||
|
@ -328,6 +306,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
$this->mailKey = Filesystem::readRandomCharacters(20);
|
||||
}
|
||||
|
||||
$this->updateUTCEpochs();
|
||||
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
|
@ -340,8 +320,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
*
|
||||
* @return int Event start date for availability caches.
|
||||
*/
|
||||
public function getDateFromForCache() {
|
||||
return ($this->getViewerDateFrom() - phutil_units('15 minutes in seconds'));
|
||||
public function getStartDateTimeEpochForCache() {
|
||||
$epoch = $this->getStartDateTimeEpoch();
|
||||
$window = phutil_units('15 minutes in seconds');
|
||||
return ($epoch - $window);
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
|
@ -349,20 +331,25 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'name' => 'text',
|
||||
'dateFrom' => 'epoch',
|
||||
'dateTo' => 'epoch',
|
||||
'allDayDateFrom' => 'epoch',
|
||||
'allDayDateTo' => 'epoch',
|
||||
'description' => 'text',
|
||||
'isCancelled' => 'bool',
|
||||
'isAllDay' => 'bool',
|
||||
'icon' => 'text32',
|
||||
'mailKey' => 'bytes20',
|
||||
'isRecurring' => 'bool',
|
||||
'recurrenceEndDate' => 'epoch?',
|
||||
'instanceOfEventPHID' => 'phid?',
|
||||
'sequenceIndex' => 'uint32?',
|
||||
'isStub' => 'bool',
|
||||
'utcInitialEpoch' => 'epoch',
|
||||
'utcUntilEpoch' => 'epoch?',
|
||||
'utcInstanceEpoch' => 'epoch?',
|
||||
|
||||
// TODO: DEPRECATED.
|
||||
'allDayDateFrom' => 'epoch',
|
||||
'allDayDateTo' => 'epoch',
|
||||
'dateFrom' => 'epoch',
|
||||
'dateTo' => 'epoch',
|
||||
'recurrenceEndDate' => 'epoch?',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_date' => array(
|
||||
|
@ -372,9 +359,17 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
'columns' => array('instanceOfEventPHID', 'sequenceIndex'),
|
||||
'unique' => true,
|
||||
),
|
||||
'key_epoch' => array(
|
||||
'columns' => array('utcInitialEpoch', 'utcUntilEpoch'),
|
||||
),
|
||||
'key_rdate' => array(
|
||||
'columns' => array('instanceOfEventPHID', 'utcInstanceEpoch'),
|
||||
'unique' => true,
|
||||
),
|
||||
),
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'recurrenceFrequency' => self::SERIALIZATION_JSON,
|
||||
'parameters' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
@ -450,27 +445,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getFrequencyRule() {
|
||||
return idx($this->recurrenceFrequency, 'rule');
|
||||
}
|
||||
|
||||
public function getFrequencyUnit() {
|
||||
$frequency = $this->getFrequencyRule();
|
||||
|
||||
switch ($frequency) {
|
||||
case 'daily':
|
||||
return 'day';
|
||||
case 'weekly':
|
||||
return 'week';
|
||||
case 'monthly':
|
||||
return 'month';
|
||||
case 'yearly':
|
||||
return 'year';
|
||||
default:
|
||||
return 'day';
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if ($this->getIsGhostEvent()) {
|
||||
$base = $this->getParentEvent()->getURI();
|
||||
|
@ -516,14 +490,12 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
PhabricatorUser $viewer,
|
||||
$show_end) {
|
||||
|
||||
if ($show_end) {
|
||||
$min_date = PhabricatorTime::getDateTimeFromEpoch(
|
||||
$this->getViewerDateFrom(),
|
||||
$viewer);
|
||||
$start = $this->newStartDateTime();
|
||||
$end = $this->newEndDateTime();
|
||||
|
||||
$max_date = PhabricatorTime::getDateTimeFromEpoch(
|
||||
$this->getViewerDateTo(),
|
||||
$viewer);
|
||||
if ($show_end) {
|
||||
$min_date = $start->newPHPDateTime();
|
||||
$max_date = $end->newPHPDateTime();
|
||||
|
||||
$min_day = $min_date->format('Y m d');
|
||||
$max_day = $max_date->format('Y m d');
|
||||
|
@ -533,8 +505,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
$show_end_date = false;
|
||||
}
|
||||
|
||||
$min_epoch = $this->getViewerDateFrom();
|
||||
$max_epoch = $this->getViewerDateTo();
|
||||
$min_epoch = $start->getEpoch();
|
||||
$max_epoch = $end->getEpoch();
|
||||
|
||||
if ($this->getIsAllDay()) {
|
||||
if ($show_end_date) {
|
||||
|
@ -629,11 +601,28 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
return $this->getMonogram().'.ics';
|
||||
}
|
||||
|
||||
public function newIntermediateEventNode(PhabricatorUser $viewer) {
|
||||
public function newIntermediateEventNode(
|
||||
PhabricatorUser $viewer,
|
||||
array $children) {
|
||||
|
||||
$base_uri = new PhutilURI(PhabricatorEnv::getProductionURI('/'));
|
||||
$domain = $base_uri->getDomain();
|
||||
|
||||
$uid = $this->getPHID().'@'.$domain;
|
||||
// NOTE: For recurring events, all of the events in the series have the
|
||||
// same UID (the UID of the parent). The child event instances are
|
||||
// differentiated by the "RECURRENCE-ID" field.
|
||||
if ($this->isChildEvent()) {
|
||||
$parent = $this->getParentEvent();
|
||||
$instance_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
|
||||
$this->getUTCInstanceEpoch());
|
||||
$recurrence_id = $instance_datetime->getISO8601();
|
||||
$rrule = null;
|
||||
} else {
|
||||
$parent = $this;
|
||||
$recurrence_id = null;
|
||||
$rrule = $this->newRecurrenceRule();
|
||||
}
|
||||
$uid = $parent->getPHID().'@'.$domain;
|
||||
|
||||
$created = $this->getDateCreated();
|
||||
$created = PhutilCalendarAbsoluteDateTime::newFromEpoch($created);
|
||||
|
@ -641,11 +630,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
$modified = $this->getDateModified();
|
||||
$modified = PhutilCalendarAbsoluteDateTime::newFromEpoch($modified);
|
||||
|
||||
$date_start = $this->getDateFrom();
|
||||
$date_start = PhutilCalendarAbsoluteDateTime::newFromEpoch($date_start);
|
||||
|
||||
$date_end = $this->getDateTo();
|
||||
$date_end = PhutilCalendarAbsoluteDateTime::newFromEpoch($date_end);
|
||||
$date_start = $this->newStartDateTime();
|
||||
$date_end = $this->newEndDateTime();
|
||||
|
||||
if ($this->getIsAllDay()) {
|
||||
$date_start->setIsAllDay(true);
|
||||
|
@ -705,6 +691,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
->setStatus($status);
|
||||
}
|
||||
|
||||
// TODO: Use $children to generate EXDATE/RDATE information.
|
||||
|
||||
$node = id(new PhutilCalendarEventNode())
|
||||
->setUID($uid)
|
||||
->setName($this->getName())
|
||||
|
@ -716,10 +704,186 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
->setOrganizer($organizer)
|
||||
->setAttendees($attendees);
|
||||
|
||||
if ($rrule) {
|
||||
$node->setRecurrenceRule($rrule);
|
||||
}
|
||||
|
||||
if ($recurrence_id) {
|
||||
$node->setRecurrenceID($recurrence_id);
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
public function newStartDateTime() {
|
||||
$datetime = $this->getParameter('startDateTime');
|
||||
if ($datetime) {
|
||||
return $this->newDateTimeFromDictionary($datetime);
|
||||
}
|
||||
|
||||
$epoch = $this->getDateFrom();
|
||||
return $this->newDateTimeFromEpoch($epoch);
|
||||
}
|
||||
|
||||
public function getStartDateTimeEpoch() {
|
||||
return $this->newStartDateTime()->getEpoch();
|
||||
}
|
||||
|
||||
public function newEndDateTime() {
|
||||
$datetime = $this->getParameter('endDateTime');
|
||||
if ($datetime) {
|
||||
return $this->newDateTimeFromDictionary($datetime);
|
||||
}
|
||||
|
||||
$epoch = $this->getDateTo();
|
||||
return $this->newDateTimeFromEpoch($epoch);
|
||||
}
|
||||
|
||||
public function getEndDateTimeEpoch() {
|
||||
return $this->newEndDateTime()->getEpoch();
|
||||
}
|
||||
|
||||
public function newUntilDateTime() {
|
||||
$datetime = $this->getParameter('untilDateTime');
|
||||
if ($datetime) {
|
||||
return $this->newDateTimeFromDictionary($datetime);
|
||||
}
|
||||
|
||||
$epoch = $this->getRecurrenceEndDate();
|
||||
if (!$epoch) {
|
||||
return null;
|
||||
}
|
||||
return $this->newDateTimeFromEpoch($epoch);
|
||||
}
|
||||
|
||||
public function getUntilDateTimeEpoch() {
|
||||
$datetime = $this->newUntilDateTime();
|
||||
|
||||
if (!$datetime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $datetime->getEpoch();
|
||||
}
|
||||
|
||||
public function newDuration() {
|
||||
return id(new PhutilCalendarDuration())
|
||||
->setSeconds($this->getDuration());
|
||||
}
|
||||
|
||||
public function newInstanceDateTime() {
|
||||
if (!$this->getIsRecurring()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$index = $this->getSequenceIndex();
|
||||
if (!$index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->newSequenceIndexDateTime($index);
|
||||
}
|
||||
|
||||
private function newDateTimeFromEpoch($epoch) {
|
||||
$datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($epoch);
|
||||
|
||||
if ($this->getIsAllDay()) {
|
||||
$datetime->setIsAllDay(true);
|
||||
}
|
||||
|
||||
return $this->newDateTimeFromDateTime($datetime);
|
||||
}
|
||||
|
||||
private function newDateTimeFromDictionary(array $dict) {
|
||||
$datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($dict);
|
||||
return $this->newDateTimeFromDateTime($datetime);
|
||||
}
|
||||
|
||||
private function newDateTimeFromDateTime(PhutilCalendarDateTime $datetime) {
|
||||
$viewer_timezone = $this->viewerTimezone;
|
||||
if ($viewer_timezone) {
|
||||
$datetime->setViewerTimezone($viewer_timezone);
|
||||
}
|
||||
|
||||
return $datetime;
|
||||
}
|
||||
|
||||
public function getParameter($key, $default = null) {
|
||||
return idx($this->parameters, $key, $default);
|
||||
}
|
||||
|
||||
public function setParameter($key, $value) {
|
||||
$this->parameters[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStartDateTime(PhutilCalendarDateTime $datetime) {
|
||||
return $this->setParameter(
|
||||
'startDateTime',
|
||||
$datetime->newAbsoluteDateTime()->toDictionary());
|
||||
}
|
||||
|
||||
public function setEndDateTime(PhutilCalendarDateTime $datetime) {
|
||||
return $this->setParameter(
|
||||
'endDateTime',
|
||||
$datetime->newAbsoluteDateTime()->toDictionary());
|
||||
}
|
||||
|
||||
public function setUntilDateTime(PhutilCalendarDateTime $datetime) {
|
||||
return $this->setParameter(
|
||||
'untilDateTime',
|
||||
$datetime->newAbsoluteDateTime()->toDictionary());
|
||||
}
|
||||
|
||||
public function setRecurrenceRule(PhutilCalendarRecurrenceRule $rrule) {
|
||||
return $this->setParameter(
|
||||
'recurrenceRule',
|
||||
$rrule->toDictionary());
|
||||
}
|
||||
|
||||
public function newRecurrenceRule() {
|
||||
if ($this->isChildEvent()) {
|
||||
return $this->getParentEvent()->newRecurrenceRule();
|
||||
}
|
||||
|
||||
if (!$this->getIsRecurring()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dict = $this->getParameter('recurrenceRule');
|
||||
if (!$dict) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$rrule = PhutilCalendarRecurrenceRule::newFromDictionary($dict);
|
||||
|
||||
$start = $this->newStartDateTime();
|
||||
$rrule->setStartDateTime($start);
|
||||
|
||||
$until = $this->newUntilDateTime();
|
||||
if ($until) {
|
||||
$rrule->setUntil($until);
|
||||
}
|
||||
|
||||
return $rrule;
|
||||
}
|
||||
|
||||
public function newRecurrenceSet() {
|
||||
if ($this->isChildEvent()) {
|
||||
return $this->getParentEvent()->newRecurrenceSet();
|
||||
}
|
||||
|
||||
$set = new PhutilCalendarRecurrenceSet();
|
||||
|
||||
$rrule = $this->newRecurrenceRule();
|
||||
if (!$rrule) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$set->addSource($rrule);
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
/* -( Markup Interface )--------------------------------------------------- */
|
||||
|
||||
|
|
198
src/applications/calendar/storage/PhabricatorCalendarExport.php
Normal file
198
src/applications/calendar/storage/PhabricatorCalendarExport.php
Normal file
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExport extends PhabricatorCalendarDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorApplicationTransactionInterface,
|
||||
PhabricatorDestructibleInterface {
|
||||
|
||||
protected $name;
|
||||
protected $authorPHID;
|
||||
protected $policyMode;
|
||||
protected $queryKey;
|
||||
protected $secretKey;
|
||||
protected $isDisabled = 0;
|
||||
|
||||
const MODE_PUBLIC = 'public';
|
||||
const MODE_PRIVILEGED = 'privileged';
|
||||
|
||||
public static function initializeNewCalendarExport(PhabricatorUser $actor) {
|
||||
return id(new self())
|
||||
->setAuthorPHID($actor->getPHID())
|
||||
->setPolicyMode(self::MODE_PRIVILEGED)
|
||||
->setIsDisabled(0);
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'name' => 'text',
|
||||
'policyMode' => 'text64',
|
||||
'queryKey' => 'text64',
|
||||
'secretKey' => 'bytes20',
|
||||
'isDisabled' => 'bool',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_author' => array(
|
||||
'columns' => array('authorPHID'),
|
||||
),
|
||||
'key_secret' => array(
|
||||
'columns' => array('secretKey'),
|
||||
'unique' => true,
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function getPHIDType() {
|
||||
return PhabricatorCalendarExportPHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public function save() {
|
||||
if (!$this->getSecretKey()) {
|
||||
$this->setSecretKey(Filesystem::readRandomCharacters(20));
|
||||
}
|
||||
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$id = $this->getID();
|
||||
return "/calendar/export/{$id}/";
|
||||
}
|
||||
|
||||
private static function getPolicyModeMap() {
|
||||
return array(
|
||||
self::MODE_PUBLIC => array(
|
||||
'icon' => 'fa-globe',
|
||||
'name' => pht('Public'),
|
||||
'color' => 'bluegrey',
|
||||
'summary' => pht(
|
||||
'Export only public data.'),
|
||||
'description' => pht(
|
||||
'Only publicly available data is exported.'),
|
||||
),
|
||||
self::MODE_PRIVILEGED => array(
|
||||
'icon' => 'fa-unlock-alt',
|
||||
'name' => pht('Privileged'),
|
||||
'color' => 'red',
|
||||
'summary' => pht(
|
||||
'Export private data.'),
|
||||
'description' => pht(
|
||||
'Anyone who knows the URI for this export can view all event '.
|
||||
'details as though they were logged in with your account.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private static function getPolicyModeSpec($const) {
|
||||
return idx(self::getPolicyModeMap(), $const, array());
|
||||
}
|
||||
|
||||
public static function getPolicyModeName($const) {
|
||||
$spec = self::getPolicyModeSpec($const);
|
||||
return idx($spec, 'name', $const);
|
||||
}
|
||||
|
||||
public static function getPolicyModeIcon($const) {
|
||||
$spec = self::getPolicyModeSpec($const);
|
||||
return idx($spec, 'icon', $const);
|
||||
}
|
||||
|
||||
public static function getPolicyModeColor($const) {
|
||||
$spec = self::getPolicyModeSpec($const);
|
||||
return idx($spec, 'color', $const);
|
||||
}
|
||||
|
||||
public static function getPolicyModeSummary($const) {
|
||||
$spec = self::getPolicyModeSpec($const);
|
||||
return idx($spec, 'summary', $const);
|
||||
}
|
||||
|
||||
public static function getPolicyModeDescription($const) {
|
||||
$spec = self::getPolicyModeSpec($const);
|
||||
return idx($spec, 'description', $const);
|
||||
}
|
||||
|
||||
public static function getPolicyModes() {
|
||||
return array_keys(self::getPolicyModeMap());
|
||||
}
|
||||
|
||||
public static function getAvailablePolicyModes() {
|
||||
$modes = array();
|
||||
|
||||
if (PhabricatorEnv::getEnvConfig('policy.allow-public')) {
|
||||
$modes[] = self::MODE_PUBLIC;
|
||||
}
|
||||
|
||||
$modes[] = self::MODE_PRIVILEGED;
|
||||
|
||||
return $modes;
|
||||
}
|
||||
|
||||
public function getICSFilename() {
|
||||
return PhabricatorSlug::normalizeProjectSlug($this->getName()).'.ics';
|
||||
}
|
||||
|
||||
public function getICSURI() {
|
||||
$secret_key = $this->getSecretKey();
|
||||
$ics_name = $this->getICSFilename();
|
||||
return "/calendar/export/ics/{$secret_key}/{$ics_name}";
|
||||
}
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
return $this->getAuthorPHID();
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
||||
public function getApplicationTransactionEditor() {
|
||||
return new PhabricatorCalendarExportEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new PhabricatorCalendarExportTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
||||
|
||||
|
||||
public function destroyObjectPermanently(
|
||||
PhabricatorDestructionEngine $engine) {
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportTransaction
|
||||
extends PhabricatorModularTransaction {
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'calendar';
|
||||
}
|
||||
|
||||
public function getApplicationTransactionType() {
|
||||
return PhabricatorCalendarExportPHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public function getBaseTransactionClass() {
|
||||
return 'PhabricatorCalendarExportTransactionType';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarICSWriter extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $events = array();
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
public function setEvents(array $events) {
|
||||
assert_instances_of($events, 'PhabricatorCalendarEvent');
|
||||
$this->events = $events;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEvents() {
|
||||
return $this->events;
|
||||
}
|
||||
|
||||
public function writeICSDocument() {
|
||||
$viewer = $this->getViewer();
|
||||
$events = $this->getEvents();
|
||||
|
||||
$events = mpull($events, null, 'getPHID');
|
||||
|
||||
if ($events) {
|
||||
$child_map = id(new PhabricatorCalendarEventQuery())
|
||||
->setViewer($viewer)
|
||||
->withParentEventPHIDs(array_keys($events))
|
||||
->execute();
|
||||
$child_map = mpull($child_map, null, 'getPHID');
|
||||
} else {
|
||||
$child_map = array();
|
||||
}
|
||||
|
||||
$all_events = $events + $child_map;
|
||||
$child_groups = mgroup($child_map, 'getInstanceOfEventPHID');
|
||||
|
||||
$document_node = new PhutilCalendarDocumentNode();
|
||||
|
||||
foreach ($all_events as $event) {
|
||||
$child_events = idx($child_groups, $event->getPHID(), array());
|
||||
$event_node = $event->newIntermediateEventNode($viewer, $child_events);
|
||||
$document_node->appendChild($event_node);
|
||||
}
|
||||
|
||||
$root_node = id(new PhutilCalendarRootNode())
|
||||
->appendChild($document_node);
|
||||
|
||||
return id(new PhutilICSWriter())
|
||||
->writeICSDocument($root_node);
|
||||
}
|
||||
}
|
|
@ -15,6 +15,25 @@ final class PhabricatorCalendarEventAllDayTransaction
|
|||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setIsAllDay($value);
|
||||
|
||||
// Adjust the flags on any other dates the event has.
|
||||
$keys = array(
|
||||
'startDateTime',
|
||||
'endDateTime',
|
||||
'untilDateTime',
|
||||
);
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$dict = $object->getParameter($key);
|
||||
if (!$dict) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($dict);
|
||||
$datetime->setIsAllDay($value);
|
||||
|
||||
$object->setParameter($key, $datetime->toDictionary());
|
||||
}
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
|
|
|
@ -6,21 +6,18 @@ final class PhabricatorCalendarEventEndDateTransaction
|
|||
const TRANSACTIONTYPE = 'calendar.enddate';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getDateTo();
|
||||
// TODO: Upgrade this.
|
||||
return $object->getEndDateTimeEpoch();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$actor = $this->getActor();
|
||||
|
||||
$object->setDateTo($value);
|
||||
|
||||
$object->setAllDayDateTo(
|
||||
$object->getDateEpochForTimezone(
|
||||
$value,
|
||||
$actor->getTimeZone(),
|
||||
'Y-m-d 23:59:00',
|
||||
null,
|
||||
new DateTimeZone('UTC')));
|
||||
$datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
|
||||
$value,
|
||||
$actor->getTimezoneIdentifier());
|
||||
$datetime->setIsAllDay($object->getIsAllDay());
|
||||
$object->setEndDateTime($datetime);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
|
|
|
@ -6,32 +6,65 @@ final class PhabricatorCalendarEventFrequencyTransaction
|
|||
const TRANSACTIONTYPE = 'calendar.frequency';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getFrequencyRule();
|
||||
$rrule = $object->newRecurrenceRule();
|
||||
|
||||
if (!$rrule) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $rrule->getFrequency();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setRecurrenceFrequency(
|
||||
array(
|
||||
'rule' => $value,
|
||||
));
|
||||
$rrule = id(new PhutilCalendarRecurrenceRule())
|
||||
->setFrequency($value);
|
||||
|
||||
$object->setRecurrenceRule($rrule);
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$errors = array();
|
||||
|
||||
$valid = array(
|
||||
PhutilCalendarRecurrenceRule::FREQUENCY_DAILY,
|
||||
PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY,
|
||||
PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY,
|
||||
PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY,
|
||||
);
|
||||
$valid = array_fuse($valid);
|
||||
|
||||
foreach ($xactions as $xaction) {
|
||||
$value = $xaction->getNewValue();
|
||||
|
||||
if (!isset($valid[$value])) {
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht(
|
||||
'Event frequency "%s" is not valid. Valid frequences are: %s.',
|
||||
$value,
|
||||
implode(', ', $valid)),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
$frequency = $this->getFrequencyRule($this->getNewValue());
|
||||
$frequency = $this->getFrequency($this->getNewValue());
|
||||
switch ($frequency) {
|
||||
case PhabricatorCalendarEvent::FREQUENCY_DAILY:
|
||||
case PhutilCalendarRecurrenceRule::FREQUENCY_DAILY:
|
||||
return pht(
|
||||
'%s set this event to repeat daily.',
|
||||
$this->renderAuthor());
|
||||
case PhabricatorCalendarEvent::FREQUENCY_WEEKLY:
|
||||
case PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY:
|
||||
return pht(
|
||||
'%s set this event to repeat weekly.',
|
||||
$this->renderAuthor());
|
||||
case PhabricatorCalendarEvent::FREQUENCY_MONTHLY:
|
||||
case PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY:
|
||||
return pht(
|
||||
'%s set this event to repeat monthly.',
|
||||
$this->renderAuthor());
|
||||
case PhabricatorCalendarEvent::FREQUENCY_YEARLY:
|
||||
case PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY:
|
||||
return pht(
|
||||
'%s set this event to repeat yearly.',
|
||||
$this->renderAuthor());
|
||||
|
@ -39,24 +72,24 @@ final class PhabricatorCalendarEventFrequencyTransaction
|
|||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
$frequency = $this->getFrequencyRule($this->getNewValue());
|
||||
$frequency = $this->getFrequency($this->getNewValue());
|
||||
switch ($frequency) {
|
||||
case PhabricatorCalendarEvent::FREQUENCY_DAILY:
|
||||
case PhutilCalendarRecurrenceRule::FREQUENCY_DAILY:
|
||||
return pht(
|
||||
'%s set %s to repeat daily.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject());
|
||||
case PhabricatorCalendarEvent::FREQUENCY_WEEKLY:
|
||||
case PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY:
|
||||
return pht(
|
||||
'%s set %s to repeat weekly.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject());
|
||||
case PhabricatorCalendarEvent::FREQUENCY_MONTHLY:
|
||||
case PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY:
|
||||
return pht(
|
||||
'%s set %s to repeat monthly.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject());
|
||||
case PhabricatorCalendarEvent::FREQUENCY_YEARLY:
|
||||
case PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY:
|
||||
return pht(
|
||||
'%s set %s to repeat yearly.',
|
||||
$this->renderAuthor(),
|
||||
|
@ -64,12 +97,18 @@ final class PhabricatorCalendarEventFrequencyTransaction
|
|||
}
|
||||
}
|
||||
|
||||
private function getFrequencyRule($value) {
|
||||
private function getFrequency($value) {
|
||||
// NOTE: This is normalizing three generations of these transactions
|
||||
// to use RRULE constants. It would be vaguely nice to migrate them
|
||||
// for consistency.
|
||||
|
||||
if (is_array($value)) {
|
||||
$value = idx($value, 'rule');
|
||||
} else {
|
||||
return $value;
|
||||
$value = $value;
|
||||
}
|
||||
|
||||
return phutil_utf8_strtoupper($value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,21 +6,18 @@ final class PhabricatorCalendarEventStartDateTransaction
|
|||
const TRANSACTIONTYPE = 'calendar.startdate';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getDateFrom();
|
||||
// TODO: Upgrade this.
|
||||
return $object->getStartDateTimeEpoch();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$actor = $this->getActor();
|
||||
|
||||
$object->setDateFrom($value);
|
||||
|
||||
$object->setAllDayDateFrom(
|
||||
$object->getDateEpochForTimezone(
|
||||
$value,
|
||||
$actor->getTimeZone(),
|
||||
'Y-m-d',
|
||||
null,
|
||||
new DateTimeZone('UTC')));
|
||||
$datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
|
||||
$value,
|
||||
$actor->getTimezoneIdentifier());
|
||||
$datetime->setIsAllDay($object->getIsAllDay());
|
||||
$object->setStartDateTime($datetime);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
|
|
|
@ -6,11 +6,21 @@ final class PhabricatorCalendarEventUntilDateTransaction
|
|||
const TRANSACTIONTYPE = 'calendar.recurrenceenddate';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getRecurrenceEndDate();
|
||||
// TODO: Upgrade this.
|
||||
return $object->getUntilDateTimeEpoch();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$actor = $this->getActor();
|
||||
|
||||
// TODO: DEPRECATED.
|
||||
$object->setRecurrenceEndDate($value);
|
||||
|
||||
$datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
|
||||
$value,
|
||||
$actor->getTimezoneIdentifier());
|
||||
$datetime->setIsAllDay($object->getIsAllDay());
|
||||
$object->setUntilDateTime($datetime);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportDisableTransaction
|
||||
extends PhabricatorCalendarExportTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'calendar.export.disable';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return (int)$object->getIsDisabled();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setIsDisabled((int)$value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
if ($this->getNewValue()) {
|
||||
return pht(
|
||||
'%s disabled this export.',
|
||||
$this->renderAuthor());
|
||||
} else {
|
||||
return pht(
|
||||
'%s enabled this export.',
|
||||
$this->renderAuthor());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportModeTransaction
|
||||
extends PhabricatorCalendarExportTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'calendar.export.mode';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getPolicyMode();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setPolicyMode($value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
$old_value = $this->getOldValue();
|
||||
$new_value = $this->getNewValue();
|
||||
|
||||
$old_name = PhabricatorCalendarExport::getPolicyModeName($old_value);
|
||||
$new_name = PhabricatorCalendarExport::getPolicyModeName($new_value);
|
||||
|
||||
return pht(
|
||||
'%s changed the policy mode for this export from %s to %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderValue($old_name),
|
||||
$this->renderValue($new_name));
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$errors = array();
|
||||
|
||||
$valid = PhabricatorCalendarExport::getPolicyModes();
|
||||
$valid = array_fuse($valid);
|
||||
|
||||
foreach ($xactions as $xaction) {
|
||||
$value = $xaction->getNewValue();
|
||||
|
||||
if (isset($valid[$value])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht(
|
||||
'Mode "%s" is not a valid policy mode. Valid modes are: %s.',
|
||||
$value,
|
||||
implode(', ', $valid)),
|
||||
$xaction);
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportNameTransaction
|
||||
extends PhabricatorCalendarExportTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'calendar.export.name';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getName();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setName($value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s renamed this export from %s to %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderOldValue(),
|
||||
$this->renderNewValue());
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$errors = array();
|
||||
|
||||
if ($this->isEmptyTextTransaction($object->getName(), $xactions)) {
|
||||
$errors[] = $this->newRequiredError(
|
||||
pht('Calendar exports must have a name.'));
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportQueryKeyTransaction
|
||||
extends PhabricatorCalendarExportTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'calendar.export.querykey';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->getQueryKey();
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$object->setQueryKey($value);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s changed the query for this export.',
|
||||
$this->renderAuthor());
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$actor = $this->getActor();
|
||||
|
||||
$errors = array();
|
||||
|
||||
foreach ($xactions as $xaction) {
|
||||
$value = $xaction->getNewValue();
|
||||
|
||||
$query = id(new PhabricatorSavedQueryQuery())
|
||||
->setViewer($actor)
|
||||
->withEngineClassNames(array('PhabricatorCalendarEventSearchEngine'))
|
||||
->withQueryKeys(array($value))
|
||||
->executeOne();
|
||||
if ($query) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$builtin = id(new PhabricatorCalendarEventSearchEngine())
|
||||
->setViewer($actor)
|
||||
->getBuiltinQueries($actor);
|
||||
if (isset($builtin[$value])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht(
|
||||
'Query key "%s" does not identify a valid event query.',
|
||||
$value),
|
||||
$xaction);
|
||||
}
|
||||
|
||||
if ($this->isEmptyTextTransaction($object->getQueryKey(), $xactions)) {
|
||||
$errors[] = $this->newRequiredError(
|
||||
pht('Calendar exports must have a query key.'));
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorCalendarExportTransactionType
|
||||
extends PhabricatorModularTransactionType {}
|
|
@ -404,19 +404,48 @@ final class PhabricatorSetupIssueView extends AphrontView {
|
|||
implode("\n", $more_loc));
|
||||
}
|
||||
|
||||
$info[] = phutil_tag(
|
||||
'p',
|
||||
array(),
|
||||
pht(
|
||||
'You can find more information about PHP configuration values in the '.
|
||||
'%s.',
|
||||
phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => 'http://php.net/manual/ini.list.php',
|
||||
'target' => '_blank',
|
||||
),
|
||||
pht('PHP Documentation'))));
|
||||
$show_standard = false;
|
||||
$show_opcache = false;
|
||||
|
||||
foreach ($configs as $key) {
|
||||
if (preg_match('/^opcache\./', $key)) {
|
||||
$show_opcache = true;
|
||||
} else {
|
||||
$show_standard = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($show_standard) {
|
||||
$info[] = phutil_tag(
|
||||
'p',
|
||||
array(),
|
||||
pht(
|
||||
'You can find more information about PHP configuration values '.
|
||||
'in the %s.',
|
||||
phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => 'http://php.net/manual/ini.list.php',
|
||||
'target' => '_blank',
|
||||
),
|
||||
pht('PHP Documentation'))));
|
||||
}
|
||||
|
||||
if ($show_opcache) {
|
||||
$info[] = phutil_tag(
|
||||
'p',
|
||||
array(),
|
||||
pht(
|
||||
'You can find more information about configuring OPCache in '.
|
||||
'the %s.',
|
||||
phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => 'http://php.net/manual/opcache.configuration.php',
|
||||
'target' => '_blank',
|
||||
),
|
||||
pht('PHP OPCache Documentation'))));
|
||||
}
|
||||
|
||||
$info[] = phutil_tag(
|
||||
'p',
|
||||
|
|
|
@ -30,20 +30,31 @@ final class PhabricatorConpherenceApplication extends PhabricatorApplication {
|
|||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/Z(?P<id>[1-9]\d*)' => 'ConpherenceViewController',
|
||||
'/Z(?P<id>[1-9]\d*)'
|
||||
=> 'ConpherenceViewController',
|
||||
'/conpherence/' => array(
|
||||
'' => 'ConpherenceListController',
|
||||
'thread/(?P<id>[1-9]\d*)/' => 'ConpherenceListController',
|
||||
'(?P<id>[1-9]\d*)/' => 'ConpherenceViewController',
|
||||
''
|
||||
=> 'ConpherenceListController',
|
||||
'thread/(?P<id>[1-9]\d*)/'
|
||||
=> 'ConpherenceListController',
|
||||
'(?P<id>[1-9]\d*)/'
|
||||
=> 'ConpherenceViewController',
|
||||
'(?P<id>[1-9]\d*)/(?P<messageID>[1-9]\d*)/'
|
||||
=> 'ConpherenceViewController',
|
||||
'columnview/' => 'ConpherenceColumnViewController',
|
||||
'new/' => 'ConpherenceNewRoomController',
|
||||
=> 'ConpherenceViewController',
|
||||
'columnview/'
|
||||
=> 'ConpherenceColumnViewController',
|
||||
'new/'
|
||||
=> 'ConpherenceNewRoomController',
|
||||
'picture/(?P<id>[1-9]\d*)/'
|
||||
=> 'ConpherenceRoomPictureController',
|
||||
'search/(?:query/(?P<queryKey>[^/]+)/)?'
|
||||
=> 'ConpherenceRoomListController',
|
||||
'panel/' => 'ConpherenceNotificationPanelController',
|
||||
'participant/(?P<id>[1-9]\d*)/' => 'ConpherenceParticipantController',
|
||||
'update/(?P<id>[1-9]\d*)/' => 'ConpherenceUpdateController',
|
||||
=> 'ConpherenceRoomListController',
|
||||
'panel/'
|
||||
=> 'ConpherenceNotificationPanelController',
|
||||
'participant/(?P<id>[1-9]\d*)/'
|
||||
=> 'ConpherenceParticipantController',
|
||||
'update/(?P<id>[1-9]\d*)/'
|
||||
=> 'ConpherenceUpdateController',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class ConpherenceImageData extends ConpherenceConstants {
|
||||
|
||||
const SIZE_ORIG = 'original';
|
||||
const SIZE_CROP = 'crop';
|
||||
|
||||
const CROP_WIDTH = 35;
|
||||
const CROP_HEIGHT = 35;
|
||||
|
||||
}
|
|
@ -16,7 +16,7 @@ final class ConpherenceColumnViewController extends
|
|||
$latest_conpherences = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->withPHIDs($conpherence_phids)
|
||||
->needCropPics(true)
|
||||
->needProfileImage(true)
|
||||
->needParticipantCache(true)
|
||||
->execute();
|
||||
$latest_conpherences = mpull($latest_conpherences, null, 'getPHID');
|
||||
|
@ -31,7 +31,7 @@ final class ConpherenceColumnViewController extends
|
|||
$conpherence = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->withIDs(array($request->getInt('id')))
|
||||
->needCropPics(true)
|
||||
->needProfileImage(true)
|
||||
->needTransactions(true)
|
||||
->setTransactionLimit(ConpherenceThreadQuery::TRANSACTION_LIMIT)
|
||||
->executeOne();
|
||||
|
@ -41,7 +41,7 @@ final class ConpherenceColumnViewController extends
|
|||
$conpherence = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->withPHIDs(array($participant->getConpherencePHID()))
|
||||
->needCropPics(true)
|
||||
->needProfileImage(true)
|
||||
->needTransactions(true)
|
||||
->setTransactionLimit(ConpherenceThreadQuery::TRANSACTION_LIMIT)
|
||||
->executeOne();
|
||||
|
|
|
@ -59,13 +59,18 @@ abstract class ConpherenceController extends PhabricatorController {
|
|||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($data['title'])
|
||||
->setSubheader($data['topic'])
|
||||
->addClass((!$data['topic']) ? 'conpherence-no-topic' : null);
|
||||
->setImage($data['image']);
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$conpherence,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
if ($can_edit) {
|
||||
$header->setImageURL(
|
||||
$this->getApplicationURI('picture/'.$conpherence->getID().'/'));
|
||||
}
|
||||
|
||||
$participating = $conpherence->getParticipantIfExists($viewer->getPHID());
|
||||
$can_join = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
|
|
|
@ -158,7 +158,7 @@ final class ConpherenceListController extends ConpherenceController {
|
|||
$conpherences = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->withPHIDs($conpherence_phids)
|
||||
->needCropPics(true)
|
||||
->needProfileImage(true)
|
||||
->needParticipantCache(true)
|
||||
->execute();
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ final class ConpherenceNotificationPanelController
|
|||
public function handleRequest(AphrontRequest $request) {
|
||||
$user = $request->getUser();
|
||||
$conpherences = array();
|
||||
require_celerity_resource('conpherence-notification-css');
|
||||
$unread_status = ConpherenceParticipationStatus::BEHIND;
|
||||
|
||||
$participant_data = id(new ConpherenceParticipantQuery())
|
||||
|
@ -17,7 +18,7 @@ final class ConpherenceNotificationPanelController
|
|||
$conpherences = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->withPHIDs(array_keys($participant_data))
|
||||
->needCropPics(true)
|
||||
->needProfileImage(true)
|
||||
->needTransactions(true)
|
||||
->setTransactionLimit(3 * 5)
|
||||
->needParticipantCache(true)
|
||||
|
@ -25,7 +26,6 @@ final class ConpherenceNotificationPanelController
|
|||
}
|
||||
|
||||
if ($conpherences) {
|
||||
require_celerity_resource('conpherence-notification-css');
|
||||
// re-order the conpherences based on participation data
|
||||
$conpherences = array_select_keys(
|
||||
$conpherences, array_keys($participant_data));
|
||||
|
@ -70,13 +70,20 @@ final class ConpherenceNotificationPanelController
|
|||
}
|
||||
$content = $view->render();
|
||||
} else {
|
||||
$rooms_uri = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/conpherence/',
|
||||
'class' => 'no-room-notification',
|
||||
),
|
||||
pht('You have joined no rooms.'));
|
||||
|
||||
$content = phutil_tag_div(
|
||||
'phabricator-notification no-notifications',
|
||||
pht('You have no messages.'));
|
||||
'phabricator-notification no-notifications', $rooms_uri);
|
||||
}
|
||||
|
||||
$content = hsprintf(
|
||||
'<div class="phabricator-notification-header">%s</div>'.
|
||||
'<div class="phabricator-notification-header grouped">%s%s</div>'.
|
||||
'%s',
|
||||
phutil_tag(
|
||||
'a',
|
||||
|
@ -84,6 +91,7 @@ final class ConpherenceNotificationPanelController
|
|||
'href' => '/conpherence/',
|
||||
),
|
||||
pht('Rooms')),
|
||||
$this->renderPersistentOption(),
|
||||
$content);
|
||||
|
||||
$unread = id(new ConpherenceParticipantCountQuery())
|
||||
|
@ -100,4 +108,32 @@ final class ConpherenceNotificationPanelController
|
|||
return id(new AphrontAjaxResponse())->setContent($json);
|
||||
}
|
||||
|
||||
private function renderPersistentOption() {
|
||||
$viewer = $this->getViewer();
|
||||
$column_key = PhabricatorConpherenceColumnVisibleSetting::SETTINGKEY;
|
||||
$show = (bool)$viewer->getUserSetting($column_key, false);
|
||||
|
||||
$view = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'persistent-option',
|
||||
),
|
||||
array(
|
||||
javelin_tag(
|
||||
'input',
|
||||
array(
|
||||
'type' => 'checkbox',
|
||||
'checked' => ($show) ? 'checked' : null,
|
||||
'value' => !$show,
|
||||
'sigil' => 'conpherence-persist-column',
|
||||
)),
|
||||
phutil_tag(
|
||||
'span',
|
||||
array(),
|
||||
pht('Persistent Chat')),
|
||||
));
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
<?php
|
||||
|
||||
final class ConpherenceRoomPictureController
|
||||
extends ConpherenceController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$conpherence = id(new ConpherenceThreadQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->needProfileImage(true)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$conpherence) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$monogram = $conpherence->getMonogram();
|
||||
|
||||
$supported_formats = PhabricatorFile::getTransformableImageFormats();
|
||||
$e_file = true;
|
||||
$errors = array();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$phid = $request->getStr('phid');
|
||||
$is_default = false;
|
||||
if ($phid == PhabricatorPHIDConstants::PHID_VOID) {
|
||||
$phid = null;
|
||||
$is_default = true;
|
||||
} else if ($phid) {
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($phid))
|
||||
->executeOne();
|
||||
} else {
|
||||
if ($request->getFileExists('picture')) {
|
||||
$file = PhabricatorFile::newFromPHPUpload(
|
||||
$_FILES['picture'],
|
||||
array(
|
||||
'authorPHID' => $viewer->getPHID(),
|
||||
'canCDN' => true,
|
||||
));
|
||||
} else {
|
||||
$e_file = pht('Required');
|
||||
$errors[] = pht(
|
||||
'You must choose a file when uploading a new room picture.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$errors && !$is_default) {
|
||||
if (!$file->isTransformableImage()) {
|
||||
$e_file = pht('Not Supported');
|
||||
$errors[] = pht(
|
||||
'This server only supports these image formats: %s.',
|
||||
implode(', ', $supported_formats));
|
||||
} else {
|
||||
$xform = PhabricatorFileTransform::getTransformByKey(
|
||||
PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE);
|
||||
$xformed = $xform->executeTransform($file);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
if ($is_default) {
|
||||
$new_value = null;
|
||||
} else {
|
||||
$xformed->attachToObject($conpherence->getPHID());
|
||||
$new_value = $xformed->getPHID();
|
||||
}
|
||||
|
||||
$xactions = array();
|
||||
$xactions[] = id(new ConpherenceTransaction())
|
||||
->setTransactionType(ConpherenceTransaction::TYPE_PICTURE)
|
||||
->setNewValue($new_value);
|
||||
|
||||
$editor = id(new ConpherenceEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContinueOnNoEffect(true);
|
||||
|
||||
$editor->applyTransactions($conpherence, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI('/'.$monogram);
|
||||
}
|
||||
}
|
||||
|
||||
$title = pht('Edit Room Picture');
|
||||
|
||||
$form = id(new PHUIFormLayoutView())
|
||||
->setUser($viewer);
|
||||
|
||||
$default_image = PhabricatorFile::loadBuiltin($viewer, 'conpherence.png');
|
||||
|
||||
$images = array();
|
||||
|
||||
$current = $conpherence->getProfileImagePHID();
|
||||
$has_current = false;
|
||||
if ($current) {
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($current))
|
||||
->executeOne();
|
||||
if ($file) {
|
||||
if ($file->isTransformableImage()) {
|
||||
$has_current = true;
|
||||
$images[$current] = array(
|
||||
'uri' => $file->getBestURI(),
|
||||
'tip' => pht('Current Picture'),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$images[PhabricatorPHIDConstants::PHID_VOID] = array(
|
||||
'uri' => $default_image->getBestURI(),
|
||||
'tip' => pht('Default Picture'),
|
||||
);
|
||||
|
||||
require_celerity_resource('people-profile-css');
|
||||
Javelin::initBehavior('phabricator-tooltips', array());
|
||||
|
||||
$buttons = array();
|
||||
foreach ($images as $phid => $spec) {
|
||||
$button = javelin_tag(
|
||||
'button',
|
||||
array(
|
||||
'class' => 'grey profile-image-button',
|
||||
'sigil' => 'has-tooltip',
|
||||
'meta' => array(
|
||||
'tip' => $spec['tip'],
|
||||
'size' => 300,
|
||||
),
|
||||
),
|
||||
phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'height' => 50,
|
||||
'width' => 50,
|
||||
'src' => $spec['uri'],
|
||||
)));
|
||||
|
||||
$button = array(
|
||||
phutil_tag(
|
||||
'input',
|
||||
array(
|
||||
'type' => 'hidden',
|
||||
'name' => 'phid',
|
||||
'value' => $phid,
|
||||
)),
|
||||
$button,
|
||||
);
|
||||
|
||||
$button = phabricator_form(
|
||||
$viewer,
|
||||
array(
|
||||
'class' => 'profile-image-form',
|
||||
'method' => 'POST',
|
||||
),
|
||||
$button);
|
||||
|
||||
$buttons[] = $button;
|
||||
}
|
||||
|
||||
if ($has_current) {
|
||||
$form->appendChild(
|
||||
id(new AphrontFormMarkupControl())
|
||||
->setLabel(pht('Current Picture'))
|
||||
->setValue(array_shift($buttons)));
|
||||
}
|
||||
|
||||
$form->appendChild(
|
||||
id(new AphrontFormMarkupControl())
|
||||
->setLabel(pht('Use Picture'))
|
||||
->setValue($buttons));
|
||||
|
||||
$form_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText($title)
|
||||
->setFormErrors($errors)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setForm($form);
|
||||
|
||||
$upload_form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->setEncType('multipart/form-data')
|
||||
->appendChild(
|
||||
id(new AphrontFormFileControl())
|
||||
->setName('picture')
|
||||
->setLabel(pht('Upload Picture'))
|
||||
->setError($e_file)
|
||||
->setCaption(
|
||||
pht('Supported formats: %s', implode(', ', $supported_formats))))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->addCancelButton('/'.$monogram)
|
||||
->setValue(pht('Upload Picture')));
|
||||
|
||||
$upload_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Upload New Picture'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setForm($upload_form);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb($conpherence->getTitle(), '/'.$monogram);
|
||||
$crumbs->addTextCrumb(pht('Room Picture'));
|
||||
$crumbs->setBorder(true);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Edit Room Picture'))
|
||||
->setHeaderIcon('fa-camera');
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setFooter(array(
|
||||
$form_box,
|
||||
$upload_box,
|
||||
));
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild(
|
||||
array(
|
||||
$view,
|
||||
));
|
||||
|
||||
}
|
||||
}
|
|
@ -36,8 +36,6 @@ final class ConpherenceUpdateController
|
|||
$conpherence = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->withIDs(array($conpherence_id))
|
||||
->needOrigPics(true)
|
||||
->needCropPics(true)
|
||||
->needParticipants($need_participants)
|
||||
->requireCapabilities($needed_capabilities)
|
||||
->executeOne();
|
||||
|
@ -131,57 +129,14 @@ final class ConpherenceUpdateController
|
|||
|
||||
break;
|
||||
case ConpherenceUpdateActions::METADATA:
|
||||
$top = $request->getInt('image_y');
|
||||
$left = $request->getInt('image_x');
|
||||
$file_id = $request->getInt('file_id');
|
||||
$title = $request->getStr('title');
|
||||
$topic = $request->getStr('topic');
|
||||
if ($file_id) {
|
||||
$orig_file = id(new PhabricatorFileQuery())
|
||||
->setViewer($user)
|
||||
->withIDs(array($file_id))
|
||||
->executeOne();
|
||||
$xactions[] = id(new ConpherenceTransaction())
|
||||
->setTransactionType(ConpherenceTransaction::TYPE_PICTURE)
|
||||
->setNewValue($orig_file);
|
||||
$okay = $orig_file->isTransformableImage();
|
||||
if ($okay) {
|
||||
$xformer = new PhabricatorImageTransformer();
|
||||
$crop_file = $xformer->executeConpherenceTransform(
|
||||
$orig_file,
|
||||
0,
|
||||
0,
|
||||
ConpherenceImageData::CROP_WIDTH,
|
||||
ConpherenceImageData::CROP_HEIGHT);
|
||||
$xactions[] = id(new ConpherenceTransaction())
|
||||
->setTransactionType(
|
||||
ConpherenceTransaction::TYPE_PICTURE_CROP)
|
||||
->setNewValue($crop_file->getPHID());
|
||||
}
|
||||
$response_mode = 'redirect';
|
||||
}
|
||||
|
||||
// all other metadata updates are continue requests
|
||||
if (!$request->isContinueRequest()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($top !== null || $left !== null) {
|
||||
$file = $conpherence->getImage(ConpherenceImageData::SIZE_ORIG);
|
||||
$xformer = new PhabricatorImageTransformer();
|
||||
$xformed = $xformer->executeConpherenceTransform(
|
||||
$file,
|
||||
$top,
|
||||
$left,
|
||||
ConpherenceImageData::CROP_WIDTH,
|
||||
ConpherenceImageData::CROP_HEIGHT);
|
||||
$image_phid = $xformed->getPHID();
|
||||
|
||||
$xactions[] = id(new ConpherenceTransaction())
|
||||
->setTransactionType(
|
||||
ConpherenceTransaction::TYPE_PICTURE_CROP)
|
||||
->setNewValue($image_phid);
|
||||
}
|
||||
$title = $request->getStr('title');
|
||||
$topic = $request->getStr('topic');
|
||||
$xactions[] = id(new ConpherenceTransaction())
|
||||
|
@ -491,35 +446,6 @@ final class ConpherenceUpdateController
|
|||
->setName('topic')
|
||||
->setValue($conpherence->getTopic()));
|
||||
|
||||
$nopic = $this->getRequest()->getExists('nopic');
|
||||
$image = $conpherence->getImage(ConpherenceImageData::SIZE_ORIG);
|
||||
if ($nopic) {
|
||||
// do not render any pic related controls
|
||||
} else if ($image) {
|
||||
$crop_uri = $conpherence->loadImageURI(ConpherenceImageData::SIZE_CROP);
|
||||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormMarkupControl())
|
||||
->setLabel(pht('Image'))
|
||||
->setValue(phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $crop_uri,
|
||||
))))
|
||||
->appendChild(
|
||||
id(new ConpherencePicCropControl())
|
||||
->setLabel(pht('Crop Image'))
|
||||
->setValue($image))
|
||||
->appendChild(
|
||||
id(new ConpherenceFormDragAndDropUploadControl())
|
||||
->setLabel(pht('Change Image')));
|
||||
} else {
|
||||
$form
|
||||
->appendChild(
|
||||
id(new ConpherenceFormDragAndDropUploadControl())
|
||||
->setLabel(pht('Image')));
|
||||
}
|
||||
|
||||
$policies = id(new PhabricatorPolicyQuery())
|
||||
->setViewer($user)
|
||||
->setObject($conpherence)
|
||||
|
@ -571,7 +497,6 @@ final class ConpherenceUpdateController
|
|||
$latest_transaction_id) {
|
||||
|
||||
$minimal_display = $this->getRequest()->getExists('minimal_display');
|
||||
$need_widget_data = false;
|
||||
$need_transactions = false;
|
||||
$need_participant_cache = true;
|
||||
switch ($action) {
|
||||
|
@ -582,7 +507,6 @@ final class ConpherenceUpdateController
|
|||
case ConpherenceUpdateActions::MESSAGE:
|
||||
case ConpherenceUpdateActions::ADD_PERSON:
|
||||
$need_transactions = true;
|
||||
$need_widget_data = !$minimal_display;
|
||||
break;
|
||||
case ConpherenceUpdateActions::REMOVE_PERSON:
|
||||
case ConpherenceUpdateActions::NOTIFICATIONS:
|
||||
|
@ -594,7 +518,7 @@ final class ConpherenceUpdateController
|
|||
$conpherence = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->setAfterTransactionID($latest_transaction_id)
|
||||
->needCropPics(true)
|
||||
->needProfileImage(true)
|
||||
->needParticipantCache($need_participant_cache)
|
||||
->needParticipants(true)
|
||||
->needTransactions($need_transactions)
|
||||
|
@ -607,8 +531,13 @@ final class ConpherenceUpdateController
|
|||
$user,
|
||||
$conpherence,
|
||||
!$minimal_display);
|
||||
$participant_obj = $conpherence->getParticipant($user->getPHID());
|
||||
$participant_obj->markUpToDate($conpherence, $data['latest_transaction']);
|
||||
$key = PhabricatorConpherenceColumnMinimizeSetting::SETTINGKEY;
|
||||
$minimized = $user->getUserSetting($key);
|
||||
if (!$minimized) {
|
||||
$participant_obj = $conpherence->getParticipant($user->getPHID());
|
||||
$participant_obj
|
||||
->markUpToDate($conpherence, $data['latest_transaction']);
|
||||
}
|
||||
} else if ($need_transactions) {
|
||||
$non_update = true;
|
||||
$data = array();
|
||||
|
|
|
@ -19,7 +19,7 @@ final class ConpherenceViewController extends
|
|||
$query = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->withIDs(array($conpherence_id))
|
||||
->needCropPics(true)
|
||||
->needProfileImage(true)
|
||||
->needParticipantCache(true)
|
||||
->needTransactions(true)
|
||||
->setTransactionLimit($this->getMainQueryLimit());
|
||||
|
|
|
@ -91,7 +91,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
$types[] = ConpherenceTransaction::TYPE_TOPIC;
|
||||
$types[] = ConpherenceTransaction::TYPE_PARTICIPANTS;
|
||||
$types[] = ConpherenceTransaction::TYPE_PICTURE;
|
||||
$types[] = ConpherenceTransaction::TYPE_PICTURE_CROP;
|
||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||
$types[] = PhabricatorTransactions::TYPE_JOIN_POLICY;
|
||||
|
@ -109,9 +108,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
case ConpherenceTransaction::TYPE_TOPIC:
|
||||
return $object->getTopic();
|
||||
case ConpherenceTransaction::TYPE_PICTURE:
|
||||
return $object->getImagePHID(ConpherenceImageData::SIZE_ORIG);
|
||||
case ConpherenceTransaction::TYPE_PICTURE_CROP:
|
||||
return $object->getImagePHID(ConpherenceImageData::SIZE_CROP);
|
||||
return $object->getProfileImagePHID();
|
||||
case ConpherenceTransaction::TYPE_PARTICIPANTS:
|
||||
if ($this->getIsNewObject()) {
|
||||
return array();
|
||||
|
@ -127,11 +124,8 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
switch ($xaction->getTransactionType()) {
|
||||
case ConpherenceTransaction::TYPE_TITLE:
|
||||
case ConpherenceTransaction::TYPE_TOPIC:
|
||||
case ConpherenceTransaction::TYPE_PICTURE_CROP:
|
||||
return $xaction->getNewValue();
|
||||
case ConpherenceTransaction::TYPE_PICTURE:
|
||||
$file = $xaction->getNewValue();
|
||||
return $file->getPHID();
|
||||
return $xaction->getNewValue();
|
||||
case ConpherenceTransaction::TYPE_PARTICIPANTS:
|
||||
return $this->getPHIDTransactionNewValue($xaction);
|
||||
}
|
||||
|
@ -224,14 +218,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
$object->setTopic($xaction->getNewValue());
|
||||
break;
|
||||
case ConpherenceTransaction::TYPE_PICTURE:
|
||||
$object->setImagePHID(
|
||||
$xaction->getNewValue(),
|
||||
ConpherenceImageData::SIZE_ORIG);
|
||||
break;
|
||||
case ConpherenceTransaction::TYPE_PICTURE_CROP:
|
||||
$object->setImagePHID(
|
||||
$xaction->getNewValue(),
|
||||
ConpherenceImageData::SIZE_CROP);
|
||||
$object->setProfileImagePHID($xaction->getNewValue());
|
||||
break;
|
||||
case ConpherenceTransaction::TYPE_PARTICIPANTS:
|
||||
if (!$this->getIsNewObject()) {
|
||||
|
@ -339,6 +326,10 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
|
||||
if (!$xactions) {
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
$message_count = 0;
|
||||
foreach ($xactions as $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
|
@ -571,19 +562,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
return true;
|
||||
}
|
||||
|
||||
protected function extractFilePHIDsFromCustomTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case ConpherenceTransaction::TYPE_PICTURE:
|
||||
case ConpherenceTransaction::TYPE_PICTURE_CROP:
|
||||
return array($xaction->getNewValue());
|
||||
}
|
||||
|
||||
return parent::extractFilePHIDsFromCustomTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function validateTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
$type,
|
||||
|
@ -612,21 +590,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
$errors[] = $error;
|
||||
}
|
||||
break;
|
||||
case ConpherenceTransaction::TYPE_PICTURE:
|
||||
foreach ($xactions as $xaction) {
|
||||
$file = $xaction->getNewValue();
|
||||
if (!$file->isTransformableImage()) {
|
||||
$detail = pht('This server only supports these image formats: %s.',
|
||||
implode(', ', PhabricatorFile::getTransformableImageFormats()));
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
$detail,
|
||||
last($xactions));
|
||||
$errors[] = $error;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ConpherenceTransaction::TYPE_PARTICIPANTS:
|
||||
foreach ($xactions as $xaction) {
|
||||
$new_phids = $this->getPHIDTransactionNewValue($xaction, array());
|
||||
|
|
|
@ -9,14 +9,13 @@ final class ConpherenceThreadQuery
|
|||
private $ids;
|
||||
private $participantPHIDs;
|
||||
private $needParticipants;
|
||||
private $needCropPics;
|
||||
private $needOrigPics;
|
||||
private $needTransactions;
|
||||
private $needParticipantCache;
|
||||
private $afterTransactionID;
|
||||
private $beforeTransactionID;
|
||||
private $transactionLimit;
|
||||
private $fulltext;
|
||||
private $needProfileImage;
|
||||
|
||||
public function needParticipantCache($participant_cache) {
|
||||
$this->needParticipantCache = $participant_cache;
|
||||
|
@ -28,13 +27,8 @@ final class ConpherenceThreadQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function needCropPics($need) {
|
||||
$this->needCropPics = $need;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needOrigPics($need_widget_data) {
|
||||
$this->needOrigPics = $need_widget_data;
|
||||
public function needProfileImage($need) {
|
||||
$this->needProfileImage = $need;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -110,14 +104,33 @@ final class ConpherenceThreadQuery
|
|||
if ($this->needTransactions) {
|
||||
$this->loadTransactionsAndHandles($conpherences);
|
||||
}
|
||||
if ($this->needOrigPics || $this->needCropPics) {
|
||||
$this->initImages($conpherences);
|
||||
}
|
||||
if ($this->needOrigPics) {
|
||||
$this->loadOrigPics($conpherences);
|
||||
}
|
||||
if ($this->needCropPics) {
|
||||
$this->loadCropPics($conpherences);
|
||||
if ($this->needProfileImage) {
|
||||
$default = null;
|
||||
$file_phids = mpull($conpherences, 'getProfileImagePHID');
|
||||
$file_phids = array_filter($file_phids);
|
||||
if ($file_phids) {
|
||||
$files = id(new PhabricatorFileQuery())
|
||||
->setParentQuery($this)
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs($file_phids)
|
||||
->execute();
|
||||
$files = mpull($files, null, 'getPHID');
|
||||
} else {
|
||||
$files = array();
|
||||
}
|
||||
|
||||
foreach ($conpherences as $conpherence) {
|
||||
$file = idx($files, $conpherence->getProfileImagePHID());
|
||||
if (!$file) {
|
||||
if (!$default) {
|
||||
$default = PhabricatorFile::loadBuiltin(
|
||||
$this->getViewer(),
|
||||
'conpherence.png');
|
||||
}
|
||||
$file = $default;
|
||||
}
|
||||
$conpherence->attachProfileImageFile($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,50 +279,6 @@ final class ConpherenceThreadQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
private function loadOrigPics(array $conpherences) {
|
||||
return $this->loadPics(
|
||||
$conpherences,
|
||||
ConpherenceImageData::SIZE_ORIG);
|
||||
}
|
||||
|
||||
private function loadCropPics(array $conpherences) {
|
||||
return $this->loadPics(
|
||||
$conpherences,
|
||||
ConpherenceImageData::SIZE_CROP);
|
||||
}
|
||||
|
||||
private function initImages($conpherences) {
|
||||
foreach ($conpherences as $conpherence) {
|
||||
$conpherence->attachImages(array());
|
||||
}
|
||||
}
|
||||
|
||||
private function loadPics(array $conpherences, $size) {
|
||||
$conpherence_pic_phids = array();
|
||||
foreach ($conpherences as $conpherence) {
|
||||
$phid = $conpherence->getImagePHID($size);
|
||||
if ($phid) {
|
||||
$conpherence_pic_phids[$conpherence->getPHID()] = $phid;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$conpherence_pic_phids) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$files = id(new PhabricatorFileQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs($conpherence_pic_phids)
|
||||
->execute();
|
||||
$files = mpull($files, null, 'getPHID');
|
||||
|
||||
foreach ($conpherence_pic_phids as $conpherence_phid => $pic_phid) {
|
||||
$conpherences[$conpherence_phid]->setImage($files[$pic_phid], $size);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorConpherenceApplication';
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
|
||||
protected $title;
|
||||
protected $topic;
|
||||
protected $imagePHIDs = array();
|
||||
protected $imagePHIDs = array(); // TODO; nuke after migrations
|
||||
protected $profileImagePHID;
|
||||
protected $messageCount;
|
||||
protected $recentParticipantPHIDs = array();
|
||||
protected $mailKey;
|
||||
|
@ -19,8 +20,8 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
|
||||
private $participants = self::ATTACHABLE;
|
||||
private $transactions = self::ATTACHABLE;
|
||||
private $profileImageFile = self::ATTACHABLE;
|
||||
private $handles = self::ATTACHABLE;
|
||||
private $images = self::ATTACHABLE;
|
||||
|
||||
public static function initializeNewRoom(PhabricatorUser $sender) {
|
||||
$default_policy = id(new ConpherenceThreadMembersPolicyRule())
|
||||
|
@ -30,7 +31,6 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
->setTitle('')
|
||||
->setTopic('')
|
||||
->attachParticipants(array())
|
||||
->attachImages(array())
|
||||
->setViewPolicy($default_policy)
|
||||
->setEditPolicy($default_policy)
|
||||
->setJoinPolicy($default_policy);
|
||||
|
@ -49,6 +49,7 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
'messageCount' => 'uint64',
|
||||
'mailKey' => 'text20',
|
||||
'joinPolicy' => 'policy',
|
||||
'profileImagePHID' => 'phid?',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_phid' => null,
|
||||
|
@ -76,46 +77,21 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
return 'Z'.$this->getID();
|
||||
}
|
||||
|
||||
public function getImagePHID($size) {
|
||||
$image_phids = $this->getImagePHIDs();
|
||||
return idx($image_phids, $size);
|
||||
}
|
||||
public function setImagePHID($phid, $size) {
|
||||
$image_phids = $this->getImagePHIDs();
|
||||
$image_phids[$size] = $phid;
|
||||
return $this->setImagePHIDs($image_phids);
|
||||
}
|
||||
|
||||
public function getImage($size) {
|
||||
$images = $this->getImages();
|
||||
return idx($images, $size);
|
||||
}
|
||||
public function setImage(PhabricatorFile $file, $size) {
|
||||
$files = $this->getImages();
|
||||
$files[$size] = $file;
|
||||
return $this->attachImages($files);
|
||||
}
|
||||
public function attachImages(array $files) {
|
||||
assert_instances_of($files, 'PhabricatorFile');
|
||||
$this->images = $files;
|
||||
return $this;
|
||||
}
|
||||
private function getImages() {
|
||||
return $this->assertAttached($this->images);
|
||||
}
|
||||
|
||||
public function attachParticipants(array $participants) {
|
||||
assert_instances_of($participants, 'ConpherenceParticipant');
|
||||
$this->participants = $participants;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParticipants() {
|
||||
return $this->assertAttached($this->participants);
|
||||
}
|
||||
|
||||
public function getParticipant($phid) {
|
||||
$participants = $this->getParticipants();
|
||||
return $participants[$phid];
|
||||
}
|
||||
|
||||
public function getParticipantIfExists($phid, $default = null) {
|
||||
$participants = $this->getParticipants();
|
||||
return idx($participants, $phid, $default);
|
||||
|
@ -131,6 +107,7 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
$this->handles = $handles;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHandles() {
|
||||
return $this->assertAttached($this->handles);
|
||||
}
|
||||
|
@ -140,9 +117,11 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
$this->transactions = $transactions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTransactions($assert_attached = true) {
|
||||
return $this->assertAttached($this->transactions);
|
||||
}
|
||||
|
||||
public function hasAttachedTransactions() {
|
||||
return $this->transactions !== self::ATTACHABLE;
|
||||
}
|
||||
|
@ -156,14 +135,17 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
$amount);
|
||||
}
|
||||
|
||||
public function loadImageURI($size) {
|
||||
$file = $this->getImage($size);
|
||||
public function getProfileImageURI() {
|
||||
return $this->getProfileImageFile()->getBestURI();
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
return $file->getBestURI();
|
||||
}
|
||||
public function attachProfileImageFile(PhabricatorFile $file) {
|
||||
$this->profileImageFile = $file;
|
||||
return $this;
|
||||
}
|
||||
|
||||
return PhabricatorUser::getDefaultProfileImageURI();
|
||||
public function getProfileImageFile() {
|
||||
return $this->assertAttached($this->profileImageFile);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -273,22 +255,26 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
$lucky_handle = reset($handles);
|
||||
}
|
||||
|
||||
$img_src = null;
|
||||
$size = ConpherenceImageData::SIZE_CROP;
|
||||
if ($this->getImagePHID($size)) {
|
||||
$img_src = $this->getImage($size)->getBestURI();
|
||||
} else if ($lucky_handle) {
|
||||
$img_src = $lucky_handle->getImageURI();
|
||||
}
|
||||
$img_src = $this->getProfileImageURI();
|
||||
|
||||
$message_title = null;
|
||||
if ($subtitle_mode == 'message') {
|
||||
$message_transaction = null;
|
||||
$action_transaction = null;
|
||||
foreach ($transactions as $transaction) {
|
||||
if ($message_transaction || $action_transaction) {
|
||||
break;
|
||||
}
|
||||
switch ($transaction->getTransactionType()) {
|
||||
case PhabricatorTransactions::TYPE_COMMENT:
|
||||
$message_transaction = $transaction;
|
||||
break 2;
|
||||
break;
|
||||
case ConpherenceTransaction::TYPE_TITLE:
|
||||
case ConpherenceTransaction::TYPE_TOPIC:
|
||||
case ConpherenceTransaction::TYPE_PICTURE:
|
||||
case ConpherenceTransaction::TYPE_PARTICIPANTS:
|
||||
$action_transaction = $transaction;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -303,6 +289,11 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
->truncateString(
|
||||
$message_transaction->getComment()->getContent()));
|
||||
}
|
||||
if ($action_transaction) {
|
||||
$message_title = id(clone $action_transaction)
|
||||
->setRenderingTarget(PhabricatorApplicationTransaction::TARGET_TEXT)
|
||||
->getTitle();
|
||||
}
|
||||
}
|
||||
switch ($subtitle_mode) {
|
||||
case 'recent':
|
||||
|
|
|
@ -7,7 +7,7 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction {
|
|||
const TYPE_PARTICIPANTS = 'participants';
|
||||
const TYPE_DATE_MARKER = 'date-marker';
|
||||
const TYPE_PICTURE = 'picture';
|
||||
const TYPE_PICTURE_CROP = 'picture-crop';
|
||||
const TYPE_PICTURE_CROP = 'picture-crop'; // TODO: Nuke these from DB.
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'conpherence';
|
||||
|
|
|
@ -150,13 +150,14 @@ final class ConpherenceDurableColumnView extends AphrontTagView {
|
|||
|
||||
$icon_bar = null;
|
||||
if ($this->conpherences) {
|
||||
$icon_bar = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'conpherence-durable-column-icon-bar',
|
||||
),
|
||||
$this->buildIconBar());
|
||||
$icon_bar = $this->buildIconBar();
|
||||
}
|
||||
$icon_bar = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'conpherence-durable-column-icon-bar',
|
||||
),
|
||||
$icon_bar);
|
||||
|
||||
$transactions = $this->buildTransactions();
|
||||
|
||||
|
@ -198,19 +199,6 @@ final class ConpherenceDurableColumnView extends AphrontTagView {
|
|||
);
|
||||
}
|
||||
|
||||
private function getPolicyIcon(
|
||||
ConpherenceThread $conpherence,
|
||||
array $policy_objects) {
|
||||
|
||||
assert_instances_of($policy_objects, 'PhabricatorPolicy');
|
||||
|
||||
$icon = $conpherence->getPolicyIconName($policy_objects);
|
||||
$icon = id(new PHUIIconView())
|
||||
->addClass('mmr')
|
||||
->setIcon($icon);
|
||||
return $icon;
|
||||
}
|
||||
|
||||
private function buildIconBar() {
|
||||
$icons = array();
|
||||
$selected_conpherence = $this->getSelectedConpherence();
|
||||
|
@ -222,12 +210,10 @@ final class ConpherenceDurableColumnView extends AphrontTagView {
|
|||
$classes[] = 'selected';
|
||||
}
|
||||
$data = $conpherence->getDisplayData($this->getUser());
|
||||
$icon = $this->getPolicyIcon($conpherence, $this->getPolicyObjects());
|
||||
$thread_title = phutil_tag(
|
||||
'span',
|
||||
array(),
|
||||
array(
|
||||
$icon,
|
||||
$data['title'],
|
||||
));
|
||||
$image = $data['image'];
|
||||
|
@ -324,17 +310,20 @@ final class ConpherenceDurableColumnView extends AphrontTagView {
|
|||
->addMenuItem($minimize)
|
||||
->addClass('phabricator-application-menu');
|
||||
|
||||
$header = null;
|
||||
if ($conpherence) {
|
||||
$data = $conpherence->getDisplayData($this->getUser());
|
||||
$header = phutil_tag(
|
||||
'span',
|
||||
array(),
|
||||
array(
|
||||
$this->getPolicyIcon($conpherence, $this->getPolicyObjects()),
|
||||
$data['title'],
|
||||
));
|
||||
}
|
||||
$data['title']);
|
||||
} else {
|
||||
$header = phutil_tag(
|
||||
'span',
|
||||
array(),
|
||||
pht('Conpherence'));
|
||||
}
|
||||
|
||||
$status = new PhabricatorNotificationStatusView();
|
||||
|
||||
return
|
||||
phutil_tag(
|
||||
|
@ -343,6 +332,7 @@ final class ConpherenceDurableColumnView extends AphrontTagView {
|
|||
'class' => 'conpherence-durable-column-header-inner',
|
||||
),
|
||||
array(
|
||||
$status,
|
||||
javelin_tag(
|
||||
'div',
|
||||
array(
|
||||
|
@ -403,22 +393,22 @@ final class ConpherenceDurableColumnView extends AphrontTagView {
|
|||
if (!$this->getVisible() || $this->getInitialLoad()) {
|
||||
return pht('Loading...');
|
||||
}
|
||||
return array(
|
||||
$view = array(
|
||||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'mmb',
|
||||
'class' => 'column-no-rooms-text',
|
||||
),
|
||||
pht('You are not in any rooms yet.')),
|
||||
pht('You have not joined any rooms yet.')),
|
||||
javelin_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/conpherence/new/',
|
||||
'href' => '/conpherence/search/',
|
||||
'class' => 'button grey',
|
||||
'sigil' => 'workflow',
|
||||
),
|
||||
pht('Create a Room')),
|
||||
pht('Find Rooms')),
|
||||
);
|
||||
return phutil_tag_div('column-no-rooms', $view);
|
||||
}
|
||||
|
||||
$data = ConpherenceTransactionRenderer::renderTransactions(
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class ConpherencePicCropControl extends AphrontFormControl {
|
||||
|
||||
protected function getCustomControlClass() {
|
||||
return 'aphront-form-crop';
|
||||
}
|
||||
|
||||
protected function renderInput() {
|
||||
$width = ConpherenceImageData::CROP_WIDTH;
|
||||
$height = ConpherenceImageData::CROP_HEIGHT;
|
||||
|
||||
$file = $this->getValue();
|
||||
|
||||
if ($file === null) {
|
||||
return phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => PhabricatorUser::getDefaultProfileImageURI(),
|
||||
),
|
||||
'');
|
||||
}
|
||||
|
||||
$c_id = celerity_generate_unique_node_id();
|
||||
$metadata = $file->getMetadata();
|
||||
$scale = PhabricatorImageTransformer::getScaleForCrop(
|
||||
$file,
|
||||
$width,
|
||||
$height);
|
||||
|
||||
Javelin::initBehavior(
|
||||
'aphront-crop',
|
||||
array(
|
||||
'cropBoxID' => $c_id,
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'scale' => $scale,
|
||||
'imageH' => $metadata[PhabricatorFile::METADATA_IMAGE_HEIGHT],
|
||||
'imageW' => $metadata[PhabricatorFile::METADATA_IMAGE_WIDTH],
|
||||
));
|
||||
|
||||
return javelin_tag(
|
||||
'div',
|
||||
array(
|
||||
'id' => $c_id,
|
||||
'sigil' => 'crop-box',
|
||||
'mustcapture' => true,
|
||||
'class' => 'crop-box',
|
||||
),
|
||||
array(
|
||||
javelin_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $file->getBestURI(),
|
||||
'class' => 'crop-image',
|
||||
'sigil' => 'crop-image',
|
||||
),
|
||||
''),
|
||||
javelin_tag(
|
||||
'input',
|
||||
array(
|
||||
'type' => 'hidden',
|
||||
'name' => 'image_x',
|
||||
'sigil' => 'crop-x',
|
||||
),
|
||||
''),
|
||||
javelin_tag(
|
||||
'input',
|
||||
array(
|
||||
'type' => 'hidden',
|
||||
'name' => 'image_y',
|
||||
'sigil' => 'crop-y',
|
||||
),
|
||||
''),
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -228,7 +228,6 @@ final class ConpherenceTransactionView extends AphrontView {
|
|||
case ConpherenceTransaction::TYPE_TITLE:
|
||||
case ConpherenceTransaction::TYPE_TOPIC:
|
||||
case ConpherenceTransaction::TYPE_PICTURE:
|
||||
case ConpherenceTransaction::TYPE_PICTURE_CROP:
|
||||
case ConpherenceTransaction::TYPE_PARTICIPANTS:
|
||||
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
||||
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
||||
|
|
|
@ -78,7 +78,7 @@ final class DiffusionRepositoryActionsManagementPanel
|
|||
$autoclose = phutil_tag('em', array(), $autoclose);
|
||||
$view->addProperty(pht('Autoclose'), $autoclose);
|
||||
|
||||
return $this->newBox(pht('Branches'), $view);
|
||||
return $this->newBox(pht('Actions'), $view);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -421,7 +421,7 @@ final class ManiphestTaskSearchEngine
|
|||
'you need to get done. Tasks assigned to you will appear here.'))
|
||||
->addAction($create_button);
|
||||
|
||||
return $view;
|
||||
return $view;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,20 @@ final class PhabricatorNotificationStatusView extends AphrontTagView {
|
|||
'open' => pht('Connected'),
|
||||
'closed' => pht('Disconnected'),
|
||||
),
|
||||
'icon' => array(
|
||||
'open' => array(
|
||||
'icon' => 'fa-circle',
|
||||
'color' => 'green',
|
||||
),
|
||||
'setup' => array(
|
||||
'icon' => 'fa-circle',
|
||||
'color' => 'yellow',
|
||||
),
|
||||
'closed' => array(
|
||||
'icon' => 'fa-circle',
|
||||
'color' => 'red',
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
return array(
|
||||
|
@ -26,9 +40,33 @@ final class PhabricatorNotificationStatusView extends AphrontTagView {
|
|||
protected function getTagContent() {
|
||||
$have = PhabricatorEnv::getEnvConfig('notification.servers');
|
||||
if ($have) {
|
||||
return pht('Connecting...');
|
||||
$icon = id(new PHUIIconView())
|
||||
->setIcon('fa-circle-o yellow');
|
||||
$text = pht('Connecting...');
|
||||
return phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'connection-status-text '.
|
||||
'aphlict-connection-status-connecting',
|
||||
),
|
||||
array(
|
||||
$icon,
|
||||
$text,
|
||||
));
|
||||
} else {
|
||||
return pht('Notification server not enabled');
|
||||
$text = pht('Notification server not enabled');
|
||||
$icon = id(new PHUIIconView())
|
||||
->setIcon('fa-circle-o grey');
|
||||
return phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'connection-status-text '.
|
||||
'aphlict-connection-status-notenabled',
|
||||
),
|
||||
array(
|
||||
$icon,
|
||||
$text,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -205,8 +205,8 @@ final class PhabricatorPeopleProfileViewController
|
|||
$event,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$epoch_min = $event->getViewerDateFrom();
|
||||
$epoch_max = $event->getViewerDateTo();
|
||||
$epoch_min = $event->getStartDateTimeEpoch();
|
||||
$epoch_max = $event->getEndDateTimeEpoch();
|
||||
|
||||
$event_view = id(new AphrontCalendarEventView())
|
||||
->setCanEdit($can_edit)
|
||||
|
|
|
@ -431,8 +431,8 @@ final class PhabricatorPeopleQuery
|
|||
// because of an event, we check again for events after that one ends.
|
||||
while (true) {
|
||||
foreach ($events as $event) {
|
||||
$from = $event->getDateFromForCache();
|
||||
$to = $event->getViewerDateTo();
|
||||
$from = $event->getStartDateTimeEpochForCache();
|
||||
$to = $event->getEndDateTimeEpoch();
|
||||
if (($from <= $cursor) && ($to > $cursor)) {
|
||||
$cursor = $to;
|
||||
continue 2;
|
||||
|
|
|
@ -252,12 +252,6 @@ final class PhabricatorApplicationSearchController
|
|||
get_class($engine)));
|
||||
}
|
||||
|
||||
if ($list->getActions()) {
|
||||
foreach ($list->getActions() as $action) {
|
||||
$header->addActionLink($action);
|
||||
}
|
||||
}
|
||||
|
||||
if ($list->getObjectList()) {
|
||||
$box->setObjectList($list->getObjectList());
|
||||
}
|
||||
|
@ -274,6 +268,21 @@ final class PhabricatorApplicationSearchController
|
|||
$result_header = $list->getHeader();
|
||||
if ($result_header) {
|
||||
$box->setHeader($result_header);
|
||||
$header = $result_header;
|
||||
}
|
||||
|
||||
if ($list->getActions()) {
|
||||
foreach ($list->getActions() as $action) {
|
||||
$header->addActionLink($action);
|
||||
}
|
||||
}
|
||||
|
||||
$use_actions = $engine->newUseResultsActions($saved_query);
|
||||
if ($use_actions) {
|
||||
$use_dropdown = $this->newUseResultsDropdown(
|
||||
$saved_query,
|
||||
$use_actions);
|
||||
$header->addActionLink($use_dropdown);
|
||||
}
|
||||
|
||||
$more_crumbs = $list->getCrumbs();
|
||||
|
@ -496,5 +505,24 @@ final class PhabricatorApplicationSearchController
|
|||
return $nux_view;
|
||||
}
|
||||
|
||||
private function newUseResultsDropdown(
|
||||
PhabricatorSavedQuery $query,
|
||||
array $dropdown_items) {
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$action_list = id(new PhabricatorActionListView())
|
||||
->setViewer($viewer);
|
||||
foreach ($dropdown_items as $dropdown_item) {
|
||||
$action_list->addAction($dropdown_item);
|
||||
}
|
||||
|
||||
return id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setHref('#')
|
||||
->setText(pht('Use Results...'))
|
||||
->setIcon('fa-road')
|
||||
->setDropdownMenu($action_list);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1390,4 +1390,8 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
|
|||
return null;
|
||||
}
|
||||
|
||||
public function newUseResultsActions(PhabricatorSavedQuery $saved) {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
97
src/docs/user/userguide/calendar_exports.diviner
Normal file
97
src/docs/user/userguide/calendar_exports.diviner
Normal file
|
@ -0,0 +1,97 @@
|
|||
@title Calendar User Guide: Exporting Events
|
||||
@group userguide
|
||||
|
||||
Exporting events to other calendars.
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
IMPORTANT: Calendar is a prototype application. See
|
||||
@{article:User Guide: Prototype Applications}.
|
||||
|
||||
You can export events from Phabricator to other calendar applications like
|
||||
**Google Calendar** or **Calendar.app**. This document will guide you through
|
||||
how to export event data from Phabricator.
|
||||
|
||||
When you export events into another application, they generally will not be
|
||||
editable from that application. Exporting events allows you to create one
|
||||
calendar that shows all the events you care about in whatever application you
|
||||
prefer (so you can keep track of everything you need to do), but does not let
|
||||
you edit Phabricator events from another application.
|
||||
|
||||
When exporting events, you can either export individual events one at a time
|
||||
or export an entire group of events (for example, all events you are attending).
|
||||
|
||||
|
||||
Exporting a Single Event
|
||||
========================
|
||||
|
||||
To export a single event, visit the event detail page and click
|
||||
{nav Export as .ics}. This will download an `.ics` file which you can import
|
||||
into most other calendar applications.
|
||||
|
||||
Mail you receive about events also has a copy of this `.ics` file attached to
|
||||
it. You can import this `.ics` file directly.
|
||||
|
||||
In **Google Calendar**, use {nav Other Calendars > Import Calendar} to import
|
||||
the `.ics` file.
|
||||
|
||||
In **Calendar.app**, use {nav File > Import...} to import the `.ics` file, or
|
||||
drag the `.ics` file onto your calendar.
|
||||
|
||||
When you export a recurring event, the `.ics` file will contain information
|
||||
about the entire event series.
|
||||
|
||||
If you want to update event information later, you can just repeat this
|
||||
process. Calendar applications will update the existing event if you've
|
||||
previously imported an older version of it.
|
||||
|
||||
|
||||
Exporting a Group of Events
|
||||
===========================
|
||||
|
||||
You can export a group of events matching an arbitrary query (like all events
|
||||
you are attending) to keep different calendars in sync.
|
||||
|
||||
To export a group of events:
|
||||
|
||||
- Run a query in Calendar which selects the events you want to export.
|
||||
- Example: All events you are attending.
|
||||
- Example: All events you are invited to.
|
||||
- Example: All events tagged `#meetup`.
|
||||
- Select the {nav Use Results... > Export Query as .ics} action to turn
|
||||
the query into an export.
|
||||
- Name the export with a descritive name.
|
||||
- Select a policy mode for the export (see below for discussion).
|
||||
- Click {nav Create New Export} to finish the process.
|
||||
|
||||
The **policy modes** for exports are:
|
||||
|
||||
- **Public**: Only public information (visible to logged-out users) will
|
||||
be exported. This mode is not available if your install does not have
|
||||
public information (per `policy.allow-public` in Config).
|
||||
- **Privileged**: All event information will be exported. This means that
|
||||
anyone who knows the export URI can see ALL of the related event
|
||||
information, as though they were logged in with your account.
|
||||
|
||||
WARNING: Anyone who learns the URI for an export can see the data you choose
|
||||
to export, even if they don't have a Phabricator account! Be careful about how
|
||||
much data you export and treat the URI as a secret. If you accidentally share
|
||||
a URI, you can disable the export.
|
||||
|
||||
After finishing the process, you'll see a screen with some details about the
|
||||
export and an **ICS URI**. This URI allows you to import the events which match
|
||||
the query into another calendar application.
|
||||
|
||||
In **Google Calendar**, use {nav Other Calendars > Add by URL} to import the
|
||||
URI.
|
||||
|
||||
In **Calendar.app**, use {nav File > New Calendar Subscription...} to subscribe
|
||||
to the URI.
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
Continue by:
|
||||
|
||||
- returning to the @{article:Calendar User Guide}.
|
|
@ -1339,11 +1339,12 @@ abstract class LiskDAO extends Phobject {
|
|||
* @task hook
|
||||
*/
|
||||
public function generatePHID() {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'To use %s, you need to overload %s to perform PHID generation.',
|
||||
'CONFIG_AUX_PHID',
|
||||
'generatePHID()'));
|
||||
$type = $this->getPHIDType();
|
||||
return PhabricatorPHID::generateNewPHID($type);
|
||||
}
|
||||
|
||||
public function getPHIDType() {
|
||||
throw new PhutilMethodNotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -805,6 +805,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView
|
|||
|
||||
return array(
|
||||
'title' => $this->getTitle(),
|
||||
'bodyClasses' => $this->getBodyClasses(),
|
||||
'aphlictDropdownData' => array(
|
||||
$dropdown_query->getNotificationData(),
|
||||
$dropdown_query->getConpherenceData(),
|
||||
|
|
|
@ -124,10 +124,16 @@
|
|||
color: {$lightgreytext};
|
||||
}
|
||||
|
||||
.aphlict-connection-status .aphlict-connection-status-connected {
|
||||
color: {$green};
|
||||
.aphlict-connection-status {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.aphlict-connection-status .aphlict-connection-status-error {
|
||||
color: {$red};
|
||||
.aphlict-connection-status .phui-icon-view {
|
||||
font-size: 9px;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.aphlict-connection-status .connection-status-text {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
|
|
@ -28,36 +28,3 @@
|
|||
background: #124A1B;
|
||||
}
|
||||
|
||||
/*--- Profile Nav Colors -----------------------------------------------------*/
|
||||
|
||||
|
||||
.phui-theme-blindigo .phui-profile-menu .phabricator-side-menu,
|
||||
.phui-theme-blindigo .phabricator-side-menu .phui-profile-menu-footer-1 {
|
||||
background: #525867;
|
||||
}
|
||||
|
||||
.phui-theme-dark .phui-profile-menu .phabricator-side-menu,
|
||||
.phui-theme-dark .phabricator-side-menu .phui-profile-menu-footer-1 {
|
||||
background: #30333A;
|
||||
}
|
||||
|
||||
.phui-theme-indigo .phui-profile-menu .phabricator-side-menu,
|
||||
.phui-theme-indigo .phabricator-side-menu .phui-profile-menu-footer-1 {
|
||||
background: #423658;
|
||||
}
|
||||
|
||||
.phui-theme-red .phui-profile-menu .phabricator-side-menu,
|
||||
.phui-theme-red .phabricator-side-menu .phui-profile-menu-footer-1 {
|
||||
background: #420C0C;
|
||||
}
|
||||
|
||||
.phui-theme-blue .phui-profile-menu .phabricator-side-menu,
|
||||
.phui-theme-blue .phabricator-side-menu .phui-profile-menu-footer-1 {
|
||||
background: #062842;
|
||||
}
|
||||
|
||||
.phui-theme-green .phui-profile-menu .phabricator-side-menu,
|
||||
.phui-theme-green .phabricator-side-menu .phui-profile-menu-footer-1 {
|
||||
background: #122916;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,24 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.conpherence-durable-column-header .aphlict-connection-status {
|
||||
width: 6px;
|
||||
float: left;
|
||||
height: 18px;
|
||||
margin-left: 10px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.conpherence-durable-column-header .aphlict-connection-status
|
||||
.connection-status-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.conpherence-durable-column-header .aphlict-connection-status
|
||||
.phui-icon-view {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.conpherence-durable-column-header .phabricator-application-menu
|
||||
.phui-list-item-view {
|
||||
margin: 0;
|
||||
|
@ -100,11 +118,6 @@
|
|||
padding: 10px 8px 10px 8px;
|
||||
}
|
||||
|
||||
.conpherence-durable-column-header-text .phui-icon-view {
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px 0 rgba(0,0,0,.6);
|
||||
}
|
||||
|
||||
.conpherence-durable-column-icon-bar {
|
||||
width: 36px;
|
||||
background-color: {$lightgreybackground};
|
||||
|
@ -153,6 +166,16 @@
|
|||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.conpherence-durable-column .column-no-rooms {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
.conpherence-durable-column .column-no-rooms-text {
|
||||
color: {$greytext};
|
||||
font-style: italic;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.conpherence-durable-column-transactions {
|
||||
padding: 8px 12px 0;
|
||||
}
|
||||
|
@ -300,12 +323,6 @@ img {
|
|||
text-shadow: none;
|
||||
}
|
||||
|
||||
.minimize-column .conpherence-durable-column
|
||||
.conpherence-durable-column-header-text .phui-icon-view {
|
||||
color: {$darkbluetext};
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.minimize-column .conpherence-durable-column
|
||||
.conpherence-durable-column-header .phabricator-application-menu
|
||||
.phui-list-item-icon.phui-font-fa {
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
* @provides conpherence-header-pane-css
|
||||
*/
|
||||
|
||||
.conpherence-header-pane {
|
||||
}
|
||||
|
||||
.conpherence-header-pane .phui-header-shell {
|
||||
padding: 8px 16px 10px;
|
||||
min-height: 38px;
|
||||
|
@ -23,8 +20,26 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .phui-header-shell.conpherence-no-topic {
|
||||
padding: 15px 16px 5px;
|
||||
.conpherence-header-pane .phui-header-col1 {
|
||||
width: 46px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .phui-header-image {
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
background-size: 35px;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .phui-header-image-href {
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .phui-header-col2 {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .phui-header-action-list .phui-header-action-item
|
||||
|
|
|
@ -26,10 +26,14 @@
|
|||
padding: 4px 0 4px 8px;
|
||||
}
|
||||
|
||||
.conpherence-menu-pane .phui-list-item-view.hidden {
|
||||
.conpherence-menu-pane.phabricator-side-menu .phui-list-item-view.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.phui-list-view.conpherence-menu {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.conpherence-menu-pane.phabricator-side-menu .room-list-href {
|
||||
padding: 10px 0 9px 8px;
|
||||
display: inline-block;
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
* @provides conpherence-notification-css
|
||||
*/
|
||||
|
||||
/* kill styles on phabricator-notification */
|
||||
.conpherence-notification {
|
||||
.phabricator-notification.conpherence-notification {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
@ -27,7 +26,6 @@
|
|||
width: 30px;
|
||||
height: 30px;
|
||||
background-size: 100%;
|
||||
box-shadow: {$borderinset};
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
|
@ -71,3 +69,19 @@
|
|||
padding: 0 5px 1px;
|
||||
font-size: {$smallestfontsize};
|
||||
}
|
||||
|
||||
.phabricator-notification .no-room-notification {
|
||||
color: {$lightgreytext};
|
||||
display: block;
|
||||
}
|
||||
|
||||
.phabricator-notification-header .persistent-option {
|
||||
white-space: nowrap;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.phabricator-notification-header .persistent-option span {
|
||||
margin-left: 4px;
|
||||
font-weight: normal;
|
||||
color: {$greytext};
|
||||
}
|
||||
|
|
|
@ -17,10 +17,14 @@
|
|||
}
|
||||
|
||||
.phui-profile-menu .phabricator-side-menu {
|
||||
background: #525867;
|
||||
background: #dee0e7;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.phabricator-side-menu .phui-profile-menu-footer-1 {
|
||||
background: #dee0e7;
|
||||
}
|
||||
|
||||
.phui-profile-menu .phabricator-side-menu .phui-list-item-view {
|
||||
position: relative;
|
||||
}
|
||||
|
@ -32,9 +36,9 @@
|
|||
height: 48px;
|
||||
font-size: {$biggerfontsize};
|
||||
-webkit-font-smoothing: antialiased;
|
||||
color: {$menu.profile.text};
|
||||
line-height: 22px;
|
||||
overflow: hidden;
|
||||
color: {$darkbluetext};
|
||||
text-overflow: ellipsis;
|
||||
line-height: 48px;
|
||||
}
|
||||
|
@ -50,7 +54,7 @@
|
|||
height: 24px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
color: {$menu.profile.text};
|
||||
color: {$darkbluetext};
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
|
@ -81,7 +85,7 @@
|
|||
.phui-profile-menu .phabricator-side-menu
|
||||
.phui-list-item-disabled
|
||||
.phui-list-item-icon {
|
||||
color: {$menu.profile.icon.disabled};
|
||||
color: {$lightgreytext};
|
||||
}
|
||||
|
||||
.phui-profile-menu .phabricator-side-menu .phui-icon-view {
|
||||
|
@ -90,28 +94,17 @@
|
|||
|
||||
.device-desktop .phui-profile-menu .phabricator-side-menu
|
||||
.phui-list-item-href:hover {
|
||||
background-color: rgba({$alphablack},0.15);
|
||||
color: {$menu.profile.text.selected};
|
||||
}
|
||||
|
||||
.phui-profile-menu .phabricator-side-menu
|
||||
.phui-list-item-selected
|
||||
.phui-list-item-icon,
|
||||
.device-desktop .phui-profile-menu .phabricator-side-menu
|
||||
.phui-list-item-href:hover
|
||||
.phui-list-item-icon {
|
||||
color: {$menu.profile.text.selected};
|
||||
background-color: rgba({$alphablack},0.05);
|
||||
}
|
||||
|
||||
.phui-profile-menu .phabricator-side-menu .phui-list-item-selected
|
||||
.phui-list-item-href {
|
||||
background-color: rgba({$alphablack},0.3);
|
||||
color: {$menu.profile.text.selected};
|
||||
background-color: rgba({$alphablack},0.1);
|
||||
}
|
||||
|
||||
.phui-profile-menu .phabricator-side-menu .phui-list-item-selected
|
||||
.phui-list-item-href:hover {
|
||||
background-color: rgba({$alphablack},0.45);
|
||||
background-color: rgba({$alphablack},0.15);
|
||||
}
|
||||
|
||||
.phui-profile-menu .phabricator-side-menu .phui-divider {
|
||||
|
@ -123,13 +116,13 @@
|
|||
white-space: normal;
|
||||
padding: 18px 15px;
|
||||
font-size: 12px;
|
||||
color: {$menu.profile.text};
|
||||
color: {$darkbluetext};
|
||||
}
|
||||
|
||||
.phui-profile-menu .phabricator-side-menu .phui-motivator .phui-icon-view {
|
||||
position: static;
|
||||
font-size: 12px;
|
||||
color: {$menu.profile.text};
|
||||
color: {$darkbluetext};
|
||||
}
|
||||
|
||||
.phui-profile-menu .phabricator-side-menu .phui-profile-menu-error {
|
||||
|
@ -150,11 +143,11 @@
|
|||
.phui-list-item-href,
|
||||
.phui-profile-menu .phui-list-sidenav .phui-list-item-disabled
|
||||
.phui-list-item-href {
|
||||
color: rgba({$alphawhite}, 0.5);
|
||||
color: rgba({$lightgreytext});
|
||||
}
|
||||
|
||||
.phui-profile-menu .phabricator-side-menu .phui-profile-segment-bar {
|
||||
color: {$menu.profile.text};
|
||||
color: {$darkbluetext};
|
||||
font-size: {$smallerfontsize};
|
||||
-webkit-font-smoothing: antialiased;
|
||||
padding: 8px 12px 16px;
|
||||
|
@ -201,25 +194,11 @@
|
|||
}
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-footer .phui-icon-circle {
|
||||
border-color: {$menu.profile.text};
|
||||
border-color: {$darkbluetext};
|
||||
}
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-footer .phui-icon-circle .phui-icon-view {
|
||||
color: {$menu.profile.text};
|
||||
}
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-footer .phui-list-item-href:hover
|
||||
.phui-icon-circle,
|
||||
.phui-profile-menu .phui-list-item-selected.phui-profile-menu-footer
|
||||
.phui-icon-circle {
|
||||
border-color: {$menu.profile.text.selected};
|
||||
}
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-footer .phui-list-item-href:hover
|
||||
.phui-icon-circle .phui-icon-view,
|
||||
.phui-profile-menu .phui-list-item-selected.phui-profile-menu-footer
|
||||
.phui-icon-circle .phui-icon-view {
|
||||
color: {$menu.profile.text.selected};
|
||||
color: {$darkbluetext};
|
||||
}
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-footer
|
||||
|
|
|
@ -24,14 +24,22 @@ JX.behavior('aphlict-status', function(config) {
|
|||
}
|
||||
|
||||
var status = client.getStatus();
|
||||
var icon = config.icon[status];
|
||||
var status_node = JX.$N(
|
||||
'span',
|
||||
{
|
||||
className: 'aphlict-connection-status-' + status
|
||||
className: 'connection-status-text aphlict-connection-status-' + status
|
||||
},
|
||||
pht(status));
|
||||
|
||||
JX.DOM.setContent(node, status_node);
|
||||
var icon_node = new JX.PHUIXIconView()
|
||||
.setIcon(icon['icon'])
|
||||
.setColor(icon['color'])
|
||||
.getNode();
|
||||
|
||||
var content = [icon_node, ' ', status_node];
|
||||
|
||||
JX.DOM.setContent(node, content);
|
||||
}
|
||||
|
||||
JX.Aphlict.listen('didChangeStatus', update);
|
||||
|
|
|
@ -66,6 +66,11 @@ JX.behavior('durable-column', function(config, statics) {
|
|||
JX.DOM.alterClass(document.body, 'minimize-column', userMinimize);
|
||||
JX.Stratcom.invoke('resize');
|
||||
|
||||
if (!userMinimize) {
|
||||
var messages = _getColumnMessagesNode();
|
||||
scrollbar.scrollTo(messages.scrollHeight);
|
||||
}
|
||||
|
||||
new JX.Request(config.minimizeURI)
|
||||
.setData({value: (userMinimize ? 1 : 0)})
|
||||
.send();
|
||||
|
@ -89,9 +94,10 @@ JX.behavior('durable-column', function(config, statics) {
|
|||
JX.Stratcom.invoke('resize');
|
||||
}
|
||||
|
||||
new JX.KeyboardShortcut('\\', 'Toggle Conpherence Column')
|
||||
.setHandler(_toggleColumn)
|
||||
.register();
|
||||
JX.Stratcom.listen(
|
||||
'click',
|
||||
'conpherence-persist-column',
|
||||
_toggleColumn);
|
||||
|
||||
JX.Stratcom.listen(
|
||||
'click',
|
||||
|
@ -346,6 +352,11 @@ JX.behavior('durable-column', function(config, statics) {
|
|||
null,
|
||||
function (e) {
|
||||
var new_data = e.getData().newResponse;
|
||||
var new_classes = new_data.bodyClasses;
|
||||
if (userMinimize) {
|
||||
new_classes = new_classes + ' minimize-column';
|
||||
}
|
||||
document.body.className = new_classes;
|
||||
JX.Title.setTitle(new_data.title);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
/**
|
||||
* @provides javelin-behavior-aphront-crop
|
||||
* @requires javelin-behavior
|
||||
* javelin-dom
|
||||
* javelin-vector
|
||||
* javelin-magical-init
|
||||
*/
|
||||
|
||||
JX.behavior('aphront-crop', function(config) {
|
||||
|
||||
var dragging = false;
|
||||
var startX, startY;
|
||||
var finalX, finalY;
|
||||
|
||||
var cropBox = JX.$(config.cropBoxID);
|
||||
var basePos = JX.$V(cropBox);
|
||||
cropBox.style.height = config.height + 'px';
|
||||
cropBox.style.width = config.width + 'px';
|
||||
var baseD = JX.$V(config.width, config.height);
|
||||
|
||||
var image = JX.DOM.find(cropBox, 'img', 'crop-image');
|
||||
image.style.height = (config.imageH * config.scale) + 'px';
|
||||
image.style.width = (config.imageW * config.scale) + 'px';
|
||||
var imageD = JX.$V(
|
||||
config.imageW * config.scale,
|
||||
config.imageH * config.scale
|
||||
);
|
||||
var minLeft = baseD.x - imageD.x;
|
||||
var minTop = baseD.y - imageD.y;
|
||||
|
||||
var ondrag = function(e) {
|
||||
e.kill();
|
||||
dragging = true;
|
||||
var p = JX.$V(e);
|
||||
startX = p.x;
|
||||
startY = p.y;
|
||||
};
|
||||
|
||||
var onmove = function(e) {
|
||||
if (!dragging) {
|
||||
return;
|
||||
}
|
||||
e.kill();
|
||||
|
||||
var p = JX.$V(e);
|
||||
var dx = startX - p.x;
|
||||
var dy = startY - p.y;
|
||||
var imagePos = JX.$V(image);
|
||||
var moveLeft = imagePos.x - basePos.x - dx;
|
||||
var moveTop = imagePos.y - basePos.y - dy;
|
||||
|
||||
image.style.left = Math.min(Math.max(minLeft, moveLeft), 0) + 'px';
|
||||
image.style.top = Math.min(Math.max(minTop, moveTop), 0) + 'px';
|
||||
|
||||
// reset these; a new beginning!
|
||||
startX = p.x;
|
||||
startY = p.y;
|
||||
|
||||
// save off where we are right now
|
||||
imagePos = JX.$V(image);
|
||||
finalX = Math.abs(imagePos.x - basePos.x);
|
||||
finalY = Math.abs(imagePos.y - basePos.y);
|
||||
JX.DOM.find(cropBox, 'input', 'crop-x').value = finalX;
|
||||
JX.DOM.find(cropBox, 'input', 'crop-y').value = finalY;
|
||||
};
|
||||
|
||||
var ondrop = function() {
|
||||
if (!dragging) {
|
||||
return;
|
||||
}
|
||||
dragging = false;
|
||||
};
|
||||
|
||||
// NOTE: Javelin does not dispatch mousemove by default.
|
||||
JX.enableDispatch(cropBox, 'mousemove');
|
||||
|
||||
JX.DOM.listen(cropBox, 'mousedown', [], ondrag);
|
||||
JX.DOM.listen(cropBox, 'mousemove', [], onmove);
|
||||
JX.DOM.listen(cropBox, 'mouseup', [], ondrop);
|
||||
JX.DOM.listen(cropBox, 'mouseout', [], ondrop);
|
||||
|
||||
});
|
Loading…
Reference in a new issue