1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-28 09:42:41 +01:00

(stable) Promote 2016 Week 31

This commit is contained in:
epriestley 2016-07-30 05:34:45 -07:00
commit 139e93916f
203 changed files with 7734 additions and 3581 deletions

View file

@ -7,10 +7,10 @@
*/
return array(
'names' => array(
'core.pkg.css' => '4e7e9bde',
'core.pkg.js' => '1bcca0f3',
'core.pkg.css' => '8b87d014',
'core.pkg.js' => '13c7e56a',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '3e81ae60',
'differential.pkg.css' => '3fb7f532',
'differential.pkg.js' => '634399e9',
'diffusion.pkg.css' => '91c5d3a6',
'diffusion.pkg.js' => '84c8f8fd',
@ -25,7 +25,7 @@ return array(
'rsrc/css/aphront/notification.css' => '3f6c89c9',
'rsrc/css/aphront/panel-view.css' => '8427b78d',
'rsrc/css/aphront/phabricator-nav-view.css' => 'ac79a758',
'rsrc/css/aphront/table-view.css' => '8df59783',
'rsrc/css/aphront/table-view.css' => '832656fd',
'rsrc/css/aphront/tokenizer.css' => '056da01b',
'rsrc/css/aphront/tooltip.css' => '1a07aea8',
'rsrc/css/aphront/typeahead-browse.css' => '8904346a',
@ -57,7 +57,7 @@ return array(
'rsrc/css/application/dashboard/dashboard.css' => 'bc6f2127',
'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a',
'rsrc/css/application/differential/add-comment.css' => 'c47f8c40',
'rsrc/css/application/differential/changeset-view.css' => '37792573',
'rsrc/css/application/differential/changeset-view.css' => '9ef7d354',
'rsrc/css/application/differential/core.css' => '5b7b8ff4',
'rsrc/css/application/differential/phui-inline-comment.css' => '5953c28e',
'rsrc/css/application/differential/revision-comment.css' => '14b8565a',
@ -115,10 +115,10 @@ return array(
'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82',
'rsrc/css/layout/phabricator-side-menu-view.css' => 'dd849797',
'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983',
'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93',
'rsrc/css/phui/calendar/phui-calendar-list.css' => '56e6381a',
'rsrc/css/phui/calendar/phui-calendar-month.css' => '476be7e0',
'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893',
'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893',
'rsrc/css/phui/calendar/phui-calendar-list.css' => 'fcc9fb41',
'rsrc/css/phui/calendar/phui-calendar-month.css' => '8e10e92c',
'rsrc/css/phui/calendar/phui-calendar.css' => 'daadaf39',
'rsrc/css/phui/phui-action-list.css' => 'c5eba19d',
'rsrc/css/phui/phui-action-panel.css' => '91c7b835',
'rsrc/css/phui/phui-badge.css' => '3baef8db',
@ -126,14 +126,14 @@ return array(
'rsrc/css/phui/phui-box.css' => '5c8387cf',
'rsrc/css/phui/phui-button.css' => '4a5fbe3d',
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
'rsrc/css/phui/phui-crumbs-view.css' => '6b813619',
'rsrc/css/phui/phui-crumbs-view.css' => 'b4fa5755',
'rsrc/css/phui/phui-curtain-view.css' => '7148ae25',
'rsrc/css/phui/phui-document-pro.css' => 'a3730b94',
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
'rsrc/css/phui/phui-document.css' => '715aedfb',
'rsrc/css/phui/phui-feed-story.css' => 'aa49845d',
'rsrc/css/phui/phui-fontkit.css' => '9cda225e',
'rsrc/css/phui/phui-form-view.css' => '6a51768e',
'rsrc/css/phui/phui-form-view.css' => 'fab0a10f',
'rsrc/css/phui/phui-form.css' => 'aac1d51d',
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
'rsrc/css/phui/phui-header-view.css' => '4c7dd8f5',
@ -156,7 +156,7 @@ return array(
'rsrc/css/phui/phui-status.css' => 'd5263e49',
'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2',
'rsrc/css/phui/phui-timeline-view.css' => 'bc523970',
'rsrc/css/phui/phui-two-column-view.css' => '9fb86c85',
'rsrc/css/phui/phui-two-column-view.css' => '5afdf637',
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7',
'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647',
'rsrc/css/phui/workboards/phui-workcard.css' => '0c62d7c5',
@ -362,8 +362,9 @@ return array(
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761',
'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' => '5c46cff2',
'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8',
'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443',
'rsrc/js/application/calendar/behavior-event-all-day.js' => '937bb700',
'rsrc/js/application/calendar/behavior-month-view.js' => 'fe33e256',
'rsrc/js/application/calendar/behavior-recurring-edit.js' => '5f1c4d5f',
'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408',
'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2',
@ -439,7 +440,7 @@ return array(
'rsrc/js/application/transactions/behavior-comment-actions.js' => '06460e71',
'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243',
'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96',
'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'dbbf48b6',
'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '94c65b72',
'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => 'b23b49e6',
'rsrc/js/application/transactions/behavior-transaction-list.js' => '13c739ea',
'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '635de1ec',
@ -536,7 +537,7 @@ return array(
'aphront-list-filter-view-css' => '5d6f0526',
'aphront-multi-column-view-css' => 'fd18389d',
'aphront-panel-view-css' => '8427b78d',
'aphront-table-view-css' => '8df59783',
'aphront-table-view-css' => '832656fd',
'aphront-tokenizer-control-css' => '056da01b',
'aphront-tooltip-css' => '1a07aea8',
'aphront-typeahead-control-css' => 'd4f16145',
@ -555,7 +556,7 @@ return array(
'conpherence-update-css' => 'faf6be09',
'conpherence-widget-pane-css' => '775eaaba',
'd3' => 'a11a5ff2',
'differential-changeset-view-css' => '37792573',
'differential-changeset-view-css' => '9ef7d354',
'differential-core-view-css' => '5b7b8ff4',
'differential-inline-comment-editor' => '64a5550f',
'differential-revision-add-comment-css' => 'c47f8c40',
@ -590,6 +591,7 @@ return array(
'javelin-behavior-audit-preview' => 'd835b03a',
'javelin-behavior-badge-view' => '8ff5e24c',
'javelin-behavior-bulk-job-reload' => 'edf8a145',
'javelin-behavior-calendar-month-view' => 'fe33e256',
'javelin-behavior-choose-control' => '327a00d1',
'javelin-behavior-comment-actions' => '06460e71',
'javelin-behavior-config-reorder-fields' => 'b6993408',
@ -603,7 +605,7 @@ return array(
'javelin-behavior-dashboard-move-panels' => '019f36c4',
'javelin-behavior-dashboard-query-panel-select' => '453c5375',
'javelin-behavior-dashboard-tab-panel' => 'd4eecc63',
'javelin-behavior-day-view' => '5c46cff2',
'javelin-behavior-day-view' => '4b3c4443',
'javelin-behavior-desktop-notifications-control' => 'edd1ba66',
'javelin-behavior-detect-timezone' => '4c193c96',
'javelin-behavior-device' => 'bb1dd507',
@ -629,7 +631,7 @@ return array(
'javelin-behavior-editengine-reorder-configs' => 'd7a74243',
'javelin-behavior-editengine-reorder-fields' => 'b59e1e96',
'javelin-behavior-error-log' => '6882e80a',
'javelin-behavior-event-all-day' => '38dcf3c8',
'javelin-behavior-event-all-day' => '937bb700',
'javelin-behavior-fancy-datepicker' => '568931f3',
'javelin-behavior-global-drag-and-drop' => 'c8e57404',
'javelin-behavior-herald-rule-editor' => '7ebaeed3',
@ -663,7 +665,7 @@ return array(
'javelin-behavior-phabricator-remarkup-assist' => '116cf19b',
'javelin-behavior-phabricator-reveal-content' => '60821bc7',
'javelin-behavior-phabricator-search-typeahead' => '06c32383',
'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6',
'javelin-behavior-phabricator-show-older-transactions' => '94c65b72',
'javelin-behavior-phabricator-tooltips' => '42fcb747',
'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6',
'javelin-behavior-phabricator-transaction-list' => '13c739ea',
@ -825,12 +827,12 @@ return array(
'phui-big-info-view-css' => 'bd903741',
'phui-box-css' => '5c8387cf',
'phui-button-css' => '4a5fbe3d',
'phui-calendar-css' => 'ccabe893',
'phui-calendar-day-css' => 'd1cf6f93',
'phui-calendar-list-css' => '56e6381a',
'phui-calendar-month-css' => '476be7e0',
'phui-calendar-css' => 'daadaf39',
'phui-calendar-day-css' => '572b1893',
'phui-calendar-list-css' => 'fcc9fb41',
'phui-calendar-month-css' => '8e10e92c',
'phui-chart-css' => '6bf6f78e',
'phui-crumbs-view-css' => '6b813619',
'phui-crumbs-view-css' => 'b4fa5755',
'phui-curtain-view-css' => '7148ae25',
'phui-document-summary-view-css' => '9ca48bdf',
'phui-document-view-css' => '715aedfb',
@ -839,7 +841,7 @@ return array(
'phui-font-icon-base-css' => '6449bce8',
'phui-fontkit-css' => '9cda225e',
'phui-form-css' => 'aac1d51d',
'phui-form-view-css' => '6a51768e',
'phui-form-view-css' => 'fab0a10f',
'phui-head-thing-view-css' => 'fd311e5f',
'phui-header-view-css' => '4c7dd8f5',
'phui-hovercard' => '1bd28176',
@ -864,7 +866,7 @@ return array(
'phui-tag-view-css' => '6bbd83e2',
'phui-theme-css' => '027ba77e',
'phui-timeline-view-css' => 'bc523970',
'phui-two-column-view-css' => '9fb86c85',
'phui-two-column-view-css' => '5afdf637',
'phui-workboard-color-css' => 'ac6fe6a7',
'phui-workboard-view-css' => 'e6d89647',
'phui-workcard-view-css' => '0c62d7c5',
@ -1143,9 +1145,6 @@ return array(
'javelin-dom',
'javelin-workflow',
),
37792573 => array(
'phui-inline-comment-view-css',
),
'3ab51e2c' => array(
'javelin-behavior',
'javelin-behavior-device',
@ -1236,6 +1235,9 @@ return array(
'javelin-dom',
'javelin-vector',
),
'4b3c4443' => array(
'phuix-icon-view',
),
'4b700e9e' => array(
'javelin-behavior',
'javelin-dom',
@ -1655,6 +1657,12 @@ return array(
'javelin-resource',
'javelin-routable',
),
'94c65b72' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-busy',
),
'988040b4' => array(
'javelin-install',
'javelin-dom',
@ -1672,6 +1680,9 @@ return array(
'phabricator-phtize',
'changeset-view-manager',
),
'9ef7d354' => array(
'phui-inline-comment-view-css',
),
'9f36c42d' => array(
'javelin-behavior',
'javelin-stratcom',
@ -2014,12 +2025,6 @@ return array(
'javelin-util',
'phabricator-shaped-request',
),
'dbbf48b6' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-busy',
),
'de2e896f' => array(
'javelin-behavior',
'javelin-dom',

View file

@ -17,7 +17,7 @@ foreach ($iterator as $event) {
// later patch. See T8209.
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($event->getUserPHID()))
->withPHIDs(array($event->getHostPHID()))
->executeOne();
if ($user) {

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
ADD isStub BOOL NOT NULL;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_file.file
ADD builtinKey VARCHAR(64) COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_file.file
ADD UNIQUE KEY `key_builtin` (builtinKey);

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
CHANGE userPHID hostPHID VARBINARY(64) NOT NULL;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
ADD allDayDateFrom INT UNSIGNED NOT NULL;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
ADD allDayDateTo INT UNSIGNED NOT NULL;

View file

@ -0,0 +1,52 @@
<?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());
}

View file

@ -0,0 +1,37 @@
<?php
$table = new PhabricatorCalendarEventTransaction();
$conn_w = $table->establishConnection('w');
echo pht(
"Restructuring calendar invite transactions...\n");
foreach (new LiskMigrationIterator($table) as $txn) {
$type = PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE;
if ($txn->getTransactionType() != $type) {
continue;
}
$old_value = array_keys($txn->getOldValue());
$orig_new = $txn->getNewValue();
$status_uninvited = 'uninvited';
foreach ($orig_new as $key => $status) {
if ($status == $status_uninvited) {
unset($orig_new[$key]);
}
}
$new_value = array_keys($orig_new);
queryfx(
$conn_w,
'UPDATE %T SET '.
'oldValue = %s, newValue = %s'.
'WHERE id = %d',
$table->getTableName(),
phutil_json_encode($old_value),
phutil_json_encode($new_value),
$txn->getID());
}
echo pht('Done.')."\n";

View file

@ -0,0 +1,11 @@
CREATE TABLE {$NAMESPACE}_packages.packages_publisher (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
name VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
publisherKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_SORT},
editPolicy VARBINARY(64) NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_publisher` (publisherKey)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_packages.packages_publishertransaction (
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};

View file

@ -0,0 +1,16 @@
CREATE TABLE {$NAMESPACE}_packages.edge (
src VARBINARY(64) NOT NULL,
type INT UNSIGNED NOT NULL,
dst VARBINARY(64) NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
seq INT UNSIGNED NOT NULL,
dataID INT UNSIGNED,
PRIMARY KEY (src, type, dst),
KEY `src` (src, type, dateCreated, seq),
UNIQUE KEY `key_dst` (dst, type, src)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
CREATE TABLE {$NAMESPACE}_packages.edgedata (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,13 @@
CREATE TABLE {$NAMESPACE}_packages.packages_package (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
name VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
publisherPHID VARBINARY(64) NOT NULL,
packageKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_SORT},
viewPolicy VARBINARY(64) NOT NULL,
editPolicy VARBINARY(64) NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_package` (publisherPHID, packageKey)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_packages.packages_packagetransaction (
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};

View file

@ -0,0 +1,10 @@
CREATE TABLE {$NAMESPACE}_packages.packages_version (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
name VARCHAR(64) NOT NULL COLLATE {$COLLATE_SORT},
packagePHID VARBINARY(64) NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_package` (packagePHID, name)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_packages.packages_versiontransaction (
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};

View file

@ -0,0 +1,7 @@
CREATE TABLE {$NAMESPACE}_packages.packages_publishername_ngrams (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
objectID INT UNSIGNED NOT NULL,
ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT},
KEY `key_object` (objectID),
KEY `key_ngram` (ngram, objectID)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,7 @@
CREATE TABLE {$NAMESPACE}_packages.packages_packagename_ngrams (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
objectID INT UNSIGNED NOT NULL,
ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT},
KEY `key_object` (objectID),
KEY `key_ngram` (ngram, objectID)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,7 @@
CREATE TABLE {$NAMESPACE}_packages.packages_versionname_ngrams (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
objectID INT UNSIGNED NOT NULL,
ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT},
KEY `key_object` (objectID),
KEY `key_ngram` (ngram, objectID)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -1626,8 +1626,7 @@ phutil_register_library_map(array(
'PHUIFormIconSetControl' => 'view/form/control/PHUIFormIconSetControl.php',
'PHUIFormInsetView' => 'view/form/PHUIFormInsetView.php',
'PHUIFormLayoutView' => 'view/form/PHUIFormLayoutView.php',
'PHUIFormMultiSubmitControl' => 'view/form/control/PHUIFormMultiSubmitControl.php',
'PHUIFormPageView' => 'view/form/PHUIFormPageView.php',
'PHUIFormNumberControl' => 'view/form/control/PHUIFormNumberControl.php',
'PHUIHandleListView' => 'applications/phid/view/PHUIHandleListView.php',
'PHUIHandleTagListView' => 'applications/phid/view/PHUIHandleTagListView.php',
'PHUIHandleView' => 'applications/phid/view/PHUIHandleView.php',
@ -1653,7 +1652,6 @@ phutil_register_library_map(array(
'PHUIObjectItemListExample' => 'applications/uiexample/examples/PHUIObjectItemListExample.php',
'PHUIObjectItemListView' => 'view/phui/PHUIObjectItemListView.php',
'PHUIObjectItemView' => 'view/phui/PHUIObjectItemView.php',
'PHUIPagedFormView' => 'view/form/PHUIPagedFormView.php',
'PHUIPagerView' => 'view/phui/PHUIPagerView.php',
'PHUIPinboardItemView' => 'view/phui/PHUIPinboardItemView.php',
'PHUIPinboardView' => 'view/phui/PHUIPinboardView.php',
@ -2020,25 +2018,48 @@ phutil_register_library_map(array(
'PhabricatorCalendarController' => 'applications/calendar/controller/PhabricatorCalendarController.php',
'PhabricatorCalendarDAO' => 'applications/calendar/storage/PhabricatorCalendarDAO.php',
'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php',
'PhabricatorCalendarEventAcceptTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventAcceptTransaction.php',
'PhabricatorCalendarEventAllDayTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php',
'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php',
'PhabricatorCalendarEventCommentController' => 'applications/calendar/controller/PhabricatorCalendarEventCommentController.php',
'PhabricatorCalendarEventCancelTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventCancelTransaction.php',
'PhabricatorCalendarEventDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php',
'PhabricatorCalendarEventDeclineTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDeclineTransaction.php',
'PhabricatorCalendarEventDefaultEditCapability' => 'applications/calendar/capability/PhabricatorCalendarEventDefaultEditCapability.php',
'PhabricatorCalendarEventDefaultViewCapability' => 'applications/calendar/capability/PhabricatorCalendarEventDefaultViewCapability.php',
'PhabricatorCalendarEventDescriptionTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDescriptionTransaction.php',
'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php',
'PhabricatorCalendarEventEditConduitAPIMethod' => 'applications/calendar/conduit/PhabricatorCalendarEventEditConduitAPIMethod.php',
'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php',
'PhabricatorCalendarEventEditEngine' => 'applications/calendar/editor/PhabricatorCalendarEventEditEngine.php',
'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php',
'PhabricatorCalendarEventEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventEmailCommand.php',
'PhabricatorCalendarEventEndDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php',
'PhabricatorCalendarEventFrequencyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php',
'PhabricatorCalendarEventFulltextEngine' => 'applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php',
'PhabricatorCalendarEventHostPolicyRule' => 'applications/calendar/policyrule/PhabricatorCalendarEventHostPolicyRule.php',
'PhabricatorCalendarEventHostTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventHostTransaction.php',
'PhabricatorCalendarEventIconTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventIconTransaction.php',
'PhabricatorCalendarEventInviteTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php',
'PhabricatorCalendarEventInvitee' => 'applications/calendar/storage/PhabricatorCalendarEventInvitee.php',
'PhabricatorCalendarEventInviteeQuery' => 'applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php',
'PhabricatorCalendarEventInviteesPolicyRule' => 'applications/calendar/policyrule/PhabricatorCalendarEventInviteesPolicyRule.php',
'PhabricatorCalendarEventJoinController' => 'applications/calendar/controller/PhabricatorCalendarEventJoinController.php',
'PhabricatorCalendarEventListController' => 'applications/calendar/controller/PhabricatorCalendarEventListController.php',
'PhabricatorCalendarEventMailReceiver' => 'applications/calendar/mail/PhabricatorCalendarEventMailReceiver.php',
'PhabricatorCalendarEventNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventNameTransaction.php',
'PhabricatorCalendarEventPHIDType' => 'applications/calendar/phid/PhabricatorCalendarEventPHIDType.php',
'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php',
'PhabricatorCalendarEventRSVPEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php',
'PhabricatorCalendarEventRecurringTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php',
'PhabricatorCalendarEventReplyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventReplyTransaction.php',
'PhabricatorCalendarEventSearchConduitAPIMethod' => 'applications/calendar/conduit/PhabricatorCalendarEventSearchConduitAPIMethod.php',
'PhabricatorCalendarEventSearchEngine' => 'applications/calendar/query/PhabricatorCalendarEventSearchEngine.php',
'PhabricatorCalendarEventStartDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php',
'PhabricatorCalendarEventTransaction' => 'applications/calendar/storage/PhabricatorCalendarEventTransaction.php',
'PhabricatorCalendarEventTransactionComment' => 'applications/calendar/storage/PhabricatorCalendarEventTransactionComment.php',
'PhabricatorCalendarEventTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarEventTransactionQuery.php',
'PhabricatorCalendarEventTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarEventTransactionType.php',
'PhabricatorCalendarEventUntilDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php',
'PhabricatorCalendarEventViewController' => 'applications/calendar/controller/PhabricatorCalendarEventViewController.php',
'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php',
'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php',
@ -2951,7 +2972,80 @@ phutil_register_library_map(array(
'PhabricatorPHPASTApplication' => 'applications/phpast/application/PhabricatorPHPASTApplication.php',
'PhabricatorPHPConfigSetupCheck' => 'applications/config/check/PhabricatorPHPConfigSetupCheck.php',
'PhabricatorPHPMailerConfigOptions' => 'applications/config/option/PhabricatorPHPMailerConfigOptions.php',
'PhabricatorPagedFormUIExample' => 'applications/uiexample/examples/PhabricatorPagedFormUIExample.php',
'PhabricatorPackagesApplication' => 'applications/packages/application/PhabricatorPackagesApplication.php',
'PhabricatorPackagesController' => 'applications/packages/controller/PhabricatorPackagesController.php',
'PhabricatorPackagesCreatePublisherCapability' => 'applications/packages/capability/PhabricatorPackagesCreatePublisherCapability.php',
'PhabricatorPackagesDAO' => 'applications/packages/storage/PhabricatorPackagesDAO.php',
'PhabricatorPackagesEditEngine' => 'applications/packages/editor/PhabricatorPackagesEditEngine.php',
'PhabricatorPackagesEditor' => 'applications/packages/editor/PhabricatorPackagesEditor.php',
'PhabricatorPackagesNgrams' => 'applications/packages/storage/PhabricatorPackagesNgrams.php',
'PhabricatorPackagesPackage' => 'applications/packages/storage/PhabricatorPackagesPackage.php',
'PhabricatorPackagesPackageController' => 'applications/packages/controller/PhabricatorPackagesPackageController.php',
'PhabricatorPackagesPackageDatasource' => 'applications/packages/typeahead/PhabricatorPackagesPackageDatasource.php',
'PhabricatorPackagesPackageDefaultEditCapability' => 'applications/packages/capability/PhabricatorPackagesPackageDefaultEditCapability.php',
'PhabricatorPackagesPackageDefaultViewCapability' => 'applications/packages/capability/PhabricatorPackagesPackageDefaultViewCapability.php',
'PhabricatorPackagesPackageEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php',
'PhabricatorPackagesPackageEditController' => 'applications/packages/controller/PhabricatorPackagesPackageEditController.php',
'PhabricatorPackagesPackageEditEngine' => 'applications/packages/editor/PhabricatorPackagesPackageEditEngine.php',
'PhabricatorPackagesPackageEditor' => 'applications/packages/editor/PhabricatorPackagesPackageEditor.php',
'PhabricatorPackagesPackageKeyTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php',
'PhabricatorPackagesPackageListController' => 'applications/packages/controller/PhabricatorPackagesPackageListController.php',
'PhabricatorPackagesPackageListView' => 'applications/packages/view/PhabricatorPackagesPackageListView.php',
'PhabricatorPackagesPackageNameNgrams' => 'applications/packages/storage/PhabricatorPackagesPackageNameNgrams.php',
'PhabricatorPackagesPackageNameTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php',
'PhabricatorPackagesPackagePHIDType' => 'applications/packages/phid/PhabricatorPackagesPackagePHIDType.php',
'PhabricatorPackagesPackagePublisherTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php',
'PhabricatorPackagesPackageQuery' => 'applications/packages/query/PhabricatorPackagesPackageQuery.php',
'PhabricatorPackagesPackageSearchConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPackageSearchConduitAPIMethod.php',
'PhabricatorPackagesPackageSearchEngine' => 'applications/packages/query/PhabricatorPackagesPackageSearchEngine.php',
'PhabricatorPackagesPackageTransaction' => 'applications/packages/storage/PhabricatorPackagesPackageTransaction.php',
'PhabricatorPackagesPackageTransactionQuery' => 'applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php',
'PhabricatorPackagesPackageTransactionType' => 'applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php',
'PhabricatorPackagesPackageViewController' => 'applications/packages/controller/PhabricatorPackagesPackageViewController.php',
'PhabricatorPackagesPublisher' => 'applications/packages/storage/PhabricatorPackagesPublisher.php',
'PhabricatorPackagesPublisherController' => 'applications/packages/controller/PhabricatorPackagesPublisherController.php',
'PhabricatorPackagesPublisherDatasource' => 'applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php',
'PhabricatorPackagesPublisherDefaultEditCapability' => 'applications/packages/capability/PhabricatorPackagesPublisherDefaultEditCapability.php',
'PhabricatorPackagesPublisherEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPublisherEditConduitAPIMethod.php',
'PhabricatorPackagesPublisherEditController' => 'applications/packages/controller/PhabricatorPackagesPublisherEditController.php',
'PhabricatorPackagesPublisherEditEngine' => 'applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php',
'PhabricatorPackagesPublisherEditor' => 'applications/packages/editor/PhabricatorPackagesPublisherEditor.php',
'PhabricatorPackagesPublisherKeyTransaction' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherKeyTransaction.php',
'PhabricatorPackagesPublisherListController' => 'applications/packages/controller/PhabricatorPackagesPublisherListController.php',
'PhabricatorPackagesPublisherListView' => 'applications/packages/view/PhabricatorPackagesPublisherListView.php',
'PhabricatorPackagesPublisherNameNgrams' => 'applications/packages/storage/PhabricatorPackagesPublisherNameNgrams.php',
'PhabricatorPackagesPublisherNameTransaction' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherNameTransaction.php',
'PhabricatorPackagesPublisherPHIDType' => 'applications/packages/phid/PhabricatorPackagesPublisherPHIDType.php',
'PhabricatorPackagesPublisherQuery' => 'applications/packages/query/PhabricatorPackagesPublisherQuery.php',
'PhabricatorPackagesPublisherSearchConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPublisherSearchConduitAPIMethod.php',
'PhabricatorPackagesPublisherSearchEngine' => 'applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php',
'PhabricatorPackagesPublisherTransaction' => 'applications/packages/storage/PhabricatorPackagesPublisherTransaction.php',
'PhabricatorPackagesPublisherTransactionQuery' => 'applications/packages/query/PhabricatorPackagesPublisherTransactionQuery.php',
'PhabricatorPackagesPublisherTransactionType' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherTransactionType.php',
'PhabricatorPackagesPublisherViewController' => 'applications/packages/controller/PhabricatorPackagesPublisherViewController.php',
'PhabricatorPackagesQuery' => 'applications/packages/query/PhabricatorPackagesQuery.php',
'PhabricatorPackagesSchemaSpec' => 'applications/packages/storage/PhabricatorPackagesSchemaSpec.php',
'PhabricatorPackagesTransactionType' => 'applications/packages/xaction/PhabricatorPackagesTransactionType.php',
'PhabricatorPackagesVersion' => 'applications/packages/storage/PhabricatorPackagesVersion.php',
'PhabricatorPackagesVersionController' => 'applications/packages/controller/PhabricatorPackagesVersionController.php',
'PhabricatorPackagesVersionEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesVersionEditConduitAPIMethod.php',
'PhabricatorPackagesVersionEditController' => 'applications/packages/controller/PhabricatorPackagesVersionEditController.php',
'PhabricatorPackagesVersionEditEngine' => 'applications/packages/editor/PhabricatorPackagesVersionEditEngine.php',
'PhabricatorPackagesVersionEditor' => 'applications/packages/editor/PhabricatorPackagesVersionEditor.php',
'PhabricatorPackagesVersionListController' => 'applications/packages/controller/PhabricatorPackagesVersionListController.php',
'PhabricatorPackagesVersionListView' => 'applications/packages/view/PhabricatorPackagesVersionListView.php',
'PhabricatorPackagesVersionNameNgrams' => 'applications/packages/storage/PhabricatorPackagesVersionNameNgrams.php',
'PhabricatorPackagesVersionNameTransaction' => 'applications/packages/xaction/version/PhabricatorPackagesVersionNameTransaction.php',
'PhabricatorPackagesVersionPHIDType' => 'applications/packages/phid/PhabricatorPackagesVersionPHIDType.php',
'PhabricatorPackagesVersionPackageTransaction' => 'applications/packages/xaction/version/PhabricatorPackagesVersionPackageTransaction.php',
'PhabricatorPackagesVersionQuery' => 'applications/packages/query/PhabricatorPackagesVersionQuery.php',
'PhabricatorPackagesVersionSearchConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesVersionSearchConduitAPIMethod.php',
'PhabricatorPackagesVersionSearchEngine' => 'applications/packages/query/PhabricatorPackagesVersionSearchEngine.php',
'PhabricatorPackagesVersionTransaction' => 'applications/packages/storage/PhabricatorPackagesVersionTransaction.php',
'PhabricatorPackagesVersionTransactionQuery' => 'applications/packages/query/PhabricatorPackagesVersionTransactionQuery.php',
'PhabricatorPackagesVersionTransactionType' => 'applications/packages/xaction/version/PhabricatorPackagesVersionTransactionType.php',
'PhabricatorPackagesVersionViewController' => 'applications/packages/controller/PhabricatorPackagesVersionViewController.php',
'PhabricatorPackagesView' => 'applications/packages/view/PhabricatorPackagesView.php',
'PhabricatorPagerUIExample' => 'applications/uiexample/examples/PhabricatorPagerUIExample.php',
'PhabricatorPassphraseApplication' => 'applications/passphrase/application/PhabricatorPassphraseApplication.php',
'PhabricatorPasswordAuthProvider' => 'applications/auth/provider/PhabricatorPasswordAuthProvider.php',
@ -2992,7 +3086,6 @@ phutil_register_library_map(array(
'PhabricatorPeopleAnyOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleAnyOwnerDatasource.php',
'PhabricatorPeopleApplication' => 'applications/people/application/PhabricatorPeopleApplication.php',
'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php',
'PhabricatorPeopleCalendarController' => 'applications/people/controller/PhabricatorPeopleCalendarController.php',
'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php',
'PhabricatorPeopleCreateController' => 'applications/people/controller/PhabricatorPeopleCreateController.php',
'PhabricatorPeopleDatasource' => 'applications/people/typeahead/PhabricatorPeopleDatasource.php',
@ -6167,8 +6260,7 @@ phutil_register_library_map(array(
'PHUIFormIconSetControl' => 'AphrontFormControl',
'PHUIFormInsetView' => 'AphrontView',
'PHUIFormLayoutView' => 'AphrontView',
'PHUIFormMultiSubmitControl' => 'AphrontFormControl',
'PHUIFormPageView' => 'AphrontView',
'PHUIFormNumberControl' => 'AphrontFormControl',
'PHUIHandleListView' => 'AphrontTagView',
'PHUIHandleTagListView' => 'AphrontTagView',
'PHUIHandleView' => 'AphrontView',
@ -6194,7 +6286,6 @@ phutil_register_library_map(array(
'PHUIObjectItemListExample' => 'PhabricatorUIExample',
'PHUIObjectItemListView' => 'AphrontTagView',
'PHUIObjectItemView' => 'AphrontTagView',
'PHUIPagedFormView' => 'AphrontView',
'PHUIPagerView' => 'AphrontView',
'PHUIPinboardItemView' => 'AphrontView',
'PHUIPinboardView' => 'AphrontView',
@ -6628,29 +6719,53 @@ phutil_register_library_map(array(
'PhabricatorFlaggableInterface',
'PhabricatorSpacesInterface',
'PhabricatorFulltextInterface',
'PhabricatorConduitResultInterface',
),
'PhabricatorCalendarEventAcceptTransaction' => 'PhabricatorCalendarEventReplyTransaction',
'PhabricatorCalendarEventAllDayTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventCommentController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventCancelTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventDateTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventDeclineTransaction' => 'PhabricatorCalendarEventReplyTransaction',
'PhabricatorCalendarEventDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PhabricatorCalendarEventDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PhabricatorCalendarEventDescriptionTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventEditEngine' => 'PhabricatorEditEngine',
'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorCalendarEventEmailCommand' => 'MetaMTAEmailTransactionCommand',
'PhabricatorCalendarEventEndDateTransaction' => 'PhabricatorCalendarEventDateTransaction',
'PhabricatorCalendarEventFrequencyTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorCalendarEventHostPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorCalendarEventHostTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventIconTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventInviteTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventInvitee' => array(
'PhabricatorCalendarDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorCalendarEventInviteeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarEventInviteesPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorCalendarEventJoinController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventListController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventMailReceiver' => 'PhabricatorObjectMailReceiver',
'PhabricatorCalendarEventNameTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventPHIDType' => 'PhabricatorPHIDType',
'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarEventRSVPEmailCommand' => 'PhabricatorCalendarEventEmailCommand',
'PhabricatorCalendarEventRecurringTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventReplyTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhabricatorCalendarEventSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorCalendarEventTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorCalendarEventStartDateTransaction' => 'PhabricatorCalendarEventDateTransaction',
'PhabricatorCalendarEventTransaction' => 'PhabricatorModularTransaction',
'PhabricatorCalendarEventTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorCalendarEventTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorCalendarEventTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCalendarEventUntilDateTransaction' => 'PhabricatorCalendarEventDateTransaction',
'PhabricatorCalendarEventViewController' => 'PhabricatorCalendarController',
'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO',
'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase',
@ -7684,7 +7799,108 @@ phutil_register_library_map(array(
'PhabricatorPHPASTApplication' => 'PhabricatorApplication',
'PhabricatorPHPConfigSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorPHPMailerConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPagedFormUIExample' => 'PhabricatorUIExample',
'PhabricatorPackagesApplication' => 'PhabricatorApplication',
'PhabricatorPackagesController' => 'PhabricatorController',
'PhabricatorPackagesCreatePublisherCapability' => 'PhabricatorPolicyCapability',
'PhabricatorPackagesDAO' => 'PhabricatorLiskDAO',
'PhabricatorPackagesEditEngine' => 'PhabricatorEditEngine',
'PhabricatorPackagesEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorPackagesNgrams' => 'PhabricatorSearchNgrams',
'PhabricatorPackagesPackage' => array(
'PhabricatorPackagesDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSubscribableInterface',
'PhabricatorProjectInterface',
'PhabricatorConduitResultInterface',
'PhabricatorNgramsInterface',
),
'PhabricatorPackagesPackageController' => 'PhabricatorPackagesController',
'PhabricatorPackagesPackageDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorPackagesPackageDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PhabricatorPackagesPackageDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PhabricatorPackagesPackageEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorPackagesPackageEditController' => 'PhabricatorPackagesPackageController',
'PhabricatorPackagesPackageEditEngine' => 'PhabricatorPackagesEditEngine',
'PhabricatorPackagesPackageEditor' => 'PhabricatorPackagesEditor',
'PhabricatorPackagesPackageKeyTransaction' => 'PhabricatorPackagesPackageTransactionType',
'PhabricatorPackagesPackageListController' => 'PhabricatorPackagesPackageController',
'PhabricatorPackagesPackageListView' => 'PhabricatorPackagesView',
'PhabricatorPackagesPackageNameNgrams' => 'PhabricatorPackagesNgrams',
'PhabricatorPackagesPackageNameTransaction' => 'PhabricatorPackagesPackageTransactionType',
'PhabricatorPackagesPackagePHIDType' => 'PhabricatorPHIDType',
'PhabricatorPackagesPackagePublisherTransaction' => 'PhabricatorPackagesPackageTransactionType',
'PhabricatorPackagesPackageQuery' => 'PhabricatorPackagesQuery',
'PhabricatorPackagesPackageSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhabricatorPackagesPackageSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorPackagesPackageTransaction' => 'PhabricatorModularTransaction',
'PhabricatorPackagesPackageTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorPackagesPackageTransactionType' => 'PhabricatorPackagesTransactionType',
'PhabricatorPackagesPackageViewController' => 'PhabricatorPackagesPackageController',
'PhabricatorPackagesPublisher' => array(
'PhabricatorPackagesDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSubscribableInterface',
'PhabricatorProjectInterface',
'PhabricatorConduitResultInterface',
'PhabricatorNgramsInterface',
),
'PhabricatorPackagesPublisherController' => 'PhabricatorPackagesController',
'PhabricatorPackagesPublisherDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorPackagesPublisherDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PhabricatorPackagesPublisherEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorPackagesPublisherEditController' => 'PhabricatorPackagesPublisherController',
'PhabricatorPackagesPublisherEditEngine' => 'PhabricatorPackagesEditEngine',
'PhabricatorPackagesPublisherEditor' => 'PhabricatorPackagesEditor',
'PhabricatorPackagesPublisherKeyTransaction' => 'PhabricatorPackagesPublisherTransactionType',
'PhabricatorPackagesPublisherListController' => 'PhabricatorPackagesPublisherController',
'PhabricatorPackagesPublisherListView' => 'PhabricatorPackagesView',
'PhabricatorPackagesPublisherNameNgrams' => 'PhabricatorPackagesNgrams',
'PhabricatorPackagesPublisherNameTransaction' => 'PhabricatorPackagesPublisherTransactionType',
'PhabricatorPackagesPublisherPHIDType' => 'PhabricatorPHIDType',
'PhabricatorPackagesPublisherQuery' => 'PhabricatorPackagesQuery',
'PhabricatorPackagesPublisherSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhabricatorPackagesPublisherSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorPackagesPublisherTransaction' => 'PhabricatorModularTransaction',
'PhabricatorPackagesPublisherTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorPackagesPublisherTransactionType' => 'PhabricatorPackagesTransactionType',
'PhabricatorPackagesPublisherViewController' => 'PhabricatorPackagesPublisherController',
'PhabricatorPackagesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPackagesSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorPackagesTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorPackagesVersion' => array(
'PhabricatorPackagesDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSubscribableInterface',
'PhabricatorProjectInterface',
'PhabricatorConduitResultInterface',
'PhabricatorNgramsInterface',
),
'PhabricatorPackagesVersionController' => 'PhabricatorPackagesController',
'PhabricatorPackagesVersionEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorPackagesVersionEditController' => 'PhabricatorPackagesVersionController',
'PhabricatorPackagesVersionEditEngine' => 'PhabricatorPackagesEditEngine',
'PhabricatorPackagesVersionEditor' => 'PhabricatorPackagesEditor',
'PhabricatorPackagesVersionListController' => 'PhabricatorPackagesVersionController',
'PhabricatorPackagesVersionListView' => 'PhabricatorPackagesView',
'PhabricatorPackagesVersionNameNgrams' => 'PhabricatorPackagesNgrams',
'PhabricatorPackagesVersionNameTransaction' => 'PhabricatorPackagesVersionTransactionType',
'PhabricatorPackagesVersionPHIDType' => 'PhabricatorPHIDType',
'PhabricatorPackagesVersionPackageTransaction' => 'PhabricatorPackagesVersionTransactionType',
'PhabricatorPackagesVersionQuery' => 'PhabricatorPackagesQuery',
'PhabricatorPackagesVersionSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhabricatorPackagesVersionSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorPackagesVersionTransaction' => 'PhabricatorModularTransaction',
'PhabricatorPackagesVersionTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorPackagesVersionTransactionType' => 'PhabricatorPackagesTransactionType',
'PhabricatorPackagesVersionViewController' => 'PhabricatorPackagesVersionController',
'PhabricatorPackagesView' => 'AphrontView',
'PhabricatorPagerUIExample' => 'PhabricatorUIExample',
'PhabricatorPassphraseApplication' => 'PhabricatorApplication',
'PhabricatorPasswordAuthProvider' => 'PhabricatorAuthProvider',
@ -7737,7 +7953,6 @@ phutil_register_library_map(array(
'PhabricatorPeopleAnyOwnerDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorPeopleApplication' => 'PhabricatorApplication',
'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController',
'PhabricatorPeopleCalendarController' => 'PhabricatorPeopleProfileController',
'PhabricatorPeopleController' => 'PhabricatorController',
'PhabricatorPeopleCreateController' => 'PhabricatorPeopleController',
'PhabricatorPeopleDatasource' => 'PhabricatorTypeaheadDatasource',

View file

@ -3,13 +3,30 @@
final class AphrontEpochHTTPParameterType
extends AphrontHTTPParameterType {
private $allowNull;
public function setAllowNull($allow_null) {
$this->allowNull = $allow_null;
return $this;
}
public function getAllowNull() {
return $this->allowNull;
}
protected function getParameterExists(AphrontRequest $request, $key) {
return $request->getExists($key) ||
$request->getExists($key.'_d');
}
protected function getParameterValue(AphrontRequest $request, $key) {
return AphrontFormDateControlValue::newFromRequest($request, $key);
$value = AphrontFormDateControlValue::newFromRequest($request, $key);
if ($this->getAllowNull()) {
$value->setOptional(true);
}
return $value;
}
protected function getParameterTypeName() {

View file

@ -132,7 +132,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
'the authenticator correctly:'));
$form->appendChild(
id(new AphrontFormTextControl())
id(new PHUIFormNumberControl())
->setLabel(pht('TOTP Code'))
->setName('totpcode')
->setValue($code)
@ -151,7 +151,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
}
$form->appendChild(
id(new AphrontFormTextControl())
id(new PHUIFormNumberControl())
->setName($this->getParameterName($config, 'totpcode'))
->setLabel(pht('App Code'))
->setCaption(pht('Factor Name: %s', $config->getFactorName()))

View file

@ -40,25 +40,21 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication {
public function getRoutes() {
return array(
'/E(?P<id>[1-9]\d*)(?:/(?P<sequence>\d+))?'
'/E(?P<id>[1-9]\d*)(?:/(?P<sequence>\d+)/)?'
=> 'PhabricatorCalendarEventViewController',
'/calendar/' => array(
'(?:query/(?P<queryKey>[^/]+)/(?:(?P<year>\d+)/'.
'(?P<month>\d+)/)?(?:(?P<day>\d+)/)?)?'
=> 'PhabricatorCalendarEventListController',
'event/' => array(
'create/'
=> 'PhabricatorCalendarEventEditController',
'edit/(?P<id>[1-9]\d*)/(?:(?P<sequence>\d+)/)?'
$this->getEditRoutePattern('edit/')
=> 'PhabricatorCalendarEventEditController',
'drag/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarEventDragController',
'cancel/(?P<id>[1-9]\d*)/(?:(?P<sequence>\d+)/)?'
'cancel/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarEventCancelController',
'(?P<action>join|decline|accept)/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarEventJoinController',
'comment/(?P<id>[1-9]\d*)/(?:(?P<sequence>\d+)/)?'
=> 'PhabricatorCalendarEventCommentController',
),
),
);
@ -87,4 +83,19 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication {
);
}
protected function getCustomCapabilities() {
return array(
PhabricatorCalendarEventDefaultViewCapability::CAPABILITY => array(
'caption' => pht('Default view policy for newly created events.'),
'template' => PhabricatorCalendarEventPHIDType::TYPECONST,
'capability' => PhabricatorPolicyCapability::CAN_VIEW,
),
PhabricatorCalendarEventDefaultEditCapability::CAPABILITY => array(
'caption' => pht('Default edit policy for newly created events.'),
'template' => PhabricatorCalendarEventPHIDType::TYPECONST,
'capability' => PhabricatorPolicyCapability::CAN_EDIT,
),
);
}
}

View file

@ -0,0 +1,12 @@
<?php
final class PhabricatorCalendarEventDefaultEditCapability
extends PhabricatorPolicyCapability {
const CAPABILITY = 'calendar.event.default.edit';
public function getCapabilityName() {
return pht('Default Edit Policy');
}
}

View file

@ -0,0 +1,16 @@
<?php
final class PhabricatorCalendarEventDefaultViewCapability
extends PhabricatorPolicyCapability {
const CAPABILITY = 'calendar.event.default.view';
public function getCapabilityName() {
return pht('Default View Policy');
}
public function shouldAllowPublicPolicySetting() {
return true;
}
}

View file

@ -44,32 +44,24 @@ final class PhabricatorCalendarEventRSVPEmailCommand
PhabricatorMetaMTAReceivedMail $mail,
$command,
array $argv) {
$status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING;
$status_declined = PhabricatorCalendarEventInvitee::STATUS_DECLINED;
$xactions = array();
$target = phutil_utf8_strtolower(implode(' ', $argv));
$rsvp = null;
$yes_values = $this->getYesValues();
$no_values = $this->getNoValues();
if (in_array($target, $yes_values)) {
$rsvp = $status_attending;
$rsvp = PhabricatorCalendarEventAcceptTransaction::TRANSACTIONTYPE;
} else if (in_array($target, $no_values)) {
$rsvp = $status_declined;
$rsvp = PhabricatorCalendarEventDeclineTransaction::TRANSACTIONTYPE;
} else {
$rsvp = null;
}
if ($rsvp === null) {
return array();
}
$xactions = array();
$xactions[] = $object->getApplicationTransactionTemplate()
->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_INVITE)
->setNewValue(array($viewer->getPHID() => $rsvp));
->setTransactionType($rsvp)
->setNewValue(true);
return $xactions;
}

View file

@ -0,0 +1,19 @@
<?php
final class PhabricatorCalendarEventEditConduitAPIMethod
extends PhabricatorEditEngineAPIMethod {
public function getAPIMethodName() {
return 'calendar.event.edit';
}
public function newEditEngine() {
return new PhabricatorCalendarEventEditEngine();
}
public function getMethodSummary() {
return pht(
'Apply transactions to create a new event or edit an existing one.');
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorCalendarEventSearchConduitAPIMethod
extends PhabricatorSearchEngineAPIMethod {
public function getAPIMethodName() {
return 'calendar.event.search';
}
public function newSearchEngine() {
return new PhabricatorCalendarEventSearchEngine();
}
public function getMethodSummary() {
return pht('Read information about events.');
}
}

View file

@ -2,77 +2,4 @@
abstract class PhabricatorCalendarController extends PhabricatorController {
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$actions = id(new PhabricatorActionListView())
->setUser($this->getViewer())
->addAction(
id(new PhabricatorActionView())
->setName(pht('Create Event'))
->setHref('/calendar/event/create/'))
->addAction(
id(new PhabricatorActionView())
->setName(pht('Create Public Event'))
->setHref('/calendar/event/create/?mode=public'))
->addAction(
id(new PhabricatorActionView())
->setName(pht('Create Recurring Event'))
->setHref('/calendar/event/create/?mode=recurring'));
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Create Event'))
->setHref($this->getApplicationURI().'event/create/')
->setIcon('fa-plus-square')
->setDropdownMenu($actions));
return $crumbs;
}
protected function getEventAtIndexForGhostPHID($viewer, $phid, $index) {
$result = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
->withInstanceSequencePairs(
array(
array(
$phid,
$index,
),
))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
return $result;
}
protected function createEventFromGhost($viewer, $event, $index) {
$invitees = $event->getInvitees();
$new_ghost = $event->generateNthGhost($index, $viewer);
$new_ghost->attachParentEvent($event);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$new_ghost
->setID(null)
->setPHID(null)
->removeViewerTimezone($viewer)
->setViewPolicy($event->getViewPolicy())
->setEditPolicy($event->getEditPolicy())
->save();
$ghost_invitees = array();
foreach ($invitees as $invitee) {
$ghost_invitee = clone $invitee;
$ghost_invitee
->setID(null)
->setEventPHID($new_ghost->getPHID())
->save();
}
unset($unguarded);
return $new_ghost;
}
}

View file

@ -6,7 +6,6 @@ final class PhabricatorCalendarEventCancelController
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$sequence = $request->getURIData('sequence');
$event = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
@ -17,45 +16,29 @@ final class PhabricatorCalendarEventCancelController
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if ($sequence) {
$parent_event = $event;
$event = $parent_event->generateNthGhost($sequence, $viewer);
$event->attachParentEvent($parent_event);
}
if (!$event) {
return new Aphront404Response();
}
if (!$sequence) {
$cancel_uri = '/E'.$event->getID();
$cancel_uri = $event->getURI();
$is_parent = $event->isParentEvent();
$is_child = $event->isChildEvent();
$is_cancelled = $event->getIsCancelled();
if ($is_child) {
$is_parent_cancelled = $event->getParentEvent()->getIsCancelled();
} else {
$cancel_uri = '/E'.$event->getID().'/'.$sequence;
$is_parent_cancelled = false;
}
$is_cancelled = $event->getIsCancelled();
$is_parent_cancelled = $event->getIsParentCancelled();
$is_parent = $event->getIsRecurrenceParent();
$validation_exception = null;
if ($request->isFormPost()) {
if ($is_cancelled && $sequence) {
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
} else if ($sequence) {
$event = $this->createEventFromGhost(
$viewer,
$event,
$sequence);
$event->applyViewerTimezone($viewer);
}
$xactions = array();
$xaction = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_CANCEL)
PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE)
->setNewValue(!$is_cancelled);
$editor = id(new PhabricatorCalendarEventEditor())
@ -73,43 +56,47 @@ final class PhabricatorCalendarEventCancelController
}
if ($is_cancelled) {
if ($sequence || $is_parent_cancelled) {
if ($is_parent_cancelled) {
$title = pht('Cannot Reinstate Instance');
$paragraph = pht(
'Cannot reinstate an instance of a cancelled recurring event.');
$cancel = pht('Cancel');
'You cannot reinstate an instance of a cancelled recurring event.');
$cancel = pht('Back');
$submit = null;
} else if ($is_parent) {
$title = pht('Reinstate Recurrence');
} else if ($is_child) {
$title = pht('Reinstate Instance');
$paragraph = pht(
'Reinstate all instances of this recurrence
that have not been individually cancelled?');
$cancel = pht("Don't Reinstate Recurrence");
$submit = pht('Reinstate Recurrence');
'Reinstate this instance of this recurring event?');
$cancel = pht('Back');
$submit = pht('Reinstate Instance');
} else if ($is_parent) {
$title = pht('Reinstate Recurring Event');
$paragraph = pht(
'Reinstate all instances of this recurring event which have not '.
'been individually cancelled?');
$cancel = pht('Back');
$submit = pht('Reinstate Recurring Event');
} else {
$title = pht('Reinstate Event');
$paragraph = pht('Reinstate this event?');
$cancel = pht("Don't Reinstate Event");
$cancel = pht('Back');
$submit = pht('Reinstate Event');
}
} else {
if ($sequence) {
if ($is_child) {
$title = pht('Cancel Instance');
$paragraph = pht(
'Cancel just this instance of a recurring event.');
$cancel = pht("Don't Cancel Instance");
$paragraph = pht('Cancel this instance of this recurring event?');
$cancel = pht('Back');
$submit = pht('Cancel Instance');
} else if ($is_parent) {
$title = pht('Cancel Recurrence');
$paragraph = pht(
'Cancel the entire series of recurring events?');
$cancel = pht("Don't Cancel Recurrence");
$submit = pht('Cancel Recurrence');
$title = pht('Cancel Recurrin Event');
$paragraph = pht('Cancel this entire series of recurring events?');
$cancel = pht('Back');
$submit = pht('Cancel Recurring Event');
} else {
$title = pht('Cancel Event');
$paragraph = pht(
'You can always reinstate the event later.');
$cancel = pht("Don't Cancel Event");
'Cancel this event? You can always reinstate the event later.');
$cancel = pht('Back');
$submit = pht('Cancel Event');
}
}

View file

@ -1,81 +0,0 @@
<?php
final class PhabricatorCalendarEventCommentController
extends PhabricatorCalendarController {
public function handleRequest(AphrontRequest $request) {
if (!$request->isFormPost()) {
return new Aphront400Response();
}
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$is_preview = $request->isPreviewRequest();
$draft = PhabricatorDraft::buildFromRequest($request);
$event = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$event) {
return new Aphront404Response();
}
$index = $request->getURIData('sequence');
if ($index && !$is_preview) {
$result = $this->getEventAtIndexForGhostPHID(
$viewer,
$event->getPHID(),
$index);
if ($result) {
$event = $result;
} else {
$event = $this->createEventFromGhost(
$viewer,
$event,
$index);
$event->applyViewerTimezone($viewer);
}
}
$view_uri = '/'.$event->getMonogram();
$xactions = array();
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(new PhabricatorCalendarEventTransactionComment())
->setContent($request->getStr('comment')));
$editor = id(new PhabricatorCalendarEventEditor())
->setActor($viewer)
->setContinueOnNoEffect($request->isContinueRequest())
->setContentSourceFromRequest($request)
->setIsPreview($is_preview);
try {
$xactions = $editor->applyTransactions($event, $xactions);
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
return id(new PhabricatorApplicationTransactionNoEffectResponse())
->setCancelURI($view_uri)
->setException($ex);
}
if ($draft) {
$draft->replaceOrDelete();
}
if ($request->isAjax() && $is_preview) {
return id(new PhabricatorApplicationTransactionResponse())
->setViewer($viewer)
->setTransactions($xactions)
->setIsPreview($is_preview);
} else {
return id(new AphrontRedirectResponse())
->setURI($view_uri);
}
}
}

View file

@ -29,7 +29,7 @@ final class PhabricatorCalendarEventDragController
$xactions = array();
$duration = $event->getDateTo() - $event->getDateFrom();
$duration = $event->getDuration();
$start = $request->getInt('start');
$start_value = id(AphrontFormDateControlValue::newFromEpoch(
@ -41,16 +41,16 @@ final class PhabricatorCalendarEventDragController
$viewer,
$end));
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_START_DATE)
->setTransactionType(
PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE)
->setNewValue($start_value);
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_END_DATE)
->setTransactionType(
PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE)
->setNewValue($end_value);
$editor = id(new PhabricatorCalendarEventEditor())
->setActor($viewer)
->setContinueOnMissingFields(true)

View file

@ -3,633 +3,10 @@
final class PhabricatorCalendarEventEditController
extends PhabricatorCalendarController {
private $id;
public function isCreate() {
return !$this->id;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$user_phid = $viewer->getPHID();
$this->id = $request->getURIData('id');
$error_name = true;
$error_recurrence_end_date = null;
$error_start_date = true;
$error_end_date = true;
$validation_exception = null;
$is_recurring_id = celerity_generate_unique_node_id();
$recurrence_end_date_id = celerity_generate_unique_node_id();
$frequency_id = celerity_generate_unique_node_id();
$all_day_id = celerity_generate_unique_node_id();
$start_date_id = celerity_generate_unique_node_id();
$end_date_id = celerity_generate_unique_node_id();
$next_workflow = $request->getStr('next');
$uri_query = $request->getStr('query');
if ($this->isCreate()) {
$mode = $request->getStr('mode');
$event = PhabricatorCalendarEvent::initializeNewCalendarEvent(
$viewer,
$mode);
$create_start_year = $request->getInt('year');
$create_start_month = $request->getInt('month');
$create_start_day = $request->getInt('day');
$create_start_time = $request->getStr('time');
if ($create_start_year) {
$start = AphrontFormDateControlValue::newFromParts(
$viewer,
$create_start_year,
$create_start_month,
$create_start_day,
$create_start_time);
if (!$start->isValid()) {
return new Aphront400Response();
}
$start_value = AphrontFormDateControlValue::newFromEpoch(
$viewer,
$start->getEpoch());
$end = clone $start_value->getDateTime();
$end->modify('+1 hour');
$end_value = AphrontFormDateControlValue::newFromEpoch(
$viewer,
$end->format('U'));
} else {
list($start_value, $end_value) = $this->getDefaultTimeValues($viewer);
}
$recurrence_end_date_value = clone $end_value;
$recurrence_end_date_value->setOptional(true);
$submit_label = pht('Create');
$title = pht('Create Event');
$header_icon = 'fa-plus-square';
$redirect = 'created';
$subscribers = array();
$invitees = array($user_phid);
$cancel_uri = $this->getApplicationURI();
} else {
$event = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$event) {
return new Aphront404Response();
}
if ($request->getURIData('sequence')) {
$index = $request->getURIData('sequence');
$result = $this->getEventAtIndexForGhostPHID(
$viewer,
$event->getPHID(),
$index);
if ($result) {
return id(new AphrontRedirectResponse())
->setURI('/calendar/event/edit/'.$result->getID().'/');
}
$event = $this->createEventFromGhost(
$viewer,
$event,
$index);
return id(new AphrontRedirectResponse())
->setURI('/calendar/event/edit/'.$event->getID().'/');
}
$end_value = AphrontFormDateControlValue::newFromEpoch(
$viewer,
$event->getDateTo());
$start_value = AphrontFormDateControlValue::newFromEpoch(
$viewer,
$event->getDateFrom());
$recurrence_end_date_value = id(clone $end_value)
->setOptional(true);
$submit_label = pht('Update');
$title = pht('Edit Event: %s', $event->getName());
$header_icon = 'fa-pencil';
$subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$event->getPHID());
$invitees = array();
foreach ($event->getInvitees() as $invitee) {
if ($invitee->isUninvited()) {
continue;
} else {
$invitees[] = $invitee->getInviteePHID();
}
}
$cancel_uri = '/'.$event->getMonogram();
}
if ($this->isCreate()) {
$projects = array();
} else {
$projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
$event->getPHID(),
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
$projects = array_reverse($projects);
}
$name = $event->getName();
$description = $event->getDescription();
$is_all_day = $event->getIsAllDay();
$is_recurring = $event->getIsRecurring();
$is_parent = $event->getIsRecurrenceParent();
$frequency = idx($event->getRecurrenceFrequency(), 'rule');
$icon = $event->getIcon();
$edit_policy = $event->getEditPolicy();
$view_policy = $event->getViewPolicy();
$space = $event->getSpacePHID();
if ($request->isFormPost()) {
$xactions = array();
$name = $request->getStr('name');
$start_value = AphrontFormDateControlValue::newFromRequest(
$request,
'start');
$end_value = AphrontFormDateControlValue::newFromRequest(
$request,
'end');
$recurrence_end_date_value = AphrontFormDateControlValue::newFromRequest(
$request,
'recurrenceEndDate');
$recurrence_end_date_value->setOptional(true);
$projects = $request->getArr('projects');
$description = $request->getStr('description');
$subscribers = $request->getArr('subscribers');
$edit_policy = $request->getStr('editPolicy');
$view_policy = $request->getStr('viewPolicy');
$space = $request->getStr('spacePHID');
$is_recurring = $request->getStr('isRecurring') ? 1 : 0;
$frequency = $request->getStr('frequency');
$is_all_day = $request->getStr('isAllDay');
$icon = $request->getStr('icon');
$invitees = $request->getArr('invitees');
$new_invitees = $this->getNewInviteeList($invitees, $event);
$status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING;
if ($this->isCreate()) {
$status = idx($new_invitees, $viewer->getPHID());
if ($status) {
$new_invitees[$viewer->getPHID()] = $status_attending;
}
}
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_NAME)
->setNewValue($name);
if ($is_recurring && $this->isCreate()) {
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_RECURRING)
->setNewValue($is_recurring);
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_FREQUENCY)
->setNewValue(array('rule' => $frequency));
if (!$recurrence_end_date_value->isDisabled()) {
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE)
->setNewValue($recurrence_end_date_value);
}
}
if (($is_recurring && $this->isCreate()) || !$is_parent) {
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_ALL_DAY)
->setNewValue($is_all_day);
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_ICON)
->setNewValue($icon);
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_START_DATE)
->setNewValue($start_value);
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_END_DATE)
->setNewValue($end_value);
}
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorTransactions::TYPE_SUBSCRIBERS)
->setNewValue(array('=' => array_fuse($subscribers)));
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_INVITE)
->setNewValue($new_invitees);
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(
PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION)
->setNewValue($description);
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($request->getStr('viewPolicy'));
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($request->getStr('editPolicy'));
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_SPACE)
->setNewValue($space);
$editor = id(new PhabricatorCalendarEventEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
try {
$proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
$xactions[] = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $proj_edge_type)
->setNewValue(array('=' => array_fuse($projects)));
$xactions = $editor->applyTransactions($event, $xactions);
$response = id(new AphrontRedirectResponse());
switch ($next_workflow) {
case 'day':
if (!$uri_query) {
$uri_query = 'month';
}
$year = $start_value->getDateTime()->format('Y');
$month = $start_value->getDateTime()->format('m');
$day = $start_value->getDateTime()->format('d');
$response->setURI(
'/calendar/query/'.$uri_query.'/'.$year.'/'.$month.'/'.$day.'/');
break;
default:
$response->setURI('/E'.$event->getID());
break;
}
return $response;
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
$error_name = $ex->getShortMessage(
PhabricatorCalendarEventTransaction::TYPE_NAME);
$error_start_date = $ex->getShortMessage(
PhabricatorCalendarEventTransaction::TYPE_START_DATE);
$error_end_date = $ex->getShortMessage(
PhabricatorCalendarEventTransaction::TYPE_END_DATE);
$error_recurrence_end_date = $ex->getShortMessage(
PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE);
}
}
$is_recurring_checkbox = null;
$recurrence_end_date_control = null;
$recurrence_frequency_select = null;
$all_day_checkbox = null;
$start_control = null;
$end_control = null;
$recurring_date_edit_label = null;
$current_policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($event)
->execute();
$name = id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($name)
->setError($error_name);
if ($this->isCreate()) {
Javelin::initBehavior('recurring-edit', array(
'isRecurring' => $is_recurring_id,
'frequency' => $frequency_id,
'recurrenceEndDate' => $recurrence_end_date_id,
));
$is_recurring_checkbox = id(new AphrontFormCheckboxControl())
->addCheckbox(
'isRecurring',
1,
pht('Recurring Event'),
$is_recurring,
$is_recurring_id);
$recurrence_end_date_control = id(new AphrontFormDateControl())
->setUser($viewer)
->setName('recurrenceEndDate')
->setLabel(pht('Recurrence End Date'))
->setError($error_recurrence_end_date)
->setValue($recurrence_end_date_value)
->setID($recurrence_end_date_id)
->setIsTimeDisabled(true)
->setIsDisabled($recurrence_end_date_value->isDisabled())
->setAllowNull(true);
$recurrence_frequency_select = id(new AphrontFormSelectControl())
->setName('frequency')
->setOptions(array(
PhabricatorCalendarEvent::FREQUENCY_DAILY => pht('Daily'),
PhabricatorCalendarEvent::FREQUENCY_WEEKLY => pht('Weekly'),
PhabricatorCalendarEvent::FREQUENCY_MONTHLY => pht('Monthly'),
PhabricatorCalendarEvent::FREQUENCY_YEARLY => pht('Yearly'),
))
->setValue($frequency)
->setLabel(pht('Recurring Event Frequency'))
->setID($frequency_id)
->setDisabled(!$is_recurring);
}
if ($this->isCreate() || (!$is_parent && !$this->isCreate())) {
Javelin::initBehavior('event-all-day', array(
'allDayID' => $all_day_id,
'startDateID' => $start_date_id,
'endDateID' => $end_date_id,
));
$all_day_checkbox = id(new AphrontFormCheckboxControl())
->addCheckbox(
'isAllDay',
1,
pht('All Day Event'),
$is_all_day,
$all_day_id);
$start_control = id(new AphrontFormDateControl())
->setUser($viewer)
->setName('start')
->setLabel(pht('Start'))
->setError($error_start_date)
->setValue($start_value)
->setID($start_date_id)
->setIsTimeDisabled($is_all_day)
->setEndDateID($end_date_id);
$end_control = id(new AphrontFormDateControl())
->setUser($viewer)
->setName('end')
->setLabel(pht('End'))
->setError($error_end_date)
->setValue($end_value)
->setID($end_date_id)
->setIsTimeDisabled($is_all_day);
} else if ($is_parent) {
$recurring_date_edit_label = id(new AphrontFormStaticControl())
->setUser($viewer)
->setValue(pht('Date and time of recurring event cannot be edited.'));
if (!$recurrence_end_date_value->isDisabled()) {
$disabled_recurrence_end_date_value =
$recurrence_end_date_value->getValueAsFormat('M d, Y');
$recurrence_end_date_control = id(new AphrontFormStaticControl())
->setUser($viewer)
->setLabel(pht('Recurrence End Date'))
->setValue($disabled_recurrence_end_date_value)
->setDisabled(true);
}
$recurrence_frequency_select = id(new AphrontFormSelectControl())
->setName('frequency')
->setOptions(array(
'daily' => pht('Daily'),
'weekly' => pht('Weekly'),
'monthly' => pht('Monthly'),
'yearly' => pht('Yearly'),
))
->setValue($frequency)
->setLabel(pht('Recurring Event Frequency'))
->setID($frequency_id)
->setDisabled(true);
$all_day_checkbox = id(new AphrontFormCheckboxControl())
->addCheckbox(
'isAllDay',
1,
pht('All Day Event'),
$is_all_day,
$all_day_id)
->setDisabled(true);
$start_disabled = $start_value->getValueAsFormat('M d, Y, g:i A');
$end_disabled = $end_value->getValueAsFormat('M d, Y, g:i A');
$start_control = id(new AphrontFormStaticControl())
->setUser($viewer)
->setLabel(pht('Start'))
->setValue($start_disabled)
->setDisabled(true);
$end_control = id(new AphrontFormStaticControl())
->setUser($viewer)
->setLabel(pht('End'))
->setValue($end_disabled);
}
$projects = id(new AphrontFormTokenizerControl())
->setLabel(pht('Tags'))
->setName('projects')
->setValue($projects)
->setUser($viewer)
->setDatasource(new PhabricatorProjectDatasource());
$description = id(new PhabricatorRemarkupControl())
->setLabel(pht('Description'))
->setName('description')
->setValue($description)
->setUser($viewer);
$view_policies = id(new AphrontFormPolicyControl())
->setUser($viewer)
->setValue($view_policy)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicyObject($event)
->setPolicies($current_policies)
->setSpacePHID($space)
->setName('viewPolicy');
$edit_policies = id(new AphrontFormPolicyControl())
->setUser($viewer)
->setValue($edit_policy)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicyObject($event)
->setPolicies($current_policies)
->setName('editPolicy');
$subscribers = id(new AphrontFormTokenizerControl())
->setLabel(pht('Subscribers'))
->setName('subscribers')
->setValue($subscribers)
->setUser($viewer)
->setDatasource(new PhabricatorMetaMTAMailableDatasource());
$invitees = id(new AphrontFormTokenizerControl())
->setLabel(pht('Invitees'))
->setName('invitees')
->setValue($invitees)
->setUser($viewer)
->setDatasource(new PhabricatorMetaMTAMailableDatasource());
$icon = id(new PHUIFormIconSetControl())
->setLabel(pht('Icon'))
->setName('icon')
->setIconSet(new PhabricatorCalendarIconSet())
->setValue($icon);
$form = id(new AphrontFormView())
->addHiddenInput('next', $next_workflow)
->addHiddenInput('query', $uri_query)
->setUser($viewer)
->appendChild($name);
if ($recurring_date_edit_label) {
$form->appendControl($recurring_date_edit_label);
}
if ($is_recurring_checkbox) {
$form->appendChild($is_recurring_checkbox);
}
if ($recurrence_end_date_control) {
$form->appendChild($recurrence_end_date_control);
}
if ($recurrence_frequency_select) {
$form->appendControl($recurrence_frequency_select);
}
$form
->appendChild($all_day_checkbox)
->appendChild($start_control)
->appendChild($end_control)
->appendControl($view_policies)
->appendControl($edit_policies)
->appendControl($subscribers)
->appendControl($invitees)
->appendChild($projects)
->appendChild($description)
->appendChild($icon);
if ($request->isAjax()) {
return $this->newDialog()
->setTitle($title)
->setWidth(AphrontDialogView::WIDTH_FULL)
->appendForm($form)
->addCancelButton($cancel_uri)
->addSubmitButton($submit_label);
}
$submit = id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($submit_label);
$form->appendChild($submit);
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Event'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setValidationException($validation_exception)
->setForm($form);
$crumbs = $this->buildApplicationCrumbs();
if (!$this->isCreate()) {
$crumbs->addTextCrumb('E'.$event->getId(), '/E'.$event->getId());
$crumb_title = pht('Edit Event');
} else {
$crumb_title = pht('Create Event');
}
$crumbs->addTextCrumb($crumb_title);
$crumbs->setBorder(true);
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon($header_icon);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter($form_box);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
public function getNewInviteeList(array $phids, $event) {
$invitees = $event->getInvitees();
$invitees = mpull($invitees, null, 'getInviteePHID');
$invited_status = PhabricatorCalendarEventInvitee::STATUS_INVITED;
$uninvited_status = PhabricatorCalendarEventInvitee::STATUS_UNINVITED;
$phids = array_fuse($phids);
$new = array();
foreach ($phids as $phid) {
$old_status = $event->getUserInviteStatus($phid);
if ($old_status != $uninvited_status) {
continue;
}
$new[$phid] = $invited_status;
}
foreach ($invitees as $invitee) {
$deleted_invitee = !idx($phids, $invitee->getInviteePHID());
if ($deleted_invitee) {
$new[$invitee->getInviteePHID()] = $uninvited_status;
}
}
return $new;
}
private function getDefaultTimeValues($viewer) {
$start = new DateTime('@'.time());
$start->setTimeZone($viewer->getTimeZone());
$start->setTime($start->format('H'), 0, 0);
$start->modify('+1 hour');
$end = id(clone $start)->modify('+1 hour');
$start_value = AphrontFormDateControlValue::newFromEpoch(
$viewer,
$start->format('U'));
$end_value = AphrontFormDateControlValue::newFromEpoch(
$viewer,
$end->format('U'));
return array($start_value, $end_value);
return id(new PhabricatorCalendarEventEditEngine())
->setController($this)
->buildResponse();
}
}

View file

@ -3,57 +3,46 @@
final class PhabricatorCalendarEventJoinController
extends PhabricatorCalendarController {
const ACTION_ACCEPT = 'accept';
const ACTION_DECLINE = 'decline';
const ACTION_JOIN = 'join';
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$action = $request->getURIData('action');
$request = $this->getRequest();
$viewer = $request->getViewer();
$declined_status = PhabricatorCalendarEventInvitee::STATUS_DECLINED;
$attending_status = PhabricatorCalendarEventInvitee::STATUS_ATTENDING;
$event = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$event) {
return new Aphront404Response();
}
$cancel_uri = '/E'.$event->getID();
$cancel_uri = $event->getURI();
$action = $request->getURIData('action');
switch ($action) {
case 'accept':
$is_join = true;
break;
case 'decline':
$is_join = false;
break;
default:
$is_join = !$event->getIsUserAttending($viewer->getPHID());
break;
}
$validation_exception = null;
$is_attending = $event->getIsUserAttending($viewer->getPHID());
if ($request->isFormPost()) {
$new_status = null;
switch ($action) {
case self::ACTION_ACCEPT:
$new_status = $attending_status;
break;
case self::ACTION_JOIN:
if ($is_attending) {
$new_status = $declined_status;
} else {
$new_status = $attending_status;
}
break;
case self::ACTION_DECLINE:
$new_status = $declined_status;
break;
if ($is_join) {
$xaction_type =
PhabricatorCalendarEventAcceptTransaction::TRANSACTIONTYPE;
} else {
$xaction_type =
PhabricatorCalendarEventDeclineTransaction::TRANSACTIONTYPE;
}
$new_status = array($viewer->getPHID() => $new_status);
$xaction = id(new PhabricatorCalendarEventTransaction())
->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_INVITE)
->setNewValue($new_status);
->setTransactionType($xaction_type)
->setNewValue(true);
$editor = id(new PhabricatorCalendarEventEditor())
->setActor($viewer)
@ -69,8 +58,7 @@ final class PhabricatorCalendarEventJoinController
}
}
if (($action == self::ACTION_JOIN && !$is_attending)
|| $action == self::ACTION_ACCEPT) {
if ($is_join) {
$title = pht('Join Event');
$paragraph = pht('Would you like to join this event?');
$submit = pht('Join');

View file

@ -19,24 +19,19 @@ final class PhabricatorCalendarEventListController
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($request->getURIData('queryKey'))
->setSearchEngine($engine)
->setNavigation($this->buildSideNav());
->setSearchEngine($engine);
return $this->delegateToController($controller);
}
public function buildSideNav() {
$user = $this->getRequest()->getUser();
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new PhabricatorCalendarEventEditEngine())
->setViewer($this->getViewer())
->addActionToCrumbs($crumbs);
id(new PhabricatorCalendarEventSearchEngine())
->setViewer($user)
->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
return $nav;
return $crumbs;
}
}

View file

@ -9,102 +9,78 @@ final class PhabricatorCalendarEventViewController
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$sequence = $request->getURIData('sequence');
$timeline = null;
$event = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
$event = $this->loadEvent();
if (!$event) {
return new Aphront404Response();
}
if ($sequence) {
$result = $this->getEventAtIndexForGhostPHID(
$viewer,
$event->getPHID(),
$sequence);
if ($result) {
$parent_event = $event;
$event = $result;
$event->attachParentEvent($parent_event);
return id(new AphrontRedirectResponse())
->setURI('/E'.$result->getID());
} else if ($sequence && $event->getIsRecurring()) {
$parent_event = $event;
$event = $event->generateNthGhost($sequence, $viewer);
$event->attachParentEvent($parent_event);
} else if ($sequence) {
return new Aphront404Response();
}
$title = $event->getMonogram().' ('.$sequence.')';
$page_title = $title.' '.$event->getName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($title, '/'.$event->getMonogram().'/'.$sequence);
} else {
$title = 'E'.$event->getID();
$page_title = $title.' '.$event->getName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($title);
$crumbs->setBorder(true);
// If we looked up or generated a stub event, redirect to that event's
// canonical URI.
$id = $request->getURIData('id');
if ($event->getID() != $id) {
$uri = $event->getURI();
return id(new AphrontRedirectResponse())->setURI($uri);
}
if (!$event->getIsGhostEvent()) {
$timeline = $this->buildTransactionTimeline(
$event,
new PhabricatorCalendarEventTransactionQuery());
}
$monogram = $event->getMonogram();
$page_title = $monogram.' '.$event->getName();
$crumbs = $this->buildApplicationCrumbs();
$start = new DateTime('@'.$event->getViewerDateFrom());
$start->setTimeZone($viewer->getTimeZone());
$crumbs->addTextCrumb(
$start->format('F Y'),
'/calendar/query/month/'.$start->format('Y/m/'));
$crumbs->addTextCrumb(
$start->format('D jS'),
'/calendar/query/month/'.$start->format('Y/m/d/'));
$crumbs->addTextCrumb($monogram);
$crumbs->setBorder(true);
$timeline = $this->buildTransactionTimeline(
$event,
new PhabricatorCalendarEventTransactionQuery());
$header = $this->buildHeaderView($event);
$subheader = $this->buildSubheaderView($event);
$curtain = $this->buildCurtain($event);
$details = $this->buildPropertySection($event);
$recurring = $this->buildRecurringSection($event);
$description = $this->buildDescriptionView($event);
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$add_comment_header = $is_serious
? pht('Add Comment')
: pht('Add To Plate');
$draft = PhabricatorDraft::newFromUserAndKey($viewer, $event->getPHID());
if ($sequence) {
$comment_uri = $this->getApplicationURI(
'/event/comment/'.$event->getID().'/'.$sequence.'/');
} else {
$comment_uri = $this->getApplicationURI(
'/event/comment/'.$event->getID().'/');
}
$add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
->setUser($viewer)
->setObjectPHID($event->getPHID())
->setDraft($draft)
->setHeaderText($add_comment_header)
->setAction($comment_uri)
->setSubmitButtonName(pht('Add Comment'));
$comment_view = id(new PhabricatorCalendarEventEditEngine())
->setViewer($viewer)
->buildEditEngineCommentView($event);
$timeline->setQuoteRef($monogram);
$comment_view->setTransactionTimeline($timeline);
$details_header = id(new PHUIHeaderView())
->setHeader(pht('Details'));
$recurring_header = $this->buildRecurringHeader($event);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setMainColumn(array(
->setSubheader($subheader)
->setMainColumn(
array(
$timeline,
$add_comment_form,
$comment_view,
))
->setCurtain($curtain)
->addPropertySection(pht('Details'), $details)
->addPropertySection($details_header, $details)
->addPropertySection($recurring_header, $recurring)
->addPropertySection(pht('Description'), $description);
return $this->newPage()
->setTitle($page_title)
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($event->getPHID()))
->appendChild(
array(
$view,
));
->appendChild($view);
}
private function buildHeaderView(
@ -112,47 +88,33 @@ final class PhabricatorCalendarEventViewController
$viewer = $this->getViewer();
$id = $event->getID();
$is_cancelled = $event->getIsCancelled();
$icon = $is_cancelled ? ('fa-ban') : ('fa-check');
$color = $is_cancelled ? ('red') : ('bluegrey');
$status = $is_cancelled ? pht('Cancelled') : pht('Active');
$invite_status = $event->getUserInviteStatus($viewer->getPHID());
$status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED;
$is_invite_pending = ($invite_status == $status_invited);
if ($event->isCancelledEvent()) {
$icon = 'fa-ban';
$color = 'red';
$status = pht('Cancelled');
} else {
$icon = 'fa-check';
$color = 'bluegrey';
$status = pht('Active');
}
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($event->getName())
->setStatus($icon, $color, $status)
->setPolicyObject($event)
->setHeaderIcon('fa-calendar');
->setHeaderIcon($event->getIcon());
if ($is_invite_pending) {
$decline_button = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-times grey')
->setHref($this->getApplicationURI("/event/decline/{$id}/"))
->setWorkflow(true)
->setText(pht('Decline'));
$accept_button = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-check green')
->setHref($this->getApplicationURI("/event/accept/{$id}/"))
->setWorkflow(true)
->setText(pht('Accept'));
$header->addActionLink($decline_button)
->addActionLink($accept_button);
foreach ($this->buildRSVPActions($event) as $action) {
$header->addActionLink($action);
}
return $header;
}
private function buildCurtain(PhabricatorCalendarEvent $event) {
$viewer = $this->getRequest()->getUser();
$id = $event->getID();
$is_cancelled = $event->getIsCancelled();
$is_attending = $event->getIsUserAttending($viewer->getPHID());
$can_edit = PhabricatorPolicyFilter::hasCapability(
@ -160,19 +122,11 @@ final class PhabricatorCalendarEventViewController
$event,
PhabricatorPolicyCapability::CAN_EDIT);
$edit_label = false;
$edit_uri = false;
if ($event->getIsGhostEvent()) {
$index = $event->getSequenceIndex();
$edit_uri = "event/edit/{$id}/";
if ($event->isChildEvent()) {
$edit_label = pht('Edit This Instance');
$edit_uri = "event/edit/{$id}/{$index}/";
} else if ($event->getIsRecurrenceException()) {
$edit_label = pht('Edit This Instance');
$edit_uri = "event/edit/{$id}/";
} else {
$edit_label = pht('Edit');
$edit_uri = "event/edit/{$id}/";
$edit_label = pht('Edit Event');
}
$curtain = $this->newCurtainView($event);
@ -204,31 +158,24 @@ final class PhabricatorCalendarEventViewController
}
$cancel_uri = $this->getApplicationURI("event/cancel/{$id}/");
$cancel_disabled = !$can_edit;
if ($event->getIsGhostEvent()) {
$index = $event->getSequenceIndex();
$can_reinstate = $event->getIsParentCancelled();
if ($event->isChildEvent()) {
$cancel_label = pht('Cancel This Instance');
$reinstate_label = pht('Reinstate This Instance');
$cancel_disabled = (!$can_edit || $can_reinstate);
$cancel_uri = $this->getApplicationURI("event/cancel/{$id}/{$index}/");
} else if ($event->getIsRecurrenceException()) {
$can_reinstate = $event->getIsParentCancelled();
$cancel_label = pht('Cancel This Instance');
$reinstate_label = pht('Reinstate This Instance');
$cancel_disabled = (!$can_edit || $can_reinstate);
} else if ($event->getIsRecurrenceParent()) {
if ($event->getParentEvent()->getIsCancelled()) {
$cancel_disabled = true;
}
} else if ($event->isParentEvent()) {
$cancel_label = pht('Cancel All');
$reinstate_label = pht('Reinstate All');
$cancel_disabled = !$can_edit;
} else {
$cancel_label = pht('Cancel Event');
$reinstate_label = pht('Reinstate Event');
$cancel_disabled = !$can_edit;
}
if ($is_cancelled) {
if ($event->isCancelledEvent()) {
$curtain->addAction(
id(new PhabricatorActionView())
->setName($reinstate_label)
@ -256,56 +203,6 @@ final class PhabricatorCalendarEventViewController
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
if ($event->getIsAllDay()) {
$date_start = phabricator_date($event->getDateFrom(), $viewer);
$date_end = phabricator_date($event->getDateTo(), $viewer);
if ($date_start == $date_end) {
$properties->addProperty(
pht('Time'),
phabricator_date($event->getDateFrom(), $viewer));
} else {
$properties->addProperty(
pht('Starts'),
phabricator_date($event->getDateFrom(), $viewer));
$properties->addProperty(
pht('Ends'),
phabricator_date($event->getDateTo(), $viewer));
}
} else {
$properties->addProperty(
pht('Starts'),
phabricator_datetime($event->getDateFrom(), $viewer));
$properties->addProperty(
pht('Ends'),
phabricator_datetime($event->getDateTo(), $viewer));
}
if ($event->getIsRecurring()) {
$properties->addProperty(
pht('Recurs'),
ucwords(idx($event->getRecurrenceFrequency(), 'rule')));
if ($event->getRecurrenceEndDate()) {
$properties->addProperty(
pht('Recurrence Ends'),
phabricator_datetime($event->getRecurrenceEndDate(), $viewer));
}
if ($event->getInstanceOfEventPHID()) {
$properties->addProperty(
pht('Recurrence of Event'),
pht('%s of %s',
$event->getSequenceIndex(),
$viewer->renderHandle($event->getInstanceOfEventPHID())->render()));
}
}
$properties->addProperty(
pht('Host'),
$viewer->renderHandle($event->getUserPHID()));
$invitees = $event->getInvitees();
foreach ($invitees as $key => $invitee) {
if ($invitee->isUninvited()) {
@ -361,10 +258,120 @@ final class PhabricatorCalendarEventViewController
$properties->invokeWillRenderEvent();
$properties->addProperty(
pht('Icon'),
id(new PhabricatorCalendarIconSet())
->getIconLabel($event->getIcon()));
return $properties;
}
private function buildRecurringHeader(PhabricatorCalendarEvent $event) {
$viewer = $this->getViewer();
if (!$event->getIsRecurring()) {
return null;
}
$header = id(new PHUIHeaderView())
->setHeader(pht('Recurring Event'));
$sequence = $event->getSequenceIndex();
if ($event->isParentEvent()) {
$parent = $event;
} else {
$parent = $event->getParentEvent();
}
$next_uri = $parent->getURI().'/'.($sequence + 1);
if ($sequence) {
if ($sequence > 1) {
$previous_uri = $parent->getURI().'/'.($sequence - 1);
} else {
$previous_uri = $parent->getURI();
}
$has_previous = true;
} else {
$has_previous = false;
$previous_uri = null;
}
$prev_button = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-chevron-left')
->setHref($previous_uri)
->setDisabled(!$has_previous)
->setText(pht('Previous'));
$next_button = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-chevron-right')
->setHref($next_uri)
->setText(pht('Next'));
$header
->addActionLink($next_button)
->addActionLink($prev_button);
return $header;
}
private function buildRecurringSection(PhabricatorCalendarEvent $event) {
$viewer = $this->getViewer();
if (!$event->getIsRecurring()) {
return null;
}
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
$is_parent = $event->isParentEvent();
if ($is_parent) {
$parent_link = null;
} else {
$parent = $event->getParentEvent();
$parent_link = $viewer
->renderHandle($parent->getPHID())
->render();
}
$rule = $event->getFrequencyRule();
switch ($rule) {
case PhabricatorCalendarEvent::FREQUENCY_DAILY:
if ($is_parent) {
$message = pht('This event repeats every day.');
} else {
$message = pht(
'This event is an instance of %s, and repeats every day.',
$parent_link);
}
break;
case PhabricatorCalendarEvent::FREQUENCY_WEEKLY:
if ($is_parent) {
$message = pht('This event repeats every week.');
} else {
$message = pht(
'This event is an instance of %s, and repeats every week.',
$parent_link);
}
break;
case PhabricatorCalendarEvent::FREQUENCY_MONTHLY:
if ($is_parent) {
$message = pht('This event repeats every month.');
} else {
$message = pht(
'This event is an instance of %s, and repeats every month.',
$parent_link);
}
break;
case PhabricatorCalendarEvent::FREQUENCY_YEARLY:
if ($is_parent) {
$message = pht('This event repeats every year.');
} else {
$message = pht(
'This event is an instance of %s, and repeats every year.',
$parent_link);
}
break;
}
$properties->addProperty(pht('Event Series'), $message);
return $properties;
}
@ -385,4 +392,120 @@ final class PhabricatorCalendarEventViewController
return null;
}
private function loadEvent() {
$request = $this->getRequest();
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$sequence = $request->getURIData('sequence');
// We're going to figure out which event you're trying to look at. Most of
// the time this is simple, but you may be looking at an instance of a
// recurring event which we haven't generated an object for.
// If you are, we're going to generate a "stub" event so we have a real
// ID and PHID to work with, since the rest of the infrastructure relies
// on these identifiers existing.
// Load the event identified by ID first.
$event = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$event) {
return null;
}
// If we aren't looking at an instance of this event, this is a completely
// normal request and we can just return this event.
if (!$sequence) {
return $event;
}
// When you view "E123/999", E123 is normally the parent event. However,
// you might visit a different instance first instead and then fiddle
// with the URI. If the event we're looking at is a child, we are going
// to act on the parent instead.
if ($event->isChildEvent()) {
$event = $event->getParentEvent();
}
// Try to load the instance. If it already exists, we're all done and
// can just return it.
$instance = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
->withInstanceSequencePairs(
array(
array($event->getPHID(), $sequence),
))
->executeOne();
if ($instance) {
return $instance;
}
if (!$viewer->isLoggedIn()) {
throw new Exception(
pht(
'This event instance has not been created yet. Log in to create '.
'it.'));
}
$instance = $event->newStub($viewer, $sequence);
return $instance;
}
private function buildSubheaderView(PhabricatorCalendarEvent $event) {
$viewer = $this->getViewer();
$host_phid = $event->getHostPHID();
$handles = $viewer->loadHandles(array($host_phid));
$handle = $handles[$host_phid];
$host = $viewer->renderHandle($host_phid);
$host = phutil_tag('strong', array(), $host);
$image_uri = $handles[$host_phid]->getImageURI();
$image_href = $handles[$host_phid]->getURI();
$date = $event->renderEventDate($viewer, true);
$content = pht('Hosted by %s on %s.', $host, $date);
return id(new PHUIHeadThingView())
->setImage($image_uri)
->setImageHref($image_href)
->setContent($content);
}
private function buildRSVPActions(PhabricatorCalendarEvent $event) {
$viewer = $this->getViewer();
$id = $event->getID();
$invite_status = $event->getUserInviteStatus($viewer->getPHID());
$status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED;
$is_invite_pending = ($invite_status == $status_invited);
if (!$is_invite_pending) {
return array();
}
$decline_button = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-times grey')
->setHref($this->getApplicationURI("/event/decline/{$id}/"))
->setWorkflow(true)
->setText(pht('Decline'));
$accept_button = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-check green')
->setHref($this->getApplicationURI("/event/accept/{$id}/"))
->setWorkflow(true)
->setText(pht('Accept'));
return array($decline_button, $accept_button);
}
}

View file

@ -0,0 +1,256 @@
<?php
final class PhabricatorCalendarEventEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'calendar.event';
public function getEngineName() {
return pht('Calendar Events');
}
public function getSummaryHeader() {
return pht('Configure Calendar Event Forms');
}
public function getSummaryText() {
return pht('Configure how users create and edit events.');
}
public function getEngineApplicationClass() {
return 'PhabricatorCalendarApplication';
}
protected function newEditableObject() {
return PhabricatorCalendarEvent::initializeNewCalendarEvent(
$this->getViewer());
}
protected function newObjectQuery() {
return new PhabricatorCalendarEventQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create New Event');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Event: %s', $object->getName());
}
protected function getObjectEditShortText($object) {
return $object->getMonogram();
}
protected function getObjectCreateShortText() {
return pht('Create Event');
}
protected function getObjectName() {
return pht('Event');
}
protected function getObjectViewURI($object) {
return $object->getURI();
}
protected function getEditorURI() {
return $this->getApplication()->getApplicationURI('event/edit/');
}
protected function buildCustomEditFields($object) {
$viewer = $this->getViewer();
if ($this->getIsCreate()) {
$invitee_phids = array($viewer->getPHID());
} else {
$invitee_phids = $object->getInviteePHIDsForEdit();
}
$frequency_options = array(
PhabricatorCalendarEvent::FREQUENCY_DAILY => pht('Daily'),
PhabricatorCalendarEvent::FREQUENCY_WEEKLY => pht('Weekly'),
PhabricatorCalendarEvent::FREQUENCY_MONTHLY => pht('Monthly'),
PhabricatorCalendarEvent::FREQUENCY_YEARLY => pht('Yearly'),
);
$fields = array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setDescription(pht('Name of the event.'))
->setIsRequired(true)
->setTransactionType(
PhabricatorCalendarEventNameTransaction::TRANSACTIONTYPE)
->setConduitDescription(pht('Rename the event.'))
->setConduitTypeDescription(pht('New event name.'))
->setValue($object->getName()),
id(new PhabricatorRemarkupEditField())
->setKey('description')
->setLabel(pht('Description'))
->setDescription(pht('Description of the event.'))
->setTransactionType(
PhabricatorCalendarEventDescriptionTransaction::TRANSACTIONTYPE)
->setConduitDescription(pht('Update the event description.'))
->setConduitTypeDescription(pht('New event description.'))
->setValue($object->getDescription()),
id(new PhabricatorBoolEditField())
->setKey('cancelled')
->setOptions(pht('Active'), pht('Cancelled'))
->setLabel(pht('Cancelled'))
->setDescription(pht('Cancel the event.'))
->setTransactionType(
PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE)
->setIsConduitOnly(true)
->setConduitDescription(pht('Cancel or restore the event.'))
->setConduitTypeDescription(pht('True to cancel the event.'))
->setValue($object->getIsCancelled()),
id(new PhabricatorUsersEditField())
->setKey('hostPHID')
->setAliases(array('host'))
->setLabel(pht('Host'))
->setDescription(pht('Host of the event.'))
->setTransactionType(
PhabricatorCalendarEventHostTransaction::TRANSACTIONTYPE)
->setIsConduitOnly($this->getIsCreate())
->setConduitDescription(pht('Change the host of the event.'))
->setConduitTypeDescription(pht('New event host.'))
->setSingleValue($object->getHostPHID()),
id(new PhabricatorDatasourceEditField())
->setKey('inviteePHIDs')
->setAliases(array('invite', 'invitee', 'invitees', 'inviteePHID'))
->setLabel(pht('Invitees'))
->setDatasource(new PhabricatorMetaMTAMailableDatasource())
->setTransactionType(
PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE)
->setDescription(pht('Users invited to the event.'))
->setConduitDescription(pht('Change invited users.'))
->setConduitTypeDescription(pht('New event invitees.'))
->setValue($invitee_phids)
->setCommentActionLabel(pht('Change Invitees')),
);
if ($this->getIsCreate()) {
$fields[] = id(new PhabricatorBoolEditField())
->setKey('isRecurring')
->setLabel(pht('Recurring'))
->setOptions(pht('One-Time Event'), pht('Recurring Event'))
->setTransactionType(
PhabricatorCalendarEventRecurringTransaction::TRANSACTIONTYPE)
->setDescription(pht('One time or recurring event.'))
->setConduitDescription(pht('Make the event recurring.'))
->setConduitTypeDescription(pht('Mark the event as a recurring event.'))
->setValue($object->getIsRecurring());
$fields[] = id(new PhabricatorSelectEditField())
->setKey('frequency')
->setLabel(pht('Frequency'))
->setOptions($frequency_options)
->setTransactionType(
PhabricatorCalendarEventFrequencyTransaction::TRANSACTIONTYPE)
->setDescription(pht('Recurring event frequency.'))
->setConduitDescription(pht('Change the event frequency.'))
->setConduitTypeDescription(pht('New event frequency.'))
->setValue($object->getFrequencyRule());
}
if ($this->getIsCreate() || $object->getIsRecurring()) {
$fields[] = id(new PhabricatorEpochEditField())
->setAllowNull(true)
->setKey('until')
->setLabel(pht('Repeat Until'))
->setTransactionType(
PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE)
->setDescription(pht('Last instance of the event.'))
->setConduitDescription(pht('Change when the event repeats until.'))
->setConduitTypeDescription(pht('New final event time.'))
->setValue($object->getRecurrenceEndDate());
}
$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());
$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());
$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());
$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());
return $fields;
}
protected function willBuildEditForm($object, array $fields) {
$all_day_field = idx($fields, 'isAllDay');
$start_field = idx($fields, 'start');
$end_field = idx($fields, 'end');
if ($all_day_field) {
$is_all_day = $all_day_field->getValueForTransaction();
$control_ids = array();
if ($start_field) {
$control_ids[] = $start_field->getControlID();
}
if ($end_field) {
$control_ids[] = $end_field->getControlID();
}
Javelin::initBehavior(
'event-all-day',
array(
'allDayID' => $all_day_field->getControlID(),
'controlIDs' => $control_ids,
));
} else {
$is_all_day = $object->getIsAllDay();
}
if ($is_all_day) {
if ($start_field) {
$start_field->setHideTime(true);
}
if ($end_field) {
$end_field->setHideTime(true);
}
}
return $fields;
}
}

View file

@ -11,24 +11,53 @@ final class PhabricatorCalendarEventEditor
return pht('Calendar');
}
protected function shouldApplyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function applyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
$actor = $this->requireActor();
if ($object->getIsStub()) {
$this->materializeStub($object);
}
}
private function materializeStub(PhabricatorCalendarEvent $event) {
if (!$event->getIsStub()) {
throw new Exception(
pht('Can not materialize an event stub: this event is not a stub.'));
}
$actor = $this->getActor();
$event->copyFromParent($actor);
$event->setIsStub(0);
$invitees = $event->getParentEvent()->getInvitees();
$new_invitees = array();
foreach ($invitees as $invitee) {
$invitee = id(new PhabricatorCalendarEventInvitee())
->setEventPHID($event->getPHID())
->setInviteePHID($invitee->getInviteePHID())
->setInviterPHID($invitee->getInviterPHID())
->setStatus($invitee->getStatus())
->save();
$new_invitees[] = $invitee;
}
$event->save();
$event->attachInvitees($new_invitees);
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorCalendarEventTransaction::TYPE_NAME;
$types[] = PhabricatorCalendarEventTransaction::TYPE_START_DATE;
$types[] = PhabricatorCalendarEventTransaction::TYPE_END_DATE;
$types[] = PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION;
$types[] = PhabricatorCalendarEventTransaction::TYPE_CANCEL;
$types[] = PhabricatorCalendarEventTransaction::TYPE_INVITE;
$types[] = PhabricatorCalendarEventTransaction::TYPE_ALL_DAY;
$types[] = PhabricatorCalendarEventTransaction::TYPE_ICON;
$types[] = PhabricatorCalendarEventTransaction::TYPE_RECURRING;
$types[] = PhabricatorCalendarEventTransaction::TYPE_FREQUENCY;
$types[] = PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE;
$types[] = PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT;
$types[] = PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX;
$types[] = PhabricatorTransactions::TYPE_COMMENT;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
@ -36,175 +65,29 @@ final class PhabricatorCalendarEventEditor
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorCalendarEventTransaction::TYPE_RECURRING:
return $object->getIsRecurring();
case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY:
return $object->getRecurrenceFrequency();
case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE:
return $object->getRecurrenceEndDate();
case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT:
return $object->getInstanceOfEventPHID();
case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX:
return $object->getSequenceIndex();
case PhabricatorCalendarEventTransaction::TYPE_NAME:
return $object->getName();
case PhabricatorCalendarEventTransaction::TYPE_START_DATE:
return $object->getDateFrom();
case PhabricatorCalendarEventTransaction::TYPE_END_DATE:
return $object->getDateTo();
case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION:
return $object->getDescription();
case PhabricatorCalendarEventTransaction::TYPE_CANCEL:
return $object->getIsCancelled();
case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY:
return (int)$object->getIsAllDay();
case PhabricatorCalendarEventTransaction::TYPE_ICON:
return $object->getIcon();
case PhabricatorCalendarEventTransaction::TYPE_INVITE:
$map = $xaction->getNewValue();
$phids = array_keys($map);
$invitees = mpull($object->getInvitees(), null, 'getInviteePHID');
$old = array();
foreach ($phids as $phid) {
$invitee = idx($invitees, $phid);
if ($invitee) {
$old[$phid] = $invitee->getStatus();
} else {
$old[$phid] = PhabricatorCalendarEventInvitee::STATUS_UNINVITED;
}
}
return $old;
}
return parent::getCustomTransactionOldValue($object, $xaction);
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorCalendarEventTransaction::TYPE_RECURRING:
case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY:
case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT:
case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX:
case PhabricatorCalendarEventTransaction::TYPE_NAME:
case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION:
case PhabricatorCalendarEventTransaction::TYPE_CANCEL:
case PhabricatorCalendarEventTransaction::TYPE_INVITE:
case PhabricatorCalendarEventTransaction::TYPE_ICON:
return $xaction->getNewValue();
case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY:
return (int)$xaction->getNewValue();
case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE:
case PhabricatorCalendarEventTransaction::TYPE_START_DATE:
case PhabricatorCalendarEventTransaction::TYPE_END_DATE:
return $xaction->getNewValue()->getEpoch();
}
return parent::getCustomTransactionNewValue($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorCalendarEventTransaction::TYPE_RECURRING:
return $object->setIsRecurring($xaction->getNewValue());
case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY:
return $object->setRecurrenceFrequency($xaction->getNewValue());
case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT:
return $object->setInstanceOfEventPHID($xaction->getNewValue());
case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX:
return $object->setSequenceIndex($xaction->getNewValue());
case PhabricatorCalendarEventTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
return;
case PhabricatorCalendarEventTransaction::TYPE_START_DATE:
$object->setDateFrom($xaction->getNewValue());
return;
case PhabricatorCalendarEventTransaction::TYPE_END_DATE:
$object->setDateTo($xaction->getNewValue());
return;
case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE:
$object->setRecurrenceEndDate($xaction->getNewValue());
return;
case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION:
$object->setDescription($xaction->getNewValue());
return;
case PhabricatorCalendarEventTransaction::TYPE_CANCEL:
$object->setIsCancelled((int)$xaction->getNewValue());
return;
case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY:
$object->setIsAllDay((int)$xaction->getNewValue());
return;
case PhabricatorCalendarEventTransaction::TYPE_ICON:
$object->setIcon($xaction->getNewValue());
return;
case PhabricatorCalendarEventTransaction::TYPE_INVITE:
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorCalendarEventTransaction::TYPE_RECURRING:
case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY:
case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE:
case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT:
case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX:
case PhabricatorCalendarEventTransaction::TYPE_NAME:
case PhabricatorCalendarEventTransaction::TYPE_START_DATE:
case PhabricatorCalendarEventTransaction::TYPE_END_DATE:
case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION:
case PhabricatorCalendarEventTransaction::TYPE_CANCEL:
case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY:
case PhabricatorCalendarEventTransaction::TYPE_ICON:
return;
case PhabricatorCalendarEventTransaction::TYPE_INVITE:
$map = $xaction->getNewValue();
$phids = array_keys($map);
$invitees = $object->getInvitees();
$invitees = mpull($invitees, null, 'getInviteePHID');
foreach ($phids as $phid) {
$invitee = idx($invitees, $phid);
if (!$invitee) {
$invitee = id(new PhabricatorCalendarEventInvitee())
->setEventPHID($object->getPHID())
->setInviteePHID($phid)
->setInviterPHID($this->getActingAsPHID());
$invitees[] = $invitee;
}
$invitee->setStatus($map[$phid])
->save();
}
$object->attachInvitees($invitees);
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
protected function didApplyInternalEffects(
protected function adjustObjectForPolicyChecks(
PhabricatorLiskDAO $object,
array $xactions) {
$object->removeViewerTimezone($this->requireActor());
$copy = parent::adjustObjectForPolicyChecks($object, $xactions);
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorCalendarEventHostTransaction::TRANSACTIONTYPE:
$copy->setHostPHID($xaction->getNewValue());
break;
case PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE:
PhabricatorPolicyRule::passTransactionHintToRule(
$copy,
new PhabricatorCalendarEventInviteesPolicyRule(),
array_fuse($xaction->getNewValue()));
break;
}
}
return $xactions;
return $copy;
}
protected function applyFinalEffects(
PhabricatorLiskDAO $object,
array $xactions) {
@ -216,22 +99,21 @@ final class PhabricatorCalendarEventEditor
$invalidate_phids = array();
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorCalendarEventTransaction::TYPE_ICON:
break;
case PhabricatorCalendarEventTransaction::TYPE_RECURRING:
case PhabricatorCalendarEventTransaction::TYPE_FREQUENCY:
case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE:
case PhabricatorCalendarEventTransaction::TYPE_INSTANCE_OF_EVENT:
case PhabricatorCalendarEventTransaction::TYPE_SEQUENCE_INDEX:
case PhabricatorCalendarEventTransaction::TYPE_START_DATE:
case PhabricatorCalendarEventTransaction::TYPE_END_DATE:
case PhabricatorCalendarEventTransaction::TYPE_CANCEL:
case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY:
case PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventAllDayTransaction::TRANSACTIONTYPE:
// For these kinds of changes, we need to invalidate the availabilty
// caches for all attendees.
$invalidate_all = true;
break;
case PhabricatorCalendarEventTransaction::TYPE_INVITE:
case PhabricatorCalendarEventAcceptTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventDeclineTransaction::TRANSACTIONTYPE:
$acting_phid = $this->getActingAsPHID();
$invalidate_phids[$acting_phid] = $acting_phid;
break;
case PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE:
foreach ($xaction->getNewValue() as $phid => $ignored) {
$invalidate_phids[$phid] = $phid;
}
@ -247,6 +129,8 @@ final class PhabricatorCalendarEventEditor
}
if ($phids) {
$object->applyViewerTimezone($this->getActor());
$user = new PhabricatorUser();
$conn_w = $user->establishConnection('w');
queryfx(
@ -265,14 +149,15 @@ final class PhabricatorCalendarEventEditor
protected function validateAllTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
$start_date_xaction =
PhabricatorCalendarEventTransaction::TYPE_START_DATE;
PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE;
$end_date_xaction =
PhabricatorCalendarEventTransaction::TYPE_END_DATE;
PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE;
$is_recurrence_xaction =
PhabricatorCalendarEventTransaction::TYPE_RECURRING;
PhabricatorCalendarEventRecurringTransaction::TRANSACTIONTYPE;
$recurrence_end_xaction =
PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE;
PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE;
$start_date = $object->getDateFrom();
$end_date = $object->getDateTo();
@ -287,25 +172,23 @@ final class PhabricatorCalendarEventEditor
} else if ($xaction->getTransactionType() == $end_date_xaction) {
$end_date = $xaction->getNewValue()->getEpoch();
} else if ($xaction->getTransactionType() == $recurrence_end_xaction) {
$recurrence_end = $xaction->getNewValue();
$recurrence_end = $xaction->getNewValue()->getEpoch();
} else if ($xaction->getTransactionType() == $is_recurrence_xaction) {
$is_recurring = $xaction->getNewValue();
}
}
if ($start_date > $end_date) {
$type = PhabricatorCalendarEventTransaction::TYPE_END_DATE;
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
$end_date_xaction,
pht('Invalid'),
pht('End date must be after start date.'),
null);
}
if ($recurrence_end && !$is_recurring) {
$type =
PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE;
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
$recurrence_end_xaction,
pht('Invalid'),
pht('Event must be recurring to have a recurrence end date.').
null);
@ -314,49 +197,6 @@ final class PhabricatorCalendarEventEditor
return $errors;
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case PhabricatorCalendarEventTransaction::TYPE_NAME:
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('Event name is required.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
break;
case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE:
case PhabricatorCalendarEventTransaction::TYPE_START_DATE:
case PhabricatorCalendarEventTransaction::TYPE_END_DATE:
foreach ($xactions as $xaction) {
$date_value = $xaction->getNewValue();
if (!$date_value->isValid()) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('Invalid date.'),
$xaction);
}
}
break;
}
return $errors;
}
protected function shouldPublishFeedStory(
PhabricatorLiskDAO $object,
array $xactions) {
@ -380,8 +220,8 @@ final class PhabricatorCalendarEventEditor
protected function getMailTo(PhabricatorLiskDAO $object) {
$phids = array();
if ($object->getUserPHID()) {
$phids[] = $object->getUserPHID();
if ($object->getHostPHID()) {
$phids[] = $object->getHostPHID();
}
$phids[] = $this->getActingAsPHID();

View file

@ -32,16 +32,16 @@ final class PhabricatorCalendarEventPHIDType extends PhabricatorPHIDType {
foreach ($handles as $phid => $handle) {
$event = $objects[$phid];
$id = $event->getID();
$monogram = $event->getMonogram();
$name = $event->getName();
$is_cancelled = $event->getIsCancelled();
$uri = $event->getURI();
$handle
->setName($name)
->setFullName(pht('E%d: %s', $id, $name))
->setURI('/E'.$id);
->setFullName(pht('%s: %s', $monogram, $name))
->setURI($uri);
if ($is_cancelled) {
if ($event->isCancelledEvent()) {
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
}
}

View file

@ -0,0 +1,43 @@
<?php
final class PhabricatorCalendarEventHostPolicyRule
extends PhabricatorPolicyRule {
public function getObjectPolicyKey() {
return 'calendar.event.host';
}
public function getObjectPolicyName() {
return pht('Event Host');
}
public function getPolicyExplanation() {
return pht('The host of this event can take this action.');
}
public function getRuleDescription() {
return pht('event host');
}
public function canApplyToObject(PhabricatorPolicyInterface $object) {
return ($object instanceof PhabricatorCalendarEvent);
}
public function applyRule(
PhabricatorUser $viewer,
$value,
PhabricatorPolicyInterface $object) {
$viewer_phid = $viewer->getPHID();
if (!$viewer_phid) {
return false;
}
return ($object->getHostPHID() == $viewer_phid);
}
public function getValueControlType() {
return self::CONTROL_TYPE_NONE;
}
}

View file

@ -0,0 +1,104 @@
<?php
final class PhabricatorCalendarEventInviteesPolicyRule
extends PhabricatorPolicyRule {
private $invited = array();
private $sourcePHIDs = array();
public function getObjectPolicyKey() {
return 'calendar.event.invitees';
}
public function getObjectPolicyName() {
return pht('Event Invitees');
}
public function getPolicyExplanation() {
return pht('Users invited to this event can take this action.');
}
public function getRuleDescription() {
return pht('event invitees');
}
public function canApplyToObject(PhabricatorPolicyInterface $object) {
return ($object instanceof PhabricatorCalendarEvent);
}
public function willApplyRules(
PhabricatorUser $viewer,
array $values,
array $objects) {
$viewer_phid = $viewer->getPHID();
if (!$viewer_phid) {
return;
}
if (empty($this->invited[$viewer_phid])) {
$this->invited[$viewer_phid] = array();
}
if (!isset($this->sourcePHIDs[$viewer_phid])) {
$source_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$viewer_phid,
PhabricatorProjectMemberOfProjectEdgeType::EDGECONST);
$source_phids[] = $viewer_phid;
$this->sourcePHIDs[$viewer_phid] = $source_phids;
}
foreach ($objects as $key => $object) {
$cache = $this->getTransactionHint($object);
if ($cache === null) {
// We don't have a hint for this object, so we'll deal with it below.
continue;
}
// We have a hint, so use that as the source of truth.
unset($objects[$key]);
foreach ($this->sourcePHIDs[$viewer_phid] as $source_phid) {
if (isset($cache[$source_phid])) {
$this->invited[$viewer_phid][$object->getPHID()] = true;
break;
}
}
}
$phids = mpull($objects, 'getPHID');
if (!$phids) {
return;
}
$invited = id(new PhabricatorCalendarEventInvitee())->loadAllWhere(
'eventPHID IN (%Ls)
AND inviteePHID IN (%Ls)
AND status != %s',
$phids,
$this->sourcePHIDs[$viewer_phid],
PhabricatorCalendarEventInvitee::STATUS_UNINVITED);
$invited = mpull($invited, 'getEventPHID');
$this->invited[$viewer_phid] += array_fill_keys($invited, true);
}
public function applyRule(
PhabricatorUser $viewer,
$value,
PhabricatorPolicyInterface $object) {
$viewer_phid = $viewer->getPHID();
if (!$viewer_phid) {
return false;
}
$invited = idx($this->invited, $viewer_phid);
return isset($invited[$object->getPHID()]);
}
public function getValueControlType() {
return self::CONTROL_TYPE_NONE;
}
}

View file

@ -8,10 +8,11 @@ final class PhabricatorCalendarEventQuery
private $rangeBegin;
private $rangeEnd;
private $inviteePHIDs;
private $creatorPHIDs;
private $hostPHIDs;
private $isCancelled;
private $eventsWithNoParent;
private $instanceSequencePairs;
private $isStub;
private $generateGhosts = false;
@ -45,8 +46,8 @@ final class PhabricatorCalendarEventQuery
return $this;
}
public function withCreatorPHIDs(array $phids) {
$this->creatorPHIDs = $phids;
public function withHostPHIDs(array $phids) {
$this->hostPHIDs = $phids;
return $this;
}
@ -55,6 +56,11 @@ final class PhabricatorCalendarEventQuery
return $this;
}
public function withIsStub($is_stub) {
$this->isStub = $is_stub;
return $this;
}
public function withEventsWithNoParent($events_with_no_parent) {
$this->eventsWithNoParent = $events_with_no_parent;
return $this;
@ -69,6 +75,15 @@ final class PhabricatorCalendarEventQuery
return array('start', 'id');
}
public function getBuiltinOrders() {
return array(
'start' => array(
'vector' => array('start', 'id'),
'name' => pht('Event Start'),
),
) + parent::getBuiltinOrders();
}
public function getOrderableColumns() {
return array(
'start' => array(
@ -84,11 +99,22 @@ final class PhabricatorCalendarEventQuery
protected function getPagingValueMap($cursor, array $keys) {
$event = $this->loadCursorObject($cursor);
return array(
'start' => $event->getDateFrom(),
'start' => $event->getViewerDateFrom(),
'id' => $event->getID(),
);
}
protected function shouldLimitResults() {
// When generating ghosts, we can't rely on database ordering because
// MySQL can't predict the ghost start times. We'll just load all matching
// events, then generate results from there.
if ($this->generateGhosts) {
return false;
}
return true;
}
protected function loadPage() {
$events = $this->loadStandardPage($this->newResultObject());
@ -101,7 +127,6 @@ final class PhabricatorCalendarEventQuery
return $events;
}
$enforced_end = null;
$raw_limit = $this->getRawResultLimit();
if (!$raw_limit && !$this->rangeEnd) {
@ -115,7 +140,6 @@ final class PhabricatorCalendarEventQuery
foreach ($events as $key => $event) {
$sequence_start = 0;
$sequence_end = null;
$duration = $event->getDateTo() - $event->getDateFrom();
$end = null;
$instance_of = $event->getInstanceOfEventPHID();
@ -126,123 +150,172 @@ final class PhabricatorCalendarEventQuery
continue;
}
}
}
if ($event->getIsRecurring() && $instance_of == null) {
$frequency = $event->getFrequencyUnit();
$modify_key = '+1 '.$frequency;
// Pull out all of the parents first. We may discard them as we begin
// generating ghost events, but we still want to process all of them.
$parents = array();
foreach ($events as $key => $event) {
if ($event->isParentEvent()) {
$parents[$key] = $event;
}
}
if ($this->rangeBegin && $this->rangeBegin > $event->getDateFrom()) {
$max_date = $this->rangeBegin - $duration;
$date = $event->getDateFrom();
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
// Now that we've picked out all the parent events, we can immediately
// discard anything outside of the time window.
$events = $this->getEventsInRange($events);
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');
}
$enforced_end = null;
foreach ($parents as $key => $event) {
$sequence_start = 0;
$sequence_end = null;
$start = null;
$start = $this->rangeBegin;
} else {
$start = $event->getDateFrom() - $duration;
}
$duration = $event->getDuration();
$date = $start;
$frequency = $event->getFrequencyUnit();
$modify_key = '+1 '.$frequency;
if (($this->rangeBegin !== null) &&
($this->rangeBegin > $event->getViewerDateFrom())) {
$max_date = $this->rangeBegin - $duration;
$date = $event->getViewerDateFrom();
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
if (($this->rangeEnd && $event->getRecurrenceEndDate()) &&
$this->rangeEnd < $event->getRecurrenceEndDate()) {
$end = $this->rangeEnd;
} else if ($event->getRecurrenceEndDate()) {
$end = $event->getRecurrenceEndDate();
} else if ($this->rangeEnd) {
$end = $this->rangeEnd;
} else if ($enforced_end) {
if ($end) {
$end = min($end, $enforced_end);
} else {
$end = $enforced_end;
}
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');
}
if ($end) {
$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;
}
$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;
}
} else {
$sequence_end = $raw_limit + $sequence_start;
}
} else {
$sequence_end = $raw_limit + $sequence_start;
}
$sequence_start = max(1, $sequence_start);
$sequence_start = max(1, $sequence_start);
for ($index = $sequence_start; $index < $sequence_end; $index++) {
$events[] = $event->newGhost($viewer, $index);
}
for ($index = $sequence_start; $index < $sequence_end; $index++) {
$events[] = $event->generateNthGhost($index, $viewer);
}
// NOTE: We're slicing results every time because this makes it cheaper
// to generate future ghosts. If we already have 100 events that occur
// before July 1, we know we never need to generate ghosts after that
// because they couldn't possibly ever appear in the result set.
// NOTE: We're slicing results every time because this makes it cheaper
// to generate future ghosts. If we already have 100 events that occur
// before July 1, we know we never need to generate ghosts after that
// because they couldn't possibly ever appear in the result set.
if ($raw_limit) {
if (count($events) >= $raw_limit) {
$events = msort($events, 'getDateFrom');
$events = array_slice($events, 0, $raw_limit, true);
$enforced_end = last($events)->getDateFrom();
}
if ($raw_limit) {
if (count($events) > $raw_limit) {
$events = msort($events, 'getViewerDateFrom');
$events = array_slice($events, 0, $raw_limit, true);
$enforced_end = last($events)->getViewerDateFrom();
}
}
}
$map = array();
$instance_sequence_pairs = array();
// Now that we're done generating ghost events, we're going to remove any
// ghosts that we have concrete events for (or which we can load the
// concrete events for). These concrete events are generated when users
// edit a ghost, and replace the ghost events.
foreach ($events as $key => $event) {
// First, generate a map of all concrete <parentPHID, sequence> events we
// already loaded. We don't need to load these again.
$have_pairs = array();
foreach ($events as $event) {
if ($event->getIsGhostEvent()) {
$index = $event->getSequenceIndex();
$instance_sequence_pairs[] = array($event->getPHID(), $index);
$map[$event->getPHID()][$index] = $key;
continue;
}
$parent_phid = $event->getInstanceOfEventPHID();
$sequence = $event->getSequenceIndex();
$have_pairs[$parent_phid][$sequence] = true;
}
if (count($instance_sequence_pairs) > 0) {
$sub_query = id(new PhabricatorCalendarEventQuery())
// Now, generate a map of all <parentPHID, sequence> events we generated
// ghosts for. We need to try to load these if we don't already have them.
$map = array();
$parent_pairs = array();
foreach ($events as $key => $event) {
if (!$event->getIsGhostEvent()) {
continue;
}
$parent_phid = $event->getInstanceOfEventPHID();
$sequence = $event->getSequenceIndex();
// We already loaded the concrete version of this event, so we can just
// throw out the ghost and move on.
if (isset($have_pairs[$parent_phid][$sequence])) {
unset($events[$key]);
continue;
}
// We didn't load the concrete version of this event, so we need to
// try to load it if it exists.
$parent_pairs[] = array($parent_phid, $sequence);
$map[$parent_phid][$sequence] = $key;
}
if ($parent_pairs) {
$instances = id(new self())
->setViewer($viewer)
->setParentQuery($this)
->withInstanceSequencePairs($instance_sequence_pairs)
->withInstanceSequencePairs($parent_pairs)
->execute();
foreach ($sub_query as $edited_ghost) {
$indexes = idx($map, $edited_ghost->getInstanceOfEventPHID());
$key = idx($indexes, $edited_ghost->getSequenceIndex());
$events[$key] = $edited_ghost;
}
foreach ($instances as $instance) {
$parent_phid = $instance->getInstanceOfEventPHID();
$sequence = $instance->getSequenceIndex();
$id_map = array();
foreach ($events as $key => $event) {
if ($event->getIsGhostEvent()) {
continue;
}
if (isset($id_map[$event->getID()])) {
unset($events[$key]);
} else {
$id_map[$event->getID()] = true;
}
$indexes = idx($map, $parent_phid);
$key = idx($indexes, $sequence);
// Replace the ghost with the corresponding concrete event.
$events[$key] = $instance;
}
}
$events = msort($events, 'getViewerDateFrom');
return $events;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) {
$parts = parent::buildJoinClauseParts($conn_r);
if ($this->inviteePHIDs !== null) {
$parts[] = qsprintf(
$conn_r,
@ -251,6 +324,7 @@ final class PhabricatorCalendarEventQuery
id(new PhabricatorCalendarEventInvitee())->getTableName(),
PhabricatorCalendarEventInvitee::STATUS_UNINVITED);
}
return $parts;
}
@ -271,18 +345,22 @@ final class PhabricatorCalendarEventQuery
$this->phids);
}
// NOTE: The date ranges we query for are larger than the requested ranges
// because we need to catch all-day events. We'll refine this range later
// after adjusting the visible range of events we load.
if ($this->rangeBegin) {
$where[] = qsprintf(
$conn,
'event.dateTo >= %d OR event.isRecurring = 1',
$this->rangeBegin);
$this->rangeBegin - phutil_units('16 hours in seconds'));
}
if ($this->rangeEnd) {
$where[] = qsprintf(
$conn,
'event.dateFrom <= %d',
$this->rangeEnd);
$this->rangeEnd + phutil_units('16 hours in seconds'));
}
if ($this->inviteePHIDs !== null) {
@ -292,11 +370,11 @@ final class PhabricatorCalendarEventQuery
$this->inviteePHIDs);
}
if ($this->creatorPHIDs) {
if ($this->hostPHIDs) {
$where[] = qsprintf(
$conn,
'event.userPHID IN (%Ls)',
$this->creatorPHIDs);
'event.hostPHID IN (%Ls)',
$this->hostPHIDs);
}
if ($this->isCancelled !== null) {
@ -329,6 +407,13 @@ final class PhabricatorCalendarEventQuery
implode(' OR ', $sql));
}
if ($this->isStub !== null) {
$where[] = qsprintf(
$conn,
'event.isStub = %d',
(int)$this->isStub);
}
return $where;
}
@ -353,23 +438,11 @@ final class PhabricatorCalendarEventQuery
protected function willFilterPage(array $events) {
$range_start = $this->rangeBegin;
$range_end = $this->rangeEnd;
$instance_of_event_phids = array();
$recurring_events = array();
$viewer = $this->getViewer();
foreach ($events as $key => $event) {
$event_start = $event->getDateFrom();
$event_end = $event->getDateTo();
if ($range_start && $event_end < $range_start) {
unset($events[$key]);
}
if ($range_end && $event_start > $range_end) {
unset($events[$key]);
}
}
$events = $this->getEventsInRange($events);
$phids = array();
@ -427,7 +500,27 @@ final class PhabricatorCalendarEventQuery
}
}
$events = msort($events, 'getDateFrom');
$events = msort($events, 'getViewerDateFrom');
return $events;
}
private function getEventsInRange(array $events) {
$range_start = $this->rangeBegin;
$range_end = $this->rangeEnd;
foreach ($events as $key => $event) {
$event_start = $event->getViewerDateFrom();
$event_end = $event->getViewerDateTo();
if ($range_start && $event_end < $range_start) {
unset($events[$key]);
}
if ($range_end && $event_start > $range_end) {
unset($events[$key]);
}
}
return $events;
}

View file

@ -26,8 +26,9 @@ final class PhabricatorCalendarEventSearchEngine
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Created By'))
->setKey('creatorPHIDs')
->setLabel(pht('Hosts'))
->setKey('hostPHIDs')
->setAliases(array('host', 'hostPHID', 'hosts'))
->setDatasource(new PhabricatorPeopleUserFunctionDatasource()),
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Invited'))
@ -78,8 +79,8 @@ final class PhabricatorCalendarEventSearchEngine
$query = $this->newQuery();
$viewer = $this->requireViewer();
if ($map['creatorPHIDs']) {
$query->withCreatorPHIDs($map['creatorPHIDs']);
if ($map['hostPHIDs']) {
$query->withHostPHIDs($map['hostPHIDs']);
}
if ($map['invitedPHIDs']) {
@ -113,7 +114,15 @@ final class PhabricatorCalendarEventSearchEngine
break;
}
return $query->setGenerateGhosts(true);
// Generate ghosts (and ignore stub events) if we aren't querying for
// specific events.
if (!$map['ids'] && !$map['phids']) {
$query
->withIsStub(false)
->setGenerateGhosts(true);
}
return $query;
}
private function getQueryDateRange(
@ -184,10 +193,11 @@ final class PhabricatorCalendarEventSearchEngine
}
if ($upcoming) {
$now = PhabricatorTime::getNow();
if ($min_range) {
$min_range = max(time(), $min_range);
$min_range = max($now, $min_range);
} else {
$min_range = time();
$min_range = $now;
}
}
@ -239,25 +249,15 @@ final class PhabricatorCalendarEventSearchEngine
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function getRequiredHandlePHIDsForResultList(
array $objects,
PhabricatorSavedQuery $query) {
$phids = array();
foreach ($objects as $event) {
$phids[$event->getUserPHID()] = 1;
}
return array_keys($phids);
}
protected function renderResultList(
array $events,
PhabricatorSavedQuery $query,
array $handles) {
if ($this->isMonthView($query)) {
return $this->buildCalendarView($events, $query, $handles);
return $this->buildCalendarMonthView($events, $query);
} else if ($this->isDayView($query)) {
return $this->buildCalendarDayView($events, $query, $handles);
return $this->buildCalendarDayView($events, $query);
}
assert_instances_of($events, 'PhabricatorCalendarEvent');
@ -265,51 +265,37 @@ final class PhabricatorCalendarEventSearchEngine
$list = new PHUIObjectItemListView();
foreach ($events as $event) {
$event_date_info = $this->getEventDateLabel($event);
$creator_handle = $handles[$event->getUserPHID()];
$attendees = array();
foreach ($event->getInvitees() as $invitee) {
$status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING;
if ($invitee->getStatus() === $status_attending) {
$attendees[] = $invitee->getInviteePHID();
}
}
if ($event->getIsGhostEvent()) {
$title_text = $event->getMonogram()
.' ('
.$event->getSequenceIndex()
.'): '
.$event->getName();
$monogram = $event->getParentEvent()->getMonogram();
$index = $event->getSequenceIndex();
$monogram = "{$monogram}/{$index}";
} else {
$title_text = $event->getMonogram().': '.$event->getName();
$monogram = $event->getMonogram();
}
$item = id(new PHUIObjectItemView())
->setUser($viewer)
->setObject($event)
->setHeader($title_text)
->setHref($event->getURI())
->addAttribute($event_date_info);
->setObjectName($monogram)
->setHeader($event->getName())
->setHref($event->getURI());
if ($attendees) {
$attending = pht(
'Attending: %s',
$viewer->renderHandleList($attendees)
->setAsInline(1)
->render());
$item->addAttribute($event->renderEventDate($viewer, false));
$item->addAttribute($attending);
if ($event->isCancelledEvent()) {
$item->setDisabled(true);
}
if (strlen($event->getDuration()) > 0) {
$duration = pht(
'Duration: %s',
$event->getDuration());
$status_icon = $event->getDisplayIcon($viewer);
$status_color = $event->getDisplayIconColor($viewer);
$status_label = $event->getDisplayIconLabel($viewer);
$item->addIcon('none', $duration);
}
$item->setStatusIcon("{$status_icon} {$status_color}", $status_label);
$host = pht(
'Hosted by %s',
$viewer->renderHandle($event->getHostPHID()));
$item->addByline($host);
$list->addItem($item);
}
@ -321,13 +307,13 @@ final class PhabricatorCalendarEventSearchEngine
return $result;
}
private function buildCalendarView(
array $statuses,
PhabricatorSavedQuery $query,
array $handles) {
private function buildCalendarMonthView(
array $events,
PhabricatorSavedQuery $query) {
assert_instances_of($events, 'PhabricatorCalendarEvent');
$viewer = $this->requireViewer();
$now = time();
$now = PhabricatorTime::getNow();
list($start_year, $start_month) =
$this->getDisplayYearAndMonthAndDay(
@ -335,9 +321,9 @@ final class PhabricatorCalendarEventSearchEngine
$this->getQueryDateTo($query)->getEpoch(),
$query->getParameter('display'));
$now_year = phabricator_format_local_time($now, $viewer, 'Y');
$now_year = phabricator_format_local_time($now, $viewer, 'Y');
$now_month = phabricator_format_local_time($now, $viewer, 'm');
$now_day = phabricator_format_local_time($now, $viewer, 'j');
$now_day = phabricator_format_local_time($now, $viewer, 'j');
if ($start_month == $now_month && $start_year == $now_year) {
$month_view = new PHUICalendarMonthView(
@ -356,40 +342,45 @@ final class PhabricatorCalendarEventSearchEngine
$month_view->setUser($viewer);
$phids = mpull($statuses, 'getUserPHID');
foreach ($events as $event) {
$epoch_min = $event->getViewerDateFrom();
$epoch_max = $event->getViewerDateTo();
foreach ($statuses as $status) {
$viewer_is_invited = $status->getIsUserInvited($viewer->getPHID());
$event_view = id(new AphrontCalendarEventView())
->setHostPHID($event->getHostPHID())
->setEpochRange($epoch_min, $epoch_max)
->setIsCancelled($event->isCancelledEvent())
->setName($event->getName())
->setURI($event->getURI())
->setIsAllDay($event->getIsAllDay())
->setIcon($event->getDisplayIcon($viewer))
->setIconColor($event->getDisplayIconColor($viewer));
$event = new AphrontCalendarEventView();
$event->setEpochRange($status->getDateFrom(), $status->getDateTo());
$event->setIsAllDay($status->getIsAllDay());
$event->setIcon($status->getIcon());
$name_text = $handles[$status->getUserPHID()]->getName();
$status_text = $status->getName();
$event->setUserPHID($status->getUserPHID());
$event->setDescription(pht('%s (%s)', $name_text, $status_text));
$event->setName($status_text);
$event->setURI($status->getURI());
$event->setViewerIsInvited($viewer_is_invited);
$month_view->addEvent($event);
$month_view->addEvent($event_view);
}
$month_view->setBrowseURI(
$this->getURI('query/'.$query->getQueryKey().'/'));
// TODO redesign-2015 : Move buttons out of PHUICalendarView?
$result = new PhabricatorApplicationSearchResultView();
$result->setContent($month_view);
$from = $this->getQueryDateFrom($query)->getDateTime();
return $result;
$crumbs = array();
$crumbs[] = id(new PHUICrumbView())
->setName($from->format('F Y'));
$header = id(new PHUIHeaderView())
->setHeader($from->format('F Y'));
return id(new PhabricatorApplicationSearchResultView())
->setCrumbs($crumbs)
->setHeader($header)
->setContent($month_view)
->setCollapsed(true);
}
private function buildCalendarDayView(
array $statuses,
PhabricatorSavedQuery $query,
array $handles) {
array $events,
PhabricatorSavedQuery $query) {
$viewer = $this->requireViewer();
@ -400,8 +391,8 @@ final class PhabricatorCalendarEventSearchEngine
$query->getParameter('display'));
$day_view = id(new PHUICalendarDayView(
$this->getQueryDateFrom($query)->getEpoch(),
$this->getQueryDateTo($query)->getEpoch(),
$this->getQueryDateFrom($query),
$this->getQueryDateTo($query),
$start_year,
$start_month,
$start_day))
@ -409,40 +400,56 @@ final class PhabricatorCalendarEventSearchEngine
$day_view->setUser($viewer);
$phids = mpull($statuses, 'getUserPHID');
foreach ($statuses as $status) {
if ($status->getIsCancelled()) {
continue;
}
$viewer_is_invited = $status->getIsUserInvited($viewer->getPHID());
$phids = mpull($events, 'getHostPHID');
foreach ($events as $event) {
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$status,
$event,
PhabricatorPolicyCapability::CAN_EDIT);
$event = new AphrontCalendarEventView();
$event->setCanEdit($can_edit);
$event->setEventID($status->getID());
$event->setEpochRange($status->getDateFrom(), $status->getDateTo());
$event->setIsAllDay($status->getIsAllDay());
$event->setIcon($status->getIcon());
$event->setViewerIsInvited($viewer_is_invited);
$epoch_min = $event->getViewerDateFrom();
$epoch_max = $event->getViewerDateTo();
$event->setName($status->getName());
$event->setURI($status->getURI());
$day_view->addEvent($event);
$status_icon = $event->getDisplayIcon($viewer);
$status_color = $event->getDisplayIconColor($viewer);
$event_view = id(new AphrontCalendarEventView())
->setCanEdit($can_edit)
->setEventID($event->getID())
->setEpochRange($epoch_min, $epoch_max)
->setIsAllDay($event->getIsAllDay())
->setIcon($status_icon)
->setIconColor($status_color)
->setName($event->getName())
->setURI($event->getURI())
->setIsCancelled($event->isCancelledEvent());
$day_view->addEvent($event_view);
}
$day_view->setBrowseURI(
$this->getURI('query/'.$query->getQueryKey().'/'));
$browse_uri = $this->getURI('query/'.$query->getQueryKey().'/');
$day_view->setBrowseURI($browse_uri);
$result = new PhabricatorApplicationSearchResultView();
$result->setContent($day_view);
$from = $this->getQueryDateFrom($query)->getDateTime();
$month_uri = $browse_uri.$from->format('Y/m/');
return $result;
$crumbs = array(
id(new PHUICrumbView())
->setName($from->format('F Y'))
->setHref($month_uri),
id(new PHUICrumbView())
->setName($from->format('D jS')),
);
$header = id(new PHUIHeaderView())
->setHeader($from->format('D, F jS'));
return id(new PhabricatorApplicationSearchResultView())
->setCrumbs($crumbs)
->setHeader($header)
->setContent($day_view)
->setCollapsed(true);
}
private function getDisplayYearAndMonthAndDay(
@ -486,6 +493,20 @@ final class PhabricatorCalendarEventSearchEngine
}
private function getQueryDateFrom(PhabricatorSavedQuery $saved) {
if ($this->calendarYear && $this->calendarMonth) {
$viewer = $this->requireViewer();
$start_year = $this->calendarYear;
$start_month = $this->calendarMonth;
$start_day = $this->calendarDay ? $this->calendarDay : 1;
return AphrontFormDateControlValue::newFromDictionary(
$viewer,
array(
'd' => "{$start_year}-{$start_month}-{$start_day}",
));
}
return $this->getQueryDate($saved, 'rangeStart');
}
@ -541,40 +562,4 @@ final class PhabricatorCalendarEventSearchEngine
return false;
}
private function getEventDateLabel($event) {
$viewer = $this->requireViewer();
$from_datetime = PhabricatorTime::getDateTimeFromEpoch(
$event->getDateFrom(),
$viewer);
$to_datetime = PhabricatorTime::getDateTimeFromEpoch(
$event->getDateTo(),
$viewer);
$from_date_formatted = $from_datetime->format('Y m d');
$to_date_formatted = $to_datetime->format('Y m d');
if ($event->getIsAllDay()) {
if ($from_date_formatted == $to_date_formatted) {
return pht(
'%s, All Day',
phabricator_date($event->getDateFrom(), $viewer));
} else {
return pht(
'%s - %s, All Day',
phabricator_date($event->getDateFrom(), $viewer),
phabricator_date($event->getDateTo(), $viewer));
}
} else if ($from_date_formatted == $to_date_formatted) {
return pht(
'%s - %s',
phabricator_datetime($event->getDateFrom(), $viewer),
phabricator_time($event->getDateTo(), $viewer));
} else {
return pht(
'%s - %s',
phabricator_datetime($event->getDateFrom(), $viewer),
phabricator_datetime($event->getDateTo(), $viewer));
}
}
}

View file

@ -17,13 +17,13 @@ final class PhabricatorCalendarEventFulltextEngine
$document->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR,
$event->getUserPHID(),
$event->getHostPHID(),
PhabricatorPeopleUserPHIDType::TYPECONST,
$event->getDateCreated());
$document->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_OWNER,
$event->getUserPHID(),
$event->getHostPHID(),
PhabricatorPeopleUserPHIDType::TYPECONST,
$event->getDateCreated());

View file

@ -1,27 +1,32 @@
<?php
final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
implements PhabricatorPolicyInterface,
PhabricatorProjectInterface,
PhabricatorMarkupInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorSubscribableInterface,
PhabricatorTokenReceiverInterface,
PhabricatorDestructibleInterface,
PhabricatorMentionableInterface,
PhabricatorFlaggableInterface,
PhabricatorSpacesInterface,
PhabricatorFulltextInterface {
implements
PhabricatorPolicyInterface,
PhabricatorProjectInterface,
PhabricatorMarkupInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorSubscribableInterface,
PhabricatorTokenReceiverInterface,
PhabricatorDestructibleInterface,
PhabricatorMentionableInterface,
PhabricatorFlaggableInterface,
PhabricatorSpacesInterface,
PhabricatorFulltextInterface,
PhabricatorConduitResultInterface {
protected $name;
protected $userPHID;
protected $hostPHID;
protected $dateFrom;
protected $dateTo;
protected $allDayDateFrom;
protected $allDayDateTo;
protected $description;
protected $isCancelled;
protected $isAllDay;
protected $icon;
protected $mailKey;
protected $isStub;
protected $isRecurring = 0;
protected $recurrenceFrequency = array();
@ -36,11 +41,11 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
protected $spacePHID;
const DEFAULT_ICON = 'fa-calendar';
private $parentEvent = self::ATTACHABLE;
private $invitees = self::ATTACHABLE;
private $appliedViewer;
private $viewerDateFrom;
private $viewerDateTo;
// Frequency Constants
const FREQUENCY_DAILY = 'daily';
@ -48,109 +53,225 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
const FREQUENCY_MONTHLY = 'monthly';
const FREQUENCY_YEARLY = 'yearly';
public static function initializeNewCalendarEvent(
PhabricatorUser $actor,
$mode) {
public static function initializeNewCalendarEvent(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorCalendarApplication'))
->executeOne();
$view_policy = null;
$is_recurring = 0;
$view_default = PhabricatorCalendarEventDefaultViewCapability::CAPABILITY;
$edit_default = PhabricatorCalendarEventDefaultEditCapability::CAPABILITY;
$view_policy = $app->getPolicy($view_default);
$edit_policy = $app->getPolicy($edit_default);
if ($mode == 'public') {
$view_policy = PhabricatorPolicies::getMostOpenPolicy();
}
$now = PhabricatorTime::getNow();
if ($mode == 'recurring') {
$is_recurring = true;
}
$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';
return id(new PhabricatorCalendarEvent())
->setUserPHID($actor->getPHID())
->setHostPHID($actor->getPHID())
->setIsCancelled(0)
->setIsAllDay(0)
->setIsRecurring($is_recurring)
->setIcon(self::DEFAULT_ICON)
->setIsStub(0)
->setIsRecurring(0)
->setRecurrenceFrequency(
array(
'rule' => self::FREQUENCY_WEEKLY,
))
->setIcon($default_icon)
->setViewPolicy($view_policy)
->setEditPolicy($actor->getPHID())
->setEditPolicy($edit_policy)
->setSpacePHID($actor->getDefaultSpacePHID())
->attachInvitees(array())
->setDateFrom($epoch_min)
->setDateTo($epoch_max)
->setAllDayDateFrom($now_min)
->setAllDayDateTo($now_max)
->applyViewerTimezone($actor);
}
private function newChild(PhabricatorUser $actor, $sequence) {
if (!$this->isParentEvent()) {
throw new Exception(
pht(
'Unable to generate a new child event for an event which is not '.
'a recurring parent event!'));
}
$child = id(new self())
->setIsCancelled(0)
->setIsStub(0)
->setInstanceOfEventPHID($this->getPHID())
->setSequenceIndex($sequence)
->setIsRecurring(true)
->setRecurrenceFrequency($this->getRecurrenceFrequency())
->attachParentEvent($this);
return $child->copyFromParent($actor);
}
protected function readField($field) {
static $inherit = array(
'hostPHID' => true,
'isAllDay' => true,
'icon' => true,
'spacePHID' => true,
'viewPolicy' => true,
'editPolicy' => true,
'name' => true,
'description' => true,
);
// Read these fields from the parent event instead of this event. For
// example, we want any changes to the parent event's name to
if (isset($inherit[$field])) {
if ($this->getIsStub()) {
// TODO: This should be unconditional, but the execution order of
// CalendarEventQuery and applyViewerTimezone() are currently odd.
if ($this->parentEvent !== self::ATTACHABLE) {
return $this->getParentEvent()->readField($field);
}
}
}
return parent::readField($field);
}
public function copyFromParent(PhabricatorUser $actor) {
if (!$this->isChildEvent()) {
throw new Exception(
pht(
'Unable to copy from parent event: this is not a child event.'));
}
$parent = $this->getParentEvent();
$this
->setHostPHID($parent->getHostPHID())
->setIsAllDay($parent->getIsAllDay())
->setIcon($parent->getIcon())
->setSpacePHID($parent->getSpacePHID())
->setViewPolicy($parent->getViewPolicy())
->setEditPolicy($parent->getEditPolicy())
->setName($parent->getName())
->setDescription($parent->getDescription());
$frequency = $parent->getFrequencyUnit();
$modify_key = '+'.$this->getSequenceIndex().' '.$frequency;
$date = $parent->getDateFrom();
$date_time = PhabricatorTime::getDateTimeFromEpoch($date, $actor);
$date_time->modify($modify_key);
$date = $date_time->format('U');
$duration = $this->getDuration();
$utc = new DateTimeZone('UTC');
$allday_from = $parent->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 = ($parent->getAllDayDateTo() - $allday_from);
$this
->setDateFrom($date)
->setDateTo($date + $duration)
->setAllDayDateFrom($allday_min)
->setAllDayDateTo($allday_min + $allday_duration);
return $this;
}
public function newStub(PhabricatorUser $actor, $sequence) {
$stub = $this->newChild($actor, $sequence);
$stub->setIsStub(1);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$stub->save();
unset($unguarded);
$stub->applyViewerTimezone($actor);
return $stub;
}
public function newGhost(PhabricatorUser $actor, $sequence) {
$ghost = $this->newChild($actor, $sequence);
$ghost
->setIsGhostEvent(true)
->makeEphemeral();
$ghost->applyViewerTimezone($actor);
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->appliedViewer) {
throw new Exception(pht('Viewer timezone is already applied!'));
}
$this->appliedViewer = $viewer;
if (!$this->getIsAllDay()) {
return $this;
}
$this->viewerDateFrom = $this->getDateFrom();
$this->viewerDateTo = $this->getDateTo();
} else {
$zone = $viewer->getTimeZone();
$zone = $viewer->getTimeZone();
$this->setDateFrom(
$this->getDateEpochForTimeZone(
$this->getDateFrom(),
new DateTimeZone('Pacific/Kiritimati'),
$this->viewerDateFrom = $this->getDateEpochForTimezone(
$this->getAllDayDateFrom(),
new DateTimeZone('UTC'),
'Y-m-d',
null,
$zone));
$zone);
$this->setDateTo(
$this->getDateEpochForTimeZone(
$this->getDateTo(),
new DateTimeZone('Pacific/Midway'),
$this->viewerDateTo = $this->getDateEpochForTimezone(
$this->getAllDayDateTo(),
new DateTimeZone('UTC'),
'Y-m-d 23:59:00',
'-1 day',
$zone));
return $this;
}
public function removeViewerTimezone(PhabricatorUser $viewer) {
if (!$this->appliedViewer) {
throw new Exception(pht('Viewer timezone is not applied!'));
}
if ($viewer->getPHID() != $this->appliedViewer->getPHID()) {
throw new Exception(pht('Removed viewer must match applied viewer!'));
}
$this->appliedViewer = null;
if (!$this->getIsAllDay()) {
return $this;
}
$zone = $viewer->getTimeZone();
$this->setDateFrom(
$this->getDateEpochForTimeZone(
$this->getDateFrom(),
$zone,
'Y-m-d',
null,
new DateTimeZone('Pacific/Kiritimati')));
$this->setDateTo(
$this->getDateEpochForTimeZone(
$this->getDateTo(),
$zone,
'Y-m-d',
'+1 day',
new DateTimeZone('Pacific/Midway')));
$zone);
}
return $this;
}
private function getDateEpochForTimeZone(
public function getDuration() {
return $this->getDateTo() - $this->getDateFrom();
}
public function getDateEpochForTimezone(
$epoch,
$src_zone,
$format,
@ -169,12 +290,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
}
public function save() {
if ($this->appliedViewer) {
throw new Exception(
pht(
'Can not save event with viewer timezone still applied!'));
}
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
@ -186,13 +301,13 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
* Get the event start epoch for evaluating invitee availability.
*
* When assessing availability, we pretend events start earlier than they
* really. This allows us to mark users away for the entire duration of a
* really do. This allows us to mark users away for the entire duration of a
* series of back-to-back meetings, even if they don't strictly overlap.
*
* @return int Event start date for availability caches.
*/
public function getDateFromForCache() {
return ($this->getDateFrom() - phutil_units('15 minutes in seconds'));
return ($this->getViewerDateFrom() - phutil_units('15 minutes in seconds'));
}
protected function getConfiguration() {
@ -202,6 +317,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
'name' => 'text',
'dateFrom' => 'epoch',
'dateTo' => 'epoch',
'allDayDateFrom' => 'epoch',
'allDayDateTo' => 'epoch',
'description' => 'text',
'isCancelled' => 'bool',
'isAllDay' => 'bool',
@ -211,10 +328,11 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
'recurrenceEndDate' => 'epoch?',
'instanceOfEventPHID' => 'phid?',
'sequenceIndex' => 'uint32?',
'isStub' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'userPHID_dateFrom' => array(
'columns' => array('userPHID', 'dateTo'),
'key_date' => array(
'columns' => array('dateFrom', 'dateTo'),
),
'key_instance' => array(
'columns' => array('instanceOfEventPHID', 'sequenceIndex'),
@ -245,6 +363,19 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
return $this;
}
public function getInviteePHIDsForEdit() {
$invitees = array();
foreach ($this->getInvitees() as $invitee) {
if ($invitee->isUninvited()) {
continue;
}
$invitees[] = $invitee->getInviteePHID();
}
return $invitees;
}
public function getUserInviteStatus($phid) {
$invitees = $this->getInvitees();
$invitees = mpull($invitees, null, 'getInviteePHID');
@ -285,40 +416,12 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
return $this;
}
public function generateNthGhost(
$sequence_index,
PhabricatorUser $actor) {
$frequency = $this->getFrequencyUnit();
$modify_key = '+'.$sequence_index.' '.$frequency;
$instance_of = ($this->getPHID()) ?
$this->getPHID() : $this->instanceOfEventPHID;
$date = $this->dateFrom;
$date_time = PhabricatorTime::getDateTimeFromEpoch($date, $actor);
$date_time->modify($modify_key);
$date = $date_time->format('U');
$duration = $this->dateTo - $this->dateFrom;
$edit_policy = PhabricatorPolicies::POLICY_NOONE;
$ghost_event = id(clone $this)
->setIsGhostEvent(true)
->setDateFrom($date)
->setDateTo($date + $duration)
->setIsRecurring(true)
->setRecurrenceFrequency($this->recurrenceFrequency)
->setInstanceOfEventPHID($instance_of)
->setSequenceIndex($sequence_index)
->setEditPolicy($edit_policy);
return $ghost_event;
public function getFrequencyRule() {
return idx($this->recurrenceFrequency, 'rule');
}
public function getFrequencyUnit() {
$frequency = idx($this->recurrenceFrequency, 'rule');
$frequency = $this->getFrequencyRule();
switch ($frequency) {
case 'daily':
@ -335,11 +438,13 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
}
public function getURI() {
$uri = '/'.$this->getMonogram();
if ($this->isGhostEvent) {
$uri = $uri.'/'.$this->sequenceIndex;
if ($this->getIsGhostEvent()) {
$base = $this->getParentEvent()->getURI();
$sequence = $this->getSequenceIndex();
return "{$base}/{$sequence}/";
}
return $uri;
return '/'.$this->getMonogram();
}
public function getParentEvent() {
@ -351,63 +456,142 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
return $this;
}
public function getIsCancelled() {
$instance_of = $this->instanceOfEventPHID;
if ($instance_of != null && $this->getIsParentCancelled()) {
return true;
}
return $this->isCancelled;
public function isParentEvent() {
return ($this->getIsRecurring() && !$this->getInstanceOfEventPHID());
}
public function getIsRecurrenceParent() {
if ($this->isRecurring && !$this->instanceOfEventPHID) {
public function isChildEvent() {
return ($this->instanceOfEventPHID !== null);
}
public function isCancelledEvent() {
if ($this->getIsCancelled()) {
return true;
}
if ($this->isChildEvent()) {
if ($this->getParentEvent()->getIsCancelled()) {
return true;
}
}
return false;
}
public function getIsRecurrenceException() {
if ($this->instanceOfEventPHID && !$this->isGhostEvent) {
return true;
public function renderEventDate(
PhabricatorUser $viewer,
$show_end) {
if ($show_end) {
$min_date = PhabricatorTime::getDateTimeFromEpoch(
$this->getViewerDateFrom(),
$viewer);
$max_date = PhabricatorTime::getDateTimeFromEpoch(
$this->getViewerDateTo(),
$viewer);
$min_day = $min_date->format('Y m d');
$max_day = $max_date->format('Y m d');
$show_end_date = ($min_day != $max_day);
} else {
$show_end_date = false;
}
$min_epoch = $this->getViewerDateFrom();
$max_epoch = $this->getViewerDateTo();
if ($this->getIsAllDay()) {
if ($show_end_date) {
return pht(
'%s - %s, All Day',
phabricator_date($min_epoch, $viewer),
phabricator_date($max_epoch, $viewer));
} else {
return pht(
'%s, All Day',
phabricator_date($min_epoch, $viewer));
}
} else if ($show_end_date) {
return pht(
'%s - %s',
phabricator_datetime($min_epoch, $viewer),
phabricator_datetime($max_epoch, $viewer));
} else if ($show_end) {
return pht(
'%s - %s',
phabricator_datetime($min_epoch, $viewer),
phabricator_time($max_epoch, $viewer));
} else {
return pht(
'%s',
phabricator_datetime($min_epoch, $viewer));
}
return false;
}
public function getIsParentCancelled() {
if ($this->instanceOfEventPHID == null) {
return false;
public function getDisplayIcon(PhabricatorUser $viewer) {
if ($this->isCancelledEvent()) {
return 'fa-times';
}
$recurring_event = $this->getParentEvent();
if ($recurring_event->getIsCancelled()) {
return true;
if ($viewer->isLoggedIn()) {
$status = $this->getUserInviteStatus($viewer->getPHID());
switch ($status) {
case PhabricatorCalendarEventInvitee::STATUS_ATTENDING:
return 'fa-check-circle';
case PhabricatorCalendarEventInvitee::STATUS_INVITED:
return 'fa-user-plus';
case PhabricatorCalendarEventInvitee::STATUS_DECLINED:
return 'fa-times';
}
}
return false;
return $this->getIcon();
}
public function getDuration() {
$seconds = $this->dateTo - $this->dateFrom;
$minutes = round($seconds / 60, 1);
$hours = round($minutes / 60, 3);
$days = round($hours / 24, 2);
$duration = '';
if ($days >= 1) {
return pht(
'%s day(s)',
round($days, 1));
} else if ($hours >= 1) {
return pht(
'%s hour(s)',
round($hours, 1));
} else if ($minutes >= 1) {
return pht(
'%s minute(s)',
round($minutes, 0));
public function getDisplayIconColor(PhabricatorUser $viewer) {
if ($this->isCancelledEvent()) {
return 'red';
}
if ($viewer->isLoggedIn()) {
$status = $this->getUserInviteStatus($viewer->getPHID());
switch ($status) {
case PhabricatorCalendarEventInvitee::STATUS_ATTENDING:
return 'green';
case PhabricatorCalendarEventInvitee::STATUS_INVITED:
return 'green';
case PhabricatorCalendarEventInvitee::STATUS_DECLINED:
return 'grey';
}
}
return 'bluegrey';
}
public function getDisplayIconLabel(PhabricatorUser $viewer) {
if ($this->isCancelledEvent()) {
return pht('Cancelled');
}
if ($viewer->isLoggedIn()) {
$status = $this->getUserInviteStatus($viewer->getPHID());
switch ($status) {
case PhabricatorCalendarEventInvitee::STATUS_ATTENDING:
return pht('Attending');
case PhabricatorCalendarEventInvitee::STATUS_INVITED:
return pht('Invited');
case PhabricatorCalendarEventInvitee::STATUS_DECLINED:
return pht('Declined');
}
}
return null;
}
/* -( Markup Interface )--------------------------------------------------- */
@ -475,8 +659,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
// The owner of a task can always view and edit it.
$user_phid = $this->getUserPHID();
// The host of an event can always view and edit it.
$user_phid = $this->getHostPHID();
if ($user_phid) {
$viewer_phid = $viewer->getPHID();
if ($viewer_phid == $user_phid) {
@ -497,11 +681,12 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
}
public function describeAutomaticCapability($capability) {
return pht('The owner of an event can always view and edit it,
and invitees can always view it, except if the event is an
instance of a recurring event.');
return pht(
'The host of an event can always view and edit it. Users who are '.
'invited to an event can always view it.');
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
@ -528,14 +713,14 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
public function isAutomaticallySubscribed($phid) {
return ($phid == $this->getUserPHID());
return ($phid == $this->getHostPHID());
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
return array($this->getUserPHID());
return array($this->getHostPHID());
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
@ -564,4 +749,32 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
return new PhabricatorCalendarEventFulltextEngine();
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('name')
->setType('string')
->setDescription(pht('The name of the event.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('description')
->setType('string')
->setDescription(pht('The event description.')),
);
}
public function getFieldValuesForConduit() {
return array(
'name' => $this->getName(),
'description' => $this->getDescription(),
);
}
public function getConduitSearchAttachments() {
return array();
}
}

View file

@ -1,23 +1,7 @@
<?php
final class PhabricatorCalendarEventTransaction
extends PhabricatorApplicationTransaction {
const TYPE_NAME = 'calendar.name';
const TYPE_START_DATE = 'calendar.startdate';
const TYPE_END_DATE = 'calendar.enddate';
const TYPE_DESCRIPTION = 'calendar.description';
const TYPE_CANCEL = 'calendar.cancel';
const TYPE_ALL_DAY = 'calendar.allday';
const TYPE_ICON = 'calendar.icon';
const TYPE_INVITE = 'calendar.invite';
const TYPE_RECURRING = 'calendar.recurring';
const TYPE_FREQUENCY = 'calendar.frequency';
const TYPE_RECURRENCE_END_DATE = 'calendar.recurrenceenddate';
const TYPE_INSTANCE_OF_EVENT = 'calendar.instanceofevent';
const TYPE_SEQUENCE_INDEX = 'calendar.sequenceindex';
extends PhabricatorModularTransaction {
const MAILTAG_RESCHEDULE = 'calendar-reschedule';
const MAILTAG_CONTENT = 'calendar-content';
@ -35,534 +19,21 @@ final class PhabricatorCalendarEventTransaction
return new PhabricatorCalendarEventTransactionComment();
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
switch ($this->getTransactionType()) {
case self::TYPE_NAME:
case self::TYPE_START_DATE:
case self::TYPE_END_DATE:
case self::TYPE_DESCRIPTION:
case self::TYPE_CANCEL:
case self::TYPE_ALL_DAY:
case self::TYPE_RECURRING:
case self::TYPE_FREQUENCY:
case self::TYPE_RECURRENCE_END_DATE:
case self::TYPE_INSTANCE_OF_EVENT:
case self::TYPE_SEQUENCE_INDEX:
$phids[] = $this->getObjectPHID();
break;
case self::TYPE_INVITE:
$new = $this->getNewValue();
foreach ($new as $phid => $status) {
$phids[] = $phid;
}
break;
}
return $phids;
}
public function shouldHide() {
$old = $this->getOldValue();
switch ($this->getTransactionType()) {
case self::TYPE_START_DATE:
case self::TYPE_END_DATE:
case self::TYPE_DESCRIPTION:
case self::TYPE_CANCEL:
case self::TYPE_ALL_DAY:
case self::TYPE_INVITE:
case self::TYPE_RECURRING:
case self::TYPE_FREQUENCY:
case self::TYPE_RECURRENCE_END_DATE:
case self::TYPE_INSTANCE_OF_EVENT:
case self::TYPE_SEQUENCE_INDEX:
return ($old === null);
}
return parent::shouldHide();
}
public function getIcon() {
switch ($this->getTransactionType()) {
case self::TYPE_ICON:
return $this->getNewValue();
case self::TYPE_NAME:
case self::TYPE_START_DATE:
case self::TYPE_END_DATE:
case self::TYPE_DESCRIPTION:
case self::TYPE_ALL_DAY:
case self::TYPE_CANCEL:
case self::TYPE_RECURRING:
case self::TYPE_FREQUENCY:
case self::TYPE_RECURRENCE_END_DATE:
case self::TYPE_INSTANCE_OF_EVENT:
case self::TYPE_SEQUENCE_INDEX:
return 'fa-pencil';
break;
case self::TYPE_INVITE:
return 'fa-user-plus';
break;
}
return parent::getIcon();
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
$type = $this->getTransactionType();
switch ($type) {
case self::TYPE_NAME:
if ($old === null) {
return pht(
'%s created this event.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s changed the name of this event from %s to %s.',
$this->renderHandleLink($author_phid),
$old,
$new);
}
case self::TYPE_START_DATE:
if ($old) {
return pht(
'%s edited the start date of this event.',
$this->renderHandleLink($author_phid));
}
break;
case self::TYPE_END_DATE:
if ($old) {
return pht(
'%s edited the end date of this event.',
$this->renderHandleLink($author_phid));
}
break;
case self::TYPE_DESCRIPTION:
return pht(
"%s updated the event's description.",
$this->renderHandleLink($author_phid));
case self::TYPE_ALL_DAY:
if ($new) {
return pht(
'%s made this an all day event.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s converted this from an all day event.',
$this->renderHandleLink($author_phid));
}
case self::TYPE_ICON:
$set = new PhabricatorCalendarIconSet();
return pht(
'%s set this event\'s icon to %s.',
$this->renderHandleLink($author_phid),
$set->getIconLabel($new));
break;
case self::TYPE_CANCEL:
if ($new) {
return pht(
'%s cancelled this event.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s reinstated this event.',
$this->renderHandleLink($author_phid));
}
case self::TYPE_INVITE:
$text = null;
if (count($old) === 1
&& count($new) === 1
&& isset($old[$author_phid])) {
// user joined/declined/accepted event themself
$old_status = $old[$author_phid];
$new_status = $new[$author_phid];
if ($old_status !== $new_status) {
switch ($new_status) {
case PhabricatorCalendarEventInvitee::STATUS_INVITED:
$text = pht(
'%s has joined this event.',
$this->renderHandleLink($author_phid));
break;
case PhabricatorCalendarEventInvitee::STATUS_ATTENDING:
$text = pht(
'%s is attending this event.',
$this->renderHandleLink($author_phid));
break;
case PhabricatorCalendarEventInvitee::STATUS_DECLINED:
case PhabricatorCalendarEventInvitee::STATUS_UNINVITED:
$text = pht(
'%s has declined this event.',
$this->renderHandleLink($author_phid));
break;
default:
$text = pht(
'%s has changed their status for this event.',
$this->renderHandleLink($author_phid));
break;
}
}
} else {
// user changed status for many users
$self_joined = null;
$self_declined = null;
$added = array();
$uninvited = array();
foreach ($new as $phid => $status) {
if ($status == PhabricatorCalendarEventInvitee::STATUS_INVITED
|| $status == PhabricatorCalendarEventInvitee::STATUS_ATTENDING) {
// added users
$added[] = $phid;
} else if (
$status == PhabricatorCalendarEventInvitee::STATUS_DECLINED
|| $status == PhabricatorCalendarEventInvitee::STATUS_UNINVITED) {
$uninvited[] = $phid;
}
}
$count_added = count($added);
$count_uninvited = count($uninvited);
$added_text = null;
$uninvited_text = null;
if ($count_added > 0 && $count_uninvited == 0) {
$added_text = $this->renderHandleList($added);
$text = pht('%s invited %s.',
$this->renderHandleLink($author_phid),
$added_text);
} else if ($count_added > 0 && $count_uninvited > 0) {
$added_text = $this->renderHandleList($added);
$uninvited_text = $this->renderHandleList($uninvited);
$text = pht('%s invited %s and uninvited %s.',
$this->renderHandleLink($author_phid),
$added_text,
$uninvited_text);
} else if ($count_added == 0 && $count_uninvited > 0) {
$uninvited_text = $this->renderHandleList($uninvited);
$text = pht('%s uninvited %s.',
$this->renderHandleLink($author_phid),
$uninvited_text);
} else {
$text = pht('%s updated the invitee list.',
$this->renderHandleLink($author_phid));
}
}
return $text;
case self::TYPE_RECURRING:
$text = pht('%s made this event recurring.',
$this->renderHandleLink($author_phid));
return $text;
case self::TYPE_FREQUENCY:
$text = '';
switch ($new['rule']) {
case PhabricatorCalendarEvent::FREQUENCY_DAILY:
$text = pht('%s set this event to repeat daily.',
$this->renderHandleLink($author_phid));
break;
case PhabricatorCalendarEvent::FREQUENCY_WEEKLY:
$text = pht('%s set this event to repeat weekly.',
$this->renderHandleLink($author_phid));
break;
case PhabricatorCalendarEvent::FREQUENCY_MONTHLY:
$text = pht('%s set this event to repeat monthly.',
$this->renderHandleLink($author_phid));
break;
case PhabricatorCalendarEvent::FREQUENCY_YEARLY:
$text = pht('%s set this event to repeat yearly.',
$this->renderHandleLink($author_phid));
break;
}
return $text;
case self::TYPE_RECURRENCE_END_DATE:
$text = pht('%s has changed the recurrence end date of this event.',
$this->renderHandleLink($author_phid));
return $text;
case self::TYPE_INSTANCE_OF_EVENT:
case self::TYPE_SEQUENCE_INDEX:
return pht('Recurring event has been updated.');
}
return parent::getTitle();
}
public function getTitleForFeed() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
$viewer = $this->getViewer();
$type = $this->getTransactionType();
switch ($type) {
case self::TYPE_NAME:
if ($old === null) {
return pht(
'%s created %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else {
return pht(
'%s changed the name of %s from %s to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$old,
$new);
}
case self::TYPE_START_DATE:
if ($old) {
$old = phabricator_datetime($old, $viewer);
$new = phabricator_datetime($new, $viewer);
return pht(
'%s changed the start date of %s from %s to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$old,
$new);
}
break;
case self::TYPE_END_DATE:
if ($old) {
$old = phabricator_datetime($old, $viewer);
$new = phabricator_datetime($new, $viewer);
return pht(
'%s edited the end date of %s from %s to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$old,
$new);
}
break;
case self::TYPE_DESCRIPTION:
return pht(
'%s updated the description of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case self::TYPE_ALL_DAY:
if ($new) {
return pht(
'%s made %s an all day event.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else {
return pht(
'%s converted %s from an all day event.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
case self::TYPE_ICON:
$set = new PhabricatorCalendarIconSet();
return pht(
'%s set the icon for %s to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$set->getIconLabel($new));
case self::TYPE_CANCEL:
if ($new) {
return pht(
'%s cancelled %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else {
return pht(
'%s reinstated %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
case self::TYPE_INVITE:
$text = null;
if (count($old) === 1
&& count($new) === 1
&& isset($old[$author_phid])) {
// user joined/declined/accepted event themself
$old_status = $old[$author_phid];
$new_status = $new[$author_phid];
if ($old_status !== $new_status) {
switch ($new_status) {
case PhabricatorCalendarEventInvitee::STATUS_INVITED:
$text = pht(
'%s has joined %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
case PhabricatorCalendarEventInvitee::STATUS_ATTENDING:
$text = pht(
'%s is attending %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
case PhabricatorCalendarEventInvitee::STATUS_DECLINED:
case PhabricatorCalendarEventInvitee::STATUS_UNINVITED:
$text = pht(
'%s has declined %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
default:
$text = pht(
'%s has changed their status of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
}
}
} else {
// user changed status for many users
$self_joined = null;
$self_declined = null;
$added = array();
$uninvited = array();
foreach ($new as $phid => $status) {
if ($status == PhabricatorCalendarEventInvitee::STATUS_INVITED
|| $status == PhabricatorCalendarEventInvitee::STATUS_ATTENDING) {
// added users
$added[] = $phid;
} else if (
$status == PhabricatorCalendarEventInvitee::STATUS_DECLINED
|| $status == PhabricatorCalendarEventInvitee::STATUS_UNINVITED) {
$uninvited[] = $phid;
}
}
$count_added = count($added);
$count_uninvited = count($uninvited);
$added_text = null;
$uninvited_text = null;
if ($count_added > 0 && $count_uninvited == 0) {
$added_text = $this->renderHandleList($added);
$text = pht('%s invited %s to %s.',
$this->renderHandleLink($author_phid),
$added_text,
$this->renderHandleLink($object_phid));
} else if ($count_added > 0 && $count_uninvited > 0) {
$added_text = $this->renderHandleList($added);
$uninvited_text = $this->renderHandleList($uninvited);
$text = pht('%s invited %s and uninvited %s to %s.',
$this->renderHandleLink($author_phid),
$added_text,
$uninvited_text,
$this->renderHandleLink($object_phid));
} else if ($count_added == 0 && $count_uninvited > 0) {
$uninvited_text = $this->renderHandleList($uninvited);
$text = pht('%s uninvited %s to %s.',
$this->renderHandleLink($author_phid),
$uninvited_text,
$this->renderHandleLink($object_phid));
} else {
$text = pht('%s updated the invitee list of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
}
return $text;
case self::TYPE_RECURRING:
$text = pht('%s made %s a recurring event.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
return $text;
case self::TYPE_FREQUENCY:
$text = '';
switch ($new['rule']) {
case PhabricatorCalendarEvent::FREQUENCY_DAILY:
$text = pht('%s set %s to repeat daily.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
case PhabricatorCalendarEvent::FREQUENCY_WEEKLY:
$text = pht('%s set %s to repeat weekly.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
case PhabricatorCalendarEvent::FREQUENCY_MONTHLY:
$text = pht('%s set %s to repeat monthly.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
case PhabricatorCalendarEvent::FREQUENCY_YEARLY:
$text = pht('%s set %s to repeat yearly.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
}
return $text;
case self::TYPE_RECURRENCE_END_DATE:
$text = pht('%s set the recurrence end date of %s to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$new);
return $text;
case self::TYPE_INSTANCE_OF_EVENT:
case self::TYPE_SEQUENCE_INDEX:
return pht('Recurring event has been updated.');
}
return parent::getTitleForFeed();
}
public function getColor() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_NAME:
case self::TYPE_START_DATE:
case self::TYPE_END_DATE:
case self::TYPE_DESCRIPTION:
case self::TYPE_CANCEL:
case self::TYPE_INVITE:
return PhabricatorTransactions::COLOR_GREEN;
}
return parent::getColor();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case self::TYPE_DESCRIPTION:
return ($this->getOldValue() !== null);
}
return parent::hasChangeDetails();
}
public function renderChangeDetails(PhabricatorUser $viewer) {
switch ($this->getTransactionType()) {
case self::TYPE_DESCRIPTION:
$old = $this->getOldValue();
$new = $this->getNewValue();
return $this->renderTextCorpusChangeDetails(
$viewer,
$old,
$new);
}
return parent::renderChangeDetails($viewer);
public function getBaseTransactionClass() {
return 'PhabricatorCalendarEventTransactionType';
}
public function getMailTags() {
$tags = array();
switch ($this->getTransactionType()) {
case self::TYPE_NAME:
case self::TYPE_DESCRIPTION:
case self::TYPE_INVITE:
case self::TYPE_ICON:
case PhabricatorCalendarEventNameTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventDescriptionTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE:
$tags[] = self::MAILTAG_CONTENT;
break;
case self::TYPE_START_DATE:
case self::TYPE_END_DATE:
case self::TYPE_CANCEL:
case PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE:
$tags[] = self::MAILTAG_RESCHEDULE;
break;
}

View file

@ -2,7 +2,7 @@
final class AphrontCalendarEventView extends AphrontView {
private $userPHID;
private $hostPHID;
private $name;
private $epochStart;
private $epochEnd;
@ -12,7 +12,27 @@ final class AphrontCalendarEventView extends AphrontView {
private $uri;
private $isAllDay;
private $icon;
private $iconColor;
private $canEdit;
private $isCancelled;
public function setIconColor($icon_color) {
$this->iconColor = $icon_color;
return $this;
}
public function getIconColor() {
return $this->iconColor;
}
public function setIsCancelled($is_cancelled) {
$this->isCancelled = $is_cancelled;
return $this;
}
public function getIsCancelled() {
return $this->isCancelled;
}
public function setURI($uri) {
$this->uri = $uri;
@ -39,13 +59,13 @@ final class AphrontCalendarEventView extends AphrontView {
return $this->viewerIsInvited;
}
public function setUserPHID($user_phid) {
$this->userPHID = $user_phid;
public function setHostPHID($host_phid) {
$this->hostPHID = $host_phid;
return $this;
}
public function getUserPHID() {
return $this->userPHID;
public function getHostPHID() {
return $this->hostPHID;
}
public function setName($name) {

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorCalendarEventAcceptTransaction
extends PhabricatorCalendarEventReplyTransaction {
const TRANSACTIONTYPE = 'calendar.accept';
public function generateNewValue($object, $value) {
return PhabricatorCalendarEventInvitee::STATUS_ATTENDING;
}
public function getTitle() {
return pht(
'%s is attending this event.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s is attending %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,46 @@
<?php
final class PhabricatorCalendarEventAllDayTransaction
extends PhabricatorCalendarEventTransactionType {
const TRANSACTIONTYPE = 'calendar.allday';
public function generateOldValue($object) {
return (int)$object->getIsAllDay();
}
public function generateNewValue($object, $value) {
return (int)$value;
}
public function applyInternalEffects($object, $value) {
$object->setIsAllDay($value);
}
public function getTitle() {
if ($this->getNewValue()) {
return pht(
'%s changed this as an all day event.',
$this->renderAuthor());
} else {
return pht(
'%s conveted this from an all day event.',
$this->renderAuthor());
}
}
public function getTitleForFeed() {
if ($this->getNewValue()) {
return pht(
'%s changed %s to an all day event.',
$this->renderAuthor(),
$this->renderObject());
} else {
return pht(
'%s converted %s from an all day event.',
$this->renderAuthor(),
$this->renderObject());
}
}
}

View file

@ -0,0 +1,46 @@
<?php
final class PhabricatorCalendarEventCancelTransaction
extends PhabricatorCalendarEventTransactionType {
const TRANSACTIONTYPE = 'calendar.cancel';
public function generateOldValue($object) {
return (int)$object->getIsCancelled();
}
public function generateNewValue($object, $value) {
return (int)$value;
}
public function applyInternalEffects($object, $value) {
$object->setIsCancelled($value);
}
public function getTitle() {
if ($this->getNewValue()) {
return pht(
'%s cancelled this event.',
$this->renderAuthor());
} else {
return pht(
'%s reinstated this event.',
$this->renderAuthor());
}
}
public function getTitleForFeed() {
if ($this->getNewValue()) {
return pht(
'%s cancelled %s.',
$this->renderAuthor(),
$this->renderObject());
} else {
return pht(
'%s reinstated %s.',
$this->renderAuthor(),
$this->renderObject());
}
}
}

View file

@ -0,0 +1,27 @@
<?php
abstract class PhabricatorCalendarEventDateTransaction
extends PhabricatorCalendarEventTransactionType {
abstract protected function getInvalidDateMessage();
public function generateNewValue($object, $value) {
return $value->getEpoch();
}
public function validateTransactions($object, array $xactions) {
$errors = array();
foreach ($xactions as $xaction) {
if ($xaction->getNewValue()->isValid()) {
continue;
}
$message = $this->getInvalidDateMessage();
$errors[] = $this->newInvalidError($message, $xaction);
}
return $errors;
}
}

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorCalendarEventDeclineTransaction
extends PhabricatorCalendarEventReplyTransaction {
const TRANSACTIONTYPE = 'calendar.decline';
public function generateNewValue($object, $value) {
return PhabricatorCalendarEventInvitee::STATUS_DECLINED;
}
public function getTitle() {
return pht(
'%s declined this event.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s declined %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,42 @@
<?php
final class PhabricatorCalendarEventDescriptionTransaction
extends PhabricatorCalendarEventTransactionType {
const TRANSACTIONTYPE = 'calendar.description';
public function generateOldValue($object) {
return $object->getDescription();
}
public function applyInternalEffects($object, $value) {
$object->setDescription($value);
}
public function getTitle() {
return pht(
'%s updated the event description.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s updated the event description for %s.',
$this->renderAuthor(),
$this->renderObject());
}
public function hasChangeDetailView() {
return true;
}
public function newChangeDetailView() {
$viewer = $this->getViewer();
return id(new PhabricatorApplicationTransactionTextDiffDetailView())
->setViewer($viewer)
->setOldText($this->getOldValue())
->setNewText($this->getNewValue());
}
}

View file

@ -0,0 +1,47 @@
<?php
final class PhabricatorCalendarEventEndDateTransaction
extends PhabricatorCalendarEventDateTransaction {
const TRANSACTIONTYPE = 'calendar.enddate';
public function generateOldValue($object) {
return $object->getDateTo();
}
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')));
}
public function getTitle() {
return pht(
'%s changed the end date for this event from %s to %s.',
$this->renderAuthor(),
$this->renderOldDate(),
$this->renderNewDate());
}
public function getTitleForFeed() {
return pht(
'%s changed the end date for %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderOldDate(),
$this->renderNewDate());
}
protected function getInvalidDateMessage() {
return pht('End date is invalid.');
}
}

View file

@ -0,0 +1,75 @@
<?php
final class PhabricatorCalendarEventFrequencyTransaction
extends PhabricatorCalendarEventTransactionType {
const TRANSACTIONTYPE = 'calendar.frequency';
public function generateOldValue($object) {
return $object->getFrequencyRule();
}
public function applyInternalEffects($object, $value) {
$object->setRecurrenceFrequency(
array(
'rule' => $value,
));
}
public function getTitle() {
$frequency = $this->getFrequencyRule($this->getNewValue());
switch ($frequency) {
case PhabricatorCalendarEvent::FREQUENCY_DAILY:
return pht(
'%s set this event to repeat daily.',
$this->renderAuthor());
case PhabricatorCalendarEvent::FREQUENCY_WEEKLY:
return pht(
'%s set this event to repeat weekly.',
$this->renderAuthor());
case PhabricatorCalendarEvent::FREQUENCY_MONTHLY:
return pht(
'%s set this event to repeat monthly.',
$this->renderAuthor());
case PhabricatorCalendarEvent::FREQUENCY_YEARLY:
return pht(
'%s set this event to repeat yearly.',
$this->renderAuthor());
}
}
public function getTitleForFeed() {
$frequency = $this->getFrequencyRule($this->getNewValue());
switch ($frequency) {
case PhabricatorCalendarEvent::FREQUENCY_DAILY:
return pht(
'%s set %s to repeat daily.',
$this->renderAuthor(),
$this->renderObject());
case PhabricatorCalendarEvent::FREQUENCY_WEEKLY:
return pht(
'%s set %s to repeat weekly.',
$this->renderAuthor(),
$this->renderObject());
case PhabricatorCalendarEvent::FREQUENCY_MONTHLY:
return pht(
'%s set %s to repeat monthly.',
$this->renderAuthor(),
$this->renderObject());
case PhabricatorCalendarEvent::FREQUENCY_YEARLY:
return pht(
'%s set %s to repeat yearly.',
$this->renderAuthor(),
$this->renderObject());
}
}
private function getFrequencyRule($value) {
if (is_array($value)) {
$value = idx($value, 'rule');
} else {
return $value;
}
}
}

View file

@ -0,0 +1,59 @@
<?php
final class PhabricatorCalendarEventHostTransaction
extends PhabricatorCalendarEventTransactionType {
const TRANSACTIONTYPE = 'calendar.host';
public function generateOldValue($object) {
return $object->getHostPHID();
}
public function applyInternalEffects($object, $value) {
$object->setHostPHID($value);
}
public function getTitle() {
return pht(
'%s changed the host of this event from %s to %s.',
$this->renderAuthor(),
$this->renderOldHandle(),
$this->renderNewHandle());
}
public function getTitleForFeed() {
return pht(
'%s changed the host of %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderOldHandle(),
$this->renderNewHandle());
}
public function validateTransactions($object, array $xactions) {
$errors = array();
foreach ($xactions as $xaction) {
$host_phid = $xaction->getNewValue();
if (!$host_phid) {
$errors[] = $this->newRequiredError(
pht('Event host is required.'));
continue;
}
$user = id(new PhabricatorPeopleQuery())
->setViewer($this->getActor())
->withPHIDs(array($host_phid))
->executeOne();
if (!$user) {
$errors[] = $this->newInvalidError(
pht(
'Host PHID "%s" is not a valid user PHID.',
$host_phid));
}
}
return $errors;
}
}

View file

@ -0,0 +1,44 @@
<?php
final class PhabricatorCalendarEventIconTransaction
extends PhabricatorCalendarEventTransactionType {
const TRANSACTIONTYPE = 'calendar.icon';
public function generateOldValue($object) {
return $object->getIcon();
}
public function applyInternalEffects($object, $value) {
$object->setIcon($value);
}
public function getTitle() {
$old = $this->getIconLabel($this->getOldValue());
$new = $this->getIconLabel($this->getNewValue());
return pht(
'%s changed the event icon from %s to %s.',
$this->renderAuthor(),
$this->renderValue($old),
$this->renderValue($new));
}
public function getTitleForFeed() {
$old = $this->getIconLabel($this->getOldValue());
$new = $this->getIconLabel($this->getNewValue());
return pht(
'%s changed the icon for %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderValue($old),
$this->renderValue($new));
}
private function getIconLabel($icon) {
$set = new PhabricatorCalendarIconSet();
return $set->getIconLabel($icon);
}
}

View file

@ -0,0 +1,183 @@
<?php
final class PhabricatorCalendarEventInviteTransaction
extends PhabricatorCalendarEventTransactionType {
const TRANSACTIONTYPE = 'calendar.invite';
public function generateOldValue($object) {
$status_uninvited = PhabricatorCalendarEventInvitee::STATUS_UNINVITED;
$invitees = $object->getInvitees();
foreach ($invitees as $key => $invitee) {
if ($invitee->getStatus() == $status_uninvited) {
unset($invitees[$key]);
}
}
return array_values(mpull($invitees, 'getInviteePHID'));
}
private function generateChangeMap($object, $new_value) {
$status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED;
$status_uninvited = PhabricatorCalendarEventInvitee::STATUS_UNINVITED;
$status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING;
$old = $this->generateOldValue($object);
$add = array_diff($new_value, $old);
$rem = array_diff($old, $new_value);
$map = array();
foreach ($add as $phid) {
$map[$phid] = $status_invited;
}
foreach ($rem as $phid) {
$map[$phid] = $status_uninvited;
}
// If we're creating this event and the actor is inviting themselves,
// mark them as attending.
if ($this->isNewObject()) {
$acting_phid = $this->getActingAsPHID();
if (isset($map[$acting_phid])) {
$map[$acting_phid] = $status_attending;
}
}
return $map;
}
public function applyExternalEffects($object, $value) {
$map = $this->generateChangeMap($object, $value);
$invitees = $object->getInvitees();
$invitees = mpull($invitees, null, 'getInviteePHID');
foreach ($map as $phid => $status) {
$invitee = idx($invitees, $phid);
if (!$invitee) {
$invitee = id(new PhabricatorCalendarEventInvitee())
->setEventPHID($object->getPHID())
->setInviteePHID($phid)
->setInviterPHID($this->getActingAsPHID());
$invitees[] = $invitee;
}
$invitee->setStatus($status)
->save();
}
$object->attachInvitees($invitees);
}
public function validateTransactions($object, array $xactions) {
$actor = $this->getActor();
$errors = array();
$old = $object->getInvitees();
$old = mpull($old, null, 'getInviteePHID');
foreach ($xactions as $xaction) {
$new = $xaction->getNewValue();
$new = array_fuse($new);
$add = array_diff_key($new, $old);
if (!$add) {
continue;
}
// In the UI, we only allow you to invite mailable objects, but there
// is no definitive marker for "invitable object" today. Just allow
// any valid object to be invited.
$objects = id(new PhabricatorObjectQuery())
->setViewer($actor)
->withPHIDs($add)
->execute();
$objects = mpull($objects, null, 'getPHID');
foreach ($add as $phid) {
if (isset($objects[$phid])) {
continue;
}
$errors[] = $this->newInvalidError(
pht(
'Invitee "%s" identifies an object that does not exist or '.
'which you do not have permission to view.',
$phid),
$xaction);
}
}
return $errors;
}
public function getIcon() {
return 'fa-user-plus';
}
public function getTitle() {
list($add, $rem) = $this->getChanges();
if ($add && !$rem) {
return pht(
'%s invited %s attendee(s): %s.',
$this->renderAuthor(),
phutil_count($add),
$this->renderHandleList($add));
} else if (!$add && $rem) {
return pht(
'%s uninvited %s attendee(s): %s.',
$this->renderAuthor(),
phutil_count($rem),
$this->renderHandleList($rem));
} else {
return pht(
'%s invited %s attendee(s): %s; uninvinted %s attendee(s): %s.',
$this->renderAuthor(),
phutil_count($add),
$this->renderHandleList($add),
phutil_count($rem),
$this->renderHandleList($rem));
}
}
public function getTitleForFeed() {
list($add, $rem) = $this->getChanges();
if ($add && !$rem) {
return pht(
'%s invited %s attendee(s) to %s: %s.',
$this->renderAuthor(),
phutil_count($add),
$this->renderObject(),
$this->renderHandleList($add));
} else if (!$add && $rem) {
return pht(
'%s uninvited %s attendee(s) to %s: %s.',
$this->renderAuthor(),
phutil_count($rem),
$this->renderObject(),
$this->renderHandleList($rem));
} else {
return pht(
'%s updated the invite list for %s, invited %s: %s; '.
'uninvinted %s: %s.',
$this->renderAuthor(),
$this->renderObject(),
phutil_count($add),
$this->renderHandleList($add),
phutil_count($rem),
$this->renderHandleList($rem));
}
}
private function getChanges() {
$old = $this->getOldValue();
$new = $this->getNewValue();
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
return array(array_fuse($add), array_fuse($rem));
}
}

View file

@ -0,0 +1,44 @@
<?php
final class PhabricatorCalendarEventNameTransaction
extends PhabricatorCalendarEventTransactionType {
const TRANSACTIONTYPE = 'calendar.name';
public function generateOldValue($object) {
return $object->getName();
}
public function applyInternalEffects($object, $value) {
$object->setName($value);
}
public function getTitle() {
return pht(
'%s renamed this event from %s to %s.',
$this->renderAuthor(),
$this->renderOldValue(),
$this->renderNewValue());
}
public function getTitleForFeed() {
return pht(
'%s renamed %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderOldValue(),
$this->renderNewValue());
}
public function validateTransactions($object, array $xactions) {
$errors = array();
if ($this->isEmptyTextTransaction($object->getName(), $xactions)) {
$errors[] = $this->newRequiredError(
pht('Events must have a name.'));
}
return $errors;
}
}

View file

@ -0,0 +1,44 @@
<?php
final class PhabricatorCalendarEventRecurringTransaction
extends PhabricatorCalendarEventTransactionType {
const TRANSACTIONTYPE = 'calendar.recurring';
public function generateOldValue($object) {
return (int)$object->getIsRecurring();
}
public function generateNewValue($object, $value) {
return (int)$value;
}
public function applyInternalEffects($object, $value) {
$object->setIsRecurring($value);
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$old = $object->getIsRecurring();
foreach ($xactions as $xaction) {
if ($this->isNewObject()) {
continue;
}
if ($xaction->getNewValue() == $old) {
continue;
}
$errors[] = $this->newInvalidError(
pht(
'An event can only be made recurring when it is created. '.
'You can not convert an existing event into a recurring '.
'event or vice versa.'),
$xaction);
}
return $errors;
}
}

View file

@ -0,0 +1,33 @@
<?php
abstract class PhabricatorCalendarEventReplyTransaction
extends PhabricatorCalendarEventTransactionType {
public function generateOldValue($object) {
$actor_phid = $this->getActingAsPHID();
return $object->getUserInviteStatus($actor_phid);
}
public function applyExternalEffects($object, $value) {
$acting_phid = $this->getActingAsPHID();
$invitees = $object->getInvitees();
$invitees = mpull($invitees, null, 'getInviteePHID');
$invitee = idx($invitees, $acting_phid);
if (!$invitee) {
$invitee = id(new PhabricatorCalendarEventInvitee())
->setEventPHID($object->getPHID())
->setInviteePHID($acting_phid)
->setInviterPHID($acting_phid);
$invitees[$acting_phid] = $invitee;
}
$invitee
->setStatus($value)
->save();
$object->attachInvitees($invitees);
}
}

View file

@ -0,0 +1,47 @@
<?php
final class PhabricatorCalendarEventStartDateTransaction
extends PhabricatorCalendarEventDateTransaction {
const TRANSACTIONTYPE = 'calendar.startdate';
public function generateOldValue($object) {
return $object->getDateFrom();
}
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')));
}
public function getTitle() {
return pht(
'%s changed the start date for this event from %s to %s.',
$this->renderAuthor(),
$this->renderOldDate(),
$this->renderNewDate());
}
public function getTitleForFeed() {
return pht(
'%s changed the start date for %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderOldDate(),
$this->renderNewDate());
}
protected function getInvalidDateMessage() {
return pht('Start date is invalid.');
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class PhabricatorCalendarEventTransactionType
extends PhabricatorModularTransactionType {}

View file

@ -0,0 +1,35 @@
<?php
final class PhabricatorCalendarEventUntilDateTransaction
extends PhabricatorCalendarEventDateTransaction {
const TRANSACTIONTYPE = 'calendar.recurrenceenddate';
public function generateOldValue($object) {
return $object->getRecurrenceEndDate();
}
public function applyInternalEffects($object, $value) {
$object->setRecurrenceEndDate($value);
}
public function getTitle() {
return pht(
'%s changed this event to repeat until %s.',
$this->renderAuthor(),
$this->renderNewDate());
}
public function getTitleForFeed() {
return pht(
'%s changed %s to repeat until %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderNewDate());
}
protected function getInvalidDateMessage() {
return pht('Repeat until date is invalid.');
}
}

View file

@ -314,6 +314,8 @@ final class ConpherenceThreadQuery
$events = array();
if ($participant_phids) {
// TODO: All of this Calendar code is probably extra-broken, but none
// of it is currently reachable in the UI.
$events = id(new PhabricatorCalendarEventQuery())
->setViewer($this->getViewer())
->withInvitedPHIDs($participant_phids)

View file

@ -46,7 +46,7 @@ final class DifferentialInlineCommentEditController
throw new Exception(
pht(
'Changeset ID "%s" is part of diff ID "%s", but that diff '.
'is attached to reivsion "%s", not revision "%s".',
'is attached to revision "%s", not revision "%s".',
$changeset_id,
$diff->getID(),
$diff->getRevisionID(),

View file

@ -342,18 +342,16 @@ final class DifferentialChangesetListView extends AphrontView {
}
$meta['containerID'] = $detail->getID();
$caret = phutil_tag('span', array('class' => 'caret'), '');
return javelin_tag(
'a',
array(
'class' => 'button grey dropdown',
'meta' => $meta,
'href' => idx($meta, 'detailURI', '#'),
'target' => '_blank',
'sigil' => 'differential-view-options',
),
array(pht('View Options'), $caret));
return id(new PHUIButtonView())
->setTag('a')
->setText(pht('View Options'))
->setIcon('fa-bars')
->setColor(PHUIButtonView::GREY)
->setHref(idx($meta, 'detailURI', '#'))
->setMetadata($meta)
->addSigil('differential-view-options');
}
}

View file

@ -35,7 +35,7 @@ final class PhabricatorFileComposeController
$data = $composer->loadBuiltinFileData();
$file = PhabricatorFile::buildFromFileDataOrHash(
$file = PhabricatorFile::newFromFileData(
$data,
array(
'name' => $composer->getBuiltinDisplayName(),

View file

@ -16,6 +16,7 @@ final class PhabricatorFileQuery
private $names;
private $isPartial;
private $needTransforms;
private $builtinKeys;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -47,6 +48,11 @@ final class PhabricatorFileQuery
return $this;
}
public function withBuiltinKeys(array $keys) {
$this->builtinKeys = $keys;
return $this;
}
/**
* Select files which are transformations of some other file. For example,
* you can use this query to find previously generated thumbnails of an image
@ -384,6 +390,13 @@ final class PhabricatorFileQuery
(int)$this->isPartial);
}
if ($this->builtinKeys !== null) {
$where[] = qsprintf(
$conn,
'builtinKey IN (%Ls)',
$this->builtinKeys);
}
return $where;
}

View file

@ -42,6 +42,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
protected $contentHash;
protected $metadata = array();
protected $mailKey;
protected $builtinKey;
protected $storageEngine;
protected $storageFormat;
@ -94,6 +95,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
'isExplicitUpload' => 'bool?',
'mailKey' => 'bytes20',
'isPartial' => 'bool',
'builtinKey' => 'text64?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
@ -116,6 +118,10 @@ final class PhabricatorFile extends PhabricatorFileDAO
'key_partial' => array(
'columns' => array('authorPHID', 'isPartial'),
),
'key_builtin' => array(
'columns' => array('builtinKey'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
@ -1070,19 +1076,11 @@ final class PhabricatorFile extends PhabricatorFileDAO
public static function loadBuiltins(PhabricatorUser $user, array $builtins) {
$builtins = mpull($builtins, null, 'getBuiltinFileKey');
$specs = array();
foreach ($builtins as $key => $buitin) {
$specs[] = array(
'originalPHID' => PhabricatorPHIDConstants::PHID_VOID,
'transform' => $key,
);
}
// NOTE: Anyone is allowed to access builtin files.
$files = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTransforms($specs)
->withBuiltinKeys(array_keys($builtins))
->execute();
$results = array();
@ -1109,12 +1107,21 @@ final class PhabricatorFile extends PhabricatorFileDAO
);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$file = self::newFromFileData($data, $params);
$xform = id(new PhabricatorTransformedFile())
->setOriginalPHID(PhabricatorPHIDConstants::PHID_VOID)
->setTransform($key)
->setTransformedPHID($file->getPHID())
->save();
try {
$file = self::newFromFileData($data, $params);
} catch (AphrontDuplicateKeyQueryException $ex) {
$file = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuiltinKeys(array($key))
->executeOne();
if (!$file) {
throw new Exception(
pht(
'Collided mid-air when generating builtin file "%s", but '.
'then failed to load the object we collided with.',
$key));
}
}
unset($unguarded);
$file->attachObjectPHIDs(array());
@ -1289,6 +1296,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
$builtin = idx($params, 'builtin');
if ($builtin) {
$this->setBuiltinName($builtin);
$this->setBuiltinKey($builtin);
}
$profile = idx($params, 'profile');

View file

@ -7,20 +7,4 @@ final class PhabricatorFileTransactionComment
return new PhabricatorFileTransaction();
}
public function shouldUseMarkupCache($field) {
// Only cache submitted comments.
return ($this->getTransactionPHID() != null);
}
protected function getConfiguration() {
$config = parent::getConfiguration();
$config[self::CONFIG_KEY_SCHEMA] = array(
'key_draft' => array(
'columns' => array('authorPHID', 'transactionPHID'),
'unique' => true,
),
) + $config[self::CONFIG_KEY_SCHEMA];
return $config;
}
}

View file

@ -87,13 +87,83 @@ final class ManiphestTaskDetailController extends ManiphestController {
->addPropertySection(pht('Description'), $description)
->addPropertySection(pht('Details'), $details);
$graph_limit = 100;
$task_graph = id(new ManiphestTaskGraph())
->setViewer($viewer)
->setSeedPHID($task->getPHID())
->setLimit($graph_limit)
->loadGraph();
if (!$task_graph->isEmpty()) {
$graph_table = $task_graph->newGraphTable();
$view->addPropertySection(pht('Task Graph'), $graph_table);
$parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
$subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
$parent_map = $task_graph->getEdges($parent_type);
$subtask_map = $task_graph->getEdges($subtask_type);
$parent_list = idx($parent_map, $task->getPHID(), array());
$subtask_list = idx($subtask_map, $task->getPHID(), array());
$has_parents = (bool)$parent_list;
$has_subtasks = (bool)$subtask_list;
$search_text = pht('Search...');
// First, get a count of direct parent tasks and subtasks. If there
// are too many of these, we just don't draw anything. You can use
// the search button to browse tasks with the search UI instead.
$direct_count = count($parent_list) + count($subtask_list);
if ($direct_count > $graph_limit) {
$message = pht(
'Task graph too large to display (this task is directly connected '.
'to more than %s other tasks). Use %s to explore connected tasks.',
$graph_limit,
phutil_tag('strong', array(), $search_text));
$message = phutil_tag('em', array(), $message);
$graph_table = id(new PHUIPropertyListView())
->addTextContent($message);
} else {
// If there aren't too many direct tasks, but there are too many total
// tasks, we'll only render directly connected tasks.
if ($task_graph->isOverLimit()) {
$task_graph->setRenderOnlyAdjacentNodes(true);
}
$graph_table = $task_graph->newGraphTable();
}
$parents_uri = urisprintf(
'/?subtaskIDs=%d#R',
$task->getID());
$parents_uri = $this->getApplicationURI($parents_uri);
$subtasks_uri = urisprintf(
'/?parentIDs=%d#R',
$task->getID());
$subtasks_uri = $this->getApplicationURI($subtasks_uri);
$dropdown_menu = id(new PhabricatorActionListView())
->setViewer($viewer)
->addAction(
id(new PhabricatorActionView())
->setHref($parents_uri)
->setName(pht('Search Parent Tasks'))
->setDisabled(!$has_parents)
->setIcon('fa-chevron-circle-up'))
->addAction(
id(new PhabricatorActionView())
->setHref($subtasks_uri)
->setName(pht('Search Subtasks'))
->setDisabled(!$has_subtasks)
->setIcon('fa-chevron-circle-down'));
$graph_menu = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-search')
->setText($search_text)
->setDropdownMenu($dropdown_menu);
$graph_header = id(new PHUIHeaderView())
->setHeader(pht('Task Graph'))
->addActionLink($graph_menu);
$view->addPropertySection($graph_header, $graph_table);
}
return $this->newPage()

View file

@ -20,6 +20,10 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $subpriorityMin;
private $subpriorityMax;
private $bridgedObjectPHIDs;
private $hasOpenParents;
private $hasOpenSubtasks;
private $parentTaskIDs;
private $subtaskIDs;
private $fullTextSearch = '';
@ -51,8 +55,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $needSubscriberPHIDs;
private $needProjectPHIDs;
private $blockingTasks;
private $blockedTasks;
public function withAuthors(array $authors) {
$this->authorPHIDs = $authors;
@ -151,32 +153,24 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $this;
}
/**
* True returns tasks that are blocking other tasks only.
* False returns tasks that are not blocking other tasks only.
* Null returns tasks regardless of blocking status.
*/
public function withBlockingTasks($mode) {
$this->blockingTasks = $mode;
public function withOpenSubtasks($value) {
$this->hasOpenSubtasks = $value;
return $this;
}
public function shouldJoinBlockingTasks() {
return $this->blockingTasks !== null;
}
/**
* True returns tasks that are blocked by other tasks only.
* False returns tasks that are not blocked by other tasks only.
* Null returns tasks regardless of blocked by status.
*/
public function withBlockedTasks($mode) {
$this->blockedTasks = $mode;
public function withOpenParents($value) {
$this->hasOpenParents = $value;
return $this;
}
public function shouldJoinBlockedTasks() {
return $this->blockedTasks !== null;
public function withParentTaskIDs(array $ids) {
$this->parentTaskIDs = $ids;
return $this;
}
public function withSubtaskIDs(array $ids) {
$this->subtaskIDs = $ids;
return $this;
}
public function withDateCreatedBefore($date_created_before) {
@ -335,7 +329,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$where = parent::buildWhereClauseParts($conn);
$where[] = $this->buildStatusWhereClause($conn);
$where[] = $this->buildDependenciesWhereClause($conn);
$where[] = $this->buildOwnerWhereClause($conn);
$where[] = $this->buildFullTextWhereClause($conn);
@ -526,71 +519,64 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$fulltext_results);
}
private function buildDependenciesWhereClause(
AphrontDatabaseConnection $conn) {
if (!$this->shouldJoinBlockedTasks() &&
!$this->shouldJoinBlockingTasks()) {
return null;
}
$parts = array();
if ($this->blockingTasks === true) {
$parts[] = qsprintf(
$conn,
'blocking.dst IS NOT NULL AND blockingtask.status IN (%Ls)',
ManiphestTaskStatus::getOpenStatusConstants());
} else if ($this->blockingTasks === false) {
$parts[] = qsprintf(
$conn,
'blocking.dst IS NULL OR blockingtask.status NOT IN (%Ls)',
ManiphestTaskStatus::getOpenStatusConstants());
}
if ($this->blockedTasks === true) {
$parts[] = qsprintf(
$conn,
'blocked.dst IS NOT NULL AND blockedtask.status IN (%Ls)',
ManiphestTaskStatus::getOpenStatusConstants());
} else if ($this->blockedTasks === false) {
$parts[] = qsprintf(
$conn,
'blocked.dst IS NULL OR blockedtask.status NOT IN (%Ls)',
ManiphestTaskStatus::getOpenStatusConstants());
}
return '('.implode(') OR (', $parts).')';
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) {
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$open_statuses = ManiphestTaskStatus::getOpenStatusConstants();
$edge_table = PhabricatorEdgeConfig::TABLE_NAME_EDGE;
$task_table = $this->newResultObject()->getTableName();
$parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
$subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
$joins = array();
if ($this->hasOpenParents !== null) {
if ($this->hasOpenParents) {
$join_type = 'JOIN';
} else {
$join_type = 'LEFT JOIN';
}
if ($this->shouldJoinBlockingTasks()) {
$joins[] = qsprintf(
$conn_r,
'LEFT JOIN %T blocking ON blocking.src = task.phid '.
'AND blocking.type = %d '.
'LEFT JOIN %T blockingtask ON blocking.dst = blockingtask.phid',
$conn,
'%Q %T e_parent
ON e_parent.src = task.phid
AND e_parent.type = %d
%Q %T parent
ON e_parent.dst = parent.phid
AND parent.status IN (%Ls)',
$join_type,
$edge_table,
ManiphestTaskDependedOnByTaskEdgeType::EDGECONST,
id(new ManiphestTask())->getTableName());
$parent_type,
$join_type,
$task_table,
$open_statuses);
}
if ($this->shouldJoinBlockedTasks()) {
if ($this->hasOpenSubtasks !== null) {
if ($this->hasOpenSubtasks) {
$join_type = 'JOIN';
} else {
$join_type = 'LEFT JOIN';
}
$joins[] = qsprintf(
$conn_r,
'LEFT JOIN %T blocked ON blocked.src = task.phid '.
'AND blocked.type = %d '.
'LEFT JOIN %T blockedtask ON blocked.dst = blockedtask.phid',
$conn,
'%Q %T e_subtask
ON e_subtask.src = task.phid
AND e_subtask.type = %d
%Q %T subtask
ON e_subtask.dst = subtask.phid
AND subtask.status IN (%Ls)',
$join_type,
$edge_table,
ManiphestTaskDependsOnTaskEdgeType::EDGECONST,
id(new ManiphestTask())->getTableName());
$subtask_type,
$join_type,
$task_table,
$open_statuses);
}
if ($this->subscriberPHIDs !== null) {
$joins[] = qsprintf(
$conn_r,
$conn,
'JOIN %T e_ccs ON e_ccs.src = task.phid '.
'AND e_ccs.type = %s '.
'AND e_ccs.dst in (%Ls)',
@ -604,7 +590,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$ignore_group_phids = $this->getIgnoreGroupedProjectPHIDs();
if ($ignore_group_phids) {
$joins[] = qsprintf(
$conn_r,
$conn,
'LEFT JOIN %T projectGroup ON task.phid = projectGroup.src
AND projectGroup.type = %d
AND projectGroup.dst NOT IN (%Ls)',
@ -613,29 +599,62 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$ignore_group_phids);
} else {
$joins[] = qsprintf(
$conn_r,
$conn,
'LEFT JOIN %T projectGroup ON task.phid = projectGroup.src
AND projectGroup.type = %d',
$edge_table,
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
}
$joins[] = qsprintf(
$conn_r,
$conn,
'LEFT JOIN %T projectGroupName
ON projectGroup.dst = projectGroupName.indexedObjectPHID',
id(new ManiphestNameIndex())->getTableName());
break;
}
$joins[] = parent::buildJoinClauseParts($conn_r);
if ($this->parentTaskIDs !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %T e_has_parent
ON e_has_parent.src = task.phid
AND e_has_parent.type = %d
JOIN %T has_parent
ON e_has_parent.dst = has_parent.phid
AND has_parent.id IN (%Ld)',
$edge_table,
$parent_type,
$task_table,
$this->parentTaskIDs);
}
if ($this->subtaskIDs !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %T e_has_subtask
ON e_has_subtask.src = task.phid
AND e_has_subtask.type = %d
JOIN %T has_subtask
ON e_has_subtask.dst = has_subtask.phid
AND has_subtask.id IN (%Ld)',
$edge_table,
$subtask_type,
$task_table,
$this->subtaskIDs);
}
$joins[] = parent::buildJoinClauseParts($conn);
return $joins;
}
protected function buildGroupClause(AphrontDatabaseConnection $conn_r) {
$joined_multiple_rows = $this->shouldJoinBlockingTasks() ||
$this->shouldJoinBlockedTasks() ||
($this->shouldGroupQueryResultRows());
$joined_multiple_rows =
($this->hasOpenParents !== null) ||
($this->hasOpenSubtasks !== null) ||
($this->parentTaskIDs !== null) ||
($this->subtaskIDs !== null) ||
$this->shouldGroupQueryResultRows();
$joined_project_name = ($this->groupBy == self::GROUP_PROJECT);
@ -652,6 +671,30 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
}
}
protected function buildHavingClauseParts(AphrontDatabaseConnection $conn) {
$having = parent::buildHavingClauseParts($conn);
if ($this->hasOpenParents !== null) {
if (!$this->hasOpenParents) {
$having[] = qsprintf(
$conn,
'COUNT(parent.phid) = 0');
}
}
if ($this->hasOpenSubtasks !== null) {
if (!$this->hasOpenSubtasks) {
$having[] = qsprintf(
$conn,
'COUNT(subtask.phid) = 0');
}
}
return $having;
}
/**
* Return project PHIDs which we should ignore when grouping tasks by
* project. For example, if a user issues a query like:

View file

@ -77,19 +77,29 @@ final class ManiphestTaskSearchEngine
->setLabel(pht('Contains Words'))
->setKey('fulltext'),
id(new PhabricatorSearchThreeStateField())
->setLabel(pht('Blocking'))
->setKey('blocking')
->setLabel(pht('Open Parents'))
->setKey('hasParents')
->setAliases(array('blocking'))
->setOptions(
pht('(Show All)'),
pht('Show Only Tasks Blocking Other Tasks'),
pht('Hide Tasks Blocking Other Tasks')),
pht('Show Only Tasks With Open Parents'),
pht('Show Only Tasks Without Open Parents')),
id(new PhabricatorSearchThreeStateField())
->setLabel(pht('Blocked'))
->setKey('blocked')
->setLabel(pht('Open Subtasks'))
->setKey('hasSubtasks')
->setAliases(array('blocked'))
->setOptions(
pht('(Show All)'),
pht('Show Only Task Blocked By Other Tasks'),
pht('Hide Tasks Blocked By Other Tasks')),
pht('Show Only Tasks With Open Subtasks'),
pht('Show Only Tasks Without Open Subtasks')),
id(new PhabricatorIDsSearchField())
->setLabel(pht('Parent IDs'))
->setKey('parentIDs')
->setAliases(array('parentID')),
id(new PhabricatorIDsSearchField())
->setLabel(pht('Subtask IDs'))
->setKey('subtaskIDs')
->setAliases(array('subtaskID')),
id(new PhabricatorSearchSelectField())
->setLabel(pht('Group By'))
->setKey('group')
@ -121,8 +131,10 @@ final class ManiphestTaskSearchEngine
'statuses',
'priorities',
'fulltext',
'blocking',
'blocked',
'hasParents',
'hasSubtasks',
'parentIDs',
'subtaskIDs',
'group',
'order',
'ids',
@ -182,18 +194,26 @@ final class ManiphestTaskSearchEngine
$query->withDateModifiedBefore($map['modifiedEnd']);
}
if ($map['blocking'] !== null) {
$query->withBlockingTasks($map['blocking']);
if ($map['hasParents'] !== null) {
$query->withOpenParents($map['hasParents']);
}
if ($map['blocked'] !== null) {
$query->withBlockedTasks($map['blocked']);
if ($map['hasSubtasks'] !== null) {
$query->withOpenSubtasks($map['hasSubtasks']);
}
if (strlen($map['fulltext'])) {
$query->withFullTextSearch($map['fulltext']);
}
if ($map['parentIDs']) {
$query->withParentTaskIDs($map['parentIDs']);
}
if ($map['subtaskIDs']) {
$query->withSubtaskIDs($map['subtaskIDs']);
}
$group = idx($map, 'group');
$group = idx($this->getGroupValues(), $group);
if ($group) {

View file

@ -0,0 +1,85 @@
<?php
final class PhabricatorPackagesApplication extends PhabricatorApplication {
public function getName() {
return pht('Packages');
}
public function getShortDescription() {
return pht('Publish Software');
}
public function getFlavorText() {
return pht('Applications and Extensions');
}
public function getBaseURI() {
return '/packages/';
}
public function getIcon() {
return 'fa-gift';
}
public function isPrototype() {
return true;
}
protected function getCustomCapabilities() {
return array(
PhabricatorPackagesCreatePublisherCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
PhabricatorPackagesPublisherDefaultEditCapability::CAPABILITY => array(
'caption' => pht('Default edit policy for newly created publishers.'),
'template' => PhabricatorPackagesPublisherPHIDType::TYPECONST,
'default' => PhabricatorPolicies::POLICY_NOONE,
),
PhabricatorPackagesPackageDefaultViewCapability::CAPABILITY => array(
'caption' => pht('Default edit policy for newly created packages.'),
'template' => PhabricatorPackagesPackagePHIDType::TYPECONST,
),
PhabricatorPackagesPackageDefaultEditCapability::CAPABILITY => array(
'caption' => pht('Default view policy for newly created packages.'),
'template' => PhabricatorPackagesPackagePHIDType::TYPECONST,
'default' => PhabricatorPolicies::POLICY_NOONE,
),
);
}
public function getRoutes() {
return array(
'/package/' => array(
'(?P<publisherKey>[^/]+)/' => array(
'' => 'PhabricatorPackagesPublisherViewController',
'(?P<packageKey>[^/]+)/' => array(
'' => 'PhabricatorPackagesPackageViewController',
'(?P<versionKey>[^/]+)/' =>
'PhabricatorPackagesVersionViewController',
),
),
),
'/packages/' => array(
'publisher/' => array(
$this->getQueryRoutePattern() =>
'PhabricatorPackagesPublisherListController',
$this->getEditRoutePattern('edit/') =>
'PhabricatorPackagesPublisherEditController',
),
'package/' => array(
$this->getQueryRoutePattern() =>
'PhabricatorPackagesPackageListController',
$this->getEditRoutePattern('edit/') =>
'PhabricatorPackagesPackageEditController',
),
'version/' => array(
$this->getQueryRoutePattern() =>
'PhabricatorPackagesVersionListController',
$this->getEditRoutePattern('edit/') =>
'PhabricatorPackagesVersionEditController',
),
),
);
}
}

View file

@ -0,0 +1,16 @@
<?php
final class PhabricatorPackagesCreatePublisherCapability
extends PhabricatorPolicyCapability {
const CAPABILITY = 'packages.publisher.create';
public function getCapabilityName() {
return pht('Can Create Publishers');
}
public function describeCapabilityRejection() {
return pht('You do not have permission to create publishers.');
}
}

View file

@ -0,0 +1,12 @@
<?php
final class PhabricatorPackagesPackageDefaultEditCapability
extends PhabricatorPolicyCapability {
const CAPABILITY = 'packages.package.default.edit';
public function getCapabilityName() {
return pht('Default Package Edit Policy');
}
}

View file

@ -0,0 +1,16 @@
<?php
final class PhabricatorPackagesPackageDefaultViewCapability
extends PhabricatorPolicyCapability {
const CAPABILITY = 'packages.package.default.view';
public function getCapabilityName() {
return pht('Default Package View Policy');
}
public function shouldAllowPublicPolicySetting() {
return true;
}
}

View file

@ -0,0 +1,12 @@
<?php
final class PhabricatorPackagesPublisherDefaultEditCapability
extends PhabricatorPolicyCapability {
const CAPABILITY = 'packages.publisher.default.edit';
public function getCapabilityName() {
return pht('Default Publisher Edit Policy');
}
}

View file

@ -0,0 +1,19 @@
<?php
final class PhabricatorPackagesPackageEditConduitAPIMethod
extends PhabricatorEditEngineAPIMethod {
public function getAPIMethodName() {
return 'packages.package.edit';
}
public function newEditEngine() {
return new PhabricatorPackagesPackageEditEngine();
}
public function getMethodSummary() {
return pht(
'Apply transactions to create a new package or edit an existing one.');
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorPackagesPackageSearchConduitAPIMethod
extends PhabricatorSearchEngineAPIMethod {
public function getAPIMethodName() {
return 'packages.package.search';
}
public function newSearchEngine() {
return new PhabricatorPackagesPackageSearchEngine();
}
public function getMethodSummary() {
return pht('Read information about packages.');
}
}

View file

@ -0,0 +1,19 @@
<?php
final class PhabricatorPackagesPublisherEditConduitAPIMethod
extends PhabricatorEditEngineAPIMethod {
public function getAPIMethodName() {
return 'packages.publisher.edit';
}
public function newEditEngine() {
return new PhabricatorPackagesPublisherEditEngine();
}
public function getMethodSummary() {
return pht(
'Apply transactions to create a new publisher or edit an existing one.');
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorPackagesPublisherSearchConduitAPIMethod
extends PhabricatorSearchEngineAPIMethod {
public function getAPIMethodName() {
return 'packages.publisher.search';
}
public function newSearchEngine() {
return new PhabricatorPackagesPublisherSearchEngine();
}
public function getMethodSummary() {
return pht('Read information about publishers.');
}
}

View file

@ -0,0 +1,19 @@
<?php
final class PhabricatorPackagesVersionEditConduitAPIMethod
extends PhabricatorEditEngineAPIMethod {
public function getAPIMethodName() {
return 'packages.version.edit';
}
public function newEditEngine() {
return new PhabricatorPackagesVersionEditEngine();
}
public function getMethodSummary() {
return pht(
'Apply transactions to create a new version or edit an existing one.');
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorPackagesVersionSearchConduitAPIMethod
extends PhabricatorSearchEngineAPIMethod {
public function getAPIMethodName() {
return 'packages.version.search';
}
public function newSearchEngine() {
return new PhabricatorPackagesVersionSearchEngine();
}
public function getMethodSummary() {
return pht('Read information about versions.');
}
}

View file

@ -0,0 +1,3 @@
<?php
abstract class PhabricatorPackagesController extends PhabricatorController {}

View file

@ -0,0 +1,4 @@
<?php
abstract class PhabricatorPackagesPackageController
extends PhabricatorPackagesController {}

View file

@ -0,0 +1,12 @@
<?php
final class PhabricatorPackagesPackageEditController
extends PhabricatorPackagesPackageController {
public function handleRequest(AphrontRequest $request) {
return id(new PhabricatorPackagesPackageEditEngine())
->setController($this)
->buildResponse();
}
}

View file

@ -0,0 +1,26 @@
<?php
final class PhabricatorPackagesPackageListController
extends PhabricatorPackagesPackageController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
return id(new PhabricatorPackagesPackageSearchEngine())
->setController($this)
->buildResponse();
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
id(new PhabricatorPackagesPackageEditEngine())
->setViewer($this->getViewer())
->addActionToCrumbs($crumbs);
return $crumbs;
}
}

View file

@ -0,0 +1,129 @@
<?php
final class PhabricatorPackagesPackageViewController
extends PhabricatorPackagesPackageController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$publisher_key = $request->getURIData('publisherKey');
$package_key = $request->getURIData('packageKey');
$full_key = $publisher_key.'/'.$package_key;
$package = id(new PhabricatorPackagesPackageQuery())
->setViewer($viewer)
->withFullKeys(array($full_key))
->executeOne();
if (!$package) {
return new Aphront404Response();
}
$publisher = $package->getPublisher();
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($publisher->getName(), $publisher->getURI())
->addTextCrumb($package->getName())
->setBorder(true);
$header = $this->buildHeaderView($package);
$curtain = $this->buildCurtain($package);
$versions_view = $this->buildVersionsView($package);
$timeline = $this->buildTransactionTimeline(
$package,
new PhabricatorPackagesPackageTransactionQuery());
$timeline->setShouldTerminate(true);
$package_view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(
array(
$versions_view,
$timeline,
));
return $this->newPage()
->setCrumbs($crumbs)
->setPageObjectPHIDs(
array(
$package->getPHID(),
))
->appendChild($package_view);
}
private function buildHeaderView(PhabricatorPackagesPackage $package) {
$viewer = $this->getViewer();
$name = $package->getName();
return id(new PHUIHeaderView())
->setViewer($viewer)
->setHeader($name)
->setPolicyObject($package)
->setHeaderIcon('fa-gift');
}
private function buildCurtain(PhabricatorPackagesPackage $package) {
$viewer = $this->getViewer();
$curtain = $this->newCurtainView($package);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$package,
PhabricatorPolicyCapability::CAN_EDIT);
$id = $package->getID();
$edit_uri = $this->getApplicationURI("package/edit/{$id}/");
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Package'))
->setIcon('fa-pencil')
->setDisabled(!$can_edit)
->setHref($edit_uri));
return $curtain;
}
private function buildVersionsView(PhabricatorPackagesPackage $package) {
$viewer = $this->getViewer();
$versions = id(new PhabricatorPackagesVersionQuery())
->setViewer($viewer)
->withPackagePHIDs(array($package->getPHID()))
->setLimit(25)
->execute();
$versions_list = id(new PhabricatorPackagesVersionListView())
->setViewer($viewer)
->setVersions($versions);
$all_href = urisprintf(
'version/?package=%s#R',
$package->getPHID());
$all_href = $this->getApplicationURI($all_href);
$view_all = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-search')
->setText(pht('View All'))
->setHref($all_href);
$header = id(new PHUIHeaderView())
->setHeader(pht('Versions'))
->addActionLink($view_all);
$versions_view = id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setObjectList($versions_list);
return $versions_view;
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class PhabricatorPackagesPublisherController
extends PhabricatorPackagesController {}

View file

@ -0,0 +1,12 @@
<?php
final class PhabricatorPackagesPublisherEditController
extends PhabricatorPackagesPublisherController {
public function handleRequest(AphrontRequest $request) {
return id(new PhabricatorPackagesPublisherEditEngine())
->setController($this)
->buildResponse();
}
}

View file

@ -0,0 +1,26 @@
<?php
final class PhabricatorPackagesPublisherListController
extends PhabricatorPackagesPublisherController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
return id(new PhabricatorPackagesPublisherSearchEngine())
->setController($this)
->buildResponse();
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
id(new PhabricatorPackagesPublisherEditEngine())
->setViewer($this->getViewer())
->addActionToCrumbs($crumbs);
return $crumbs;
}
}

View file

@ -0,0 +1,127 @@
<?php
final class PhabricatorPackagesPublisherViewController
extends PhabricatorPackagesPublisherController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$publisher_key = $request->getURIData('publisherKey');
$publisher = id(new PhabricatorPackagesPublisherQuery())
->setViewer($viewer)
->withPublisherKeys(array($publisher_key))
->executeOne();
if (!$publisher) {
return new Aphront404Response();
}
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(
pht('Publishers'),
$this->getApplicationURI('publisher/'))
->addTextCrumb($publisher->getName())
->setBorder(true);
$header = $this->buildHeaderView($publisher);
$curtain = $this->buildCurtain($publisher);
$packages_view = $this->buildPackagesView($publisher);
$timeline = $this->buildTransactionTimeline(
$publisher,
new PhabricatorPackagesPublisherTransactionQuery());
$timeline->setShouldTerminate(true);
$publisher_view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(
array(
$packages_view,
$timeline,
));
return $this->newPage()
->setCrumbs($crumbs)
->setPageObjectPHIDs(
array(
$publisher->getPHID(),
))
->appendChild($publisher_view);
}
private function buildHeaderView(PhabricatorPackagesPublisher $publisher) {
$viewer = $this->getViewer();
$name = $publisher->getName();
return id(new PHUIHeaderView())
->setViewer($viewer)
->setHeader($name)
->setPolicyObject($publisher)
->setHeaderIcon('fa-paw');
}
private function buildCurtain(PhabricatorPackagesPublisher $publisher) {
$viewer = $this->getViewer();
$curtain = $this->newCurtainView($publisher);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$publisher,
PhabricatorPolicyCapability::CAN_EDIT);
$id = $publisher->getID();
$edit_uri = $this->getApplicationURI("publisher/edit/{$id}/");
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Publisher'))
->setIcon('fa-pencil')
->setDisabled(!$can_edit)
->setHref($edit_uri));
return $curtain;
}
private function buildPackagesView(PhabricatorPackagesPublisher $publisher) {
$viewer = $this->getViewer();
$packages = id(new PhabricatorPackagesPackageQuery())
->setViewer($viewer)
->withPublisherPHIDs(array($publisher->getPHID()))
->setLimit(25)
->execute();
$packages_list = id(new PhabricatorPackagesPackageListView())
->setViewer($viewer)
->setPackages($packages);
$all_href = urisprintf(
'package/?publisher=%s#R',
$publisher->getPHID());
$all_href = $this->getApplicationURI($all_href);
$view_all = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-search')
->setText(pht('View All'))
->setHref($all_href);
$header = id(new PHUIHeaderView())
->setHeader(pht('Packages'))
->addActionLink($view_all);
$packages_view = id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setObjectList($packages_list);
return $packages_view;
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class PhabricatorPackagesVersionController
extends PhabricatorPackagesController {}

View file

@ -0,0 +1,12 @@
<?php
final class PhabricatorPackagesVersionEditController
extends PhabricatorPackagesVersionController {
public function handleRequest(AphrontRequest $request) {
return id(new PhabricatorPackagesVersionEditEngine())
->setController($this)
->buildResponse();
}
}

View file

@ -0,0 +1,26 @@
<?php
final class PhabricatorPackagesVersionListController
extends PhabricatorPackagesVersionController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
return id(new PhabricatorPackagesVersionSearchEngine())
->setController($this)
->buildResponse();
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
id(new PhabricatorPackagesVersionEditEngine())
->setViewer($this->getViewer())
->addActionToCrumbs($crumbs);
return $crumbs;
}
}

View file

@ -0,0 +1,92 @@
<?php
final class PhabricatorPackagesVersionViewController
extends PhabricatorPackagesVersionController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$publisher_key = $request->getURIData('publisherKey');
$package_key = $request->getURIData('packageKey');
$full_key = $publisher_key.'/'.$package_key;
$version_key = $request->getURIData('versionKey');
$version = id(new PhabricatorPackagesVersionQuery())
->setViewer($viewer)
->withFullKeys(array($full_key))
->withNames(array($version_key))
->executeOne();
if (!$version) {
return new Aphront404Response();
}
$package = $version->getPackage();
$publisher = $package->getPublisher();
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($publisher->getName(), $publisher->getURI())
->addTextCrumb($package->getName(), $package->getURI())
->addTextCrumb($version->getName())
->setBorder(true);
$header = $this->buildHeaderView($version);
$curtain = $this->buildCurtain($version);
$timeline = $this->buildTransactionTimeline(
$version,
new PhabricatorPackagesVersionTransactionQuery());
$timeline->setShouldTerminate(true);
$version_view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn($timeline);
return $this->newPage()
->setCrumbs($crumbs)
->setPageObjectPHIDs(
array(
$version->getPHID(),
))
->appendChild($version_view);
}
private function buildHeaderView(PhabricatorPackagesVersion $version) {
$viewer = $this->getViewer();
$name = $version->getName();
return id(new PHUIHeaderView())
->setViewer($viewer)
->setHeader($name)
->setPolicyObject($version)
->setHeaderIcon('fa-tag');
}
private function buildCurtain(PhabricatorPackagesVersion $version) {
$viewer = $this->getViewer();
$curtain = $this->newCurtainView($version);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$version,
PhabricatorPolicyCapability::CAN_EDIT);
$id = $version->getID();
$edit_uri = $this->getApplicationURI("version/edit/{$id}/");
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Version'))
->setIcon('fa-pencil')
->setDisabled(!$can_edit)
->setHref($edit_uri));
return $curtain;
}
}

View file

@ -0,0 +1,14 @@
<?php
abstract class PhabricatorPackagesEditEngine
extends PhabricatorEditEngine {
public function isEngineConfigurable() {
return false;
}
public function getEngineApplicationClass() {
return 'PhabricatorPackagesApplication';
}
}

Some files were not shown because too many files have changed in this diff Show more