1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 11:30:55 +01:00

Rough in most of Calendar exports

Summary:
Ref T10747. Rough flow is:

  - Run a query.
  - Select a new "Export Events..." action.
  - This lets you define an "Export", which has a unique URL you can paste into Google Calendar or Calendar.app or whatever.

Most of this does nothing yet but here's the boilerplate.

Test Plan: Doesn't do anything yet.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10747

Differential Revision: https://secure.phabricator.com/D16675
This commit is contained in:
epriestley 2016-10-05 11:55:04 -07:00
parent c5efa3ecb5
commit 49448a87c1
21 changed files with 818 additions and 6 deletions

View file

@ -0,0 +1,14 @@
CREATE TABLE {$NAMESPACE}_calendar.calendar_export (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
name LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
authorPHID VARBINARY(64) NOT NULL,
policyMode VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
queryKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
secretKey BINARY(20) NOT NULL,
isDisabled BOOL NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
KEY `key_author` (authorPHID),
UNIQUE KEY `key_secret` (secretKey)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_calendar.calendar_exporttransaction (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
authorPHID VARBINARY(64) NOT NULL,
objectPHID VARBINARY(64) NOT NULL,
viewPolicy VARBINARY(64) NOT NULL,
editPolicy VARBINARY(64) NOT NULL,
commentPHID VARBINARY(64) DEFAULT NULL,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -2075,6 +2075,20 @@ phutil_register_library_map(array(
'PhabricatorCalendarEventTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarEventTransactionType.php',
'PhabricatorCalendarEventUntilDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php',
'PhabricatorCalendarEventViewController' => 'applications/calendar/controller/PhabricatorCalendarEventViewController.php',
'PhabricatorCalendarExport' => 'applications/calendar/storage/PhabricatorCalendarExport.php',
'PhabricatorCalendarExportDisableTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportDisableTransaction.php',
'PhabricatorCalendarExportEditController' => 'applications/calendar/controller/PhabricatorCalendarExportEditController.php',
'PhabricatorCalendarExportEditEngine' => 'applications/calendar/editor/PhabricatorCalendarExportEditEngine.php',
'PhabricatorCalendarExportEditor' => 'applications/calendar/editor/PhabricatorCalendarExportEditor.php',
'PhabricatorCalendarExportListController' => 'applications/calendar/controller/PhabricatorCalendarExportListController.php',
'PhabricatorCalendarExportModeTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportModeTransaction.php',
'PhabricatorCalendarExportNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportNameTransaction.php',
'PhabricatorCalendarExportPHIDType' => 'applications/calendar/phid/PhabricatorCalendarExportPHIDType.php',
'PhabricatorCalendarExportQuery' => 'applications/calendar/query/PhabricatorCalendarExportQuery.php',
'PhabricatorCalendarExportQueryKeyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php',
'PhabricatorCalendarExportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarExportSearchEngine.php',
'PhabricatorCalendarExportTransaction' => 'applications/calendar/storage/PhabricatorCalendarExportTransaction.php',
'PhabricatorCalendarExportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php',
'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php',
'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php',
'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php',
@ -6825,6 +6839,25 @@ phutil_register_library_map(array(
'PhabricatorCalendarEventTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCalendarEventUntilDateTransaction' => 'PhabricatorCalendarEventDateTransaction',
'PhabricatorCalendarEventViewController' => 'PhabricatorCalendarController',
'PhabricatorCalendarExport' => array(
'PhabricatorCalendarDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorCalendarExportDisableTransaction' => 'PhabricatorCalendarExportTransactionType',
'PhabricatorCalendarExportEditController' => 'PhabricatorCalendarController',
'PhabricatorCalendarExportEditEngine' => 'PhabricatorEditEngine',
'PhabricatorCalendarExportEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorCalendarExportListController' => 'PhabricatorCalendarController',
'PhabricatorCalendarExportModeTransaction' => 'PhabricatorCalendarExportTransactionType',
'PhabricatorCalendarExportNameTransaction' => 'PhabricatorCalendarExportTransactionType',
'PhabricatorCalendarExportPHIDType' => 'PhabricatorPHIDType',
'PhabricatorCalendarExportQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarExportQueryKeyTransaction' => 'PhabricatorCalendarExportTransactionType',
'PhabricatorCalendarExportSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorCalendarExportTransaction' => 'PhabricatorModularTransaction',
'PhabricatorCalendarExportTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO',
'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase',
'PhabricatorCalendarIconSet' => 'PhabricatorIconSet',

View file

@ -62,6 +62,16 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication {
'export/(?P<id>[1-9]\d*)/(?P<filename>[^/]*)'
=> 'PhabricatorCalendarEventExportController',
),
'export/' => array(
$this->getQueryRoutePattern()
=> 'PhabricatorCalendarExportListController',
$this->getEditRoutePattern('edit/')
=> 'PhabricatorCalendarExportEditController',
'(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarExportViewController',
'ics/(?P<secretKey>[^/]+)/(?P<filename>[^/]*)'
=> 'PhabricatorCalendarExportICSController',
),
),
);
}

View file

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

View file

@ -0,0 +1,27 @@
<?php
final class PhabricatorCalendarExportListController
extends PhabricatorCalendarController {
public function handleRequest(AphrontRequest $request) {
return id(new PhabricatorCalendarExportSearchEngine())
->setController($this)
->buildResponse();
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$doc_name = 'Calendar User Guide: Exporting Events';
$doc_href = PhabricatorEnv::getDoclink($doc_name);
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Guide: Exporting Events'))
->setIcon('fa-book')
->setHref($doc_href));
return $crumbs;
}
}

View file

@ -0,0 +1,96 @@
<?php
final class PhabricatorCalendarExportEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'calendar.export';
public function getEngineName() {
return pht('Calendar Exports');
}
public function isEngineConfigurable() {
return false;
}
public function getSummaryHeader() {
return pht('Configure Calendar Export Forms');
}
public function getSummaryText() {
return pht('Configure how users create and edit exports.');
}
public function getEngineApplicationClass() {
return 'PhabricatorCalendarApplication';
}
protected function newEditableObject() {
return PhabricatorCalendarExport::initializeNewCalendarExport(
$this->getViewer());
}
protected function newObjectQuery() {
return new PhabricatorCalendarExportQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create New Export');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Export: %s', $object->getName());
}
protected function getObjectEditShortText($object) {
return $object->getMonogram();
}
protected function getObjectCreateShortText() {
return pht('Create Export');
}
protected function getObjectName() {
return pht('Export');
}
protected function getObjectViewURI($object) {
return $object->getURI();
}
protected function getEditorURI() {
return $this->getApplication()->getApplicationURI('export/edit/');
}
protected function buildCustomEditFields($object) {
$viewer = $this->getViewer();
$fields = array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setDescription(pht('Name of the export.'))
->setIsRequired(true)
->setTransactionType(
PhabricatorCalendarExportNameTransaction::TRANSACTIONTYPE)
->setConduitDescription(pht('Rename the export.'))
->setConduitTypeDescription(pht('New export name.'))
->setValue($object->getName()),
id(new PhabricatorBoolEditField())
->setKey('disabled')
->setOptions(pht('Active'), pht('Disabled'))
->setLabel(pht('Disabled'))
->setDescription(pht('Disable the export.'))
->setTransactionType(
PhabricatorCalendarExportDisableTransaction::TRANSACTIONTYPE)
->setIsConduitOnly(true)
->setConduitDescription(pht('Disable or restore the export.'))
->setConduitTypeDescription(pht('True to cancel the export.'))
->setValue($object->getIsDisabled()),
);
return $fields;
}
}

View file

@ -0,0 +1,14 @@
<?php
final class PhabricatorCalendarExportEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorCalendarApplication';
}
public function getEditorObjectsDescription() {
return pht('Calendar Exports');
}
}

View file

@ -0,0 +1,50 @@
<?php
final class PhabricatorCalendarExportPHIDType extends PhabricatorPHIDType {
const TYPECONST = 'CEXP';
public function getTypeName() {
return pht('Calendar Export');
}
public function newObject() {
return new PhabricatorCalendarExport();
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorCalendarApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new PhabricatorCalendarExportQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
foreach ($handles as $phid => $handle) {
$export = $objects[$phid];
$id = $export->getID();
$name = $export->getName();
$uri = $export->getURI();
$handle
->setName($name)
->setFullName(pht('Calendar Export %s: %s', $id, $name))
->setURI($uri);
if ($export->getIsDisabled()) {
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
}
}
}
}

View file

@ -0,0 +1,81 @@
<?php
final class PhabricatorCalendarExportQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $isDisabled;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs(array $phids) {
$this->authorPHIDs = $phids;
return $this;
}
public function withIsDisabled($is_disabled) {
$this->isDisabled = $is_disabled;
return $this;
}
public function newResultObject() {
return new PhabricatorCalendarExport();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'export.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'export.phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'export.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->isDisabled !== null) {
$where[] = qsprintf(
$conn,
'export.isDisabled = %d',
(int)$this->isDisabled);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'export';
}
public function getQueryApplicationClass() {
return 'PhabricatorCalendarApplication';
}
}

View file

@ -0,0 +1,109 @@
<?php
final class PhabricatorCalendarExportSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Calendar Exports');
}
public function getApplicationClassName() {
return 'PhabricatorCalendarApplication';
}
public function newQuery() {
$viewer = $this->requireViewer();
return id(new PhabricatorCalendarExportQuery())
->withAuthorPHIDs(array($viewer->getPHID()));
}
protected function buildCustomSearchFields() {
return array();
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
return $query;
}
protected function getURI($path) {
return '/calendar/export/'.$path;
}
protected function getBuiltinQueryNames() {
$names = array(
'all' => pht('All Exports'),
);
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'all':
return $query;
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function renderResultList(
array $exports,
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($exports, 'PhabricatorCalendarExport');
$viewer = $this->requireViewer();
$list = new PHUIObjectItemListView();
foreach ($exports as $export) {
$item = id(new PHUIObjectItemView())
->setViewer($viewer)
->setHeader($export->getName())
->setHref($export->getURI());
if ($export->getIsDisabled()) {
$item->setDisabled(true);
}
$list->addItem($item);
}
$result = new PhabricatorApplicationSearchResultView();
$result->setObjectList($list);
$result->setNoDataString(pht('No exports found.'));
return $result;
}
protected function getNewUserBody() {
$doc_name = 'Calendar User Guide: Exporting Events';
$doc_href = PhabricatorEnv::getDoclink($doc_name);
$create_button = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-book white')
->setText($doc_name)
->setHref($doc_href)
->setColor(PHUIButtonView::GREEN);
$icon = $this->getApplication()->getIcon();
$app_name = $this->getApplication()->getName();
$view = id(new PHUIBigInfoView())
->setIcon('fa-download')
->setTitle(pht('No Exports Configured'))
->setDescription(
pht(
'You have not set up any events for export from Calendar yet. '.
'See the documentation for instructions on how to get started.'))
->addAction($create_button);
return $view;
}
}

View file

@ -0,0 +1,144 @@
<?php
final class PhabricatorCalendarExport extends PhabricatorCalendarDAO
implements
PhabricatorPolicyInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorDestructibleInterface {
protected $name;
protected $authorPHID;
protected $policyMode;
protected $queryKey;
protected $secretKey;
protected $isDisabled = 0;
const MODE_PUBLIC = 'public';
const MODE_PRIVATE = 'private';
public static function initializeNewCalendarExport(PhabricatorUser $actor) {
return id(new self())
->setAuthorPHID($actor->getPHID())
->setPolicyMode(self::MODE_PRIVATE)
->setIsDisabled(0);
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text',
'policyMode' => 'text64',
'queryKey' => 'text64',
'secretKey' => 'bytes20',
'isDisabled' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'key_author' => array(
'columns' => array('authorPHID'),
),
'key_secret' => array(
'columns' => array('secretKey'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function getPHIDType() {
return PhabricatorCalendarExportPHIDType::TYPECONST;
}
public function save() {
if (!$this->getSecretKey()) {
$this->setSecretKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
}
public function getURI() {
$id = $this->getID();
return "/calendar/export/{$id}/";
}
private static function getPolicyModeMap() {
return array(
self::MODE_PUBLIC => array(
'name' => pht('Public'),
),
self::MODE_PRIVATE => array(
'name' => pht('Private'),
),
);
}
private static function getPolicyModeSpec($const) {
return idx(self::getPolicyModeMap(), $const, array());
}
public static function getPolicyModeName($const) {
$map = self::getPolicyModeSpec($const);
return idx($map, 'name', $const);
}
public static function getPolicyModes() {
return array_keys(self::getPolicyModeMap());
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return $this->getAuthorPHID();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorCalendarExportEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorCalendarExportTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->delete();
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorCalendarExportTransaction
extends PhabricatorModularTransaction {
public function getApplicationName() {
return 'calendar';
}
public function getApplicationTransactionType() {
return PhabricatorCalendarExportPHIDType::TYPECONST;
}
public function getBaseTransactionClass() {
return 'PhabricatorCalendarExportTransactionType';
}
}

View file

@ -0,0 +1,28 @@
<?php
final class PhabricatorCalendarExportDisableTransaction
extends PhabricatorCalendarExportTransactionType {
const TRANSACTIONTYPE = 'calendar.export.disable';
public function generateOldValue($object) {
return (int)$object->getIsDisabled();
}
public function applyInternalEffects($object, $value) {
$object->setIsDisabled((int)$value);
}
public function getTitle() {
if ($this->getNewValue()) {
return pht(
'%s disabled this export.',
$this->renderAuthor());
} else {
return pht(
'%s enabled this export.',
$this->renderAuthor());
}
}
}

View file

@ -0,0 +1,54 @@
<?php
final class PhabricatorCalendarExportModeTransaction
extends PhabricatorCalendarExportTransactionType {
const TRANSACTIONTYPE = 'calendar.export.mode';
public function generateOldValue($object) {
return $object->getPolicyMode();
}
public function applyInternalEffects($object, $value) {
$object->setPolicyMode($value);
}
public function getTitle() {
$old_value = $this->getOldValue();
$new_value = $this->getNewValue();
$old_name = PhabricatorCalendarExport::getPolicyModeName($old_value);
$new_name = PhabricatorCalendarExport::getPolicyModeName($new_value);
return pht(
'%s changed the policy mode for this export from %s to %s.',
$this->renderAuthor(),
$this->renderValue($old_name),
$this->renderValue($new_name));
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$valid = PhabricatorCalendarExport::getPolicyModes();
$valid = array_fuse($valid);
foreach ($xactions as $xaction) {
$value = $xaction->getNewValue();
if (isset($valid[$value])) {
continue;
}
$errors[] = $this->newInvalidError(
pht(
'Mode "%s" is not a valid policy mode. Valid modes are: %s.',
$value,
implode(', ', $valid)),
$xaction);
}
return $errors;
}
}

View file

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

View file

@ -0,0 +1,51 @@
<?php
final class PhabricatorCalendarExportQueryKeyTransaction
extends PhabricatorCalendarExportTransactionType {
const TRANSACTIONTYPE = 'calendar.export.querykey';
public function generateOldValue($object) {
return $object->getQueryKey();
}
public function applyInternalEffects($object, $value) {
$object->setQueryKey($value);
}
public function getTitle() {
return pht(
'%s changed the query for this export.',
$this->renderAuthor());
}
public function validateTransactions($object, array $xactions) {
$errors = array();
foreach ($xactions as $xaction) {
$value = $xaction->getNewValue();
$query = id(new PhabricatorSavedQueryQuery())
->withEngineClassNames(array('PhabricatorCalendarEventSearchEngine'))
->withQueryKeys(array($value))
->executeOne();
if ($query) {
continue;
}
$errors[] = $this->newInvalidError(
pht(
'Query key "%s" does not identify a valid event query.',
$value),
$xaction);
}
if ($this->isEmptyTextTransaction($object->getQueryKey(), $xactions)) {
$errors[] = $this->newRequiredError(
pht('Calendar exports must have a query key.'));
}
return $errors;
}
}

View file

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

View file

@ -421,7 +421,7 @@ final class ManiphestTaskSearchEngine
'you need to get done. Tasks assigned to you will appear here.'))
->addAction($create_button);
return $view;
return $view;
}
}

View file

@ -0,0 +1,12 @@
@title Calendar User Guide: Exporting Events
@group userguide
Exporting events to other calendars.
Overview
========
IMPORTANT: Calendar is a prototype application. See
@{article:User Guide: Prototype Applications}.
Coming soon!

View file

@ -1339,11 +1339,12 @@ abstract class LiskDAO extends Phobject {
* @task hook
*/
public function generatePHID() {
throw new Exception(
pht(
'To use %s, you need to overload %s to perform PHID generation.',
'CONFIG_AUX_PHID',
'generatePHID()'));
$type = $this->getPHIDType();
return PhabricatorPHID::generateNewPHID($type);
}
public function getPHIDType() {
throw new PhutilMethodNotImplementedException();
}