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

(stable) Promote 2016 Week 44

This commit is contained in:
epriestley 2016-10-28 12:08:08 -07:00
commit bd256e9f3f
37 changed files with 1022 additions and 61 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phortune.phortune_merchant
ADD contactInfo LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL;

View file

@ -0,0 +1,8 @@
ALTER TABLE {$NAMESPACE}_calendar.calendar_import
ADD triggerPHID VARBINARY(64);
ALTER TABLE {$NAMESPACE}_calendar.calendar_import
ADD triggerFrequency VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT};
UPDATE {$NAMESPACE}_calendar.calendar_import
SET triggerFrequency = 'once' WHERE triggerFrequency = '';

View file

@ -0,0 +1,12 @@
CREATE TABLE {$NAMESPACE}_calendar.calendar_externalinvitee (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
name LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
nameIndex BINARY(12) NOT NULL,
uri LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
parameters LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
sourcePHID VARBINARY(64) NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_name` (`nameIndex`)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -2098,6 +2098,9 @@ phutil_register_library_map(array(
'PhabricatorCalendarExportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php', 'PhabricatorCalendarExportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php',
'PhabricatorCalendarExportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php', 'PhabricatorCalendarExportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php',
'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.php', 'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.php',
'PhabricatorCalendarExternalInvitee' => 'applications/calendar/storage/PhabricatorCalendarExternalInvitee.php',
'PhabricatorCalendarExternalInviteePHIDType' => 'applications/calendar/phid/PhabricatorCalendarExternalInviteePHIDType.php',
'PhabricatorCalendarExternalInviteeQuery' => 'applications/calendar/query/PhabricatorCalendarExternalInviteeQuery.php',
'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php',
'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php',
'PhabricatorCalendarICSFileImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php', 'PhabricatorCalendarICSFileImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php',
@ -2121,6 +2124,7 @@ phutil_register_library_map(array(
'PhabricatorCalendarImportEpochLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportEpochLogType.php', 'PhabricatorCalendarImportEpochLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportEpochLogType.php',
'PhabricatorCalendarImportFetchLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFetchLogType.php', 'PhabricatorCalendarImportFetchLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFetchLogType.php',
'PhabricatorCalendarImportFrequencyLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFrequencyLogType.php', 'PhabricatorCalendarImportFrequencyLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFrequencyLogType.php',
'PhabricatorCalendarImportFrequencyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportFrequencyTransaction.php',
'PhabricatorCalendarImportICSFileTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php', 'PhabricatorCalendarImportICSFileTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php',
'PhabricatorCalendarImportICSLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php', 'PhabricatorCalendarImportICSLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php',
'PhabricatorCalendarImportICSURITransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php', 'PhabricatorCalendarImportICSURITransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php',
@ -2137,10 +2141,14 @@ phutil_register_library_map(array(
'PhabricatorCalendarImportOrphanLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportOrphanLogType.php', 'PhabricatorCalendarImportOrphanLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportOrphanLogType.php',
'PhabricatorCalendarImportPHIDType' => 'applications/calendar/phid/PhabricatorCalendarImportPHIDType.php', 'PhabricatorCalendarImportPHIDType' => 'applications/calendar/phid/PhabricatorCalendarImportPHIDType.php',
'PhabricatorCalendarImportQuery' => 'applications/calendar/query/PhabricatorCalendarImportQuery.php', 'PhabricatorCalendarImportQuery' => 'applications/calendar/query/PhabricatorCalendarImportQuery.php',
'PhabricatorCalendarImportReloadController' => 'applications/calendar/controller/PhabricatorCalendarImportReloadController.php',
'PhabricatorCalendarImportReloadTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportReloadTransaction.php',
'PhabricatorCalendarImportReloadWorker' => 'applications/calendar/worker/PhabricatorCalendarImportReloadWorker.php',
'PhabricatorCalendarImportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarImportSearchEngine.php', 'PhabricatorCalendarImportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarImportSearchEngine.php',
'PhabricatorCalendarImportTransaction' => 'applications/calendar/storage/PhabricatorCalendarImportTransaction.php', 'PhabricatorCalendarImportTransaction' => 'applications/calendar/storage/PhabricatorCalendarImportTransaction.php',
'PhabricatorCalendarImportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php', 'PhabricatorCalendarImportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php',
'PhabricatorCalendarImportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php', 'PhabricatorCalendarImportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php',
'PhabricatorCalendarImportTriggerLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportTriggerLogType.php',
'PhabricatorCalendarImportUpdateLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportUpdateLogType.php', 'PhabricatorCalendarImportUpdateLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportUpdateLogType.php',
'PhabricatorCalendarImportViewController' => 'applications/calendar/controller/PhabricatorCalendarImportViewController.php', 'PhabricatorCalendarImportViewController' => 'applications/calendar/controller/PhabricatorCalendarImportViewController.php',
'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php', 'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php',
@ -6934,6 +6942,12 @@ phutil_register_library_map(array(
'PhabricatorCalendarExportTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorCalendarExportTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorCalendarExportTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorCalendarExportTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController', 'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController',
'PhabricatorCalendarExternalInvitee' => array(
'PhabricatorCalendarDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorCalendarExternalInviteePHIDType' => 'PhabricatorPHIDType',
'PhabricatorCalendarExternalInviteeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO',
'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase',
'PhabricatorCalendarICSFileImportEngine' => 'PhabricatorCalendarICSImportEngine', 'PhabricatorCalendarICSFileImportEngine' => 'PhabricatorCalendarICSImportEngine',
@ -6962,6 +6976,7 @@ phutil_register_library_map(array(
'PhabricatorCalendarImportEpochLogType' => 'PhabricatorCalendarImportLogType', 'PhabricatorCalendarImportEpochLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportFetchLogType' => 'PhabricatorCalendarImportLogType', 'PhabricatorCalendarImportFetchLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportFrequencyLogType' => 'PhabricatorCalendarImportLogType', 'PhabricatorCalendarImportFrequencyLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportFrequencyTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportICSFileTransaction' => 'PhabricatorCalendarImportTransactionType', 'PhabricatorCalendarImportICSFileTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportICSLogType' => 'PhabricatorCalendarImportLogType', 'PhabricatorCalendarImportICSLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportICSURITransaction' => 'PhabricatorCalendarImportTransactionType', 'PhabricatorCalendarImportICSURITransaction' => 'PhabricatorCalendarImportTransactionType',
@ -6982,10 +6997,14 @@ phutil_register_library_map(array(
'PhabricatorCalendarImportOrphanLogType' => 'PhabricatorCalendarImportLogType', 'PhabricatorCalendarImportOrphanLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportPHIDType' => 'PhabricatorPHIDType', 'PhabricatorCalendarImportPHIDType' => 'PhabricatorPHIDType',
'PhabricatorCalendarImportQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCalendarImportQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarImportReloadController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportReloadTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportReloadWorker' => 'PhabricatorWorker',
'PhabricatorCalendarImportSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorCalendarImportSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorCalendarImportTransaction' => 'PhabricatorModularTransaction', 'PhabricatorCalendarImportTransaction' => 'PhabricatorModularTransaction',
'PhabricatorCalendarImportTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorCalendarImportTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorCalendarImportTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorCalendarImportTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCalendarImportTriggerLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportUpdateLogType' => 'PhabricatorCalendarImportLogType', 'PhabricatorCalendarImportUpdateLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportViewController' => 'PhabricatorCalendarController', 'PhabricatorCalendarImportViewController' => 'PhabricatorCalendarController',
'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule',

View file

@ -85,6 +85,8 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication {
=> 'PhabricatorCalendarImportDisableController', => 'PhabricatorCalendarImportDisableController',
'delete/(?P<id>[1-9]\d*)/' 'delete/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarImportDeleteController', => 'PhabricatorCalendarImportDeleteController',
'reload/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarImportReloadController',
'drop/' 'drop/'
=> 'PhabricatorCalendarImportDropController', => 'PhabricatorCalendarImportDropController',
'log/' => array( 'log/' => array(

View file

@ -3,6 +3,10 @@
final class PhabricatorCalendarEventListController final class PhabricatorCalendarEventListController
extends PhabricatorCalendarController { extends PhabricatorCalendarController {
private $viewYear;
private $viewMonth;
private $viewDay;
public function shouldAllowPublic() { public function shouldAllowPublic() {
return true; return true;
} }
@ -16,6 +20,10 @@ final class PhabricatorCalendarEventListController
$month = $request->getURIData('month'); $month = $request->getURIData('month');
$day = $request->getURIData('day'); $day = $request->getURIData('day');
$this->viewYear = $year;
$this->viewMonth = $month;
$this->viewDay = $day;
$engine = new PhabricatorCalendarEventSearchEngine(); $engine = new PhabricatorCalendarEventSearchEngine();
if ($month && $year) { if ($month && $year) {
@ -33,9 +41,36 @@ final class PhabricatorCalendarEventListController
protected function buildApplicationCrumbs() { protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs(); $crumbs = parent::buildApplicationCrumbs();
$viewer = $this->getViewer();
$year = $this->viewYear;
$month = $this->viewMonth;
$day = $this->viewDay;
$parameters = array();
// If the viewer clicks "Create Event" while on a particular day view,
// default the times to that day.
if ($year && $month && $day) {
$datetimes = PhabricatorCalendarEvent::newDefaultEventDateTimes(
$viewer,
PhabricatorTime::getNow());
foreach ($datetimes as $datetime) {
$datetime
->setYear($year)
->setMonth($month)
->setDay($day);
}
list($start, $end) = $datetimes;
$parameters['start'] = $start->getEpoch();
$parameters['end'] = $end->getEpoch();
}
id(new PhabricatorCalendarEventEditEngine()) id(new PhabricatorCalendarEventEditEngine())
->setViewer($this->getViewer()) ->setViewer($this->getViewer())
->addActionToCrumbs($crumbs); ->addActionToCrumbs($crumbs, $parameters);
return $crumbs; return $crumbs;
} }

View file

@ -33,7 +33,7 @@ final class PhabricatorCalendarImportDropController
->addCancelButton($cancel_uri, pht('Done')); ->addCancelButton($cancel_uri, pht('Done'));
} }
$engine = new PhabricatorCalendarICSImportEngine(); $engine = new PhabricatorCalendarICSFileImportEngine();
$imports = array(); $imports = array();
foreach ($files as $file) { foreach ($files as $file) {
$import = PhabricatorCalendarImport::initializeNewCalendarImport( $import = PhabricatorCalendarImport::initializeNewCalendarImport(

View file

@ -0,0 +1,52 @@
<?php
final class PhabricatorCalendarImportReloadController
extends PhabricatorCalendarController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$import = id(new PhabricatorCalendarImportQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$import) {
return new Aphront404Response();
}
$import_uri = $import->getURI();
if ($request->isFormPost()) {
$xactions = array();
$xactions[] = id(new PhabricatorCalendarImportTransaction())
->setTransactionType(
PhabricatorCalendarImportReloadTransaction::TRANSACTIONTYPE)
->setNewValue(true);
$editor = id(new PhabricatorCalendarImportEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->setContentSourceFromRequest($request);
$editor->applyTransactions($import, $xactions);
return id(new AphrontRedirectResponse())->setURI($import_uri);
}
return $this->newDialog()
->setTitle(pht('Reload Events'))
->appendParagraph(
pht(
'Reload this source? Events imported from this source will '.
'be updated.'))
->addCancelButton($import_uri)
->addSubmitButton(pht('Reload Events'));
}
}

View file

@ -105,6 +105,17 @@ final class PhabricatorCalendarImportViewController
->setWorkflow(!$can_edit) ->setWorkflow(!$can_edit)
->setHref($edit_uri)); ->setHref($edit_uri));
$reload_uri = "import/reload/{$id}/";
$reload_uri = $this->getApplicationURI($reload_uri);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Reload Import'))
->setIcon('fa-refresh')
->setDisabled(!$can_edit)
->setWorkflow(true)
->setHref($reload_uri));
$disable_uri = "import/disable/{$id}/"; $disable_uri = "import/disable/{$id}/";
$disable_uri = $this->getApplicationURI($disable_uri); $disable_uri = $this->getApplicationURI($disable_uri);
if ($import->getIsDisabled()) { if ($import->getIsDisabled()) {
@ -123,7 +134,6 @@ final class PhabricatorCalendarImportViewController
->setWorkflow(true) ->setWorkflow(true)
->setHref($disable_uri)); ->setHref($disable_uri));
if ($can_edit) { if ($can_edit) {
$can_delete = $engine->canDeleteAnyEvents($viewer, $import); $can_delete = $engine->canDeleteAnyEvents($viewer, $import);
} else { } else {
@ -157,6 +167,50 @@ final class PhabricatorCalendarImportViewController
pht('Source Type'), pht('Source Type'),
$engine->getImportEngineTypeName()); $engine->getImportEngineTypeName());
if ($import->getIsDisabled()) {
$auto_updates = phutil_tag('em', array(), pht('Import Disabled'));
$has_trigger = false;
} else {
$frequency = $import->getTriggerFrequency();
$frequency_map = PhabricatorCalendarImport::getTriggerFrequencyMap();
$frequency_names = ipull($frequency_map, 'name');
$auto_updates = idx($frequency_names, $frequency, $frequency);
if ($frequency == PhabricatorCalendarImport::FREQUENCY_ONCE) {
$has_trigger = false;
$auto_updates = phutil_tag('em', array(), $auto_updates);
} else {
$has_trigger = true;
}
}
$properties->addProperty(
pht('Automatic Updates'),
$auto_updates);
if ($has_trigger) {
$trigger = id(new PhabricatorWorkerTriggerQuery())
->setViewer($viewer)
->withPHIDs(array($import->getTriggerPHID()))
->needEvents(true)
->executeOne();
if (!$trigger) {
$next_trigger = phutil_tag('em', array(), pht('Invalid Trigger'));
} else {
$now = PhabricatorTime::getNow();
$next_epoch = $trigger->getNextEventPrediction();
$next_trigger = pht(
'%s (%s)',
phabricator_datetime($next_epoch, $viewer),
phutil_format_relative_time($next_epoch - $now));
}
$properties->addProperty(
pht('Next Update'),
$next_trigger);
}
$engine->appendImportProperties( $engine->appendImportProperties(
$viewer, $viewer,
$import, $import,

View file

@ -164,6 +164,8 @@ final class PhabricatorCalendarEventEditEngine
if ($this->getIsCreate() || $object->getIsRecurring()) { if ($this->getIsCreate() || $object->getIsRecurring()) {
$fields[] = id(new PhabricatorEpochEditField()) $fields[] = id(new PhabricatorEpochEditField())
->setIsLockable(false)
->setIsDefaultable(false)
->setAllowNull(true) ->setAllowNull(true)
->setKey('until') ->setKey('until')
->setLabel(pht('Repeat Until')) ->setLabel(pht('Repeat Until'))
@ -189,6 +191,8 @@ final class PhabricatorCalendarEventEditEngine
$fields[] = id(new PhabricatorEpochEditField()) $fields[] = id(new PhabricatorEpochEditField())
->setKey('start') ->setKey('start')
->setLabel(pht('Start')) ->setLabel(pht('Start'))
->setIsLockable(false)
->setIsDefaultable(false)
->setTransactionType( ->setTransactionType(
PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE) PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE)
->setDescription(pht('Start time of the event.')) ->setDescription(pht('Start time of the event.'))
@ -199,6 +203,8 @@ final class PhabricatorCalendarEventEditEngine
$fields[] = id(new PhabricatorEpochEditField()) $fields[] = id(new PhabricatorEpochEditField())
->setKey('end') ->setKey('end')
->setLabel(pht('End')) ->setLabel(pht('End'))
->setIsLockable(false)
->setIsDefaultable(false)
->setTransactionType( ->setTransactionType(
PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE) PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE)
->setDescription(pht('End time of the event.')) ->setDescription(pht('End time of the event.'))

View file

@ -34,25 +34,22 @@ final class PhabricatorCalendarEventEditor
} }
$actor = $this->getActor(); $actor = $this->getActor();
$invitees = $event->getInvitees();
$event->copyFromParent($actor); $event->copyFromParent($actor);
$event->setIsStub(0); $event->setIsStub(0);
$invitees = $event->getParentEvent()->getInvitees(); $event->openTransaction();
$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->save();
$event->attachInvitees($new_invitees); foreach ($invitees as $invitee) {
$invitee
->setEventPHID($event->getPHID())
->save();
}
$event->saveTransaction();
$event->attachInvitees($invitees);
} }
public function getTransactionTypes() { public function getTransactionTypes() {

View file

@ -80,6 +80,9 @@ final class PhabricatorCalendarImportEditEngine
protected function buildCustomEditFields($object) { protected function buildCustomEditFields($object) {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$engine = $object->getEngine();
$can_trigger = $engine->supportsTriggers($object);
$fields = array( $fields = array(
id(new PhabricatorTextEditField()) id(new PhabricatorTextEditField())
->setKey('name') ->setKey('name')
@ -89,6 +92,7 @@ final class PhabricatorCalendarImportEditEngine
PhabricatorCalendarImportNameTransaction::TRANSACTIONTYPE) PhabricatorCalendarImportNameTransaction::TRANSACTIONTYPE)
->setConduitDescription(pht('Rename the import.')) ->setConduitDescription(pht('Rename the import.'))
->setConduitTypeDescription(pht('New import name.')) ->setConduitTypeDescription(pht('New import name.'))
->setPlaceholder($object->getDisplayName())
->setValue($object->getName()), ->setValue($object->getName()),
id(new PhabricatorBoolEditField()) id(new PhabricatorBoolEditField())
->setKey('disabled') ->setKey('disabled')
@ -101,8 +105,44 @@ final class PhabricatorCalendarImportEditEngine
->setConduitDescription(pht('Disable or restore the import.')) ->setConduitDescription(pht('Disable or restore the import.'))
->setConduitTypeDescription(pht('True to cancel the import.')) ->setConduitTypeDescription(pht('True to cancel the import.'))
->setValue($object->getIsDisabled()), ->setValue($object->getIsDisabled()),
id(new PhabricatorBoolEditField())
->setKey('delete')
->setLabel(pht('Delete Imported Events'))
->setDescription(pht('Delete all events from this source.'))
->setTransactionType(
PhabricatorCalendarImportDisableTransaction::TRANSACTIONTYPE)
->setIsConduitOnly(true)
->setConduitDescription(pht('Disable or restore the import.'))
->setConduitTypeDescription(pht('True to delete imported events.'))
->setValue(false),
id(new PhabricatorBoolEditField())
->setKey('reload')
->setLabel(pht('Reload Import'))
->setDescription(pht('Reload events imported from this source.'))
->setTransactionType(
PhabricatorCalendarImportDisableTransaction::TRANSACTIONTYPE)
->setIsConduitOnly(true)
->setConduitDescription(pht('Disable or restore the import.'))
->setConduitTypeDescription(pht('True to reload the import.'))
->setValue(false),
); );
if ($can_trigger) {
$frequency_map = PhabricatorCalendarImport::getTriggerFrequencyMap();
$frequency_options = ipull($frequency_map, 'name');
$fields[] = id(new PhabricatorSelectEditField())
->setKey('frequency')
->setLabel(pht('Update Automatically'))
->setDescription(pht('Configure an automatic update frequncy.'))
->setTransactionType(
PhabricatorCalendarImportFrequencyTransaction::TRANSACTIONTYPE)
->setConduitDescription(pht('Set the automatic update frequency.'))
->setConduitTypeDescription(pht('Update frequency constant.'))
->setValue($object->getTriggerFrequency())
->setOptions($frequency_options);
}
$import_engine = $object->getEngine(); $import_engine = $object->getEngine();
foreach ($import_engine->newEditEngineFields($this, $object) as $field) { foreach ($import_engine->newEditEngineFields($this, $object) as $field) {
$fields[] = $field; $fields[] = $field;

View file

@ -27,12 +27,110 @@ final class PhabricatorCalendarImportEditor
protected function applyFinalEffects( protected function applyFinalEffects(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
array $xactions) { array $xactions) {
if ($this->getIsNewObject()) {
$actor = $this->getActor(); $actor = $this->getActor();
// We import events when you create a source, or if you later reload it
// explicitly.
$should_reload = $this->getIsNewObject();
// We adjust the import trigger if you change the import frequency or
// disable the import.
$should_trigger = false;
foreach ($xactions as $xaction) {
$xaction_type = $xaction->getTransactionType();
switch ($xaction_type) {
case PhabricatorCalendarImportReloadTransaction::TRANSACTIONTYPE:
$should_reload = true;
break;
case PhabricatorCalendarImportFrequencyTransaction::TRANSACTIONTYPE:
$should_trigger = true;
break;
case PhabricatorCalendarImportDisableTransaction::TRANSACTIONTYPE:
$should_trigger = true;
break;
}
}
if ($should_reload) {
$import_engine = $object->getEngine(); $import_engine = $object->getEngine();
$import_engine->didCreateImport($actor, $object); $import_engine->importEventsFromSource($actor, $object);
}
if ($should_trigger) {
$trigger_phid = $object->getTriggerPHID();
if ($trigger_phid) {
$trigger = id(new PhabricatorWorkerTriggerQuery())
->setViewer($actor)
->withPHIDs(array($trigger_phid))
->executeOne();
if ($trigger) {
$engine = new PhabricatorDestructionEngine();
$engine->destroyObject($trigger);
}
}
$frequency = $object->getTriggerFrequency();
$now = PhabricatorTime::getNow();
switch ($frequency) {
case PhabricatorCalendarImport::FREQUENCY_ONCE:
$clock = null;
break;
case PhabricatorCalendarImport::FREQUENCY_HOURLY:
$clock = new PhabricatorMetronomicTriggerClock(
array(
'period' => phutil_units('1 hour in seconds'),
));
break;
case PhabricatorCalendarImport::FREQUENCY_DAILY:
$clock = new PhabricatorDailyRoutineTriggerClock(
array(
'start' => $now,
));
break;
default:
throw new Exception(
pht(
'Unknown import trigger frequency "%s".',
$frequency));
}
// If the object has been disabled, don't write a new trigger.
if ($object->getIsDisabled()) {
$clock = null;
}
if ($clock) {
$trigger_action = new PhabricatorScheduleTaskTriggerAction(
array(
'class' => 'PhabricatorCalendarImportReloadWorker',
'data' => array(
'importPHID' => $object->getPHID(),
),
'options' => array(
'objectPHID' => $object->getPHID(),
'priority' => PhabricatorWorker::PRIORITY_BULK,
),
));
$trigger_phid = PhabricatorPHID::generateNewPHID(
PhabricatorWorkerTriggerPHIDType::TYPECONST);
$object
->setTriggerPHID($trigger_phid)
->save();
$trigger = id(new PhabricatorWorkerTrigger())
->setClock($clock)
->setAction($trigger_action)
->setPHID($trigger_phid)
->save();
} else {
$object
->setTriggerPHID(null)
->save();
}
} }
return $xactions; return $xactions;

View file

@ -17,6 +17,10 @@ final class PhabricatorCalendarICSFileImportEngine
return pht('Import an event in ".ics" (iCalendar) format.'); return pht('Import an event in ".ics" (iCalendar) format.');
} }
public function supportsTriggers(PhabricatorCalendarImport $import) {
return false;
}
public function appendImportProperties( public function appendImportProperties(
PhabricatorUser $viewer, PhabricatorUser $viewer,
PhabricatorCalendarImport $import, PhabricatorCalendarImport $import,
@ -59,7 +63,7 @@ final class PhabricatorCalendarICSFileImportEngine
} }
} }
public function didCreateImport( public function importEventsFromSource(
PhabricatorUser $viewer, PhabricatorUser $viewer,
PhabricatorCalendarImport $import) { PhabricatorCalendarImport $import) {

View file

@ -17,6 +17,10 @@ final class PhabricatorCalendarICSURIImportEngine
return pht('Import or subscribe to a calendar in .ics format by URI.'); return pht('Import or subscribe to a calendar in .ics format by URI.');
} }
public function supportsTriggers(PhabricatorCalendarImport $import) {
return true;
}
public function appendImportProperties( public function appendImportProperties(
PhabricatorUser $viewer, PhabricatorUser $viewer,
PhabricatorCalendarImport $import, PhabricatorCalendarImport $import,
@ -71,7 +75,7 @@ final class PhabricatorCalendarICSURIImportEngine
return pht('ICS URI'); return pht('ICS URI');
} }
public function didCreateImport( public function importEventsFromSource(
PhabricatorUser $viewer, PhabricatorUser $viewer,
PhabricatorCalendarImport $import) { PhabricatorCalendarImport $import) {

View file

@ -25,7 +25,7 @@ abstract class PhabricatorCalendarImportEngine
abstract public function getDisplayName(PhabricatorCalendarImport $import); abstract public function getDisplayName(PhabricatorCalendarImport $import);
abstract public function didCreateImport( abstract public function importEventsFromSource(
PhabricatorUser $viewer, PhabricatorUser $viewer,
PhabricatorCalendarImport $import); PhabricatorCalendarImport $import);
@ -39,6 +39,9 @@ abstract class PhabricatorCalendarImportEngine
throw new PhutilMethodNotImplementedException(); throw new PhutilMethodNotImplementedException();
} }
abstract public function supportsTriggers(
PhabricatorCalendarImport $import);
final public static function getAllImportEngines() { final public static function getAllImportEngines() {
return id(new PhutilClassMapQuery()) return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__) ->setAncestorClass(__CLASS__)
@ -204,6 +207,8 @@ abstract class PhabricatorCalendarImportEngine
$xactions = array(); $xactions = array();
$update_map = array(); $update_map = array();
$invitee_map = array();
$attendee_map = array();
foreach ($node_map as $full_uid => $node) { foreach ($node_map as $full_uid => $node) {
$event = idx($events, $full_uid); $event = idx($events, $full_uid);
if (!$event) { if (!$event) {
@ -219,6 +224,66 @@ abstract class PhabricatorCalendarImportEngine
$this->updateEventFromNode($viewer, $event, $node); $this->updateEventFromNode($viewer, $event, $node);
$xactions[$full_uid] = $this->newUpdateTransactions($event, $node); $xactions[$full_uid] = $this->newUpdateTransactions($event, $node);
$update_map[$full_uid] = $event; $update_map[$full_uid] = $event;
$attendees = $node->getAttendees();
$private_index = 1;
foreach ($attendees as $attendee) {
// Generate a "name" for this attendee which is not an email address.
// We avoid disclosing email addresses to be consistent with the rest
// of the product.
$name = $attendee->getName();
if (preg_match('/@/', $name)) {
$name = new PhutilEmailAddress($name);
$name = $name->getDisplayName();
}
// If we don't have a name or the name still looks like it's an
// email address, give them a dummy placeholder name.
if (!strlen($name) || preg_match('/@/', $name)) {
$name = pht('Private User %d', $private_index);
$private_index++;
}
$attendee_map[$full_uid][$name] = $attendee;
}
}
$attendee_names = array();
foreach ($attendee_map as $full_uid => $event_attendees) {
foreach ($event_attendees as $name => $attendee) {
$attendee_names[$name] = $attendee;
}
}
if ($attendee_names) {
$external_invitees = id(new PhabricatorCalendarExternalInviteeQuery())
->setViewer($viewer)
->withNames($attendee_names)
->execute();
$external_invitees = mpull($external_invitees, null, 'getName');
foreach ($attendee_names as $name => $attendee) {
if (isset($external_invitees[$name])) {
continue;
}
$external_invitee = id(new PhabricatorCalendarExternalInvitee())
->setName($name)
->setURI($attendee->getURI())
->setSourcePHID($import->getPHID());
try {
$external_invitee->save();
} catch (AphrontDuplicateKeyQueryException $ex) {
$external_invitee =
id(new PhabricatorCalendarExternalInviteeQuery())
->setViewer($viewer)
->withNames(array($name))
->executeOne();
}
$external_invitees[$name] = $external_invitee;
}
} }
// Reorder events so we create parents first. This allows us to populate // Reorder events so we create parents first. This allows us to populate
@ -285,6 +350,51 @@ abstract class PhabricatorCalendarImportEngine
$editor->applyTransactions($event, $event_xactions); $editor->applyTransactions($event, $event_xactions);
// We're just forcing attendees to the correct values here because
// transactions intentionally don't let you RSVP for other users. This
// might need to be turned into a special type of transaction eventually.
$attendees = $attendee_map[$full_uid];
$old_map = $event->getInvitees();
$old_map = mpull($old_map, null, 'getInviteePHID');
$new_map = array();
foreach ($attendees as $name => $attendee) {
$phid = $external_invitees[$name]->getPHID();
$invitee = idx($old_map, $phid);
if (!$invitee) {
$invitee = id(new PhabricatorCalendarEventInvitee())
->setEventPHID($event->getPHID())
->setInviteePHID($phid)
->setInviterPHID($import->getPHID());
}
switch ($attendee->getStatus()) {
case PhutilCalendarUserNode::STATUS_ACCEPTED:
$status = PhabricatorCalendarEventInvitee::STATUS_ATTENDING;
break;
case PhutilCalendarUserNode::STATUS_DECLINED:
$status = PhabricatorCalendarEventInvitee::STATUS_DECLINED;
break;
case PhutilCalendarUserNode::STATUS_INVITED:
default:
$status = PhabricatorCalendarEventInvitee::STATUS_INVITED;
break;
}
$invitee->setStatus($status);
$invitee->save();
$new_map[$phid] = $invitee;
}
foreach ($old_map as $phid => $invitee) {
if (empty($new_map[$phid])) {
$invitee->delete();
}
}
$event->attachInvitees($new_map);
$import->newLogMessage( $import->newLogMessage(
PhabricatorCalendarImportUpdateLogType::LOGTYPE, PhabricatorCalendarImportUpdateLogType::LOGTYPE,
array( array(
@ -403,6 +513,9 @@ abstract class PhabricatorCalendarImportEngine
$until_datetime->setViewerTimezone($timezone); $until_datetime->setViewerTimezone($timezone);
$event->setUntilDateTime($until_datetime); $event->setUntilDateTime($until_datetime);
} }
$count = $rrule->getCount();
$event->setParameter('recurrenceCount', $count);
} }
return $event; return $event;

View file

@ -0,0 +1,32 @@
<?php
final class PhabricatorCalendarImportTriggerLogType
extends PhabricatorCalendarImportLogType {
const LOGTYPE = 'trigger';
public function getDisplayType(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('Import Triggered');
}
public function getDisplayDescription(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('Triggered a periodic update.');
}
public function getDisplayIcon(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return 'fa-clock-o';
}
public function getDisplayColor(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return 'blue';
}
}

View file

@ -26,7 +26,7 @@ final class PhabricatorCalendarImportUpdateLogType
public function getDisplayIcon( public function getDisplayIcon(
PhabricatorUser $viewer, PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) { PhabricatorCalendarImportLog $log) {
return 'fa-upload'; return 'fa-calendar';
} }
public function getDisplayColor( public function getDisplayColor(

View file

@ -0,0 +1,40 @@
<?php
final class PhabricatorCalendarExternalInviteePHIDType
extends PhabricatorPHIDType {
const TYPECONST = 'CXNV';
public function getTypeName() {
return pht('External Invitee');
}
public function newObject() {
return new PhabricatorCalendarExternalInvitee();
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorCalendarApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new PhabricatorCalendarExternalInviteeQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
foreach ($handles as $phid => $handle) {
$invitee = $objects[$phid];
$name = $invitee->getName();
$handle->setName($name);
}
}
}

View file

@ -633,6 +633,11 @@ final class PhabricatorCalendarEventQuery
PhabricatorCalendarEvent $event, PhabricatorCalendarEvent $event,
$raw_limit) { $raw_limit) {
$count = $event->getRecurrenceCount();
if ($count && ($count <= $raw_limit)) {
return ($count - 1);
}
return $raw_limit; return $raw_limit;
} }

View file

@ -0,0 +1,68 @@
<?php
final class PhabricatorCalendarExternalInviteeQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $names;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function newResultObject() {
return new PhabricatorCalendarExternalInvitee();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->names !== null) {
$name_indexes = array();
foreach ($this->names as $name) {
$name_indexes[] = PhabricatorHash::digestForIndex($name);
}
$where[] = qsprintf(
$conn,
'nameIndex IN (%Ls)',
$name_indexes);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorCalendarApplication';
}
}

View file

@ -27,7 +27,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
protected $isRecurring = 0; protected $isRecurring = 0;
private $isGhostEvent = false;
protected $instanceOfEventPHID; protected $instanceOfEventPHID;
protected $sequenceIndex; protected $sequenceIndex;
@ -60,6 +59,9 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
protected $recurrenceEndDate; protected $recurrenceEndDate;
protected $recurrenceFrequency = array(); protected $recurrenceFrequency = array();
private $isGhostEvent = false;
private $stubInvitees;
public static function initializeNewCalendarEvent(PhabricatorUser $actor) { public static function initializeNewCalendarEvent(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery()) $app = id(new PhabricatorApplicationQuery())
->setViewer($actor) ->setViewer($actor)
@ -75,10 +77,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
$default_icon = 'fa-calendar'; $default_icon = 'fa-calendar';
$datetime_start = PhutilCalendarAbsoluteDateTime::newFromEpoch( $datetime_defaults = self::newDefaultEventDateTimes(
$now, $actor,
$actor->getTimezoneIdentifier()); $now);
$datetime_end = $datetime_start->newRelativeDateTime('PT1H'); list($datetime_start, $datetime_end) = $datetime_defaults;
return id(new PhabricatorCalendarEvent()) return id(new PhabricatorCalendarEvent())
->setDescription('') ->setDescription('')
@ -102,6 +104,31 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
->applyViewerTimezone($actor); ->applyViewerTimezone($actor);
} }
public static function newDefaultEventDateTimes(
PhabricatorUser $viewer,
$now) {
$datetime_start = PhutilCalendarAbsoluteDateTime::newFromEpoch(
$now,
$viewer->getTimezoneIdentifier());
// Advance the time by an hour, then round downwards to the nearest hour.
// For example, if it is currently 3:25 PM, we suggest a default start time
// of 4 PM.
$datetime_start = $datetime_start
->newRelativeDateTime('PT1H')
->newAbsoluteDateTime();
$datetime_start->setMinute(0);
$datetime_start->setSecond(0);
// Default the end time to an hour after the start time.
$datetime_end = $datetime_start
->newRelativeDateTime('PT1H')
->newAbsoluteDateTime();
return array($datetime_start, $datetime_end);
}
private function newChild( private function newChild(
PhabricatorUser $actor, PhabricatorUser $actor,
$sequence, $sequence,
@ -226,10 +253,16 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
return null; return null;
} }
$limit = $sequence + 1;
$count = $this->getRecurrenceCount();
if ($count && ($count < $limit)) {
return null;
}
$instances = $set->getEventsBetween( $instances = $set->getEventsBetween(
null, null,
$this->newUntilDateTime(), $this->newUntilDateTime(),
$sequence + 1); $limit);
return idx($instances, $sequence, null); return idx($instances, $sequence, null);
} }
@ -418,9 +451,34 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
} }
public function getInvitees() { public function getInvitees() {
if ($this->getIsGhostEvent() || $this->getIsStub()) {
if ($this->stubInvitees === null) {
$this->stubInvitees = $this->newStubInvitees();
}
return $this->stubInvitees;
}
return $this->assertAttached($this->invitees); return $this->assertAttached($this->invitees);
} }
private function newStubInvitees() {
$parent = $this->getParentEvent();
$parent_invitees = $parent->getInvitees();
$stub_invitees = array();
foreach ($parent_invitees as $invitee) {
$stub_invitee = id(new PhabricatorCalendarEventInvitee())
->setInviteePHID($invitee->getInviteePHID())
->setInviterPHID($invitee->getInviterPHID())
->setStatus(PhabricatorCalendarEventInvitee::STATUS_INVITED);
$stub_invitees[] = $stub_invitee;
}
return $stub_invitees;
}
public function attachInvitees(array $invitees) { public function attachInvitees(array $invitees) {
$this->invitees = $invitees; $this->invitees = $invitees;
return $this; return $this;
@ -447,6 +505,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
if (!$invited) { if (!$invited) {
return PhabricatorCalendarEventInvitee::STATUS_UNINVITED; return PhabricatorCalendarEventInvitee::STATUS_UNINVITED;
} }
$invited = $invited->getStatus(); $invited = $invited->getStatus();
return $invited; return $invited;
} }
@ -907,9 +966,24 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
$rrule->setUntil($until); $rrule->setUntil($until);
} }
$count = $this->getRecurrenceCount();
if ($count) {
$rrule->setCount($count);
}
return $rrule; return $rrule;
} }
public function getRecurrenceCount() {
$count = (int)$this->getParameter('recurrenceCount');
if (!$count) {
return null;
}
return $count;
}
public function newRecurrenceSet() { public function newRecurrenceSet() {
if ($this->isChildEvent()) { if ($this->isChildEvent()) {
return $this->getParentEvent()->newRecurrenceSet(); return $this->getParentEvent()->newRecurrenceSet();

View file

@ -0,0 +1,74 @@
<?php
final class PhabricatorCalendarExternalInvitee
extends PhabricatorCalendarDAO
implements PhabricatorPolicyInterface {
protected $name;
protected $nameIndex;
protected $uri;
protected $parameters = array();
protected $sourcePHID;
public static function initializeNewCalendarEventInvitee(
PhabricatorUser $actor, $event) {
return id(new PhabricatorCalendarEventInvitee())
->setInviterPHID($actor->getPHID())
->setStatus(self::STATUS_INVITED)
->setEventPHID($event->getPHID());
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'parameters' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text',
'nameIndex' => 'bytes12',
'uri' => 'text',
),
self::CONFIG_KEY_SCHEMA => array(
'key_name' => array(
'columns' => array('nameIndex'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function getPHIDType() {
return PhabricatorCalendarExternalInviteePHIDType::TYPECONST;
}
public function save() {
$this->nameIndex = PhabricatorHash::digestForIndex($this->getName());
return parent::save();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return PhabricatorPolicies::getMostOpenPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -14,6 +14,12 @@ final class PhabricatorCalendarImport
protected $engineType; protected $engineType;
protected $parameters = array(); protected $parameters = array();
protected $isDisabled = 0; protected $isDisabled = 0;
protected $triggerPHID;
protected $triggerFrequency;
const FREQUENCY_ONCE = 'once';
const FREQUENCY_HOURLY = 'hourly';
const FREQUENCY_DAILY = 'daily';
private $engine = self::ATTACHABLE; private $engine = self::ATTACHABLE;
@ -27,7 +33,8 @@ final class PhabricatorCalendarImport
->setEditPolicy($actor->getPHID()) ->setEditPolicy($actor->getPHID())
->setIsDisabled(0) ->setIsDisabled(0)
->setEngineType($engine->getImportEngineType()) ->setEngineType($engine->getImportEngineType())
->attachEngine($engine); ->attachEngine($engine)
->setTriggerFrequency(self::FREQUENCY_ONCE);
} }
protected function getConfiguration() { protected function getConfiguration() {
@ -40,6 +47,8 @@ final class PhabricatorCalendarImport
'name' => 'text', 'name' => 'text',
'engineType' => 'text64', 'engineType' => 'text64',
'isDisabled' => 'bool', 'isDisabled' => 'bool',
'triggerPHID' => 'phid?',
'triggerFrequency' => 'text64',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_author' => array( 'key_author' => array(
@ -85,6 +94,21 @@ final class PhabricatorCalendarImport
return $this->getEngine()->getDisplayName($this); return $this->getEngine()->getDisplayName($this);
} }
public static function getTriggerFrequencyMap() {
return array(
self::FREQUENCY_ONCE => array(
'name' => pht('No Automatic Updates'),
),
self::FREQUENCY_HOURLY => array(
'name' => pht('Update Hourly'),
),
self::FREQUENCY_DAILY => array(
'name' => pht('Update Daily'),
),
);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */ /* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -155,6 +179,17 @@ final class PhabricatorCalendarImport
$this->openTransaction(); $this->openTransaction();
$trigger_phid = $this->getTriggerPHID();
if ($trigger_phid) {
$trigger = id(new PhabricatorWorkerTriggerQuery())
->setViewer($viewer)
->withPHIDs(array($trigger_phid))
->executeOne();
if ($trigger) {
$engine->destroyObject($trigger);
}
}
$events = id(new PhabricatorCalendarEventQuery()) $events = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer) ->setViewer($viewer)
->withImportSourcePHIDs(array($this->getPHID())) ->withImportSourcePHIDs(array($this->getPHID()))

View file

@ -0,0 +1,48 @@
<?php
final class PhabricatorCalendarImportReloadWorker extends PhabricatorWorker {
protected function doWork() {
$import = $this->loadImport();
$viewer = PhabricatorUser::getOmnipotentUser();
if ($import->getIsDisabled()) {
return;
}
$author = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($import->getAuthorPHID()))
->needUserSettings(true)
->executeOne();
$import_engine = $import->getEngine();
$import->newLogMessage(
PhabricatorCalendarImportTriggerLogType::LOGTYPE,
array());
$import_engine->importEventsFromSource($author, $import);
}
private function loadImport() {
$viewer = PhabricatorUser::getOmnipotentUser();
$data = $this->getTaskData();
$import_phid = idx($data, 'importPHID');
$import = id(new PhabricatorCalendarImportQuery())
->setViewer($viewer)
->withPHIDs(array($import_phid))
->executeOne();
if (!$import) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Failed to load import with PHID "%s".',
$import_phid));
}
return $import;
}
}

View file

@ -32,6 +32,7 @@ final class PhabricatorCalendarEventInviteTransaction
foreach ($add as $phid) { foreach ($add as $phid) {
$map[$phid] = $status_invited; $map[$phid] = $status_invited;
} }
foreach ($rem as $phid) { foreach ($rem as $phid) {
$map[$phid] = $status_uninvited; $map[$phid] = $status_uninvited;
} }

View file

@ -0,0 +1,45 @@
<?php
final class PhabricatorCalendarImportFrequencyTransaction
extends PhabricatorCalendarImportTransactionType {
const TRANSACTIONTYPE = 'calendar.import.frequency';
public function generateOldValue($object) {
return $object->getTriggerFrequency();
}
public function applyInternalEffects($object, $value) {
$object->setTriggerFrequency($value);
}
public function getTitle() {
return pht(
'%s changed the automatic update frequency for this import.',
$this->renderAuthor());
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$frequency_map = PhabricatorCalendarImport::getTriggerFrequencyMap();
$valid = array_keys($frequency_map);
$valid = array_fuse($valid);
foreach ($xactions as $xaction) {
$value = $xaction->getNewValue();
if (!isset($valid[$value])) {
$errors[] = $this->newInvalidError(
pht(
'Import frequency "%s" is not valid. Valid frequences are: %s.',
$value,
implode(', ', $valid)),
$xaction);
}
}
return $errors;
}
}

View file

@ -0,0 +1,23 @@
<?php
final class PhabricatorCalendarImportReloadTransaction
extends PhabricatorCalendarImportTransactionType {
const TRANSACTIONTYPE = 'calendar.import.reload';
public function generateOldValue($object) {
return false;
}
public function applyExternalEffects($object, $value) {
// NOTE: This transaction does nothing directly; instead, the Editor
// reacts to it and performs the reload.
}
public function getTitle() {
return pht(
'%s reloaded this event source.',
$this->renderAuthor());
}
}

View file

@ -47,6 +47,7 @@ final class PhortuneMerchantEditController
$e_name = true; $e_name = true;
$v_name = $merchant->getName(); $v_name = $merchant->getName();
$v_desc = $merchant->getDescription(); $v_desc = $merchant->getDescription();
$v_cont = $merchant->getContactInfo();
$v_members = $merchant->getMemberPHIDs(); $v_members = $merchant->getMemberPHIDs();
$e_members = null; $e_members = null;
@ -54,12 +55,14 @@ final class PhortuneMerchantEditController
if ($request->isFormPost()) { if ($request->isFormPost()) {
$v_name = $request->getStr('name'); $v_name = $request->getStr('name');
$v_desc = $request->getStr('desc'); $v_desc = $request->getStr('desc');
$v_cont = $request->getStr('cont');
$v_view = $request->getStr('viewPolicy'); $v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy'); $v_edit = $request->getStr('editPolicy');
$v_members = $request->getArr('memberPHIDs'); $v_members = $request->getArr('memberPHIDs');
$type_name = PhortuneMerchantTransaction::TYPE_NAME; $type_name = PhortuneMerchantTransaction::TYPE_NAME;
$type_desc = PhortuneMerchantTransaction::TYPE_DESCRIPTION; $type_desc = PhortuneMerchantTransaction::TYPE_DESCRIPTION;
$type_cont = PhortuneMerchantTransaction::TYPE_CONTACTINFO;
$type_edge = PhabricatorTransactions::TYPE_EDGE; $type_edge = PhabricatorTransactions::TYPE_EDGE;
$type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
@ -75,6 +78,10 @@ final class PhortuneMerchantEditController
->setTransactionType($type_desc) ->setTransactionType($type_desc)
->setNewValue($v_desc); ->setNewValue($v_desc);
$xactions[] = id(new PhortuneMerchantTransaction())
->setTransactionType($type_cont)
->setNewValue($v_cont);
$xactions[] = id(new PhortuneMerchantTransaction()) $xactions[] = id(new PhortuneMerchantTransaction())
->setTransactionType($type_view) ->setTransactionType($type_view)
->setNewValue($v_view); ->setNewValue($v_view);
@ -127,6 +134,12 @@ final class PhortuneMerchantEditController
->setName('desc') ->setName('desc')
->setLabel(pht('Description')) ->setLabel(pht('Description'))
->setValue($v_desc)) ->setValue($v_desc))
->appendChild(
id(new PhabricatorRemarkupControl())
->setUser($viewer)
->setName('cont')
->setLabel(pht('Contact Info'))
->setValue($v_cont))
->appendControl( ->appendControl(
id(new AphrontFormTokenizerControl()) id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorPeopleDatasource()) ->setDatasource(new PhabricatorPeopleDatasource())

View file

@ -36,7 +36,6 @@ final class PhortuneMerchantViewController
->execute(); ->execute();
$details = $this->buildDetailsView($merchant, $providers); $details = $this->buildDetailsView($merchant, $providers);
$description = $this->buildDescriptionView($merchant);
$curtain = $this->buildCurtainView($merchant); $curtain = $this->buildCurtainView($merchant);
$provider_list = $this->buildProviderList( $provider_list = $this->buildProviderList(
@ -53,7 +52,6 @@ final class PhortuneMerchantViewController
->setCurtain($curtain) ->setCurtain($curtain)
->setMainColumn(array( ->setMainColumn(array(
$details, $details,
$description,
$provider_list, $provider_list,
$timeline, $timeline,
)); ));
@ -130,30 +128,30 @@ final class PhortuneMerchantViewController
$view->addProperty(pht('Status'), $status_view); $view->addProperty(pht('Status'), $status_view);
$description = $merchant->getDescription();
if (strlen($description)) {
$description = new PHUIRemarkupView($viewer, $description);
$view->addSectionHeader(
pht('Description'),
PHUIPropertyListView::ICON_SUMMARY);
$view->addTextContent($description);
}
$contact_info = $merchant->getContactInfo();
if (strlen($contact_info)) {
$contact_info = new PHUIRemarkupView($viewer, $contact_info);
$view->addSectionHeader(
pht('Contact Info'),
PHUIPropertyListView::ICON_SUMMARY);
$view->addTextContent($contact_info);
}
return id(new PHUIObjectBoxView()) return id(new PHUIObjectBoxView())
->setHeaderText(pht('Details')) ->setHeaderText(pht('Details'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($view); ->appendChild($view);
} }
private function buildDescriptionView(PhortuneMerchant $merchant) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer);
$description = $merchant->getDescription();
if (strlen($description)) {
$description = new PHUIRemarkupView($viewer, $description);
$view->addTextContent($description);
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Description'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($view);
}
return null;
}
private function buildCurtainView(PhortuneMerchant $merchant) { private function buildCurtainView(PhortuneMerchant $merchant) {
$viewer = $this->getRequest()->getUser(); $viewer = $this->getRequest()->getUser();
$id = $merchant->getID(); $id = $merchant->getID();

View file

@ -16,6 +16,7 @@ final class PhortuneMerchantEditor
$types[] = PhortuneMerchantTransaction::TYPE_NAME; $types[] = PhortuneMerchantTransaction::TYPE_NAME;
$types[] = PhortuneMerchantTransaction::TYPE_DESCRIPTION; $types[] = PhortuneMerchantTransaction::TYPE_DESCRIPTION;
$types[] = PhortuneMerchantTransaction::TYPE_CONTACTINFO;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_EDGE;
@ -30,6 +31,8 @@ final class PhortuneMerchantEditor
return $object->getName(); return $object->getName();
case PhortuneMerchantTransaction::TYPE_DESCRIPTION: case PhortuneMerchantTransaction::TYPE_DESCRIPTION:
return $object->getDescription(); return $object->getDescription();
case PhortuneMerchantTransaction::TYPE_CONTACTINFO:
return $object->getContactInfo();
} }
return parent::getCustomTransactionOldValue($object, $xaction); return parent::getCustomTransactionOldValue($object, $xaction);
@ -42,6 +45,7 @@ final class PhortuneMerchantEditor
switch ($xaction->getTransactionType()) { switch ($xaction->getTransactionType()) {
case PhortuneMerchantTransaction::TYPE_NAME: case PhortuneMerchantTransaction::TYPE_NAME:
case PhortuneMerchantTransaction::TYPE_DESCRIPTION: case PhortuneMerchantTransaction::TYPE_DESCRIPTION:
case PhortuneMerchantTransaction::TYPE_CONTACTINFO:
return $xaction->getNewValue(); return $xaction->getNewValue();
} }
@ -59,6 +63,9 @@ final class PhortuneMerchantEditor
case PhortuneMerchantTransaction::TYPE_DESCRIPTION: case PhortuneMerchantTransaction::TYPE_DESCRIPTION:
$object->setDescription($xaction->getNewValue()); $object->setDescription($xaction->getNewValue());
return; return;
case PhortuneMerchantTransaction::TYPE_CONTACTINFO:
$object->setContactInfo($xaction->getNewValue());
return;
} }
return parent::applyCustomInternalTransaction($object, $xaction); return parent::applyCustomInternalTransaction($object, $xaction);
@ -71,6 +78,7 @@ final class PhortuneMerchantEditor
switch ($xaction->getTransactionType()) { switch ($xaction->getTransactionType()) {
case PhortuneMerchantTransaction::TYPE_NAME: case PhortuneMerchantTransaction::TYPE_NAME:
case PhortuneMerchantTransaction::TYPE_DESCRIPTION: case PhortuneMerchantTransaction::TYPE_DESCRIPTION:
case PhortuneMerchantTransaction::TYPE_CONTACTINFO:
return; return;
} }

View file

@ -8,6 +8,7 @@ final class PhortuneMerchant extends PhortuneDAO
protected $name; protected $name;
protected $viewPolicy; protected $viewPolicy;
protected $description; protected $description;
protected $contactInfo;
private $memberPHIDs = self::ATTACHABLE; private $memberPHIDs = self::ATTACHABLE;
@ -23,6 +24,7 @@ final class PhortuneMerchant extends PhortuneDAO
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255', 'name' => 'text255',
'description' => 'text', 'description' => 'text',
'contactInfo' => 'text',
), ),
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }

View file

@ -5,6 +5,7 @@ final class PhortuneMerchantTransaction
const TYPE_NAME = 'merchant:name'; const TYPE_NAME = 'merchant:name';
const TYPE_DESCRIPTION = 'merchant:description'; const TYPE_DESCRIPTION = 'merchant:description';
const TYPE_CONTACTINFO = 'merchant:contactinfo';
public function getApplicationName() { public function getApplicationName() {
return 'phortune'; return 'phortune';
@ -42,6 +43,10 @@ final class PhortuneMerchantTransaction
return pht( return pht(
'%s updated the description for this merchant.', '%s updated the description for this merchant.',
$this->renderHandleLink($author_phid)); $this->renderHandleLink($author_phid));
case self::TYPE_CONTACTINFO:
return pht(
'%s updated the contact information for this merchant.',
$this->renderHandleLink($author_phid));
} }
return parent::getTitle(); return parent::getTitle();
@ -51,6 +56,7 @@ final class PhortuneMerchantTransaction
$old = $this->getOldValue(); $old = $this->getOldValue();
switch ($this->getTransactionType()) { switch ($this->getTransactionType()) {
case self::TYPE_DESCRIPTION: case self::TYPE_DESCRIPTION:
case self::TYPE_CONTACTINFO:
return ($old === null); return ($old === null);
} }
return parent::shouldHide(); return parent::shouldHide();
@ -60,6 +66,8 @@ final class PhortuneMerchantTransaction
switch ($this->getTransactionType()) { switch ($this->getTransactionType()) {
case self::TYPE_DESCRIPTION: case self::TYPE_DESCRIPTION:
return ($this->getOldValue() !== null); return ($this->getOldValue() !== null);
case self::TYPE_CONTACTINFO:
return ($this->getOldValue() !== null);
} }
return parent::hasChangeDetails(); return parent::hasChangeDetails();

View file

@ -36,17 +36,32 @@ final class PhortuneSubscriptionWorker extends PhabricatorWorker {
->setSubscription($subscription); ->setSubscription($subscription);
// TODO: This isn't really ideal. It would be better to use an application // TODO: This isn't really ideal. It would be better to use an application
// actor than the original author of the subscription. In particular, if // actor than a fairly arbitrary account member.
// someone initiates a subscription, adds some other account managers, and
// later leaves the company, they'll continue "acting" here indefinitely.
// However, for now, some of the stuff later in the pipeline requires a // However, for now, some of the stuff later in the pipeline requires a
// valid actor with a real PHID. The subscription should eventually be // valid actor with a real PHID. The subscription should eventually be
// able to create these invoices "as" the application it is acting on // able to create these invoices "as" the application it is acting on
// behalf of. // behalf of.
$actor = id(new PhabricatorPeopleQuery())
$members = id(new PhabricatorPeopleQuery())
->setViewer($viewer) ->setViewer($viewer)
->withPHIDs(array($subscription->getAuthorPHID())) ->withPHIDs($account->getMemberPHIDs())
->executeOne(); ->execute();
$actor = null;
foreach ($members as $member) {
// Don't act as a disabled user. If all of the users on the account are
// disabled this means we won't charge the subscription, but that's
// probably correct since it means no one can cancel or pay it anyway.
if ($member->getIsDisabled()) {
continue;
}
// For now, just pick the first valid user we encounter as the actor.
$actor = $member;
break;
}
if (!$actor) { if (!$actor) {
throw new Exception(pht('Failed to load actor to bill subscription!')); throw new Exception(pht('Failed to load actor to bill subscription!'));
} }

View file

@ -1349,7 +1349,9 @@ abstract class PhabricatorEditEngine
} }
final public function addActionToCrumbs(PHUICrumbsView $crumbs) { final public function addActionToCrumbs(
PHUICrumbsView $crumbs,
array $parameters = array()) {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$can_create = $this->hasCreateCapability(); $can_create = $this->hasCreateCapability();
@ -1385,6 +1387,11 @@ abstract class PhabricatorEditEngine
$form_key = $config->getIdentifier(); $form_key = $config->getIdentifier();
$create_uri = $this->getEditURI(null, "form/{$form_key}/"); $create_uri = $this->getEditURI(null, "form/{$form_key}/");
if ($parameters) {
$create_uri = (string)id(new PhutilURI($create_uri))
->setQueryParams($parameters);
}
if (count($configs) > 1) { if (count($configs) > 1) {
$menu_icon = 'fa-caret-square-o-down'; $menu_icon = 'fa-caret-square-o-down';
@ -1395,6 +1402,11 @@ abstract class PhabricatorEditEngine
$form_key = $config->getIdentifier(); $form_key = $config->getIdentifier();
$config_uri = $this->getEditURI(null, "form/{$form_key}/"); $config_uri = $this->getEditURI(null, "form/{$form_key}/");
if ($parameters) {
$config_uri = (string)id(new PhutilURI($config_uri))
->setQueryParams($parameters);
}
$item_icon = 'fa-plus'; $item_icon = 'fa-plus';
$dropdown->addAction( $dropdown->addAction(

View file

@ -128,6 +128,9 @@ final class PhabricatorEditEngineConfiguration
$values = $this->getProperty('defaults', array()); $values = $this->getProperty('defaults', array());
foreach ($fields as $key => $field) { foreach ($fields as $key => $field) {
if (!$field->getIsDefaultable()) {
continue;
}
if ($is_new) { if ($is_new) {
if (array_key_exists($key, $values)) { if (array_key_exists($key, $values)) {
$field->readDefaultValueFromConfiguration($values[$key]); $field->readDefaultValueFromConfiguration($values[$key]);
@ -157,6 +160,11 @@ final class PhabricatorEditEngineConfiguration
} }
} }
// If the field isn't lockable, remove any lock we applied.
if (!$field->getIsLockable()) {
$field->setIsLocked(false);
}
$fields = $this->reorderFields($fields); $fields = $this->reorderFields($fields);
$preamble = $this->getPreamble(); $preamble = $this->getPreamble();

View file

@ -202,6 +202,12 @@ final class PhabricatorEnv extends Phobject {
phutil_load_library($library); phutil_load_library($library);
} }
// Drop any class map caches, since they will have generated without
// any classes from libraries. Without this, preflight setup checks can
// cause generation of a setup check cache that omits checks defined in
// libraries, for example.
PhutilClassMapQuery::deleteCaches();
// If custom libraries specify config options, they won't get default // If custom libraries specify config options, they won't get default
// values as the Default source has already been loaded, so we get it to // values as the Default source has already been loaded, so we get it to
// pull in all options from non-phabricator libraries now they are loaded. // pull in all options from non-phabricator libraries now they are loaded.