diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 5763349c0e..33f5f5af12 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -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', diff --git a/resources/sql/autopatches/20150506.calendarunnamedevents.1.php b/resources/sql/autopatches/20150506.calendarunnamedevents.1.php index 00512de9ed..89a2650c73 100644 --- a/resources/sql/autopatches/20150506.calendarunnamedevents.1.php +++ b/resources/sql/autopatches/20150506.calendarunnamedevents.1.php @@ -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) { diff --git a/resources/sql/autopatches/20160707.calendar.01.stub.sql b/resources/sql/autopatches/20160707.calendar.01.stub.sql new file mode 100644 index 0000000000..b872f17eeb --- /dev/null +++ b/resources/sql/autopatches/20160707.calendar.01.stub.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD isStub BOOL NOT NULL; diff --git a/resources/sql/autopatches/20160711.files.01.builtin.sql b/resources/sql/autopatches/20160711.files.01.builtin.sql new file mode 100644 index 0000000000..d8849ec053 --- /dev/null +++ b/resources/sql/autopatches/20160711.files.01.builtin.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_file.file + ADD builtinKey VARCHAR(64) COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160711.files.02.builtinkey.sql b/resources/sql/autopatches/20160711.files.02.builtinkey.sql new file mode 100644 index 0000000000..3551f6c3cd --- /dev/null +++ b/resources/sql/autopatches/20160711.files.02.builtinkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_file.file + ADD UNIQUE KEY `key_builtin` (builtinKey); diff --git a/resources/sql/autopatches/20160713.event.01.host.sql b/resources/sql/autopatches/20160713.event.01.host.sql new file mode 100644 index 0000000000..d1a6dd643b --- /dev/null +++ b/resources/sql/autopatches/20160713.event.01.host.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + CHANGE userPHID hostPHID VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20160715.event.01.alldayfrom.sql b/resources/sql/autopatches/20160715.event.01.alldayfrom.sql new file mode 100644 index 0000000000..269345b3d9 --- /dev/null +++ b/resources/sql/autopatches/20160715.event.01.alldayfrom.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD allDayDateFrom INT UNSIGNED NOT NULL; diff --git a/resources/sql/autopatches/20160715.event.02.alldayto.sql b/resources/sql/autopatches/20160715.event.02.alldayto.sql new file mode 100644 index 0000000000..7038274487 --- /dev/null +++ b/resources/sql/autopatches/20160715.event.02.alldayto.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD allDayDateTo INT UNSIGNED NOT NULL; diff --git a/resources/sql/autopatches/20160715.event.03.allday.php b/resources/sql/autopatches/20160715.event.03.allday.php new file mode 100644 index 0000000000..8bc3ffe568 --- /dev/null +++ b/resources/sql/autopatches/20160715.event.03.allday.php @@ -0,0 +1,52 @@ +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()); +} diff --git a/resources/sql/autopatches/20160720.calendar.invitetxn.php b/resources/sql/autopatches/20160720.calendar.invitetxn.php new file mode 100644 index 0000000000..1d9ade6e67 --- /dev/null +++ b/resources/sql/autopatches/20160720.calendar.invitetxn.php @@ -0,0 +1,37 @@ +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"; diff --git a/resources/sql/autopatches/20160721.pack.01.pub.sql b/resources/sql/autopatches/20160721.pack.01.pub.sql new file mode 100644 index 0000000000..b123740920 --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.01.pub.sql @@ -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}; diff --git a/resources/sql/autopatches/20160721.pack.02.pubxaction.sql b/resources/sql/autopatches/20160721.pack.02.pubxaction.sql new file mode 100644 index 0000000000..f42f5ba742 --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.02.pubxaction.sql @@ -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}; diff --git a/resources/sql/autopatches/20160721.pack.03.edge.sql b/resources/sql/autopatches/20160721.pack.03.edge.sql new file mode 100644 index 0000000000..d735df50a3 --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.03.edge.sql @@ -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}; diff --git a/resources/sql/autopatches/20160721.pack.04.pkg.sql b/resources/sql/autopatches/20160721.pack.04.pkg.sql new file mode 100644 index 0000000000..c5f427f1c0 --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.04.pkg.sql @@ -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}; diff --git a/resources/sql/autopatches/20160721.pack.05.pkgxaction.sql b/resources/sql/autopatches/20160721.pack.05.pkgxaction.sql new file mode 100644 index 0000000000..7fd82569de --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.05.pkgxaction.sql @@ -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}; diff --git a/resources/sql/autopatches/20160721.pack.06.version.sql b/resources/sql/autopatches/20160721.pack.06.version.sql new file mode 100644 index 0000000000..8ca2870f78 --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.06.version.sql @@ -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}; diff --git a/resources/sql/autopatches/20160721.pack.07.versionxaction.sql b/resources/sql/autopatches/20160721.pack.07.versionxaction.sql new file mode 100644 index 0000000000..706460b025 --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.07.versionxaction.sql @@ -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}; diff --git a/resources/sql/autopatches/20160722.pack.01.pubngrams.sql b/resources/sql/autopatches/20160722.pack.01.pubngrams.sql new file mode 100644 index 0000000000..956ec58e8b --- /dev/null +++ b/resources/sql/autopatches/20160722.pack.01.pubngrams.sql @@ -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}; diff --git a/resources/sql/autopatches/20160722.pack.02.pkgngrams.sql b/resources/sql/autopatches/20160722.pack.02.pkgngrams.sql new file mode 100644 index 0000000000..514482539a --- /dev/null +++ b/resources/sql/autopatches/20160722.pack.02.pkgngrams.sql @@ -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}; diff --git a/resources/sql/autopatches/20160722.pack.03.versionngrams.sql b/resources/sql/autopatches/20160722.pack.03.versionngrams.sql new file mode 100644 index 0000000000..a5f85f546b --- /dev/null +++ b/resources/sql/autopatches/20160722.pack.03.versionngrams.sql @@ -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}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 791c4f8e5e..1ac5528904 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php b/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php index f1932bd872..1b74d8f736 100644 --- a/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php +++ b/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php @@ -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() { diff --git a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php index b5124ab3ec..8875b960e2 100644 --- a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php +++ b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php @@ -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())) diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index 71358366df..16e6daf3bf 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -40,25 +40,21 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { public function getRoutes() { return array( - '/E(?P[1-9]\d*)(?:/(?P\d+))?' + '/E(?P[1-9]\d*)(?:/(?P\d+)/)?' => 'PhabricatorCalendarEventViewController', '/calendar/' => array( '(?:query/(?P[^/]+)/(?:(?P\d+)/'. '(?P\d+)/)?(?:(?P\d+)/)?)?' => 'PhabricatorCalendarEventListController', 'event/' => array( - 'create/' - => 'PhabricatorCalendarEventEditController', - 'edit/(?P[1-9]\d*)/(?:(?P\d+)/)?' + $this->getEditRoutePattern('edit/') => 'PhabricatorCalendarEventEditController', 'drag/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventDragController', - 'cancel/(?P[1-9]\d*)/(?:(?P\d+)/)?' + 'cancel/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventCancelController', '(?Pjoin|decline|accept)/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventJoinController', - 'comment/(?P[1-9]\d*)/(?:(?P\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, + ), + ); + } + } diff --git a/src/applications/calendar/capability/PhabricatorCalendarEventDefaultEditCapability.php b/src/applications/calendar/capability/PhabricatorCalendarEventDefaultEditCapability.php new file mode 100644 index 0000000000..ae8e4f8980 --- /dev/null +++ b/src/applications/calendar/capability/PhabricatorCalendarEventDefaultEditCapability.php @@ -0,0 +1,12 @@ +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; } diff --git a/src/applications/calendar/conduit/PhabricatorCalendarEventEditConduitAPIMethod.php b/src/applications/calendar/conduit/PhabricatorCalendarEventEditConduitAPIMethod.php new file mode 100644 index 0000000000..4f9e50f19a --- /dev/null +++ b/src/applications/calendar/conduit/PhabricatorCalendarEventEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +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; - } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php index 81736436a0..63ce289970 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php @@ -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'); } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventCommentController.php b/src/applications/calendar/controller/PhabricatorCalendarEventCommentController.php deleted file mode 100644 index 876712a455..0000000000 --- a/src/applications/calendar/controller/PhabricatorCalendarEventCommentController.php +++ /dev/null @@ -1,81 +0,0 @@ -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); - } - } - -} diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php b/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php index 6f88aae0c7..ddb11dee5b 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php @@ -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) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 421084ceaf..b5beea9019 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -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(); } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php index a7f41c4a0c..9b58d39dec 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php @@ -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'); diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php index 519841d1f3..a2915b0be9 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php @@ -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; } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 98d3a2fe74..9d1c5b6349 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -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); + } + } diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php new file mode 100644 index 0000000000..5fa6e95732 --- /dev/null +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php @@ -0,0 +1,256 @@ +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; + } +} diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index dd5b361f71..2b457418d0 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -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(); diff --git a/src/applications/calendar/phid/PhabricatorCalendarEventPHIDType.php b/src/applications/calendar/phid/PhabricatorCalendarEventPHIDType.php index d37be2ef32..90beff28f9 100644 --- a/src/applications/calendar/phid/PhabricatorCalendarEventPHIDType.php +++ b/src/applications/calendar/phid/PhabricatorCalendarEventPHIDType.php @@ -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); } } diff --git a/src/applications/calendar/policyrule/PhabricatorCalendarEventHostPolicyRule.php b/src/applications/calendar/policyrule/PhabricatorCalendarEventHostPolicyRule.php new file mode 100644 index 0000000000..98906b8ce2 --- /dev/null +++ b/src/applications/calendar/policyrule/PhabricatorCalendarEventHostPolicyRule.php @@ -0,0 +1,43 @@ +getPHID(); + if (!$viewer_phid) { + return false; + } + + return ($object->getHostPHID() == $viewer_phid); + } + + public function getValueControlType() { + return self::CONTROL_TYPE_NONE; + } + +} diff --git a/src/applications/calendar/policyrule/PhabricatorCalendarEventInviteesPolicyRule.php b/src/applications/calendar/policyrule/PhabricatorCalendarEventInviteesPolicyRule.php new file mode 100644 index 0000000000..4e7b928901 --- /dev/null +++ b/src/applications/calendar/policyrule/PhabricatorCalendarEventInviteesPolicyRule.php @@ -0,0 +1,104 @@ +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; + } + +} diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 51cd0ea25e..d0006ba3fe 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -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 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 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; } diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 9eb50048fb..f093130bb4 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -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)); - } - } } diff --git a/src/applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php b/src/applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php index 8447a6a30d..d50a86915e 100644 --- a/src/applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php +++ b/src/applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php @@ -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()); diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 8432a157e2..6e53751117 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -1,27 +1,32 @@ 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(); + } + } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php index f9c9e79409..cd4d788c1a 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -1,23 +1,7 @@ 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; } diff --git a/src/applications/calendar/view/AphrontCalendarEventView.php b/src/applications/calendar/view/AphrontCalendarEventView.php index 5bc0b71e31..0dc9231e6f 100644 --- a/src/applications/calendar/view/AphrontCalendarEventView.php +++ b/src/applications/calendar/view/AphrontCalendarEventView.php @@ -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) { diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventAcceptTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventAcceptTransaction.php new file mode 100644 index 0000000000..dc0e4ac1ea --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventAcceptTransaction.php @@ -0,0 +1,25 @@ +renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s is attending %s.', + $this->renderAuthor(), + $this->renderObject()); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php new file mode 100644 index 0000000000..1fe9320d6f --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php @@ -0,0 +1,46 @@ +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()); + } + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventCancelTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventCancelTransaction.php new file mode 100644 index 0000000000..10a1fc9531 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventCancelTransaction.php @@ -0,0 +1,46 @@ +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()); + } + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php new file mode 100644 index 0000000000..5a4958ba4a --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php @@ -0,0 +1,27 @@ +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; + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventDeclineTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventDeclineTransaction.php new file mode 100644 index 0000000000..89579a7144 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventDeclineTransaction.php @@ -0,0 +1,25 @@ +renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s declined %s.', + $this->renderAuthor(), + $this->renderObject()); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventDescriptionTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventDescriptionTransaction.php new file mode 100644 index 0000000000..9c14f1ae80 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventDescriptionTransaction.php @@ -0,0 +1,42 @@ +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()); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php new file mode 100644 index 0000000000..fc7f9859ba --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php @@ -0,0 +1,47 @@ +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.'); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php new file mode 100644 index 0000000000..9690a97dce --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php @@ -0,0 +1,75 @@ +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; + } + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventHostTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventHostTransaction.php new file mode 100644 index 0000000000..febdbbd1a1 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventHostTransaction.php @@ -0,0 +1,59 @@ +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; + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventIconTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventIconTransaction.php new file mode 100644 index 0000000000..d69cf20859 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventIconTransaction.php @@ -0,0 +1,44 @@ +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); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php new file mode 100644 index 0000000000..ea9355eb25 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php @@ -0,0 +1,183 @@ +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)); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventNameTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventNameTransaction.php new file mode 100644 index 0000000000..64fbd84010 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventNameTransaction.php @@ -0,0 +1,44 @@ +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; + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php new file mode 100644 index 0000000000..07aa26fa94 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php @@ -0,0 +1,44 @@ +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; + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventReplyTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventReplyTransaction.php new file mode 100644 index 0000000000..e2ad9ea4be --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventReplyTransaction.php @@ -0,0 +1,33 @@ +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); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php new file mode 100644 index 0000000000..9823ec336f --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php @@ -0,0 +1,47 @@ +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.'); + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventTransactionType.php b/src/applications/calendar/xaction/PhabricatorCalendarEventTransactionType.php new file mode 100644 index 0000000000..53c00969e9 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventTransactionType.php @@ -0,0 +1,4 @@ +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.'); + } + +} diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index 7ad653f486..6a70faf29f 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -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) diff --git a/src/applications/differential/controller/DifferentialInlineCommentEditController.php b/src/applications/differential/controller/DifferentialInlineCommentEditController.php index 11c91fde10..42b96920eb 100644 --- a/src/applications/differential/controller/DifferentialInlineCommentEditController.php +++ b/src/applications/differential/controller/DifferentialInlineCommentEditController.php @@ -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(), diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index f0c611c609..533f579b0a 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -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'); + } } diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php index 6a4536d94a..930ec61483 100644 --- a/src/applications/files/controller/PhabricatorFileComposeController.php +++ b/src/applications/files/controller/PhabricatorFileComposeController.php @@ -35,7 +35,7 @@ final class PhabricatorFileComposeController $data = $composer->loadBuiltinFileData(); - $file = PhabricatorFile::buildFromFileDataOrHash( + $file = PhabricatorFile::newFromFileData( $data, array( 'name' => $composer->getBuiltinDisplayName(), diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index 5e1876acd6..c2ac083fea 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -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; } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 7e8f4b18fc..8d81c3ae98 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -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'); diff --git a/src/applications/files/storage/PhabricatorFileTransactionComment.php b/src/applications/files/storage/PhabricatorFileTransactionComment.php index ee668a49fb..6bada88eac 100644 --- a/src/applications/files/storage/PhabricatorFileTransactionComment.php +++ b/src/applications/files/storage/PhabricatorFileTransactionComment.php @@ -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; - } - } diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index f544fa6b2c..4e27479337 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -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() diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index ab90029a81..8d2fe79735 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -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: diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php index 0287966fa3..0022940542 100644 --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -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) { diff --git a/src/applications/packages/application/PhabricatorPackagesApplication.php b/src/applications/packages/application/PhabricatorPackagesApplication.php new file mode 100644 index 0000000000..7a100e82b8 --- /dev/null +++ b/src/applications/packages/application/PhabricatorPackagesApplication.php @@ -0,0 +1,85 @@ + 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[^/]+)/' => array( + '' => 'PhabricatorPackagesPublisherViewController', + '(?P[^/]+)/' => array( + '' => 'PhabricatorPackagesPackageViewController', + '(?P[^/]+)/' => + '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', + ), + ), + ); + } + +} diff --git a/src/applications/packages/capability/PhabricatorPackagesCreatePublisherCapability.php b/src/applications/packages/capability/PhabricatorPackagesCreatePublisherCapability.php new file mode 100644 index 0000000000..d744c35438 --- /dev/null +++ b/src/applications/packages/capability/PhabricatorPackagesCreatePublisherCapability.php @@ -0,0 +1,16 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesPackageListController.php b/src/applications/packages/controller/PhabricatorPackagesPackageListController.php new file mode 100644 index 0000000000..5b29ab83f6 --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesPackageListController.php @@ -0,0 +1,26 @@ +setController($this) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + id(new PhabricatorPackagesPackageEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); + + return $crumbs; + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesPackageViewController.php b/src/applications/packages/controller/PhabricatorPackagesPackageViewController.php new file mode 100644 index 0000000000..a79f5d3b4e --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesPackageViewController.php @@ -0,0 +1,129 @@ +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; + } +} diff --git a/src/applications/packages/controller/PhabricatorPackagesPublisherController.php b/src/applications/packages/controller/PhabricatorPackagesPublisherController.php new file mode 100644 index 0000000000..812fcaf464 --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesPublisherController.php @@ -0,0 +1,4 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesPublisherListController.php b/src/applications/packages/controller/PhabricatorPackagesPublisherListController.php new file mode 100644 index 0000000000..74f05962ef --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesPublisherListController.php @@ -0,0 +1,26 @@ +setController($this) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + id(new PhabricatorPackagesPublisherEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); + + return $crumbs; + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesPublisherViewController.php b/src/applications/packages/controller/PhabricatorPackagesPublisherViewController.php new file mode 100644 index 0000000000..f0df21e93f --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesPublisherViewController.php @@ -0,0 +1,127 @@ +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; + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesVersionController.php b/src/applications/packages/controller/PhabricatorPackagesVersionController.php new file mode 100644 index 0000000000..914208cb78 --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesVersionController.php @@ -0,0 +1,4 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesVersionListController.php b/src/applications/packages/controller/PhabricatorPackagesVersionListController.php new file mode 100644 index 0000000000..b3c71708a9 --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesVersionListController.php @@ -0,0 +1,26 @@ +setController($this) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + id(new PhabricatorPackagesVersionEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); + + return $crumbs; + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesVersionViewController.php b/src/applications/packages/controller/PhabricatorPackagesVersionViewController.php new file mode 100644 index 0000000000..f2aacea057 --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesVersionViewController.php @@ -0,0 +1,92 @@ +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; + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesEditEngine.php b/src/applications/packages/editor/PhabricatorPackagesEditEngine.php new file mode 100644 index 0000000000..1ba71d7274 --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesEditEngine.php @@ -0,0 +1,14 @@ +getViewer(); + return PhabricatorPackagesPackage::initializeNewPackage($viewer); + } + + protected function newObjectQuery() { + return new PhabricatorPackagesPackageQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Package'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Package'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Package: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Package'); + } + + protected function getObjectCreateShortText() { + return pht('Create Package'); + } + + protected function getObjectName() { + return pht('Package'); + } + + protected function getEditorURI() { + return '/packages/package/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/packages/package/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + $fields = array(); + + if ($this->getIsCreate()) { + $fields[] = id(new PhabricatorDatasourceEditField()) + ->setKey('publisher') + ->setAliases(array('publisherPHID')) + ->setLabel(pht('Publisher')) + ->setDescription(pht('Publisher for this package.')) + ->setTransactionType( + PhabricatorPackagesPackagePublisherTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setDatasource(new PhabricatorPackagesPublisherDatasource()) + ->setSingleValue($object->getPublisherPHID()); + } + + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the package.')) + ->setTransactionType( + PhabricatorPackagesPackageNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getName()); + + if ($this->getIsCreate()) { + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('packageKey') + ->setLabel(pht('Package Key')) + ->setDescription(pht('Unique key to identify the package.')) + ->setTransactionType( + PhabricatorPackagesPackageKeyTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getPackageKey()); + } + + return $fields; + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesPackageEditor.php b/src/applications/packages/editor/PhabricatorPackagesPackageEditor.php new file mode 100644 index 0000000000..241981d706 --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesPackageEditor.php @@ -0,0 +1,49 @@ +getPackageKey()), + null); + + throw new PhabricatorApplicationTransactionValidationException($errors); + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php b/src/applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php new file mode 100644 index 0000000000..3db4ef0925 --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php @@ -0,0 +1,96 @@ +getViewer(); + return PhabricatorPackagesPublisher::initializeNewPublisher($viewer); + } + + protected function newObjectQuery() { + return new PhabricatorPackagesPublisherQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Publisher'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Publisher'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Publisher: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Publisher'); + } + + protected function getObjectCreateShortText() { + return pht('Create Publisher'); + } + + protected function getObjectName() { + return pht('Publisher'); + } + + protected function getEditorURI() { + return '/packages/publisher/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/packages/publisher/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + PhabricatorPackagesCreatePublisherCapability::CAPABILITY); + } + + protected function buildCustomEditFields($object) { + $fields = array(); + + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the publisher.')) + ->setTransactionType( + PhabricatorPackagesPublisherNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getName()); + + if ($this->getIsCreate()) { + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('publisherKey') + ->setLabel(pht('Publisher Key')) + ->setDescription(pht('Unique key to identify the publisher.')) + ->setTransactionType( + PhabricatorPackagesPublisherKeyTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getPublisherKey()); + } + + return $fields; + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesPublisherEditor.php b/src/applications/packages/editor/PhabricatorPackagesPublisherEditor.php new file mode 100644 index 0000000000..5cadb6329f --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesPublisherEditor.php @@ -0,0 +1,45 @@ +getPublisherKey()), + null); + + throw new PhabricatorApplicationTransactionValidationException($errors); + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesVersionEditEngine.php b/src/applications/packages/editor/PhabricatorPackagesVersionEditEngine.php new file mode 100644 index 0000000000..a3c50a886f --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesVersionEditEngine.php @@ -0,0 +1,93 @@ +getViewer(); + return PhabricatorPackagesVersion::initializeNewVersion($viewer); + } + + protected function newObjectQuery() { + return new PhabricatorPackagesVersionQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Version'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Version'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Version: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Version'); + } + + protected function getObjectCreateShortText() { + return pht('Create Version'); + } + + protected function getObjectName() { + return pht('Version'); + } + + protected function getEditorURI() { + return '/packages/version/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/packages/version/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + $fields = array(); + + if ($this->getIsCreate()) { + $fields[] = id(new PhabricatorDatasourceEditField()) + ->setKey('package') + ->setAliases(array('packagePHID')) + ->setLabel(pht('Package')) + ->setDescription(pht('Package for this version.')) + ->setTransactionType( + PhabricatorPackagesVersionPackageTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setDatasource(new PhabricatorPackagesPackageDatasource()) + ->setSingleValue($object->getPackagePHID()); + + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the version.')) + ->setTransactionType( + PhabricatorPackagesVersionNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getName()); + } + + return $fields; + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesVersionEditor.php b/src/applications/packages/editor/PhabricatorPackagesVersionEditor.php new file mode 100644 index 0000000000..72bde82738 --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesVersionEditor.php @@ -0,0 +1,40 @@ +getName()), + null); + + throw new PhabricatorApplicationTransactionValidationException($errors); + } + +} diff --git a/src/applications/packages/phid/PhabricatorPackagesPackagePHIDType.php b/src/applications/packages/phid/PhabricatorPackagesPackagePHIDType.php new file mode 100644 index 0000000000..6f8e982c81 --- /dev/null +++ b/src/applications/packages/phid/PhabricatorPackagesPackagePHIDType.php @@ -0,0 +1,45 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $package = $objects[$phid]; + + $name = $package->getName(); + $uri = $package->getURI(); + + $handle + ->setName($name) + ->setURI($uri); + } + } + +} diff --git a/src/applications/packages/phid/PhabricatorPackagesPublisherPHIDType.php b/src/applications/packages/phid/PhabricatorPackagesPublisherPHIDType.php new file mode 100644 index 0000000000..f746ddce22 --- /dev/null +++ b/src/applications/packages/phid/PhabricatorPackagesPublisherPHIDType.php @@ -0,0 +1,45 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $publisher = $objects[$phid]; + + $name = $publisher->getName(); + $uri = $publisher->getURI(); + + $handle + ->setName($name) + ->setURI($uri); + } + } + +} diff --git a/src/applications/packages/phid/PhabricatorPackagesVersionPHIDType.php b/src/applications/packages/phid/PhabricatorPackagesVersionPHIDType.php new file mode 100644 index 0000000000..203cca20e0 --- /dev/null +++ b/src/applications/packages/phid/PhabricatorPackagesVersionPHIDType.php @@ -0,0 +1,45 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $version = $objects[$phid]; + + $name = $version->getName(); + $uri = $version->getURI(); + + $handle + ->setName($name) + ->setURI($uri); + } + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPackageQuery.php b/src/applications/packages/query/PhabricatorPackagesPackageQuery.php new file mode 100644 index 0000000000..67cd3954c9 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPackageQuery.php @@ -0,0 +1,135 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withPublisherPHIDs(array $phids) { + $this->publisherPHIDs = $phids; + return $this; + } + + public function withPackageKeys(array $keys) { + $this->packageKeys = $keys; + return $this; + } + + public function withFullKeys(array $keys) { + $this->fullKeys = $keys; + return $this; + } + + public function withNameNgrams($ngrams) { + return $this->withNgramsConstraint( + new PhabricatorPackagesPackageNameNgrams(), + $ngrams); + } + + public function newResultObject() { + return new PhabricatorPackagesPackage(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'p.id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'p.phid IN (%Ls)', + $this->phids); + } + + if ($this->publisherPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'p.publisherPHID IN (%Ls)', + $this->publisherPHIDs); + } + + if ($this->packageKeys !== null) { + $where[] = qsprintf( + $conn, + 'p.packageKey IN (%Ls)', + $this->packageKeys); + } + + if ($this->fullKeys !== null) { + $parts = $this->buildFullKeyClauseParts($conn, $this->fullKeys); + $where[] = qsprintf($conn, '%Q', $parts); + } + + return $where; + } + + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + + $join_publisher = ($this->fullKeys !== null); + if ($join_publisher) { + $publisher_table = new PhabricatorPackagesPublisher(); + + $joins[] = qsprintf( + $conn, + 'JOIN %T u ON u.phid = p.publisherPHID', + $publisher_table->getTableName()); + } + + return $joins; + } + + protected function willFilterPage(array $packages) { + $publisher_phids = mpull($packages, 'getPublisherPHID'); + + $publishers = id(new PhabricatorPackagesPublisherQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withPHIDs($publisher_phids) + ->execute(); + $publishers = mpull($publishers, null, 'getPHID'); + + foreach ($packages as $key => $package) { + $publisher = idx($publishers, $package->getPublisherPHID()); + + if (!$publisher) { + unset($packages[$key]); + $this->didRejectResult($package); + continue; + } + + $package->attachPublisher($publisher); + } + + return $packages; + } + + protected function getPrimaryTableAlias() { + return 'p'; + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php b/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php new file mode 100644 index 0000000000..e01c8db503 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php @@ -0,0 +1,89 @@ +newQuery(); + + if ($map['match'] !== null) { + $query->withNameNgrams($map['match']); + } + + if ($map['publisherPHIDs']) { + $query->withPublisherPHIDs($map['publisherPHIDs']); + } + + return $query; + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('match') + ->setDescription(pht('Search for packages by name substring.')), + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Publishers')) + ->setKey('publisherPHIDs') + ->setAliases(array('publisherPHID', 'publisher', 'publishers')) + ->setDatasource(new PhabricatorPackagesPublisherDatasource()) + ->setDescription(pht('Search for packages by publisher.')), + ); + } + + protected function getURI($path) { + return '/packages/package/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Packages'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $packages, + PhabricatorSavedQuery $query, + array $handles) { + + assert_instances_of($packages, 'PhabricatorPackagesPackage'); + $viewer = $this->requireViewer(); + + $list = id(new PhabricatorPackagesPackageListView()) + ->setViewer($viewer) + ->setPackages($packages) + ->newListView(); + + return id(new PhabricatorApplicationSearchResultView()) + ->setObjectList($list) + ->setNoDataString(pht('No packages found.')); + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php b/src/applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php new file mode 100644 index 0000000000..146f138119 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php @@ -0,0 +1,10 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withPublisherKeys(array $keys) { + $this->publisherKeys = $keys; + return $this; + } + + public function withNameNgrams($ngrams) { + return $this->withNgramsConstraint( + new PhabricatorPackagesPublisherNameNgrams(), + $ngrams); + } + + public function newResultObject() { + return new PhabricatorPackagesPublisher(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'u.id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'u.phid IN (%Ls)', + $this->phids); + } + + if ($this->publisherKeys !== null) { + $where[] = qsprintf( + $conn, + 'u.publisherKey IN (%Ls)', + $this->publisherKeys); + } + + return $where; + } + + protected function getPrimaryTableAlias() { + return 'u'; + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php b/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php new file mode 100644 index 0000000000..f11738f730 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php @@ -0,0 +1,80 @@ +newQuery(); + + if ($map['match'] !== null) { + $query->withNameNgrams($map['match']); + } + + return $query; + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('match') + ->setDescription(pht('Search for publishers by name substring.')), + ); + } + + protected function getURI($path) { + return '/packages/publisher/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Publishers'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $publishers, + PhabricatorSavedQuery $query, + array $handles) { + + assert_instances_of($publishers, 'PhabricatorPackagesPublisher'); + + $viewer = $this->requireViewer(); + + $list = id(new PhabricatorPackagesPublisherListView()) + ->setViewer($viewer) + ->setPublishers($publishers) + ->newListView(); + + return id(new PhabricatorApplicationSearchResultView()) + ->setObjectList($list) + ->setNoDataString(pht('No publishers found.')); + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPublisherTransactionQuery.php b/src/applications/packages/query/PhabricatorPackagesPublisherTransactionQuery.php new file mode 100644 index 0000000000..e7af82cfa8 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPublisherTransactionQuery.php @@ -0,0 +1,10 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withPackagePHIDs(array $phids) { + $this->packagePHIDs = $phids; + return $this; + } + + public function withFullKeys(array $keys) { + $this->fullKeys = $keys; + return $this; + } + + public function withNames(array $names) { + $this->names = $names; + return $this; + } + + public function withNameNgrams($ngrams) { + return $this->withNgramsConstraint( + new PhabricatorPackagesVersionNameNgrams(), + $ngrams); + } + + public function newResultObject() { + return new PhabricatorPackagesVersion(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'v.id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'v.phid IN (%Ls)', + $this->phids); + } + + if ($this->packagePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'v.packagePHID IN (%Ls)', + $this->packagePHIDs); + } + + if ($this->names !== null) { + $where[] = qsprintf( + $conn, + 'v.name IN (%Ls)', + $this->names); + } + + if ($this->fullKeys !== null) { + $parts = $this->buildFullKeyClauseParts($conn, $this->fullKeys); + $where[] = qsprintf($conn, '%Q', $parts); + } + + return $where; + } + + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + + $join_publisher = ($this->fullKeys !== null); + $join_package = ($this->fullKeys !== null) || $join_publisher; + + if ($join_package) { + $package_table = new PhabricatorPackagesPackage(); + + $joins[] = qsprintf( + $conn, + 'JOIN %T p ON v.packagePHID = p.phid', + $package_table->getTableName()); + } + + if ($join_publisher) { + $publisher_table = new PhabricatorPackagesPublisher(); + + $joins[] = qsprintf( + $conn, + 'JOIN %T u ON u.phid = p.publisherPHID', + $publisher_table->getTableName()); + } + + return $joins; + } + + protected function willFilterPage(array $versions) { + $package_phids = mpull($versions, 'getPackagePHID'); + + $packages = id(new PhabricatorPackagesPackageQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withPHIDs($package_phids) + ->execute(); + $packages = mpull($packages, null, 'getPHID'); + + foreach ($versions as $key => $version) { + $package = idx($packages, $version->getPackagePHID()); + + if (!$package) { + unset($versions[$key]); + $this->didRejectResult($version); + continue; + } + + $version->attachPackage($package); + } + + return $versions; + } + + protected function getPrimaryTableAlias() { + return 'v'; + } + + +} diff --git a/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php b/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php new file mode 100644 index 0000000000..da1581bf80 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesVersionSearchEngine.php @@ -0,0 +1,88 @@ +newQuery(); + + if ($map['match'] !== null) { + $query->withNameNgrams($map['match']); + } + + if ($map['packagePHIDs']) { + $query->withPackagePHIDs($map['packagePHIDs']); + } + + return $query; + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('match') + ->setDescription(pht('Search for versions by name substring.')), + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Packages')) + ->setKey('packagePHIDs') + ->setAliases(array('packagePHID', 'package', 'packages')) + ->setDatasource(new PhabricatorPackagesPackageDatasource()) + ->setDescription(pht('Search for versions by package.')), + ); + } + protected function getURI($path) { + return '/packages/version/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Versions'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $versions, + PhabricatorSavedQuery $query, + array $handles) { + + assert_instances_of($versions, 'PhabricatorPackagesVersion'); + $viewer = $this->requireViewer(); + + $list = id(new PhabricatorPackagesVersionListView()) + ->setViewer($viewer) + ->setVersions($versions) + ->newListView(); + + return id(new PhabricatorApplicationSearchResultView()) + ->setObjectList($list) + ->setNoDataString(pht('No versions found.')); + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesVersionTransactionQuery.php b/src/applications/packages/query/PhabricatorPackagesVersionTransactionQuery.php new file mode 100644 index 0000000000..7ab10e2490 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesVersionTransactionQuery.php @@ -0,0 +1,10 @@ +setViewer($actor) + ->withClasses(array('PhabricatorPackagesApplication')) + ->executeOne(); + + $view_policy = $packages_application->getPolicy( + PhabricatorPackagesPackageDefaultViewCapability::CAPABILITY); + + $edit_policy = $packages_application->getPolicy( + PhabricatorPackagesPackageDefaultEditCapability::CAPABILITY); + + return id(new self()) + ->setViewPolicy($view_policy) + ->setEditPolicy($edit_policy); + } + + protected function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'sort64', + 'packageKey' => 'sort64', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_package' => array( + 'columns' => array('publisherPHID', 'packageKey'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorPackagesPackagePHIDType::TYPECONST); + } + + public function getURI() { + $full_key = $this->getFullKey(); + return "/package/{$full_key}/"; + } + + public function getFullKey() { + $publisher = $this->getPublisher(); + $publisher_key = $publisher->getPublisherKey(); + $package_key = $this->getPackageKey(); + return "{$publisher_key}/{$package_key}"; + } + + public function attachPublisher(PhabricatorPackagesPublisher $publisher) { + $this->publisher = $publisher; + return $this; + } + + public function getPublisher() { + return $this->assertAttached($this->publisher); + } + + public static function assertValidPackageName($value) { + $length = phutil_utf8_strlen($value); + if (!$length) { + throw new Exception( + pht( + 'Package name "%s" is not valid: package names are required.', + $value)); + } + + $max_length = 64; + if ($length > $max_length) { + throw new Exception( + pht( + 'Package name "%s" is not valid: package names must not be '. + 'more than %s characters long.', + $value, + new PhutilNumber($max_length))); + } + } + + public static function assertValidPackageKey($value) { + $length = phutil_utf8_strlen($value); + if (!$length) { + throw new Exception( + pht( + 'Package key "%s" is not valid: package keys are required.', + $value)); + } + + $max_length = 64; + if ($length > $max_length) { + throw new Exception( + pht( + 'Package key "%s" is not valid: package keys must not be '. + 'more than %s characters long.', + $value, + new PhutilNumber($max_length))); + } + + if (!preg_match('/^[a-z]+\z/', $value)) { + throw new Exception( + pht( + 'Package key "%s" is not valid: package keys may only contain '. + 'lowercase latin letters.', + $value)); + } + } + + +/* -( PhabricatorSubscribableInterface )----------------------------------- */ + + + public function isAutomaticallySubscribed($phid) { + return false; + } + + +/* -( Policy Interface )--------------------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $user) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $viewer = $engine->getViewer(); + + $this->openTransaction(); + + $versions = id(new PhabricatorPackagesVersionQuery()) + ->setViewer($viewer) + ->withPackagePHIDs(array($this->getPHID())) + ->execute(); + foreach ($versions as $version) { + $engine->destroyObject($version); + } + + $this->delete(); + + $this->saveTransaction(); + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorPackagesPackageEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorPackagesPackageTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + + +/* -( PhabricatorNgramsInterface )----------------------------------------- */ + + + public function newNgrams() { + return array( + id(new PhabricatorPackagesPackageNameNgrams()) + ->setValue($this->getName()), + ); + } + + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of the package.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('packageKey') + ->setType('string') + ->setDescription(pht('The unique key of the package.')), + ); + } + + public function getFieldValuesForConduit() { + $publisher = $this->getPublisher(); + + $publisher_map = array( + 'id' => (int)$publisher->getID(), + 'phid' => $publisher->getPHID(), + 'name' => $publisher->getName(), + 'publisherKey' => $publisher->getPublisherKey(), + ); + + return array( + 'name' => $this->getName(), + 'packageKey' => $this->getPackageKey(), + 'fullKey' => $this->getFullKey(), + 'publisher' => $publisher_map, + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + +} diff --git a/src/applications/packages/storage/PhabricatorPackagesPackageNameNgrams.php b/src/applications/packages/storage/PhabricatorPackagesPackageNameNgrams.php new file mode 100644 index 0000000000..cfba763075 --- /dev/null +++ b/src/applications/packages/storage/PhabricatorPackagesPackageNameNgrams.php @@ -0,0 +1,14 @@ +setViewer($actor) + ->withClasses(array('PhabricatorPackagesApplication')) + ->executeOne(); + + $edit_policy = $packages_application->getPolicy( + PhabricatorPackagesPublisherDefaultEditCapability::CAPABILITY); + + return id(new self()) + ->setEditPolicy($edit_policy); + } + + protected function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'sort64', + 'publisherKey' => 'sort64', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_publisher' => array( + 'columns' => array('publisherKey'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorPackagesPublisherPHIDType::TYPECONST); + } + + public function getURI() { + $publisher_key = $this->getPublisherKey(); + return "/package/{$publisher_key}/"; + } + + public static function assertValidPublisherName($value) { + $length = phutil_utf8_strlen($value); + if (!$length) { + throw new Exception( + pht( + 'Publisher name "%s" is not valid: publisher names are required.', + $value)); + } + + $max_length = 64; + if ($length > $max_length) { + throw new Exception( + pht( + 'Publisher name "%s" is not valid: publisher names must not be '. + 'more than %s characters long.', + $value, + new PhutilNumber($max_length))); + } + } + + public static function assertValidPublisherKey($value) { + $length = phutil_utf8_strlen($value); + if (!$length) { + throw new Exception( + pht( + 'Publisher key "%s" is not valid: publisher keys are required.', + $value)); + } + + $max_length = 64; + if ($length > $max_length) { + throw new Exception( + pht( + 'Publisher key "%s" is not valid: publisher keys must not be '. + 'more than %s characters long.', + $value, + new PhutilNumber($max_length))); + } + + if (!preg_match('/^[a-z]+\z/', $value)) { + throw new Exception( + pht( + 'Publisher key "%s" is not valid: publisher keys may only contain '. + 'lowercase latin letters.', + $value)); + } + } + + +/* -( PhabricatorSubscribableInterface )----------------------------------- */ + + + public function isAutomaticallySubscribed($phid) { + return false; + } + + +/* -( Policy Interface )--------------------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::getMostOpenPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $user) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $viewer = $engine->getViewer(); + + $this->openTransaction(); + + $packages = id(new PhabricatorPackagesPackageQuery()) + ->setViewer($viewer) + ->withPublisherPHIDs(array($this->getPHID())) + ->execute(); + foreach ($packages as $package) { + $engine->destroyObject($package); + } + + $this->delete(); + + $this->saveTransaction(); + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorPackagesPublisherEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorPackagesPublisherTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + + +/* -( PhabricatorNgramsInterface )----------------------------------------- */ + + + public function newNgrams() { + return array( + id(new PhabricatorPackagesPublisherNameNgrams()) + ->setValue($this->getName()), + ); + } + + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of the publisher.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('publisherKey') + ->setType('string') + ->setDescription(pht('The unique key of the publisher.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getName(), + 'publisherKey' => $this->getPublisherKey(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + +} diff --git a/src/applications/packages/storage/PhabricatorPackagesPublisherNameNgrams.php b/src/applications/packages/storage/PhabricatorPackagesPublisherNameNgrams.php new file mode 100644 index 0000000000..290404ebe6 --- /dev/null +++ b/src/applications/packages/storage/PhabricatorPackagesPublisherNameNgrams.php @@ -0,0 +1,14 @@ +buildEdgeSchemata(new PhabricatorPackagesPublisher()); + } + +} diff --git a/src/applications/packages/storage/PhabricatorPackagesVersion.php b/src/applications/packages/storage/PhabricatorPackagesVersion.php new file mode 100644 index 0000000000..65ee005ff8 --- /dev/null +++ b/src/applications/packages/storage/PhabricatorPackagesVersion.php @@ -0,0 +1,212 @@ + true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'sort64', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_package' => array( + 'columns' => array('packagePHID', 'name'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorPackagesVersionPHIDType::TYPECONST); + } + + public function getURI() { + $package = $this->getPackage(); + $full_key = $package->getFullKey(); + $name = $this->getName(); + + return "/package/{$full_key}/{$name}/"; + } + + public function attachPackage(PhabricatorPackagesPackage $package) { + $this->package = $package; + return $this; + } + + public function getPackage() { + return $this->assertAttached($this->package); + } + + public static function assertValidVersionName($value) { + $length = phutil_utf8_strlen($value); + if (!$length) { + throw new Exception( + pht( + 'Version name "%s" is not valid: version names are required.', + $value)); + } + + $max_length = 64; + if ($length > $max_length) { + throw new Exception( + pht( + 'Version name "%s" is not valid: version names must not be '. + 'more than %s characters long.', + $value, + new PhutilNumber($max_length))); + } + + if (!preg_match('/^[A-Za-z0-9.-]+\z/', $value)) { + throw new Exception( + pht( + 'Version name "%s" is not valid: version names may only contain '. + 'latin letters, digits, periods, and hyphens.', + $value)); + } + + if (preg_match('/^[.-]|[.-]$/', $value)) { + throw new Exception( + pht( + 'Version name "%s" is not valid: version names may not start or '. + 'end with a period or hyphen.', + $value)); + } + } + + +/* -( PhabricatorSubscribableInterface )----------------------------------- */ + + + public function isAutomaticallySubscribed($phid) { + return false; + } + + +/* -( Policy Interface )--------------------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::getMostOpenPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return PhabricatorPolicies::POLICY_USER; + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $user) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ + + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + return array( + array( + $this->getPackage(), + $capability, + ), + ); + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $this->delete(); + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorPackagesVersionEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorPackagesVersionTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + + +/* -( PhabricatorNgramsInterface )----------------------------------------- */ + + + public function newNgrams() { + return array( + id(new PhabricatorPackagesVersionNameNgrams()) + ->setValue($this->getName()), + ); + } + + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of the version.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getName(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + +} diff --git a/src/applications/packages/storage/PhabricatorPackagesVersionNameNgrams.php b/src/applications/packages/storage/PhabricatorPackagesVersionNameNgrams.php new file mode 100644 index 0000000000..cad5c9ad94 --- /dev/null +++ b/src/applications/packages/storage/PhabricatorPackagesVersionNameNgrams.php @@ -0,0 +1,14 @@ +getViewer(); + $raw_query = $this->getRawQuery(); + + $package_query = id(new PhabricatorPackagesPackageQuery()); + $packages = $this->executeQuery($package_query); + + $results = array(); + foreach ($packages as $package) { + $results[] = id(new PhabricatorTypeaheadResult()) + ->setName($package->getName()) + ->setPHID($package->getPHID()); + } + + return $this->filterResultsAgainstTokens($results); + } + +} diff --git a/src/applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php b/src/applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php new file mode 100644 index 0000000000..e8ca7d4c7f --- /dev/null +++ b/src/applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php @@ -0,0 +1,35 @@ +getViewer(); + $raw_query = $this->getRawQuery(); + + $publisher_query = id(new PhabricatorPackagesPublisherQuery()); + $publishers = $this->executeQuery($publisher_query); + + $results = array(); + foreach ($publishers as $publisher) { + $results[] = id(new PhabricatorTypeaheadResult()) + ->setName($publisher->getName()) + ->setPHID($publisher->getPHID()); + } + + return $this->filterResultsAgainstTokens($results); + } + +} diff --git a/src/applications/packages/view/PhabricatorPackagesPackageListView.php b/src/applications/packages/view/PhabricatorPackagesPackageListView.php new file mode 100644 index 0000000000..78022bde38 --- /dev/null +++ b/src/applications/packages/view/PhabricatorPackagesPackageListView.php @@ -0,0 +1,41 @@ +packages = $packages; + return $this; + } + + public function getPackages() { + return $this->packages; + } + + public function render() { + return $this->newListView(); + } + + public function newListView() { + $viewer = $this->getViewer(); + $packages = $this->getPackages(); + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer); + + foreach ($packages as $package) { + $item = id(new PHUIObjectItemView()) + ->setObjectName($package->getFullKey()) + ->setHeader($package->getName()) + ->setHref($package->getURI()); + + $list->addItem($item); + } + + return $list; + } + +} diff --git a/src/applications/packages/view/PhabricatorPackagesPublisherListView.php b/src/applications/packages/view/PhabricatorPackagesPublisherListView.php new file mode 100644 index 0000000000..fe7b2b37e4 --- /dev/null +++ b/src/applications/packages/view/PhabricatorPackagesPublisherListView.php @@ -0,0 +1,41 @@ +publishers = $publishers; + return $this; + } + + public function getPublishers() { + return $this->publishers; + } + + public function render() { + return $this->newListView(); + } + + public function newListView() { + $viewer = $this->getViewer(); + $publishers = $this->getPublishers(); + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer); + + foreach ($publishers as $publisher) { + $item = id(new PHUIObjectItemView()) + ->setObjectName($publisher->getPublisherKey()) + ->setHeader($publisher->getName()) + ->setHref($publisher->getURI()); + + $list->addItem($item); + } + + return $list; + } + +} diff --git a/src/applications/packages/view/PhabricatorPackagesVersionListView.php b/src/applications/packages/view/PhabricatorPackagesVersionListView.php new file mode 100644 index 0000000000..45840016ee --- /dev/null +++ b/src/applications/packages/view/PhabricatorPackagesVersionListView.php @@ -0,0 +1,40 @@ +versions = $versions; + return $this; + } + + public function getVersions() { + return $this->versions; + } + + public function render() { + return $this->newListView(); + } + + public function newListView() { + $viewer = $this->getViewer(); + $versions = $this->getVersions(); + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer); + + foreach ($versions as $version) { + $item = id(new PHUIObjectItemView()) + ->setHeader($version->getName()) + ->setHref($version->getURI()); + + $list->addItem($item); + } + + return $list; + } + +} diff --git a/src/applications/packages/view/PhabricatorPackagesView.php b/src/applications/packages/view/PhabricatorPackagesView.php new file mode 100644 index 0000000000..88cc6730cb --- /dev/null +++ b/src/applications/packages/view/PhabricatorPackagesView.php @@ -0,0 +1,3 @@ +getPackageKey(); + } + + public function applyInternalEffects($object, $value) { + $object->setPackageKey($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht( + 'Each package provided by a publisher must have a '. + 'unique package key.')); + return $errors; + } + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Once a package is created, its key can not be changed.'), + $xaction); + } + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + try { + PhabricatorPackagesPackage::assertValidPackageKey($value); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError($ex->getMessage(), $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php b/src/applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php new file mode 100644 index 0000000000..0496911e57 --- /dev/null +++ b/src/applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php @@ -0,0 +1,54 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s changed the name of this package from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the name for %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('Packages must have a name.')); + return $errors; + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + try { + PhabricatorPackagesPackage::assertValidPackageName($value); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError($ex->getMessage(), $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php b/src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php new file mode 100644 index 0000000000..77c8fa8301 --- /dev/null +++ b/src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php @@ -0,0 +1,67 @@ +getPublisherPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setPublisherPHID($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $current_value = $object->getPublisherPHID(); + if ($this->isEmptyTextTransaction($current_value, $xactions)) { + $errors[] = $this->newRequiredError( + pht( + 'You must select a publisher when creating a package.')); + return $errors; + } + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Once a package is created, its publisher can not be changed.'), + $xaction); + } + } + + $viewer = $this->getActor(); + foreach ($xactions as $xaction) { + $publisher_phid = $xaction->getNewValue(); + + $publisher = id(new PhabricatorPackagesPublisherQuery()) + ->setViewer($viewer) + ->withPHIDs(array($publisher_phid)) + ->setRaisePolicyExceptions(false) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + if (!$publisher) { + $errors[] = $this->newInvalidError( + pht( + 'Publisher "%s" is invalid: the publisher must exist and you '. + 'must have permission to edit it in order to create a new '. + 'package.', + $publisher_phid), + $xaction); + continue; + } + + $object->attachPublisher($publisher); + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php b/src/applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php new file mode 100644 index 0000000000..08cbb113b4 --- /dev/null +++ b/src/applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php @@ -0,0 +1,4 @@ +getPublisherKey(); + } + + public function applyInternalEffects($object, $value) { + $object->setPublisherKey($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Publishers must have a unique publisher key.')); + } + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Once a publisher is created, its key can not be changed.'), + $xaction); + } + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + try { + PhabricatorPackagesPublisher::assertValidPublisherKey($value); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError($ex->getMessage(), $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherNameTransaction.php b/src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherNameTransaction.php new file mode 100644 index 0000000000..fbc85c9733 --- /dev/null +++ b/src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherNameTransaction.php @@ -0,0 +1,53 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s changed the name of this publisher from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the name for %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('Publishers must have a name.')); + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + try { + PhabricatorPackagesPublisher::assertValidPublisherName($value); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError($ex->getMessage(), $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherTransactionType.php b/src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherTransactionType.php new file mode 100644 index 0000000000..3d39f707dc --- /dev/null +++ b/src/applications/packages/xaction/publisher/PhabricatorPackagesPublisherTransactionType.php @@ -0,0 +1,4 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s changed the name of this version from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the name for %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('Versions must have a name.')); + return $errors; + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + try { + PhabricatorPackagesVersion::assertValidVersionName($value); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError($ex->getMessage(), $xaction); + } + } + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Once a version is created, its name can not be changed.'), + $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/version/PhabricatorPackagesVersionPackageTransaction.php b/src/applications/packages/xaction/version/PhabricatorPackagesVersionPackageTransaction.php new file mode 100644 index 0000000000..733427c925 --- /dev/null +++ b/src/applications/packages/xaction/version/PhabricatorPackagesVersionPackageTransaction.php @@ -0,0 +1,66 @@ +getPackagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setPackagePHID($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getPackagePHID(), $xactions)) { + $errors[] = $this->newRequiredError( + pht( + 'You must select a package when creating a version')); + return $errors; + } + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Once a version is created, its package can not be changed.'), + $xaction); + } + } + + $viewer = $this->getActor(); + foreach ($xactions as $xaction) { + $package_phid = $xaction->getNewValue(); + + $package = id(new PhabricatorPackagesPackageQuery()) + ->setViewer($viewer) + ->withPHIDs(array($package_phid)) + ->setRaisePolicyExceptions(false) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + if (!$package) { + $errors[] = $this->newInvalidError( + pht( + 'Package "%s" is invalid: the package must exist and you '. + 'must have permission to edit it in order to create a new '. + 'package.', + $package_phid), + $xaction); + continue; + } + + $object->attachPackage($package); + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/version/PhabricatorPackagesVersionTransactionType.php b/src/applications/packages/xaction/version/PhabricatorPackagesVersionTransactionType.php new file mode 100644 index 0000000000..f25034097c --- /dev/null +++ b/src/applications/packages/xaction/version/PhabricatorPackagesVersionTransactionType.php @@ -0,0 +1,4 @@ +renderAuthor(), - $this->renderValue($this->getOldValue()), - $this->renderValue($this->getNewValue())); + $this->renderOldValue(), + $this->renderNewValue()); } public function getTitleForFeed() { @@ -26,8 +26,8 @@ final class PhabricatorPasteLanguageTransaction '%s updated the language for %s from %s to %s.', $this->renderAuthor(), $this->renderObject(), - $this->renderValue($this->getOldValue()), - $this->renderValue($this->getNewValue())); + $this->renderOldValue(), + $this->renderNewValue()); } } diff --git a/src/applications/paste/xaction/PhabricatorPasteTitleTransaction.php b/src/applications/paste/xaction/PhabricatorPasteTitleTransaction.php index 0fc86b3d96..f9d24a0a93 100644 --- a/src/applications/paste/xaction/PhabricatorPasteTitleTransaction.php +++ b/src/applications/paste/xaction/PhabricatorPasteTitleTransaction.php @@ -15,19 +15,19 @@ final class PhabricatorPasteTitleTransaction public function getTitle() { return pht( - '%s updated the paste\'s title from "%s" to "%s".', + '%s changed the title of this paste from %s to %s.', $this->renderAuthor(), - $this->getOldValue(), - $this->getNewValue()); + $this->renderOldValue(), + $this->renderNewValue()); } public function getTitleForFeed() { return pht( - '%s updated the title for %s from "%s" to "%s".', + '%s updated the title for %s from %s to %s.', $this->renderAuthor(), $this->renderObject(), - $this->getOldValue(), - $this->getNewValue()); + $this->renderOldValue(), + $this->renderNewValue()); } } diff --git a/src/applications/people/application/PhabricatorPeopleApplication.php b/src/applications/people/application/PhabricatorPeopleApplication.php index 5ea4e6aa1c..adc3d87560 100644 --- a/src/applications/people/application/PhabricatorPeopleApplication.php +++ b/src/applications/people/application/PhabricatorPeopleApplication.php @@ -69,7 +69,6 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication { '' => 'PhabricatorPeopleProfileViewController', 'panel/' => $this->getPanelRouting('PhabricatorPeopleProfilePanelController'), - 'calendar/' => 'PhabricatorPeopleCalendarController', ), ); } diff --git a/src/applications/people/controller/PhabricatorPeopleCalendarController.php b/src/applications/people/controller/PhabricatorPeopleCalendarController.php deleted file mode 100644 index ed3d557fb8..0000000000 --- a/src/applications/people/controller/PhabricatorPeopleCalendarController.php +++ /dev/null @@ -1,97 +0,0 @@ -getViewer(); - $username = $request->getURIData('username'); - - $user = id(new PhabricatorPeopleQuery()) - ->setViewer($viewer) - ->withUsernames(array($username)) - ->needProfileImage(true) - ->executeOne(); - if (!$user) { - return new Aphront404Response(); - } - - $this->setUser($user); - - $picture = $user->getProfileImageURI(); - - $now = time(); - $request = $this->getRequest(); - $year_d = phabricator_format_local_time($now, $user, 'Y'); - $year = $request->getInt('year', $year_d); - $month_d = phabricator_format_local_time($now, $user, 'm'); - $month = $request->getInt('month', $month_d); - $day = phabricator_format_local_time($now, $user, 'j'); - - $start_epoch = strtotime("{$year}-{$month}-01"); - $end_epoch = strtotime("{$year}-{$month}-01 next month"); - - $statuses = id(new PhabricatorCalendarEventQuery()) - ->setViewer($user) - ->withInvitedPHIDs(array($user->getPHID())) - ->withDateRange( - $start_epoch, - $end_epoch) - ->execute(); - - $start_range_value = AphrontFormDateControlValue::newFromEpoch( - $user, - $start_epoch); - $end_range_value = AphrontFormDateControlValue::newFromEpoch( - $user, - $end_epoch); - - if ($month == $month_d && $year == $year_d) { - $month_view = new PHUICalendarMonthView( - $start_range_value, - $end_range_value, - $month, - $year, - $day); - } else { - $month_view = new PHUICalendarMonthView( - $start_range_value, - $end_range_value, - $month, - $year); - } - - $month_view->setBrowseURI($request->getRequestURI()); - $month_view->setUser($user); - $month_view->setImage($picture); - - $phids = mpull($statuses, 'getUserPHID'); - $handles = $this->loadViewerHandles($phids); - - foreach ($statuses as $status) { - $event = new AphrontCalendarEventView(); - $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); - $event->setUserPHID($status->getUserPHID()); - $event->setName($status->getName()); - $event->setDescription($status->getDescription()); - $event->setEventID($status->getID()); - $month_view->addEvent($event); - } - - $nav = $this->getProfileMenu(); - $nav->selectFilter('calendar'); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Calendar')); - - return $this->newPage() - ->setTitle(pht('Calendar')) - ->setNavigation($nav) - ->setCrumbs($crumbs) - ->appendChild($month_view); - } -} diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index e5073eff94..4621aba3c5 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -189,41 +189,44 @@ final class PhabricatorPeopleProfileViewController $range_start = $midnight->format('U'); $range_end = $week_end->format('U'); - $query = id(new PhabricatorCalendarEventQuery()) + $events = id(new PhabricatorCalendarEventQuery()) ->setViewer($viewer) ->withDateRange($range_start, $range_end) ->withInvitedPHIDs(array($user->getPHID())) - ->withIsCancelled(false); + ->withIsCancelled(false) + ->execute(); - $statuses = $query->execute(); - $phids = mpull($statuses, 'getUserPHID'); - $events = array(); - - foreach ($statuses as $status) { - $viewer_is_invited = $status->getIsUserInvited($user->getPHID()); + $event_views = array(); + foreach ($events as $event) { + $viewer_is_invited = $event->getIsUserInvited($viewer->getPHID()); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, - $status, + $event, PhabricatorPolicyCapability::CAN_EDIT); - $event = id(new AphrontCalendarEventView()) + $epoch_min = $event->getViewerDateFrom(); + $epoch_max = $event->getViewerDateTo(); + + $event_view = id(new AphrontCalendarEventView()) ->setCanEdit($can_edit) - ->setEventID($status->getID()) - ->setEpochRange($status->getDateFrom(), $status->getDateTo()) - ->setIsAllDay($status->getIsAllDay()) - ->setIcon($status->getIcon()) + ->setEventID($event->getID()) + ->setEpochRange($epoch_min, $epoch_max) + ->setIsAllDay($event->getIsAllDay()) + ->setIcon($event->getIcon()) ->setViewerIsInvited($viewer_is_invited) - ->setName($status->getName()) - ->setURI($status->getURI()); - $events[] = $event; + ->setName($event->getName()) + ->setURI($event->getURI()); + + $event_views[] = $event_view; } - $events = msort($events, 'getEpochStart'); + $event_views = msort($event_views, 'getEpochStart'); + $day_view = id(new PHUICalendarWeekView()) ->setViewer($viewer) ->setView('week') - ->setEvents($events) + ->setEvents($event_views) ->setWeekLength(3) ->render(); @@ -231,11 +234,13 @@ final class PhabricatorPeopleProfileViewController ->setHeader(pht('Calendar')) ->setHref( urisprintf( - '/calendar/?invitedPHIDs=%s#R', - $user->getPHID())); + '/calendar/?invited=%s#R', + $user->getUsername())); + $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($day_view) + ->addClass('calendar-profile-box') ->setBackground(PHUIObjectBoxView::GREY); return $box; diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index 6c1fa7b68d..69ce099111 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -410,9 +410,21 @@ final class PhabricatorPeopleQuery } } + // We need to load these users' timezone settings to figure out their + // availability if they're attending all-day events. + $this->needUserSettings(true); + $this->fillUserCaches($rebuild); + foreach ($rebuild as $phid => $user) { $events = idx($map, $phid, array()); + // We loaded events with the omnipotent user, but want to shift them + // into the user's timezone before building the cache because they will + // be unavailable during their own local day. + foreach ($events as $event) { + $event->applyViewerTimezone($user); + } + $cursor = $min_range; if ($events) { // Find the next time when the user has no meetings. If we move forward @@ -420,7 +432,7 @@ final class PhabricatorPeopleQuery while (true) { foreach ($events as $event) { $from = $event->getDateFromForCache(); - $to = $event->getDateTo(); + $to = $event->getViewerDateTo(); if (($from <= $cursor) && ($to > $cursor)) { $cursor = $to; continue 2; diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index 81a5126474..acbf477eb4 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -832,6 +832,14 @@ final class PhabricatorUser return $offset; } + public function getTimeZoneOffsetInHours() { + $offset = $this->getTimeZoneOffset(); + $offset = (int)round($offset / 60); + $offset = -$offset; + + return $offset; + } + public function formatShortDateTime($when, $now = null) { if ($now === null) { $now = PhabricatorTime::getNow(); diff --git a/src/applications/phame/editor/PhameBlogEditor.php b/src/applications/phame/editor/PhameBlogEditor.php index ea1c132f64..14f63f51ac 100644 --- a/src/applications/phame/editor/PhameBlogEditor.php +++ b/src/applications/phame/editor/PhameBlogEditor.php @@ -160,6 +160,33 @@ final class PhameBlogEditor $error->setIsMissingFieldError(true); $errors[] = $error; } + + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (phutil_utf8_strlen($new) > 64) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'The selected blog title is too long. The maximum length '. + 'of a blog title is 64 characters.'), + $xaction); + } + } + break; + case PhameBlogTransaction::TYPE_SUBTITLE: + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (phutil_utf8_strlen($new) > 64) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'The selected blog subtitle is too long. The maximum length '. + 'of a blog subtitle is 64 characters.'), + $xaction); + } + } break; case PhameBlogTransaction::TYPE_PARENTDOMAIN: if (!$xactions) { diff --git a/src/applications/phid/view/PHUIHandleListView.php b/src/applications/phid/view/PHUIHandleListView.php index cc9f3d00bd..f7f02a391b 100644 --- a/src/applications/phid/view/PHUIHandleListView.php +++ b/src/applications/phid/view/PHUIHandleListView.php @@ -12,6 +12,7 @@ final class PHUIHandleListView private $handleList; private $asInline; + private $asText; public function setHandleList(PhabricatorHandleList $list) { $this->handleList = $list; @@ -27,6 +28,15 @@ final class PHUIHandleListView return $this->asInline; } + public function setAsText($as_text) { + $this->asText = $as_text; + return $this; + } + + public function getAsText() { + return $this->asText; + } + protected function getTagName() { // TODO: It would be nice to render this with a proper
    , at least in // block mode, but don't stir the waters up too much for now. @@ -37,9 +47,9 @@ final class PHUIHandleListView $list = $this->handleList; $items = array(); foreach ($list as $handle) { - $view = $list->renderHandle($handle->getPHID()); - - $view->setShowHovercard(true); + $view = $list->renderHandle($handle->getPHID()) + ->setShowHovercard(true) + ->setAsText($this->getAsText()); $items[] = $view; } diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php index acb533999b..7e814fc723 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php @@ -40,11 +40,15 @@ final class PhabricatorRepositoryManagementImportingWorkflow $rows = queryfx_all( $conn_r, 'SELECT repositoryID, commitIdentifier, importStatus FROM %T - WHERE repositoryID IN (%Ld) AND (importStatus & %d) != %d', + WHERE repositoryID IN (%Ld) + AND (importStatus & %d) != %d + AND (importStatus & %d) != %d', $table->getTableName(), array_keys($repos), PhabricatorRepositoryCommit::IMPORTED_ALL, - PhabricatorRepositoryCommit::IMPORTED_ALL); + PhabricatorRepositoryCommit::IMPORTED_ALL, + PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE, + PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE); $console = PhutilConsole::getConsole(); if ($rows) { diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php index f435861c60..1b52a7c0e4 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php @@ -152,15 +152,19 @@ final class PhabricatorRepositoryManagementUpdateWorkflow return; } - // Look for any commit which hasn't imported. + // Look for any commit which is reachable and hasn't imported. $unparsed_commit = queryfx_one( $repository->establishConnection('r'), - 'SELECT * FROM %T WHERE repositoryID = %d AND (importStatus & %d) != %d + 'SELECT * FROM %T WHERE repositoryID = %d + AND (importStatus & %d) != %d + AND (importStatus & %d) != %d LIMIT 1', id(new PhabricatorRepositoryCommit())->getTableName(), $repository->getID(), PhabricatorRepositoryCommit::IMPORTED_ALL, - PhabricatorRepositoryCommit::IMPORTED_ALL); + PhabricatorRepositoryCommit::IMPORTED_ALL, + PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE, + PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE); if ($unparsed_commit) { // We found a commit which still needs to import, so we can't clear the // flag. diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php index f6e2661b87..b565d30c55 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php @@ -29,8 +29,11 @@ abstract class PhabricatorRepositoryCommitParserWorker if ($commit->isUnreachable()) { throw new PhabricatorWorkerPermanentFailureException( pht( - 'Commit "%s" has been deleted: it is no longer reachable from '. - 'any ref.', + 'Commit "%s" (with internal ID "%s") is no longer reachable from '. + 'any branch, tag, or ref in this repository, so it will not be '. + 'imported. This usually means that the branch the commit was on '. + 'was deleted or overwritten.', + $commit->getMonogram(), $commit_id)); } diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index 688a7db527..b9ed90e80e 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -210,7 +210,7 @@ final class PhabricatorApplicationSearchController } $body[] = $box; - + $more_crumbs = null; if ($run_query) { $exec_errors = array(); @@ -272,6 +272,13 @@ final class PhabricatorApplicationSearchController $box->setCollapsed(true); } + $result_header = $list->getHeader(); + if ($result_header) { + $box->setHeader($result_header); + } + + $more_crumbs = $list->getCrumbs(); + if ($pager->willShowPagingControls()) { $pager_box = id(new PHUIBoxView()) ->addPadding(PHUI::PADDING_MEDIUM) @@ -301,8 +308,18 @@ final class PhabricatorApplicationSearchController } $crumbs = $parent - ->buildApplicationCrumbs() - ->addTextCrumb($title); + ->buildApplicationCrumbs(); + + if ($more_crumbs) { + $query_uri = $engine->getQueryResultsPageURI($saved_query->getQueryKey()); + $crumbs->addTextCrumb($title, $query_uri); + + foreach ($more_crumbs as $crumb) { + $crumbs->addCrumb($crumb); + } + } else { + $crumbs->addTextCrumb($title); + } return $this->newPage() ->setApplicationMenu($this->buildApplicationMenu()) diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index d7268facb3..3d6b4d9cff 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -712,50 +712,6 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { } - /** - * Read a list of project PHIDs from a request in a flexible way. - * - * @param AphrontRequest Request to read user PHIDs from. - * @param string Key to read in the request. - * @return list List of projet PHIDs and selector functions. - * @task read - */ - protected function readProjectsFromRequest(AphrontRequest $request, $key) { - $list = $this->readListFromRequest($request, $key); - - $phids = array(); - $slugs = array(); - $project_type = PhabricatorProjectProjectPHIDType::TYPECONST; - foreach ($list as $item) { - $type = phid_get_type($item); - if ($type == $project_type) { - $phids[] = $item; - } else { - if (PhabricatorTypeaheadDatasource::isFunctionToken($item)) { - // If this is a function, pass it through unchanged; we'll evaluate - // it later. - $phids[] = $item; - } else { - $slugs[] = $item; - } - } - } - - if ($slugs) { - $projects = id(new PhabricatorProjectQuery()) - ->setViewer($this->requireViewer()) - ->withSlugs($slugs) - ->execute(); - foreach ($projects as $project) { - $phids[] = $project->getPHID(); - } - $phids = array_unique($phids); - } - - return $phids; - } - - /** * Read a list of subscribers from a request in a flexible way. * @@ -849,19 +805,6 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return $list; } - protected function readDateFromRequest( - AphrontRequest $request, - $key) { - - $value = AphrontFormDateControlValue::newFromRequest($request, $key); - - if ($value->isEmpty()) { - return null; - } - - return $value->getDictionary(); - } - protected function readBoolFromRequest( AphrontRequest $request, $key) { diff --git a/src/applications/search/field/PhabricatorSearchDateField.php b/src/applications/search/field/PhabricatorSearchDateField.php index 84b7e2580a..21b1627de7 100644 --- a/src/applications/search/field/PhabricatorSearchDateField.php +++ b/src/applications/search/field/PhabricatorSearchDateField.php @@ -35,6 +35,14 @@ final class PhabricatorSearchDateField return null; } + // If this appears to be an epoch timestamp, just return it unmodified. + // This assumes values like "2016" or "20160101" are "Ymd". + if (is_int($value) || ctype_digit($value)) { + if ((int)$value > 30000000) { + return (int)$value; + } + } + return PhabricatorTime::parseLocalTime($value, $this->getViewer()); } diff --git a/src/applications/search/view/PhabricatorApplicationSearchResultView.php b/src/applications/search/view/PhabricatorApplicationSearchResultView.php index a6b8aa3b8b..5bcfdcf675 100644 --- a/src/applications/search/view/PhabricatorApplicationSearchResultView.php +++ b/src/applications/search/view/PhabricatorApplicationSearchResultView.php @@ -1,13 +1,11 @@ objectList = $list; @@ -84,4 +84,26 @@ final class PhabricatorApplicationSearchResultView extends Phobject { return $this; } + public function setCrumbs(array $crumbs) { + assert_instances_of($crumbs, 'PHUICrumbView'); + + $this->crumbs = $crumbs; + return $this; + } + + public function getCrumbs() { + return $this->crumbs; + } + + public function setHeader(PHUIHeaderView $header) { + $this->header = $header; + return $this; + } + + public function getHeader() { + return $this->header; + } + + + } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 181369a58f..2805162336 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -1176,6 +1176,8 @@ abstract class PhabricatorEditEngine $controller = $this->getController(); $request = $controller->getRequest(); + $fields = $this->willBuildEditForm($object, $fields); + $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('editEngine', 'true'); @@ -1210,6 +1212,10 @@ abstract class PhabricatorEditEngine return $form; } + protected function willBuildEditForm($object, array $fields) { + return $fields; + } + private function buildEditFormActionButton($object) { if (!$this->isEngineConfigurable()) { return null; diff --git a/src/applications/transactions/editfield/PhabricatorEpochEditField.php b/src/applications/transactions/editfield/PhabricatorEpochEditField.php index c5dabe6171..9ac9726593 100644 --- a/src/applications/transactions/editfield/PhabricatorEpochEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEpochEditField.php @@ -3,19 +3,41 @@ final class PhabricatorEpochEditField extends PhabricatorEditField { + private $allowNull; + private $hideTime; + + public function setAllowNull($allow_null) { + $this->allowNull = $allow_null; + return $this; + } + + public function getAllowNull() { + return $this->allowNull; + } + + public function setHideTime($hide_time) { + $this->hideTime = $hide_time; + return $this; + } + + public function getHideTime() { + return $this->hideTime; + } + protected function newControl() { return id(new AphrontFormDateControl()) + ->setAllowNull($this->getAllowNull()) + ->setIsTimeDisabled($this->getHideTime()) ->setViewer($this->getViewer()); } protected function newHTTPParameterType() { - return new AphrontEpochHTTPParameterType(); + return id(new AphrontEpochHTTPParameterType()) + ->setAllowNull($this->getAllowNull()); } protected function newConduitParameterType() { - // TODO: This isn't correct, but we don't have any methods which use this - // yet. - return new ConduitIntParameterType(); + return new ConduitEpochParameterType(); } } diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index a76f734c14..a35eb02cd3 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -86,6 +86,14 @@ abstract class PhabricatorModularTransactionType return $this->viewer; } + final public function getActor() { + return $this->getEditor()->getActor(); + } + + final public function getActingAsPHID() { + return $this->getEditor()->getActingAsPHID(); + } + final public function setEditor( PhabricatorApplicationTransactionEditor $editor) { $this->editor = $editor; @@ -133,8 +141,27 @@ abstract class PhabricatorModularTransactionType $viewer = $this->getViewer(); $display = $viewer->renderHandle($phid); - $rendering_target = $this->getStorage()->getRenderingTarget(); - if ($rendering_target == PhabricatorApplicationTransaction::TARGET_TEXT) { + if ($this->isTextMode()) { + $display->setAsText(true); + } + + return $display; + } + + final protected function renderOldHandle() { + return $this->renderHandle($this->getOldValue()); + } + + final protected function renderNewHandle() { + return $this->renderHandle($this->getNewValue()); + } + + final protected function renderHandleList(array $phids) { + $viewer = $this->getViewer(); + $display = $viewer->renderHandleList($phids) + ->setAsInline(true); + + if ($this->isTextMode()) { $display->setAsText(true); } @@ -142,8 +169,7 @@ abstract class PhabricatorModularTransactionType } final protected function renderValue($value) { - $rendering_target = $this->getStorage()->getRenderingTarget(); - if ($rendering_target == PhabricatorApplicationTransaction::TARGET_TEXT) { + if ($this->isTextMode()) { return sprintf('"%s"', $value); } @@ -155,6 +181,44 @@ abstract class PhabricatorModularTransactionType $value); } + final protected function renderOldValue() { + return $this->renderValue($this->getOldValue()); + } + + final protected function renderNewValue() { + return $this->renderValue($this->getNewValue()); + } + + final protected function renderDate($epoch) { + $viewer = $this->getViewer(); + + $display = phabricator_datetime($epoch, $viewer); + + // When rendering to text, we explicitly render the offset from UTC to + // provide context to the date: the mail may be generating with the + // server's settings, or the user may later refer back to it after changing + // timezones. + + if ($this->isTextMode()) { + $offset = $viewer->getTimeZoneOffsetInHours(); + if ($offset >= 0) { + $display = pht('%s (UTC+%d)', $display, $offset); + } else { + $display = pht('%s (UTC-%d)', $display, abs($offset)); + } + } + + return $this->renderValue($display); + } + + final protected function renderOldDate() { + return $this->renderDate($this->getOldValue()); + } + + final protected function renderNewDate() { + return $this->renderDate($this->getNewValue()); + } + final protected function newError($title, $message, $xaction = null) { return new PhabricatorApplicationTransactionValidationError( $this->getTransactionTypeConstant(), @@ -163,4 +227,30 @@ abstract class PhabricatorModularTransactionType $xaction); } + final protected function newRequiredError($message, $xaction = null) { + return $this->newError(pht('Required'), $message, $xaction) + ->setIsMissingFieldError(true); + } + + final protected function newInvalidError($message, $xaction = null) { + return $this->newError(pht('Invalid'), $message, $xaction); + } + + final protected function isNewObject() { + return $this->getEditor()->getIsNewObject(); + } + + final protected function isEmptyTextTransaction($value, array $xactions) { + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + } + + return !strlen($value); + } + + private function isTextMode() { + $target = $this->getStorage()->getRenderingTarget(); + return ($target == PhabricatorApplicationTransaction::TARGET_TEXT); + } + } diff --git a/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php b/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php deleted file mode 100644 index 3f42be9ce1..0000000000 --- a/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php +++ /dev/null @@ -1,71 +0,0 @@ -PHUIPagedFormView')); - } - - public function renderExample() { - $request = $this->getRequest(); - $user = $request->getUser(); - - - $page1 = id(new PHUIFormPageView()) - ->setPageName(pht('Page 1')) - ->addControl( - id(new AphrontFormTextControl()) - ->setName('page1') - ->setLabel(pht('Page 1'))); - - $page2 = id(new PHUIFormPageView()) - ->setPageName(pht('Page 2')) - ->addControl( - id(new AphrontFormTextControl()) - ->setName('page2') - ->setLabel(pht('Page 2'))); - - $page3 = id(new PHUIFormPageView()) - ->setPageName(pht('Page 3')) - ->addControl( - id(new AphrontFormTextControl()) - ->setName('page3') - ->setLabel(pht('Page 3'))); - - $page4 = id(new PHUIFormPageView()) - ->setPageName(pht('Page 4')) - ->addControl( - id(new AphrontFormTextControl()) - ->setName('page4') - ->setLabel(pht('Page 4'))); - - $form = new PHUIPagedFormView(); - $form->setUser($user); - - $form->addPage('page1', $page1); - $form->addPage('page2', $page2); - $form->addPage('page3', $page3); - $form->addPage('page4', $page4); - - if ($request->isFormPost()) { - $form->readFromRequest($request); - if ($form->isComplete()) { - return id(new AphrontDialogView()) - ->setUser($user) - ->setTitle(pht('Form Complete')) - ->appendChild(pht('You submitted the form. Well done!')) - ->addCancelButton($request->getRequestURI(), pht('Again!')); - } - } else { - $form->readFromObject(null); - } - - return $form; - } -} diff --git a/src/docs/flavor/so_many_databases.diviner b/src/docs/flavor/so_many_databases.diviner index 8fa41e6c97..74fb21332f 100644 --- a/src/docs/flavor/so_many_databases.diviner +++ b/src/docs/flavor/so_many_databases.diviner @@ -16,7 +16,7 @@ on shared hosts which require a lot of work to create or authorize access to each database. However, Phabricator does a lot of advanced or complex things which are difficult to configure or manage on shared hosts, and we don't recommend installing it on a shared host. The install documentation explicitly -discouarges installing on shared hosts. +discourages installing on shared hosts. Broadly, in cases where we must choose between operating well at scale for growing organizations and installing easily on shared hosts, we prioritize diff --git a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php index 2b54b9ab51..49e283c946 100644 --- a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php +++ b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php @@ -23,9 +23,11 @@ final class PhabricatorTaskmasterDaemon extends PhabricatorDaemon { $ex = $task->getExecutionException(); if ($ex) { if ($ex instanceof PhabricatorWorkerPermanentFailureException) { - throw new PhutilProxyException( - pht('Permanent failure while executing Task ID %d.', $id), - $ex); + $this->log( + pht( + 'Task %d was cancelled: %s', + $id, + $ex->getMessage())); } else if ($ex instanceof PhabricatorWorkerYieldException) { $this->log(pht('Task %s yielded.', $id)); } else { diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php index be2408e54a..01fa18b188 100644 --- a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php +++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php @@ -11,31 +11,57 @@ abstract class PhabricatorWorkerManagementWorkflow 'repeat' => true, 'help' => pht('Select one or more tasks by ID.'), ), + array( + 'name' => 'class', + 'param' => 'name', + 'help' => pht('Select all tasks of a given class.'), + ), ); } protected function loadTasks(PhutilArgumentParser $args) { $ids = $args->getArg('id'); - if (!$ids) { + $class = $args->getArg('class'); + + if (!$ids && !$class) { throw new PhutilArgumentUsageException( - pht('Use --id to select tasks by ID.')); + pht('Use --id or --class to select tasks.')); + } if ($ids && $class) { + throw new PhutilArgumentUsageException( + pht('Use one of --id or --class to select tasks, but not both.')); } - $active_tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere( - 'id IN (%Ls)', - $ids); - $archive_tasks = id(new PhabricatorWorkerArchiveTaskQuery()) - ->withIDs($ids) - ->execute(); + if ($ids) { + $active_tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere( + 'id IN (%Ls)', + $ids); + $archive_tasks = id(new PhabricatorWorkerArchiveTaskQuery()) + ->withIDs($ids) + ->execute(); + } else { + $active_tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere( + 'taskClass IN (%Ls)', + array($class)); + $archive_tasks = id(new PhabricatorWorkerArchiveTaskQuery()) + ->withClassNames(array($class)) + ->execute(); + } $tasks = mpull($active_tasks, null, 'getID') + mpull($archive_tasks, null, 'getID'); - foreach ($ids as $id) { - if (empty($tasks[$id])) { + if ($ids) { + foreach ($ids as $id) { + if (empty($tasks[$id])) { + throw new PhutilArgumentUsageException( + pht('No task exists with id "%s"!', $id)); + } + } + } else { + if (!$tasks) { throw new PhutilArgumentUsageException( - pht('No task exists with id "%s"!', $id)); + pht('No task exists with class "%s"!', $class)); } } diff --git a/src/infrastructure/daemon/workers/query/PhabricatorWorkerArchiveTaskQuery.php b/src/infrastructure/daemon/workers/query/PhabricatorWorkerArchiveTaskQuery.php index b0aa6addd3..69abac27b0 100644 --- a/src/infrastructure/daemon/workers/query/PhabricatorWorkerArchiveTaskQuery.php +++ b/src/infrastructure/daemon/workers/query/PhabricatorWorkerArchiveTaskQuery.php @@ -7,6 +7,7 @@ final class PhabricatorWorkerArchiveTaskQuery private $dateModifiedSince; private $dateCreatedBefore; private $objectPHIDs; + private $classNames; private $limit; public function withIDs(array $ids) { @@ -29,6 +30,11 @@ final class PhabricatorWorkerArchiveTaskQuery return $this; } + public function withClassNames(array $names) { + $this->classNames = $names; + return $this; + } + public function setLimit($limit) { $this->limit = $limit; return $this; @@ -67,20 +73,27 @@ final class PhabricatorWorkerArchiveTaskQuery $this->objectPHIDs); } - if ($this->dateModifiedSince) { + if ($this->dateModifiedSince !== null) { $where[] = qsprintf( $conn_r, 'dateModified > %d', $this->dateModifiedSince); } - if ($this->dateCreatedBefore) { + if ($this->dateCreatedBefore !== null) { $where[] = qsprintf( $conn_r, 'dateCreated < %d', $this->dateCreatedBefore); } + if ($this->classNames !== null) { + $where[] = qsprintf( + $conn_r, + 'taskClass IN (%Ls)', + $this->classNames); + } + return $this->formatWhereClause($where); } diff --git a/src/infrastructure/graph/ManiphestTaskGraph.php b/src/infrastructure/graph/ManiphestTaskGraph.php index 1568c8c656..2fe62af599 100644 --- a/src/infrastructure/graph/ManiphestTaskGraph.php +++ b/src/infrastructure/graph/ManiphestTaskGraph.php @@ -3,6 +3,8 @@ final class ManiphestTaskGraph extends PhabricatorObjectGraph { + private $seedMaps = array(); + protected function getEdgeTypes() { return array( ManiphestTaskDependedOnByTaskEdgeType::EDGECONST, @@ -57,7 +59,12 @@ final class ManiphestTaskGraph $object->getTitle()); $link = array( - $object->getMonogram(), + phutil_tag( + 'span', + array( + 'class' => 'object-name', + ), + $object->getMonogram()), ' ', $link, ); @@ -67,9 +74,31 @@ final class ManiphestTaskGraph $link = $viewer->renderHandle($phid); } + if ($this->isParentTask($phid)) { + $marker = 'fa-chevron-circle-up bluegrey'; + $marker_tip = pht('Direct Parent'); + } else if ($this->isChildTask($phid)) { + $marker = 'fa-chevron-circle-down bluegrey'; + $marker_tip = pht('Direct Subtask'); + } else { + $marker = null; + } + + if ($marker) { + $marker = id(new PHUIIconView()) + ->setIcon($marker) + ->addSigil('has-tooltip') + ->setMetadata( + array( + 'tip' => $marker_tip, + 'align' => 'E', + )); + } + $link = AphrontTableView::renderSingleDisplayLine($link); return array( + $marker, $trace, $status, $assigned, @@ -81,6 +110,7 @@ final class ManiphestTaskGraph return $table ->setHeaders( array( + null, null, pht('Status'), pht('Assigned'), @@ -88,6 +118,7 @@ final class ManiphestTaskGraph )) ->setColumnClasses( array( + 'nudgeright', 'threads', 'graph-status', null, @@ -95,4 +126,36 @@ final class ManiphestTaskGraph )); } + private function isParentTask($task_phid) { + $map = $this->getSeedMap(ManiphestTaskDependedOnByTaskEdgeType::EDGECONST); + return isset($map[$task_phid]); + } + + private function isChildTask($task_phid) { + $map = $this->getSeedMap(ManiphestTaskDependsOnTaskEdgeType::EDGECONST); + return isset($map[$task_phid]); + } + + private function getSeedMap($type) { + if (!isset($this->seedMaps[$type])) { + $maps = $this->getEdges($type); + $phids = idx($maps, $this->getSeedPHID(), array()); + $phids = array_fuse($phids); + $this->seedMaps[$type] = $phids; + } + + return $this->seedMaps[$type]; + } + + protected function newEllipsisRow() { + return array( + null, + null, + null, + null, + pht("\xC2\xB7 \xC2\xB7 \xC2\xB7"), + ); + } + + } diff --git a/src/infrastructure/graph/PhabricatorObjectGraph.php b/src/infrastructure/graph/PhabricatorObjectGraph.php index 7a3790dea4..48e0fcbe73 100644 --- a/src/infrastructure/graph/PhabricatorObjectGraph.php +++ b/src/infrastructure/graph/PhabricatorObjectGraph.php @@ -9,6 +9,8 @@ abstract class PhabricatorObjectGraph private $seedPHID; private $objects; private $loadEntireGraph = false; + private $limit; + private $adjacent; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -23,6 +25,24 @@ abstract class PhabricatorObjectGraph return $this->viewer; } + public function setLimit($limit) { + $this->limit = $limit; + return $this; + } + + public function getLimit() { + return $this->limit; + } + + final public function setRenderOnlyAdjacentNodes($adjacent) { + $this->adjacent = $adjacent; + return $this; + } + + final public function getRenderOnlyAdjacentNodes() { + return $this->adjacent; + } + abstract protected function getEdgeTypes(); abstract protected function getParentEdgeType(); abstract protected function newQuery(); @@ -30,6 +50,12 @@ abstract class PhabricatorObjectGraph abstract protected function newTable(AphrontTableView $table); abstract protected function isClosed($object); + protected function newEllipsisRow() { + return array( + '...', + ); + } + final public function setSeedPHID($phid) { $this->seedPHID = $phid; $this->edgeReach[$phid] = array_fill_keys($this->getEdgeTypes(), true); @@ -40,10 +66,24 @@ abstract class PhabricatorObjectGraph )); } + final public function getSeedPHID() { + return $this->seedPHID; + } + final public function isEmpty() { return (count($this->getNodes()) <= 2); } + final public function isOverLimit() { + $limit = $this->getLimit(); + + if (!$limit) { + return false; + } + + return (count($this->edgeReach) > $limit); + } + final public function getEdges($type) { $edges = idx($this->edges, $type, array()); @@ -72,6 +112,10 @@ abstract class PhabricatorObjectGraph } final protected function loadEdges(array $nodes) { + if ($this->isOverLimit()) { + return array_fill_keys($nodes, array()); + } + $edge_types = $this->getEdgeTypes(); $query = id(new PhabricatorEdgeQuery()) @@ -111,6 +155,32 @@ abstract class PhabricatorObjectGraph $ancestry = $this->getEdges($this->getParentEdgeType()); + $only_adjacent = $this->getRenderOnlyAdjacentNodes(); + if ($only_adjacent) { + $adjacent = array( + $this->getSeedPHID() => $this->getSeedPHID(), + ); + + foreach ($this->getEdgeTypes() as $edge_type) { + $map = $this->getEdges($edge_type); + $direct = idx($map, $this->getSeedPHID(), array()); + $adjacent += array_fuse($direct); + } + + foreach ($ancestry as $key => $list) { + if (!isset($adjacent[$key])) { + unset($ancestry[$key]); + continue; + } + + foreach ($list as $list_key => $item) { + if (!isset($adjacent[$item])) { + unset($ancestry[$key][$list_key]); + } + } + } + } + $objects = $this->newQuery() ->setViewer($viewer) ->withPHIDs(array_keys($ancestry)) @@ -129,6 +199,12 @@ abstract class PhabricatorObjectGraph $ii = 0; $rows = array(); $rowc = array(); + + if ($only_adjacent) { + $rows[] = $this->newEllipsisRow(); + $rowc[] = 'more'; + } + foreach ($ancestry as $phid => $ignored) { $object = idx($objects, $phid); $rows[] = $this->newTableRow($phid, $object, $traces[$ii++]); @@ -153,6 +229,11 @@ abstract class PhabricatorObjectGraph $rowc[] = $classes; } + if ($only_adjacent) { + $rows[] = $this->newEllipsisRow(); + $rowc[] = 'more'; + } + $table = id(new AphrontTableView($rows)) ->setClassName('object-graph-table') ->setRowClasses($rowc); diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 23cb079297..3b4cf6abc7 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1560,6 +1560,24 @@ final class PhabricatorUSEnglishTranslation ), ), ), + + '%s invited %s attendee(s): %s.' => + '%s invited: %3$s.', + + '%s uninvited %s attendee(s): %s.' => + '%s uninvited: %3$s.', + + '%s invited %s attendee(s): %s; uninvinted %s attendee(s): %s.' => + '%s invited: %3$s; uninvited: %5$s.', + + '%s invited %s attendee(s) to %s: %s.' => + '%s added invites for %3$s: %4$s.', + + '%s uninvited %s attendee(s) to %s: %s.' => + '%s removed invites for %3$s: %4$s.', + + '%s updated the invite list for %s, invited %s: %s; uninvinted %s: %s.' => + '%s updated the invite list for %s, invited: %4$s; uninvited: %6$s.', ); } diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 0efca33b6a..8d43980122 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -142,11 +142,18 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } final protected function buildLimitClause(AphrontDatabaseConnection $conn_r) { - if ($this->getRawResultLimit()) { - return qsprintf($conn_r, 'LIMIT %d', $this->getRawResultLimit()); - } else { - return ''; + if ($this->shouldLimitResults()) { + $limit = $this->getRawResultLimit(); + if ($limit) { + return qsprintf($conn_r, 'LIMIT %d', $limit); + } } + + return ''; + } + + protected function shouldLimitResults() { + return true; } final protected function didLoadResults(array $results) { diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php index 3cf85e5c35..45f4d2d8e8 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php @@ -31,7 +31,7 @@ final class PhabricatorStorageManagementShellWorkflow } return phutil_passthru( - 'mysql --default-character-set=utf8 '. + 'mysql --protocol=TCP --default-character-set=utf8 '. '-u %s %C -h %s %C', $api->getUser(), $flag_password, diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index ce3d8da981..6bdb14fe8a 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -111,6 +111,7 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { 'db.spaces' => array(), 'db.phurl' => array(), 'db.badges' => array(), + 'db.packages' => array(), '0000.legacy.sql' => array( 'legacy' => 0, ), diff --git a/src/view/form/PHUIFormPageView.php b/src/view/form/PHUIFormPageView.php deleted file mode 100644 index aeff9e38f5..0000000000 --- a/src/view/form/PHUIFormPageView.php +++ /dev/null @@ -1,221 +0,0 @@ -pageName = $page_name; - return $this; - } - - public function getPageName() { - return $this->pageName; - } - - public function addPageError($page_error) { - $this->pageErrors[] = $page_error; - return $this; - } - - public function getPageErrors() { - return $this->pageErrors; - } - - public function setAdjustFormPageCallback($adjust_form_page_callback) { - $this->adjustFormPageCallback = $adjust_form_page_callback; - return $this; - } - - public function setValidateFormPageCallback($validate_form_page_callback) { - $this->validateFormPageCallback = $validate_form_page_callback; - return $this; - } - - public function addInstructions($text, $before = null) { - $tag = phutil_tag( - 'div', - array( - 'class' => 'aphront-form-instructions', - ), - $text); - - $append = true; - if ($before !== null) { - for ($ii = 0; $ii < count($this->content); $ii++) { - if ($this->content[$ii] instanceof AphrontFormControl) { - if ($this->content[$ii]->getName() == $before) { - array_splice($this->content, $ii, 0, array($tag)); - $append = false; - break; - } - } - } - } - - if ($append) { - $this->content[] = $tag; - } - - return $this; - } - - public function addRemarkupInstructions($remarkup, $before = null) { - $remarkup = new PHUIRemarkupView($this->getUser(), $remarkup); - return $this->addInstructions($remarkup, $before); - } - - public function addControl(AphrontFormControl $control) { - $name = $control->getName(); - - if (!strlen($name)) { - throw new Exception(pht('Form control has no name!')); - } - - if (isset($this->controls[$name])) { - throw new Exception( - pht("Form page contains duplicate control with name '%s'!", $name)); - } - - $this->controls[$name] = $control; - $this->content[] = $control; - $control->setFormPage($this); - - return $this; - } - - public function getControls() { - return $this->controls; - } - - public function getControl($name) { - if (empty($this->controls[$name])) { - throw new Exception(pht("No page control '%s'!", $name)); - } - return $this->controls[$name]; - } - - protected function canAppendChild() { - return false; - } - - public function setPagedFormView(PHUIPagedFormView $view, $key) { - if ($this->key) { - throw new Exception(pht('This page is already part of a form!')); - } - $this->form = $view; - $this->key = $key; - return $this; - } - - public function adjustFormPage() { - if ($this->adjustFormPageCallback) { - call_user_func($this->adjustFormPageCallback, $this); - } - return $this; - } - - protected function validateFormPage() { - if ($this->validateFormPageCallback) { - return call_user_func($this->validateFormPageCallback, $this); - } - return true; - } - - public function getKey() { - return $this->key; - } - - public function render() { - return $this->content; - } - - public function getForm() { - return $this->form; - } - - public function getRequestKey($key) { - return $this->getForm()->getRequestKey('p:'.$this->key.':'.$key); - } - - public function validateObjectType($object) { - return true; - } - - public function validateResponseType($response) { - return true; - } - - protected function validateControls() { - $result = true; - foreach ($this->getControls() as $name => $control) { - if (!$control->isValid()) { - $result = false; - break; - } - } - - return $result; - } - - public function isValid() { - if ($this->isValid === null) { - $this->isValid = $this->validateControls() && $this->validateFormPage(); - } - return $this->isValid; - } - - public function readFromRequest(AphrontRequest $request) { - foreach ($this->getControls() as $name => $control) { - $control->readValueFromRequest($request); - } - - return $this; - } - - public function readFromObject($object) { - foreach ($this->getControls() as $name => $control) { - if (is_array($object)) { - $control->readValueFromDictionary($object); - } - } - - return $this; - } - - public function writeToResponse($response) { - return $this; - } - - public function readSerializedValues(AphrontRequest $request) { - foreach ($this->getControls() as $name => $control) { - $key = $this->getRequestKey($name); - $control->readSerializedValue($request->getStr($key)); - } - - return $this; - } - - public function getSerializedValues() { - $dict = array(); - foreach ($this->getControls() as $name => $control) { - $key = $this->getRequestKey($name); - $dict[$key] = $control->getSerializedValue(); - } - return $dict; - } - -} diff --git a/src/view/form/PHUIPagedFormView.php b/src/view/form/PHUIPagedFormView.php deleted file mode 100644 index cfd18add58..0000000000 --- a/src/view/form/PHUIPagedFormView.php +++ /dev/null @@ -1,279 +0,0 @@ -pages[$key])) { - throw new Exception(pht("Duplicate page with key '%s'!", $key)); - } - $this->pages[$key] = $page; - $page->setPagedFormView($this, $key); - - $this->selectedPage = null; - $this->complete = null; - - return $this; - } - - - /** - * @task page - */ - public function getPage($key) { - if (!$this->pageExists($key)) { - throw new Exception(pht("No page '%s' exists!", $key)); - } - return $this->pages[$key]; - } - - - /** - * @task page - */ - public function pageExists($key) { - return isset($this->pages[$key]); - } - - - /** - * @task page - */ - protected function getPageIndex($key) { - $page = $this->getPage($key); - - $index = 0; - foreach ($this->pages as $target_page) { - if ($page === $target_page) { - break; - } - $index++; - } - - return $index; - } - - - /** - * @task page - */ - protected function getPageByIndex($index) { - foreach ($this->pages as $page) { - if (!$index) { - return $page; - } - $index--; - } - - throw new Exception(pht("Requesting out-of-bounds page '%s'.", $index)); - } - - protected function getLastIndex() { - return count($this->pages) - 1; - } - - protected function isFirstPage(PHUIFormPageView $page) { - return ($this->getPageIndex($page->getKey()) === 0); - - } - - protected function isLastPage(PHUIFormPageView $page) { - return ($this->getPageIndex($page->getKey()) === (count($this->pages) - 1)); - } - - public function getSelectedPage() { - return $this->selectedPage; - } - - public function readFromObject($object) { - return $this->processForm($is_request = false, $object); - } - - public function writeToResponse($response) { - foreach ($this->pages as $page) { - $page->validateResponseType($response); - $response = $page->writeToResponse($page); - } - - return $response; - } - - public function readFromRequest(AphrontRequest $request) { - $this->choosePage = $request->getStr($this->getRequestKey('page')); - $this->nextPage = $request->getStr('__submit__'); - $this->prevPage = $request->getStr('__back__'); - - return $this->processForm($is_request = true, $request); - } - - public function setName($name) { - $this->name = $name; - return $this; - } - - - public function getValue($page, $key, $default = null) { - return $this->getPage($page)->getValue($key, $default); - } - - public function setValue($page, $key, $value) { - $this->getPage($page)->setValue($key, $value); - return $this; - } - - private function processForm($is_request, $source) { - if ($this->pageExists($this->choosePage)) { - $selected = $this->getPage($this->choosePage); - } else { - $selected = $this->getPageByIndex(0); - } - - $is_attempt_complete = false; - if ($this->prevPage) { - $prev_index = $this->getPageIndex($selected->getKey()) - 1; - $index = max(0, $prev_index); - $selected = $this->getPageByIndex($index); - } else if ($this->nextPage) { - $next_index = $this->getPageIndex($selected->getKey()) + 1; - if ($next_index > $this->getLastIndex()) { - $is_attempt_complete = true; - } - $index = min($this->getLastIndex(), $next_index); - $selected = $this->getPageByIndex($index); - } - - $validation_error = false; - $found_selected = false; - foreach ($this->pages as $key => $page) { - if ($is_request) { - if ($key === $this->choosePage) { - $page->readFromRequest($source); - } else { - $page->readSerializedValues($source); - } - } else { - $page->readFromObject($source); - } - - if (!$found_selected) { - $page->adjustFormPage(); - } - - if ($page === $selected) { - $found_selected = true; - } - - if (!$found_selected || $is_attempt_complete) { - if (!$page->isValid()) { - $selected = $page; - $validation_error = true; - break; - } - } - } - - if ($is_attempt_complete && !$validation_error) { - $this->complete = true; - } else { - $this->selectedPage = $selected; - } - - return $this; - } - - public function isComplete() { - return $this->complete; - } - - public function getRequestKey($key) { - return $this->name.':'.$key; - } - - public function setCancelURI($cancel_uri) { - $this->cancelURI = $cancel_uri; - return $this; - } - - public function getCancelURI() { - return $this->cancelURI; - } - - public function render() { - $form = id(new AphrontFormView()) - ->setUser($this->getUser()); - - $selected_page = $this->getSelectedPage(); - if (!$selected_page) { - throw new Exception(pht('No selected page!')); - } - - $form->addHiddenInput( - $this->getRequestKey('page'), - $selected_page->getKey()); - - $errors = array(); - - foreach ($this->pages as $page) { - if ($page == $selected_page) { - $errors = $page->getPageErrors(); - continue; - } - foreach ($page->getSerializedValues() as $key => $value) { - $form->addHiddenInput($key, $value); - } - } - - $submit = id(new PHUIFormMultiSubmitControl()); - - if (!$this->isFirstPage($selected_page)) { - $submit->addBackButton(); - } else if ($this->getCancelURI()) { - $submit->addCancelButton($this->getCancelURI()); - } - - if ($this->isLastPage($selected_page)) { - $submit->addSubmitButton(pht('Save')); - } else { - $submit->addSubmitButton(pht('Continue')." \xC2\xBB"); - } - - $form->appendChild($selected_page); - $form->appendChild($submit); - - $box = id(new PHUIObjectBoxView()) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setFormErrors($errors) - ->setForm($form); - - if ($selected_page->getPageName()) { - $header = id(new PHUIHeaderView()) - ->setHeader($selected_page->getPageName()); - $box->setHeader($header); - } - - return $box; - } - -} diff --git a/src/view/form/control/AphrontFormControl.php b/src/view/form/control/AphrontFormControl.php index 16e36be663..0125fa87b3 100644 --- a/src/view/form/control/AphrontFormControl.php +++ b/src/view/form/control/AphrontFormControl.php @@ -11,7 +11,6 @@ abstract class AphrontFormControl extends AphrontView { private $id; private $controlID; private $controlStyle; - private $formPage; private $required; private $hidden; private $classes; @@ -132,21 +131,6 @@ abstract class AphrontFormControl extends AphrontView { return $this; } - public function setFormPage(PHUIFormPageView $page) { - if ($this->formPage) { - throw new Exception(pht('This control is already a member of a page!')); - } - $this->formPage = $page; - return $this; - } - - public function getFormPage() { - if ($this->formPage === null) { - throw new Exception(pht('This control does not have a page!')); - } - return $this->formPage; - } - public function setDisabled($disabled) { $this->disabled = $disabled; return $this; diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php index aeb35cdb15..f9dc14b238 100644 --- a/src/view/form/control/AphrontFormDateControlValue.php +++ b/src/view/form/control/AphrontFormDateControlValue.php @@ -59,27 +59,6 @@ final class AphrontFormDateControlValue extends Phobject { return $this->viewer; } - public static function newFromParts( - PhabricatorUser $viewer, - $year, - $month, - $day, - $time = null, - $enabled = true) { - - $value = new AphrontFormDateControlValue(); - $value->viewer = $viewer; - list($value->valueDate, $value->valueTime) = - $value->getFormattedDateFromParts( - $year, - $month, - $day, - coalesce($time, '12:00 AM')); - $value->valueEnabled = $enabled; - - return $value; - } - public static function newFromRequest(AphrontRequest $request, $key) { $value = new AphrontFormDateControlValue(); $value->viewer = $request->getViewer(); diff --git a/src/view/form/control/PHUIFormMultiSubmitControl.php b/src/view/form/control/PHUIFormMultiSubmitControl.php deleted file mode 100644 index 4c7454bd6d..0000000000 --- a/src/view/form/control/PHUIFormMultiSubmitControl.php +++ /dev/null @@ -1,61 +0,0 @@ -addButton('__back__', $label, 'grey'); - } - - public function addSubmitButton($label) { - return $this->addButton('__submit__', $label); - } - - public function addCancelButton($uri, $label = null) { - if ($label === null) { - $label = pht('Cancel'); - } - - $this->buttons[] = phutil_tag( - 'a', - array( - 'class' => 'grey button', - 'href' => $uri, - ), - $label); - - return $this; - } - - public function addButtonView(PHUIButtonView $button) { - $this->buttons[] = $button; - return $this; - } - - public function addButton($name, $label, $class = null) { - $this->buttons[] = javelin_tag( - 'input', - array( - 'type' => 'submit', - 'name' => $name, - 'value' => $label, - 'class' => $class, - 'sigil' => 'alternate-submit-button', - 'disabled' => $this->getDisabled() ? 'disabled' : null, - )); - return $this; - } - - protected function getCustomControlClass() { - return 'phui-form-control-multi-submit'; - } - - protected function renderInput() { - return array_reverse($this->buttons); - } - -} diff --git a/src/view/form/control/PHUIFormNumberControl.php b/src/view/form/control/PHUIFormNumberControl.php new file mode 100644 index 0000000000..d65e590746 --- /dev/null +++ b/src/view/form/control/PHUIFormNumberControl.php @@ -0,0 +1,22 @@ + 'text', + 'pattern' => '\d*', + 'name' => $this->getName(), + 'value' => $this->getValue(), + 'disabled' => $this->getDisabled() ? 'disabled' : null, + 'id' => $this->getID(), + )); + } + +} diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index 4ada03240d..1c56886f7a 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -48,7 +48,10 @@ final class PHUITwoColumnView extends AphrontTagView { } public function addPropertySection($title, $section) { - $this->propertySection[] = array($title, $section); + $this->propertySection[] = array( + 'header' => $title, + 'content' => $section, + ); return $this; } @@ -146,13 +149,25 @@ final class PHUITwoColumnView extends AphrontTagView { $sections = $this->propertySection; if ($sections) { - foreach ($sections as $content) { - if ($content[1]) { - $view[] = id(new PHUIObjectBoxView()) - ->setHeaderText($content[0]) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($content[1]); + foreach ($sections as $section) { + $section_header = $section['header']; + + $section_content = $section['content']; + if ($section_content === null) { + continue; } + + if ($section_header instanceof PHUIHeaderView) { + $header = $section_header; + } else { + $header = id(new PHUIHeaderView()) + ->setHeader($section_header); + } + + $view[] = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($section_content); } } diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index d7b5f9bda2..a2cab191a5 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -61,7 +61,7 @@ final class PHUICalendarDayView extends AphrontView { foreach ($hours as $hour) { $js_hours[] = array( 'hour' => $hour->format('G'), - 'hour_meridian' => $hour->format('g A'), + 'displayTime' => phabricator_time($hour->format('U'), $viewer), ); } @@ -85,6 +85,8 @@ final class PHUICalendarDayView extends AphrontView { 'id' => $all_day_event->getEventID(), 'viewerIsInvited' => $all_day_event->getViewerIsInvited(), 'uri' => $all_day_event->getURI(), + 'displayIcon' => $all_day_event->getIcon(), + 'displayIconColor' => $all_day_event->getIconColor(), ); } } @@ -140,6 +142,8 @@ final class PHUICalendarDayView extends AphrontView { 'top' => $top.'px', 'height' => $height.'px', 'canEdit' => $event->getCanEdit(), + 'displayIcon' => $event->getIcon(), + 'displayIconColor' => $event->getIconColor(), ); } } @@ -183,17 +187,11 @@ final class PHUICalendarDayView extends AphrontView { ->setFlush(true); $layout = id(new AphrontMultiColumnView()) - ->addColumn($sidebar, 'third') + ->addColumn($sidebar, 'third phui-day-view-upcoming') ->addColumn($table_box, 'thirds phui-day-view-column') - ->setFluidLayout(true) - ->setGutter(AphrontMultiColumnView::GUTTER_MEDIUM); + ->setFluidLayout(true); - return phutil_tag( - 'div', - array( - 'class' => 'ml', - ), - $layout); + return $layout; } private function getAllDayEvents() { diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php index 7dd921d769..88bbde351a 100644 --- a/src/view/phui/calendar/PHUICalendarListView.php +++ b/src/view/phui/calendar/PHUICalendarListView.php @@ -5,6 +5,16 @@ final class PHUICalendarListView extends AphrontTagView { private $events = array(); private $blankState; private $view; + private $moreLink; + + public function setMoreLink($more_link) { + $this->moreLink = $more_link; + return $this; + } + + public function getMoreLink() { + return $this->moreLink; + } private function getView() { return $this->view; @@ -32,7 +42,11 @@ final class PHUICalendarListView extends AphrontTagView { protected function getTagAttributes() { require_celerity_resource('phui-calendar-css'); require_celerity_resource('phui-calendar-list-css'); - return array('class' => 'phui-calendar-event-list'); + + return array( + 'sigil' => 'calendar-event-list', + 'class' => 'phui-calendar-event-list', + ); } protected function getTagContent() { @@ -53,14 +67,11 @@ final class PHUICalendarListView extends AphrontTagView { $this->getUser()); } - if ($event->getViewerIsInvited()) { - $icon_color = 'green'; - } else { - $icon_color = null; - } + $icon_icon = $event->getIcon(); + $icon_color = $event->getIconColor(); - $dot = id(new PHUIIconView()) - ->setIcon($event->getIcon(), $icon_color) + $icon = id(new PHUIIconView()) + ->setIcon($icon_icon, $icon_color) ->addClass('phui-calendar-list-item-icon'); $title = phutil_tag( @@ -76,12 +87,14 @@ final class PHUICalendarListView extends AphrontTagView { ), $timelabel); - $class = 'phui-calendar-list-item'; - if ($event->getViewerIsInvited()) { - $class = $class.' phui-calendar-viewer-invited'; - } + $event_classes = array(); + $event_classes[] = 'phui-calendar-list-item'; if ($event->getIsAllDay()) { - $class = $class.' all-day'; + $event_classes[] = 'all-day'; + } + + if ($event->getIsCancelled()) { + $event_classes[] = 'event-cancelled'; } $tip = $this->getEventTooltip($event); @@ -92,6 +105,7 @@ final class PHUICalendarListView extends AphrontTagView { } else { $tip_align = 'W'; } + $content = javelin_tag( 'a', array( @@ -104,7 +118,7 @@ final class PHUICalendarListView extends AphrontTagView { ), ), array( - $dot, + $icon, $time, $title, )); @@ -112,11 +126,34 @@ final class PHUICalendarListView extends AphrontTagView { $singletons[] = phutil_tag( 'li', array( - 'class' => $class, + 'class' => implode(' ', $event_classes), ), $content); } + if ($this->moreLink) { + $singletons[] = phutil_tag( + 'li', + array( + 'class' => 'phui-calendar-list-item', + ), + phutil_tag( + 'a', + array( + 'href' => $this->moreLink, + 'class' => 'phui-calendar-list-more', + ), + array( + id(new PHUIIconView())->setIcon('fa-ellipsis-h grey'), + phutil_tag( + 'span', + array( + 'class' => 'phui-calendar-list-title', + ), + pht('View More...')), + ))); + } + if (empty($singletons)) { $singletons[] = phutil_tag( 'li', diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index da7220f015..61fd8b1e99 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -53,6 +53,8 @@ final class PHUICalendarMonthView extends AphrontView { public function render() { $viewer = $this->getViewer(); + Javelin::initBehavior('calendar-month-view'); + $events = msort($this->events, 'getEpochStart'); $days = $this->getDatesInMonth(); @@ -63,7 +65,6 @@ final class PHUICalendarMonthView extends AphrontView { foreach ($days as $day) { $day_number = $day->format('j'); - $class = 'phui-calendar-month-day'; $weekday = $day->format('w'); $day->setTime(0, 0, 0); @@ -94,13 +95,13 @@ final class PHUICalendarMonthView extends AphrontView { ->setViewer($viewer) ->setView('month'); foreach ($all_day_events as $item) { - if ($counter <= $max_daily) { + if ($counter < $max_daily) { $list->addEvent($item); } $counter++; } foreach ($list_events as $item) { - if ($counter <= $max_daily) { + if ($counter < $max_daily) { $list->addEvent($item); } $counter++; @@ -111,32 +112,60 @@ final class PHUICalendarMonthView extends AphrontView { $day->format('m').'/'. $day->format('d').'/'; - $cell_lists[] = array( + if ($counter > $max_daily) { + $list->setMoreLink($uri); + } + + $day_id = $day->format('Ymd'); + + $classes = array(); + if ($day->format('m') != $this->month) { + $classes[] = 'phui-calendar-month-adjacent'; + } + $classes = implode(' ', $classes); + + $cell_lists[$day_id] = array( + 'dayID' => $day_id, 'list' => $list, 'date' => $day, - 'uri' => $uri, + 'dayURI' => $uri, 'count' => count($all_day_events) + count($list_events), - 'class' => $class, - ); + 'class' => $classes, + ); } $rows = array(); - $cell_lists_by_week = array_chunk($cell_lists, 7); - + $cell_lists_by_week = array_chunk($cell_lists, 7, true); foreach ($cell_lists_by_week as $week_of_cell_lists) { $cells = array(); - $max_count = $this->getMaxDailyEventsForWeek($week_of_cell_lists); + $action_map = array(); + foreach ($week_of_cell_lists as $day_id => $cell_list) { + $cells[] = $this->getEventListCell($cell_list); - foreach ($week_of_cell_lists as $cell_list) { - $cells[] = $this->getEventListCell($cell_list, $max_count); + $action_map[$day_id] = array( + 'dayURI' => $cell_list['dayURI'], + ); } - $rows[] = phutil_tag('tr', array(), $cells); + $rows[] = javelin_tag( + 'tr', + array( + 'sigil' => 'calendar-week calendar-week-body', + 'meta' => array( + 'actionMap' => $action_map, + ), + ), + $cells); $cells = array(); - foreach ($week_of_cell_lists as $cell_list) { + foreach ($week_of_cell_lists as $day_id => $cell_list) { $cells[] = $this->getDayNumberCell($cell_list); } - $rows[] = phutil_tag('tr', array(), $cells); + $rows[] = javelin_tag( + 'tr', + array( + 'sigil' => 'calendar-week calendar-week-foot', + ), + $cells); } $header = $this->getDayNamesHeader(); @@ -175,51 +204,54 @@ final class PHUICalendarMonthView extends AphrontView { return $max_count; } - private function getEventListCell($event_list, $max_count = 0) { + private function getEventListCell($event_list) { $list = $event_list['list']; $class = $event_list['class']; - $uri = $event_list['uri']; $count = $event_list['count']; $viewer_is_invited = $list->getIsViewerInvitedOnList(); - $event_count_badge = $this->getEventCountBadge($count, $viewer_is_invited); - $cell_day_secret_link = $this->getHiddenDayLink($uri, $max_count, 125); - $cell_data_div = phutil_tag( + $cell_content = phutil_tag( 'div', array( 'class' => 'phui-calendar-month-cell-div', ), array( - $cell_day_secret_link, $event_count_badge, $list, )); - return phutil_tag( + $cell_meta = array( + 'dayID' => $event_list['dayID'], + ); + + $classes = array(); + $classes[] = 'phui-calendar-month-event-list'; + $classes[] = 'phui-calendar-month-day'; + $classes[] = $event_list['class']; + $classes = implode(' ', $classes); + + return javelin_tag( 'td', array( - 'class' => 'phui-calendar-month-event-list '.$class, + 'class' => $classes, + 'meta' => $cell_meta, ), - $cell_data_div); + $cell_content); } private function getDayNumberCell($event_list) { $class = $event_list['class']; $date = $event_list['date']; - $cell_day_secret_link = null; $week_number = null; if ($date) { - $uri = $event_list['uri']; - $cell_day_secret_link = $this->getHiddenDayLink($uri, 0, 25); - $cell_day = phutil_tag( 'a', array( 'class' => 'phui-calendar-date-number', - 'href' => $uri, + 'href' => $event_list['dayURI'], ), $date->format('j')); @@ -228,7 +260,7 @@ final class PHUICalendarMonthView extends AphrontView { 'a', array( 'class' => 'phui-calendar-week-number', - 'href' => $uri, + 'href' => $event_list['dayURI'], ), $date->format('W')); } @@ -236,16 +268,7 @@ final class PHUICalendarMonthView extends AphrontView { $cell_day = null; } - if ($date && $date->format('j') == $this->day && - $date->format('m') == $this->month) { - $today_class = 'phui-calendar-today-slot phui-calendar-today'; - } else { - $today_class = 'phui-calendar-today-slot'; - } - - if ($this->isDateInCurrentWeek($date)) { - $today_class .= ' phui-calendar-this-week'; - } + $today_class = 'phui-calendar-today-slot'; $last_week_day = 6; if ($date->format('w') == $last_week_day) { @@ -265,16 +288,36 @@ final class PHUICalendarMonthView extends AphrontView { 'class' => 'phui-calendar-month-cell-div', ), array( - $cell_day_secret_link, $week_number, $cell_day, $today_slot, )); - return phutil_tag( + $classes = array(); + $classes[] = 'phui-calendar-month-number'; + $classes[] = $event_list['class']; + + if ($date) { + if ($this->isDateInCurrentWeek($date)) { + $classes[] = 'phui-calendar-this-week'; + } + + if ($date->format('j') == $this->day) { + if ($date->format('m') == $this->month) { + $classes[] = 'phui-calendar-today'; + } + } + } + + $cell_meta = array( + 'dayID' => $event_list['dayID'], + ); + + return javelin_tag( 'td', array( - 'class' => 'phui-calendar-date-number-container '.$class, + 'class' => implode(' ', $classes), + 'meta' => $cell_meta, ), $cell_div); } @@ -314,21 +357,6 @@ final class PHUICalendarMonthView extends AphrontView { $event_count); } - private function getHiddenDayLink($uri, $count, $max_height) { - // approximately the height of the tallest cell - $height = 18 * $count + 5; - $height = ($height > $max_height) ? $height : $max_height; - $height_style = 'height: '.$height.'px'; - return phutil_tag( - 'a', - array( - 'class' => 'phui-calendar-month-secret-link', - 'style' => $height_style, - 'href' => $uri, - ), - null); - } - private function getDayNamesHeader() { list($week_start, $week_end) = $this->getWeekStartAndEnd(); @@ -417,8 +445,7 @@ final class PHUICalendarMonthView extends AphrontView { } - $header = id(new PHUIHeaderView()) - ->setHeader($date->format('F Y')); + $header = id(new PHUIHeaderView()); if ($button_bar) { $header->setButtonBar($button_bar); diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php index 6c50ed603e..0a27a616e3 100644 --- a/support/PhabricatorStartup.php +++ b/support/PhabricatorStartup.php @@ -374,7 +374,7 @@ final class PhabricatorStartup { $http_error = 500); error_log($log_message); - echo $message; + echo $message."\n"; exit(1); } @@ -529,6 +529,13 @@ final class PhabricatorStartup { "Downgrade to version 3.1.13."); } } + + if (isset($_SERVER['HTTP_PROXY'])) { + self::didFatal( + 'This HTTP request included a "Proxy:" header, poisoning the '. + 'environment (CVE-2016-5385 / httpoxy). Declining to process this '. + 'request. For details, see: https://phurl.io/u/httpoxy'); + } } diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index dddd78f103..bf40c0e352 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -228,8 +228,8 @@ span.single-display-line-content { position: static; } -.aphront-table-view tr.closed td.object-link, -.aphront-table-view tr.alt-closed td.object-link { +.aphront-table-view tr.closed td.object-link .object-name, +.aphront-table-view tr.alt-closed td.object-link .object-name { text-decoration: line-through; color: rgba({$alphablack}, 0.5); } diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 2cedb39db5..9474553f28 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -348,12 +348,9 @@ td.cov-I { margin-right: 12px; } -.device-phone .differential-changeset-buttons { - float: none; - margin: 0 0 8px 4px; -} - -.differential-changeset-buttons a.button { +.device-phone .differential-changeset-buttons .button .phui-button-text { + visibility: hidden; + width: 0; margin-left: 8px; } diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css index e012be0afa..1c97c42265 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css @@ -10,6 +10,15 @@ width: 100%; } +.aphront-multi-column-column-outer.phui-day-view-upcoming, +.aphront-multi-column-column-outer.phui-day-view-column{ + padding: 0; +} + +.aphront-multi-column-column-outer.phui-day-view-upcoming .phui-header-shell { + margin: 0; +} + .phui-calendar-day-view { overflow: scroll; width: 100%; @@ -61,17 +70,13 @@ div.phui-calendar-day-event.all-day { } .phui-calendar-day-event-link { - padding: 8px; - border: 1px solid {$greyborder}; - background-color: {$darkgreybackground}; - margin: 0 1px; + margin: 0 0 -1px -1px; + box-sizing: border-box; position: absolute; left: 0; right: 0; top: 0; bottom: 0; - text-decoration: none; - color: {$greytext}; } .phui-calendar-day-event-link.viewer-invited-day-event { @@ -80,21 +85,18 @@ div.phui-calendar-day-event.all-day { color: {$green}; } -.day-view-all-day { - border: 1px solid {$greyborder}; - height: 12px; - margin: 0; - display: block; +.day-view-all-day, +.phui-calendar-day-event-link { padding: 8px; - background-color: {$darkgreybackground}; - text-decoration: none; + border: 1px solid {$lightblueborder}; + background-color: {$bluebackground}; color: {$greytext}; + text-decoration: none; } -.day-view-all-day.viewer-invited-day-event { - border-color: {$green}; - background-color: {$lightgreen}; - color: {$green}; +.day-view-all-day { + margin: 3px 0 3px 4px; + display: block; } .phui-calendar-day-event + .phui-calendar-day-event .day-view-all-day { diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css index 9580343415..d5a4a89dc9 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css @@ -6,7 +6,7 @@ width: auto; } -.phui-calendar-list-container.calendar-day-view-sidebar .phui-object-box { +.calendar-profile-box .calendar-day-view-sidebar .phui-object-box { border-bottom: none; margin: 0; padding: 0 12px 12px; @@ -66,4 +66,22 @@ .phui-calendar-list-item-empty { color: {$lightgreytext}; + padding: 0 12px; + font-style: italic; +} + +.phui-calendar-list-item.all-day { + background: {$bluebackground}; +} + +.calendar-day-view-sidebar .phui-calendar-list { + padding: 12px 0; +} + +.calendar-day-view-sidebar .phui-calendar-list-item { + padding: 0 12px; +} + +.calendar-day-view-sidebar .phui-calendar-list-item a { + position: relative; } diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css index a219e49cf7..c649021f0d 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css @@ -34,10 +34,18 @@ tr.phui-calendar-day-of-week-header th .short-weekday-name { table.phui-calendar-view td { border: solid #dfdfdf; - border-width: 1px 1px 0 1px; + border-width: 1px 0 0; width: 14.2857%; /* This is one seventh, approximately. */ } +table.phui-calendar-view td + td { + border-left-width: 1px; +} + +table.phui-calendar-view td.phui-calendar-month-number { + border-top-width: 0; +} + .phui-calendar-month-cell-div { position: relative; } @@ -50,16 +58,6 @@ table.phui-calendar-view td { min-height: 32px; } -a.phui-calendar-month-secret-link { - position: absolute; - left: 0; - right: 0; -} - -table.phui-calendar-view tr td:first-child { - border-left-width: 0px; -} - .device table.phui-calendar-view .phui-calendar-event-list { display: none; } @@ -113,10 +111,9 @@ table.phui-calendar-view a.phui-calendar-date-number { width: 12px; } -table.phui-calendar-view td.phui-calendar-date-number-container { +table.phui-calendar-view td.phui-calendar-month-number { font-weight: normal; color: {$lightgreytext}; - border-width: 0 1px 0 1px; text-align: right; } @@ -133,12 +130,17 @@ table.phui-calendar-view td.phui-calendar-date-number-container { right: 0px; } -.phui-calendar-today-slot.phui-calendar-this-week { - background-color: {$blueborder}; +.phui-calendar-this-week .phui-calendar-today-slot { + background-color: {$blue}; } -.phui-calendar-today-slot.phui-calendar-today { - background-color: {$lightblueborder}; +.phui-calendar-today .phui-calendar-today-slot, +.phui-calendar-today .phui-calendar-date-number { + background-color: {$sky}; +} + +table.phui-calendar-view .phui-calendar-today a.phui-calendar-date-number { + color: #ffffff; } .phui-calendar-event-empty { @@ -159,7 +161,7 @@ table.phui-calendar-view td.phui-calendar-date-number-container { .phui-calendar-view .phui-calendar-list li.phui-calendar-list-item.all-day { height: 20px; - background-color: {$darkgreybackground}; + background-color: {$bluebackground}; display: block; float: none; } @@ -194,7 +196,7 @@ li.phui-calendar-viewer-invited.all-day { .phui-calendar-view li.phui-calendar-list-item .phui-calendar-list-title { width: auto; position: absolute; - left: 20px; + left: 16px; right: 60px; text-overflow: ellipsis; overflow: hidden; @@ -210,3 +212,17 @@ li.phui-calendar-viewer-invited.all-day { color: {$lightgreytext}; text-align: right; } + +td.phui-calendar-month-day, +td.phui-calendar-month-number { + cursor: pointer; +} + +.device-desktop td.phui-calendar-month-day.calendar-hover, +.device-desktop td.phui-calendar-month-number.calendar-hover { + background: {$lightblue}; +} + +.phui-calendar-month-adjacent { + background: {$greybackground}; +} diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar.css b/webroot/rsrc/css/phui/calendar/phui-calendar.css index d2a094c65a..6c369b21df 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar.css @@ -2,9 +2,19 @@ * @provides phui-calendar-css */ - .phui-calendar-list a { - color: {$lightgreytext}; - } +.phui-calendar-list { + /* When hovering over a day, this allows the hover color to peek through + the event name, but for event names to mostly remain readable. */ + background: rgba(255, 255, 255, 0.75); +} + +.phui-calendar-list a { + color: {$greytext}; +} + +.phui-calendar-list .event-cancelled .phui-calendar-list-title { + text-decoration: line-through; +} .phui-calendar-viewer-invited a { color: {$green}; diff --git a/webroot/rsrc/css/phui/phui-crumbs-view.css b/webroot/rsrc/css/phui/phui-crumbs-view.css index b47418e240..dd070647fb 100644 --- a/webroot/rsrc/css/phui/phui-crumbs-view.css +++ b/webroot/rsrc/css/phui/phui-crumbs-view.css @@ -24,7 +24,7 @@ .device-phone .phui-crumbs-view, .project-board-nav .phui-crumbs-view { padding-left: 8px; - padding-right: 0; + padding-right: 8px; } .phui-crumbs-view a.phui-crumbs-action-disabled, diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css index 6f88db302f..a3fdac4574 100644 --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -128,6 +128,10 @@ padding: 4px; } +.device-phone .aphront-form-control { + padding: 4px 8px 8px; +} + .phui-form-full-width .aphront-form-control { padding: 4px 0; } @@ -176,13 +180,15 @@ .aphront-form-instructions { width: 60%; margin-left: 20%; - padding: 10px 4px; + padding: 12px 4px; + color: {$darkbluetext}; } .device .aphront-form-instructions, .phui-form-full-width .aphront-form-instructions { - width: 100%; + width: auto; margin: 0; + padding: 12px 8px 8px; } .aphront-form-important { diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 3fc3ba6836..41cfd2db21 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -203,6 +203,11 @@ padding: 16px; } +.device .phui-two-column-view .phui-info-view { + margin: 0 0 20px 0; + padding: 12px; +} + .phui-two-column-view .phui-side-column .phui-object-item-empty .phui-info-view { margin-bottom: 0; @@ -214,6 +219,11 @@ margin: 16px; } +.device .phui-two-column-view .phui-box-blue-property + .phui-header-shell + .phui-info-view { + margin: 8px; +} + /* Navigation */ .phui-two-column-view .side-has-nav .phabricator-nav-local { diff --git a/webroot/rsrc/js/application/calendar/behavior-day-view.js b/webroot/rsrc/js/application/calendar/behavior-day-view.js index 5be51ce99c..7f821ffe83 100644 --- a/webroot/rsrc/js/application/calendar/behavior-day-view.js +++ b/webroot/rsrc/js/application/calendar/behavior-day-view.js @@ -1,8 +1,7 @@ /** * @provides javelin-behavior-day-view + * @requires phuix-icon-view */ - - JX.behavior('day-view', function(config) { function findTodayClusters() { @@ -92,13 +91,20 @@ JX.behavior('day-view', function(config) { link_class = link_class + ' viewer-invited-day-event'; } + var icon = new JX.PHUIXIconView() + .setIcon(e.displayIcon) + .setColor(e.displayIconColor) + .getNode(); + + var content = [icon, ' ', name]; + var name_link = JX.$N( 'a', { className : link_class, href: uri }, - name); + content); var class_name = 'phui-calendar-day-event'; if (e.canEdit) { @@ -123,22 +129,22 @@ JX.behavior('day-view', function(config) { return div; } - function drawAllDayEvent( - viewerIsInvited, - uri, - name) { + function drawAllDayEvent(e) { var class_name = 'day-view-all-day'; - if (viewerIsInvited) { - class_name = class_name + ' viewer-invited-day-event'; - } - name = JX.$N( + var icon = new JX.PHUIXIconView() + .setIcon(e.displayIcon) + .setColor(e.displayIconColor) + .getNode(); + var content = [icon, ' ', e.name]; + + var name = JX.$N( 'a', { className: class_name, - href: uri + href: e.uri }, - name); + content); var all_day_label = JX.$N( 'span', @@ -169,13 +175,13 @@ JX.behavior('day-view', function(config) { var cell_time = JX.$N( 'td', {className: 'phui-calendar-day-hour'}, - hours[i]['hour_meridian']); + hours[i].displayTime); var cell_event = JX.$N( 'td', { meta: { - time: hours[i]['hour_meridian'] + time: hours[i].displayTime }, className: 'phui-calendar-day-events', sigil: 'phui-calendar-day-event-cell' @@ -220,10 +226,7 @@ JX.behavior('day-view', function(config) { var all_day_events = []; for(i=0; i < today_all_day_events.length; i++) { var all_day_event = today_all_day_events[i]; - all_day_events.push(drawAllDayEvent( - all_day_event['viewerIsInvited'], - all_day_event['uri'], - all_day_event['name'])); + all_day_events.push(drawAllDayEvent(all_day_event)); } var table = JX.$N( @@ -329,15 +332,14 @@ JX.behavior('day-view', function(config) { } var data = e.getNodeData('phui-calendar-day-event-cell'); var time = data.time; + new JX.Workflow( - '/calendar/event/create/', + '/calendar/event/edit/', { - year: year, - month: month, - day: day, - time: time, - next: 'day', - query: query + start_d: year + '-' + month + '-' + day, + start_t: time, + end_d: year + '-' + month + '-' + day, + end_t: time + ' +1 hour' }) .start(); }); diff --git a/webroot/rsrc/js/application/calendar/behavior-event-all-day.js b/webroot/rsrc/js/application/calendar/behavior-event-all-day.js index a8bd7da7a8..1121759e3c 100644 --- a/webroot/rsrc/js/application/calendar/behavior-event-all-day.js +++ b/webroot/rsrc/js/application/calendar/behavior-event-all-day.js @@ -2,15 +2,16 @@ * @provides javelin-behavior-event-all-day */ - JX.behavior('event-all-day', function(config) { - var checkbox = JX.$(config.allDayID); - JX.DOM.listen(checkbox, 'change', null, function() { - var start = JX.$(config.startDateID); - var end = JX.$(config.endDateID); + var all_day = JX.$(config.allDayID); - JX.DOM.alterClass(start, 'no-time', checkbox.checked); - JX.DOM.alterClass(end, 'no-time', checkbox.checked); + JX.DOM.listen(all_day, 'change', null, function() { + var is_all_day = !!parseInt(all_day.value, 10); + + for (var ii = 0; ii < config.controlIDs.length; ii++) { + var control = JX.$(config.controlIDs[ii]); + JX.DOM.alterClass(control, 'no-time', is_all_day); + } }); }); diff --git a/webroot/rsrc/js/application/calendar/behavior-month-view.js b/webroot/rsrc/js/application/calendar/behavior-month-view.js new file mode 100644 index 0000000000..914b3b929f --- /dev/null +++ b/webroot/rsrc/js/application/calendar/behavior-month-view.js @@ -0,0 +1,89 @@ +/** + * @provides javelin-behavior-calendar-month-view + */ +JX.behavior('calendar-month-view', function() { + + var hover_nodes = []; + + function get_info(e) { + var week_body = e.getNode('calendar-week-body'); + if (!week_body) { + week_body = e.getNode('calendar-week-foot').previousSibling; + } + + var week_foot = week_body.nextSibling; + var day_id = JX.Stratcom.getData(e.getNode('tag:td')).dayID; + + var day_body; + var day_foot; + var body_nodes = JX.DOM.scry(week_body, 'td'); + var foot_nodes = JX.DOM.scry(week_foot, 'td'); + for (var ii = 0; ii < body_nodes.length; ii++) { + if (JX.Stratcom.getData(body_nodes[ii]).dayID == day_id) { + day_body = body_nodes[ii]; + day_foot = foot_nodes[ii]; + break; + } + } + + return { + data: JX.Stratcom.getData(week_body), + dayID: day_id, + nodes: { + week: { + body: week_body, + foot: week_foot + }, + day: { + body: day_body, + foot: day_foot + } + } + }; + } + + function alter_hover(enable) { + for (var ii = 0; ii < hover_nodes.length; ii++) { + JX.DOM.alterClass(hover_nodes[ii], 'calendar-hover', enable); + } + } + + JX.enableDispatch(document.body, 'mouseover'); + JX.enableDispatch(document.body, 'mouseout'); + + JX.Stratcom.listen('mouseover', ['calendar-week', 'tag:td'], function(e) { + if (e.getNode('calendar-event-list')) { + alter_hover(false); + hover_nodes = []; + return; + } + + var info = get_info(e); + hover_nodes = [ + info.nodes.day.body, + info.nodes.day.foot + ]; + + alter_hover(true); + }); + + JX.Stratcom.listen('mouseout', ['calendar-week', 'tag:td'], function() { + alter_hover(false); + }); + + JX.Stratcom.listen('click', ['calendar-week', 'tag:td'], function(e) { + if (!e.isNormalClick()) { + return; + } + + // If this is a click in the event list or on a link, ignore it. This + // allows users to follow links to events and select text. + if (e.getNode('calendar-event-list') || e.getNode('tag:a')) { + return; + } + + var info = get_info(e); + JX.$U(info.data.actionMap[info.dayID].dayURI).go(); + }); + +}); diff --git a/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js b/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js index 5fe1caabea..b4dcd2975b 100644 --- a/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js +++ b/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js @@ -97,6 +97,8 @@ JX.behavior('phabricator-show-older-transactions', function(config) { JX.Router.getInstance().queue(routable); }); + JX.Stratcom.listen('hashchange', null, check_hash); + check_hash(); new JX.KeyboardShortcut(['@'], 'Show all older changes in the timeline.')