Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/audit/storage/PhabricatorAuditTransactionComment.php b/src/applications/audit/storage/PhabricatorAuditTransactionComment.php
index 7b5cebbc39..55508d6243 100644
--- a/src/applications/audit/storage/PhabricatorAuditTransactionComment.php
+++ b/src/applications/audit/storage/PhabricatorAuditTransactionComment.php
@@ -1,41 +1,58 @@
<?php
final class PhabricatorAuditTransactionComment
extends PhabricatorApplicationTransactionComment {
protected $commitPHID;
protected $pathID;
protected $isNewFile = 0;
protected $lineNumber = 0;
protected $lineLength = 0;
protected $fixedState;
protected $hasReplies = 0;
protected $replyToCommentPHID;
protected $legacyCommentID;
public function getApplicationTransactionObject() {
return new PhabricatorAuditTransaction();
}
public function shouldUseMarkupCache($field) {
// Only cache submitted comments.
return ($this->getTransactionPHID() != null);
}
public function getConfiguration() {
$config = parent::getConfiguration();
+
$config[self::CONFIG_COLUMN_SCHEMA] = array(
'commitPHID' => 'phid?',
'pathID' => 'id?',
'isNewFile' => 'bool',
'lineNumber' => 'uint32',
'lineLength' => 'uint32',
'fixedState' => 'text12?',
'hasReplies' => 'bool',
'replyToCommentPHID' => 'phid?',
'legacyCommentID' => 'id?',
) + $config[self::CONFIG_COLUMN_SCHEMA];
+
+ $config[self::CONFIG_KEY_SCHEMA] = array(
+ 'key_path' => array(
+ 'columns' => array('pathID'),
+ ),
+ 'key_draft' => array(
+ 'columns' => array('authorPHID', 'transactionPHID'),
+ ),
+ 'key_commit' => array(
+ 'columns' => array('commitPHID'),
+ ),
+ 'key_legacy' => array(
+ 'columns' => array('legacyCommentID'),
+ ),
+ ) + $config[self::CONFIG_KEY_SCHEMA];
+
return $config;
}
}
diff --git a/src/applications/auth/storage/PhabricatorAuthFactorConfig.php b/src/applications/auth/storage/PhabricatorAuthFactorConfig.php
index 662ab9f47e..2e2870ef65 100644
--- a/src/applications/auth/storage/PhabricatorAuthFactorConfig.php
+++ b/src/applications/auth/storage/PhabricatorAuthFactorConfig.php
@@ -1,47 +1,52 @@
<?php
final class PhabricatorAuthFactorConfig extends PhabricatorAuthDAO {
protected $userPHID;
protected $factorKey;
protected $factorName;
protected $factorSecret;
protected $properties = array();
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'factorKey' => 'text64',
'factorName' => 'text',
'factorSecret' => 'text',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_user' => array(
+ 'columns' => array('userPHID'),
+ ),
+ ),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorAuthAuthFactorPHIDType::TYPECONST);
}
public function getImplementation() {
return idx(PhabricatorAuthFactor::getAllFactors(), $this->getFactorKey());
}
public function requireImplementation() {
$impl = $this->getImplementation();
if (!$impl) {
throw new Exception(
pht(
'Attempting to operate on multi-factor auth which has no '.
'corresponding implementation (factor key is "%s").',
$this->getFactorKey()));
}
return $impl;
}
}
diff --git a/src/applications/auth/storage/PhabricatorAuthProviderConfig.php b/src/applications/auth/storage/PhabricatorAuthProviderConfig.php
index 7dd425f477..c60aace712 100644
--- a/src/applications/auth/storage/PhabricatorAuthProviderConfig.php
+++ b/src/applications/auth/storage/PhabricatorAuthProviderConfig.php
@@ -1,100 +1,109 @@
<?php
final class PhabricatorAuthProviderConfig extends PhabricatorAuthDAO
implements PhabricatorPolicyInterface {
protected $providerClass;
protected $providerType;
protected $providerDomain;
protected $isEnabled;
protected $shouldAllowLogin = 0;
protected $shouldAllowRegistration = 0;
protected $shouldAllowLink = 0;
protected $shouldAllowUnlink = 0;
protected $shouldTrustEmails = 0;
protected $properties = array();
private $provider;
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_AUTH);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'isEnabled' => 'bool',
'providerClass' => 'text128',
'providerType' => 'text64',
'providerDomain' => 'text128',
'shouldAllowLogin' => 'bool',
'shouldAllowRegistration' => 'bool',
'shouldAllowLink' => 'bool',
'shouldAllowUnlink' => 'bool',
'shouldTrustEmails' => 'bool',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_provider' => array(
+ 'columns' => array('providerType', 'providerDomain'),
+ 'unique' => true,
+ ),
+ 'key_class' => array(
+ 'columns' => array('providerClass'),
+ ),
+ ),
) + parent::getConfiguration();
}
public function getProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function setProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
public function getProvider() {
if (!$this->provider) {
$base = PhabricatorAuthProvider::getAllBaseProviders();
$found = null;
foreach ($base as $provider) {
if (get_class($provider) == $this->providerClass) {
$found = $provider;
break;
}
}
if ($found) {
$this->provider = id(clone $found)->attachProviderConfig($this);
}
}
return $this->provider;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return PhabricatorPolicies::POLICY_USER;
case PhabricatorPolicyCapability::CAN_EDIT:
return PhabricatorPolicies::POLICY_ADMIN;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php b/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php
index 43a617d810..8a71f6024b 100644
--- a/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php
+++ b/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php
@@ -1,104 +1,108 @@
<?php
final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO
implements PhabricatorPolicyInterface {
// TODO: OAuth1 stores a client identifier here, which is not a real PHID.
// At some point, we should rename this column to be a little more generic.
protected $objectPHID;
protected $tokenType;
protected $tokenExpires;
protected $tokenCode;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'tokenType' => 'text64',
'tokenExpires' => 'epoch',
'tokenCode' => 'text64',
),
self::CONFIG_KEY_SCHEMA => array(
'key_token' => array(
'columns' => array('objectPHID', 'tokenType', 'tokenCode'),
+ 'unique' => true,
+ ),
+ 'key_expires' => array(
+ 'columns' => array('tokenExpires'),
),
),
) + parent::getConfiguration();
}
public function getTokenReadableTypeName() {
// Eventually, it would be nice to let applications implement token types
// so we can put this in modular subclasses.
switch ($this->tokenType) {
case PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE:
return pht('One-Time Login Token');
case PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE:
return pht('Password Reset Token');
}
return $this->tokenType;
}
public function isRevocable() {
if ($this->tokenExpires < time()) {
return false;
}
switch ($this->tokenType) {
case PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE:
case PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE:
return true;
}
return false;
}
public function revokeToken() {
if ($this->isRevocable()) {
$this->setTokenExpires(PhabricatorTime::getNow() - 1)->save();
}
return $this;
}
public static function revokeTokens(
PhabricatorUser $viewer,
array $object_phids,
array $token_types) {
$tokens = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer($viewer)
->withObjectPHIDs($object_phids)
->withTokenTypes($token_types)
->withExpired(false)
->execute();
foreach ($tokens as $token) {
$token->revokeToken();
}
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
// We're just implement this interface to get access to the standard
// query infrastructure.
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/cache/storage/PhabricatorCacheSchemaSpec.php b/src/applications/cache/storage/PhabricatorCacheSchemaSpec.php
index ed481a25df..4abd8fd388 100644
--- a/src/applications/cache/storage/PhabricatorCacheSchemaSpec.php
+++ b/src/applications/cache/storage/PhabricatorCacheSchemaSpec.php
@@ -1,31 +1,39 @@
<?php
final class PhabricatorCacheSchemaSpec extends PhabricatorConfigSchemaSpec {
public function buildSchemata() {
$this->buildLiskSchemata('PhabricatorCacheDAO');
$this->buildRawSchema(
'cache',
id(new PhabricatorKeyValueDatabaseCache())->getTableName(),
array(
'id' => 'id64',
'cacheKeyHash' => 'bytes12',
'cacheKey' => 'text128',
'cacheFormat' => 'text16',
'cacheData' => 'bytes',
'cacheCreated' => 'epoch',
'cacheExpires' => 'epoch?',
),
array(
'PRIMARY' => array(
'columns' => array('id'),
+ 'unique' => true,
),
'key_cacheKeyHash' => array(
'columns' => array('cacheKeyHash'),
+ 'unique' => true,
+ ),
+ 'key_cacheCreated' => array(
+ 'columns' => array('cacheCreated'),
+ ),
+ 'key_ttl' => array(
+ 'columns' => array('cacheExpires'),
),
));
}
}
diff --git a/src/applications/cache/storage/PhabricatorMarkupCache.php b/src/applications/cache/storage/PhabricatorMarkupCache.php
index a67e4d63ff..e4f7f6722a 100644
--- a/src/applications/cache/storage/PhabricatorMarkupCache.php
+++ b/src/applications/cache/storage/PhabricatorMarkupCache.php
@@ -1,29 +1,33 @@
<?php
final class PhabricatorMarkupCache extends PhabricatorCacheDAO {
protected $cacheKey;
protected $cacheData;
protected $metadata;
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'cacheData' => self::SERIALIZATION_PHP,
'metadata' => self::SERIALIZATION_JSON,
),
self::CONFIG_BINARY => array(
'cacheData' => true,
),
self::CONFIG_COLUMN_SCHEMA => array(
'cacheKey' => 'text128',
),
self::CONFIG_KEY_SCHEMA => array(
'cacheKey' => array(
'columns' => array('cacheKey'),
+ 'unique' => true,
+ ),
+ 'dateCreated' => array(
+ 'columns' => array('dateCreated'),
),
),
) + parent::getConfiguration();
}
}
diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
index 481e5112b5..aba4fdbc96 100644
--- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php
+++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
@@ -1,121 +1,126 @@
<?php
final class PhabricatorCalendarEvent
extends PhabricatorCalendarDAO
implements PhabricatorPolicyInterface {
protected $userPHID;
protected $dateFrom;
protected $dateTo;
protected $status;
protected $description;
const STATUS_AWAY = 1;
const STATUS_SPORADIC = 2;
private static $statusTexts = array(
self::STATUS_AWAY => 'away',
self::STATUS_SPORADIC => 'sporadic',
);
public function getTextStatus() {
return self::$statusTexts[$this->status];
}
public function getStatusOptions() {
return array(
self::STATUS_AWAY => pht('Away'),
self::STATUS_SPORADIC => pht('Sporadic'),
);
}
public function getHumanStatus() {
$options = $this->getStatusOptions();
return $options[$this->status];
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'dateFrom' => 'epoch',
'dateTo' => 'epoch',
'status' => 'uint32',
'description' => 'text',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'userPHID_dateFrom' => array(
+ 'columns' => array('userPHID', 'dateTo'),
+ ),
+ ),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorCalendarEventPHIDType::TYPECONST);
}
public function getTerseSummary(PhabricatorUser $viewer) {
$until = phabricator_date($this->dateTo, $viewer);
if ($this->status == PhabricatorCalendarEvent::STATUS_SPORADIC) {
return pht('Sporadic until %s', $until);
} else {
return pht('Away until %s', $until);
}
}
public function setTextStatus($status) {
$statuses = array_flip(self::$statusTexts);
return $this->setStatus($statuses[$status]);
}
public function loadCurrentStatuses($user_phids) {
if (!$user_phids) {
return array();
}
$statuses = $this->loadAllWhere(
'userPHID IN (%Ls) AND UNIX_TIMESTAMP() BETWEEN dateFrom AND dateTo',
$user_phids);
return mpull($statuses, null, 'getUserPHID');
}
/**
* Validates data and throws exceptions for non-sensical status
* windows
*/
public function save() {
if ($this->getDateTo() <= $this->getDateFrom()) {
throw new PhabricatorCalendarEventInvalidEpochException();
}
return parent::save();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
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->getUserPHID();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/calendar/storage/PhabricatorCalendarHoliday.php b/src/applications/calendar/storage/PhabricatorCalendarHoliday.php
index e6d6a2c708..71549a1c0e 100644
--- a/src/applications/calendar/storage/PhabricatorCalendarHoliday.php
+++ b/src/applications/calendar/storage/PhabricatorCalendarHoliday.php
@@ -1,41 +1,42 @@
<?php
final class PhabricatorCalendarHoliday extends PhabricatorCalendarDAO {
protected $day;
protected $name;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'day' => 'date',
'name' => 'text64',
),
self::CONFIG_KEY_SCHEMA => array(
'day' => array(
'columns' => array('day'),
+ 'unique' => true,
),
),
) + parent::getConfiguration();
}
public static function getNthBusinessDay($epoch, $n) {
// Sadly, there are not many holidays. So we can load all of them.
$holidays = id(new PhabricatorCalendarHoliday())->loadAll();
$holidays = mpull($holidays, null, 'getDay');
$interval = ($n > 0 ? 1 : -1) * 24 * 60 * 60;
$return = $epoch;
for ($i = abs($n); $i > 0; ) {
$return += $interval;
$weekday = date('w', $return);
if ($weekday != 0 && $weekday != 6 && // Sunday and Saturday
!isset($holidays[date('Y-m-d', $return)])) {
$i--;
}
}
return $return;
}
}
diff --git a/src/applications/chatlog/storage/PhabricatorChatLogChannel.php b/src/applications/chatlog/storage/PhabricatorChatLogChannel.php
index ce6117185a..7416f7b4e6 100644
--- a/src/applications/chatlog/storage/PhabricatorChatLogChannel.php
+++ b/src/applications/chatlog/storage/PhabricatorChatLogChannel.php
@@ -1,54 +1,55 @@
<?php
final class PhabricatorChatLogChannel
extends PhabricatorChatLogDAO
implements PhabricatorPolicyInterface {
protected $serviceName;
protected $serviceType;
protected $channelName;
protected $viewPolicy;
protected $editPolicy;
public function getConfiguration() {
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'serviceName' => 'text64',
'serviceType' => 'text32',
'channelName' => 'text64',
),
self::CONFIG_KEY_SCHEMA => array(
'key_channel' => array(
'columns' => array('channelName', 'serviceType', 'serviceName'),
+ 'unique' => true,
),
),
) + parent::getConfiguration();
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->viewPolicy;
break;
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->editPolicy;
break;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/chatlog/storage/PhabricatorChatLogEvent.php b/src/applications/chatlog/storage/PhabricatorChatLogEvent.php
index 68c6968f77..39fd1d37e9 100644
--- a/src/applications/chatlog/storage/PhabricatorChatLogEvent.php
+++ b/src/applications/chatlog/storage/PhabricatorChatLogEvent.php
@@ -1,58 +1,63 @@
<?php
final class PhabricatorChatLogEvent
extends PhabricatorChatLogDAO
implements PhabricatorPolicyInterface {
protected $channelID;
protected $epoch;
protected $author;
protected $type;
protected $message;
protected $loggedByPHID;
private $channel = self::ATTACHABLE;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'author' => 'text64',
'type' => 'text4',
'message' => 'text',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'channel' => array(
+ 'columns' => array('epoch'),
+ ),
+ ),
) + parent::getConfiguration();
}
public function attachChannel(PhabricatorChatLogChannel $channel) {
$this->channel = $channel;
return $this;
}
public function getChannel() {
return $this->assertAttached($this->channel);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return $this->getChannel()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getChannel()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/conduit/storage/PhabricatorConduitCertificateToken.php b/src/applications/conduit/storage/PhabricatorConduitCertificateToken.php
index c324e4fc08..0baf243e9a 100644
--- a/src/applications/conduit/storage/PhabricatorConduitCertificateToken.php
+++ b/src/applications/conduit/storage/PhabricatorConduitCertificateToken.php
@@ -1,24 +1,26 @@
<?php
final class PhabricatorConduitCertificateToken extends PhabricatorConduitDAO {
protected $userPHID;
protected $token;
public function getConfiguration() {
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'token' => 'text64?',
),
self::CONFIG_KEY_SCHEMA => array(
'userPHID' => array(
'columns' => array('userPHID'),
+ 'unique' => true,
),
'token' => array(
'columns' => array('token'),
+ 'unique' => true,
),
),
) + parent::getConfiguration();
}
}
diff --git a/src/applications/conduit/storage/PhabricatorConduitConnectionLog.php b/src/applications/conduit/storage/PhabricatorConduitConnectionLog.php
index 0d47fcf735..9f4bcf0be0 100644
--- a/src/applications/conduit/storage/PhabricatorConduitConnectionLog.php
+++ b/src/applications/conduit/storage/PhabricatorConduitConnectionLog.php
@@ -1,21 +1,26 @@
<?php
final class PhabricatorConduitConnectionLog extends PhabricatorConduitDAO {
protected $client;
protected $clientVersion;
protected $clientDescription;
protected $username;
public function getConfiguration() {
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'client' => 'text255?',
'clientVersion' => 'text255?',
'clientDescription' => 'text255?',
'username' => 'text255?',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_created' => array(
+ 'columns' => array('dateCreated'),
+ ),
+ ),
) + parent::getConfiguration();
}
}
diff --git a/src/applications/conduit/storage/PhabricatorConduitMethodCallLog.php b/src/applications/conduit/storage/PhabricatorConduitMethodCallLog.php
index c3be27440b..6d6f3dfc4f 100644
--- a/src/applications/conduit/storage/PhabricatorConduitMethodCallLog.php
+++ b/src/applications/conduit/storage/PhabricatorConduitMethodCallLog.php
@@ -1,48 +1,59 @@
<?php
final class PhabricatorConduitMethodCallLog
extends PhabricatorConduitDAO
implements PhabricatorPolicyInterface {
protected $callerPHID;
protected $connectionID;
protected $method;
protected $error;
protected $duration;
public function getConfiguration() {
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'id' => 'id64',
'connectionID' => 'id64?',
'method' => 'text255',
'error' => 'text255',
'duration' => 'uint64',
'callerPHID' => 'phid?',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_date' => array(
+ 'columns' => array('dateCreated'),
+ ),
+ 'key_method' => array(
+ 'columns' => array('method'),
+ ),
+ 'key_callermethod' => array(
+ 'columns' => array('callerPHID', 'method'),
+ ),
+ ),
) + parent::getConfiguration();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_USER;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php
index 199529e684..1997d660be 100644
--- a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php
+++ b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php
@@ -1,169 +1,176 @@
<?php
final class PhabricatorConfigDatabaseIssueController
extends PhabricatorConfigDatabaseController {
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$query = $this->buildSchemaQuery();
$actual = $query->loadActualSchema();
$expect = $query->loadExpectedSchema();
$comp = $query->buildComparisonSchema($expect, $actual);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Database Issues'));
// Collect all open issues.
$issues = array();
foreach ($comp->getDatabases() as $database_name => $database) {
foreach ($database->getLocalIssues() as $issue) {
$issues[] = array(
$database_name,
null,
null,
null,
$issue);
}
foreach ($database->getTables() as $table_name => $table) {
foreach ($table->getLocalIssues() as $issue) {
$issues[] = array(
$database_name,
$table_name,
null,
null,
$issue);
}
foreach ($table->getColumns() as $column_name => $column) {
foreach ($column->getLocalIssues() as $issue) {
$issues[] = array(
$database_name,
$table_name,
'column',
$column_name,
$issue);
}
}
foreach ($table->getKeys() as $key_name => $key) {
foreach ($key->getLocalIssues() as $issue) {
$issues[] = array(
$database_name,
$table_name,
'key',
$key_name,
$issue);
}
}
}
}
// Sort all open issues so that the most severe issues appear first.
$order = array();
$counts = array();
foreach ($issues as $key => $issue) {
$const = $issue[4];
$status = PhabricatorConfigStorageSchema::getIssueStatus($const);
$severity = PhabricatorConfigStorageSchema::getStatusSeverity($status);
$order[$key] = sprintf(
'~%d~%s%s%s',
9 - $severity,
$issue[0],
$issue[1],
$issue[3]);
if (empty($counts[$status])) {
$counts[$status] = 0;
}
$counts[$status]++;
}
asort($order);
$issues = array_select_keys($issues, array_keys($order));
// Render the issues.
$rows = array();
foreach ($issues as $issue) {
$const = $issue[4];
+ $database_link = phutil_tag(
+ 'a',
+ array(
+ 'href' => $this->getApplicationURI('/database/'.$issue[0].'/'),
+ ),
+ $issue[0]);
+
$rows[] = array(
$this->renderIcon(
PhabricatorConfigStorageSchema::getIssueStatus($const)),
- $issue[0],
+ $database_link,
$issue[1],
$issue[2],
$issue[3],
PhabricatorConfigStorageSchema::getIssueDescription($const),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Database'),
pht('Table'),
pht('Type'),
pht('Column/Key'),
pht('Issue'),
))
->setColumnClasses(
array(
null,
null,
null,
null,
null,
'wide',
));
$errors = array();
$errors[] = pht(
'IMPORTANT: This feature is in development and the information below '.
'is not accurate! Ignore it for now. See T1191.');
if (isset($counts[PhabricatorConfigStorageSchema::STATUS_FAIL])) {
$errors[] = pht(
'Detected %s serious issue(s) with the schemata.',
new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_FAIL]));
}
if (isset($counts[PhabricatorConfigStorageSchema::STATUS_WARN])) {
$errors[] = pht(
'Detected %s warning(s) with the schemata.',
new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_WARN]));
}
if (isset($counts[PhabricatorConfigStorageSchema::STATUS_NOTE])) {
$errors[] = pht(
'Detected %s minor issue(s) with the scheamata.',
new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_NOTE]));
}
$title = pht('Database Issues');
$table_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormErrors($errors)
->appendChild($table);
$nav = $this->buildSideNavView();
$nav->selectFilter('dbissue/');
$nav->appendChild(
array(
$crumbs,
$table_box,
));
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
));
}
}
diff --git a/src/applications/config/schema/PhabricatorConfigStorageSchema.php b/src/applications/config/schema/PhabricatorConfigStorageSchema.php
index 9b4d9ee2b9..8b59d8f1fe 100644
--- a/src/applications/config/schema/PhabricatorConfigStorageSchema.php
+++ b/src/applications/config/schema/PhabricatorConfigStorageSchema.php
@@ -1,219 +1,219 @@
<?php
abstract class PhabricatorConfigStorageSchema extends Phobject {
const ISSUE_MISSING = 'missing';
const ISSUE_MISSINGKEY = 'missingkey';
const ISSUE_SURPLUS = 'surplus';
const ISSUE_SURPLUSKEY = 'surpluskey';
const ISSUE_CHARSET = 'charset';
const ISSUE_COLLATION = 'collation';
const ISSUE_COLUMNTYPE = 'columntype';
const ISSUE_NULLABLE = 'nullable';
const ISSUE_KEYCOLUMNS = 'keycolumns';
const ISSUE_UNIQUE = 'unique';
const ISSUE_SUBNOTE = 'subnote';
const ISSUE_SUBWARN = 'subwarn';
const ISSUE_SUBFAIL = 'subfail';
const STATUS_OKAY = 'okay';
const STATUS_NOTE = 'note';
const STATUS_WARN = 'warn';
const STATUS_FAIL = 'fail';
private $issues = array();
private $name;
abstract public function newEmptyClone();
abstract protected function compareToSimilarSchema(
PhabricatorConfigStorageSchema $expect);
abstract protected function getSubschemata();
public function compareTo(PhabricatorConfigStorageSchema $expect) {
if (get_class($expect) != get_class($this)) {
throw new Exception(pht('Classes must match to compare schemata!'));
}
if ($this->getName() != $expect->getName()) {
throw new Exception(pht('Names must match to compare schemata!'));
}
return $this->compareToSimilarSchema($expect);
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setIssues(array $issues) {
$this->issues = array_fuse($issues);
return $this;
}
public function getIssues() {
$issues = $this->issues;
foreach ($this->getSubschemata() as $sub) {
switch ($sub->getStatus()) {
case self::STATUS_NOTE:
$issues[self::ISSUE_SUBNOTE] = self::ISSUE_SUBNOTE;
break;
case self::STATUS_WARN:
$issues[self::ISSUE_SUBWARN] = self::ISSUE_SUBWARN;
break;
case self::STATUS_FAIL:
$issues[self::ISSUE_SUBFAIL] = self::ISSUE_SUBFAIL;
break;
}
}
return $issues;
}
public function getLocalIssues() {
return $this->issues;
}
public function hasIssue($issue) {
return (bool)idx($this->getIssues(), $issue);
}
public function getAllIssues() {
$issues = $this->getIssues();
foreach ($this->getSubschemata() as $sub) {
$issues += $sub->getAllIssues();
}
return $issues;
}
public function getStatus() {
$status = self::STATUS_OKAY;
foreach ($this->getAllIssues() as $issue) {
$issue_status = self::getIssueStatus($issue);
$status = self::getStrongestStatus($status, $issue_status);
}
return $status;
}
public static function getIssueName($issue) {
switch ($issue) {
case self::ISSUE_MISSING:
return pht('Missing');
case self::ISSUE_MISSINGKEY:
return pht('Missing Key');
case self::ISSUE_SURPLUS:
return pht('Surplus');
case self::ISSUE_SURPLUSKEY:
return pht('Surplus Key');
case self::ISSUE_CHARSET:
return pht('Better Character Set Available');
case self::ISSUE_COLLATION:
return pht('Better Collation Available');
case self::ISSUE_COLUMNTYPE:
return pht('Wrong Column Type');
case self::ISSUE_NULLABLE:
return pht('Wrong Nullable Setting');
case self::ISSUE_KEYCOLUMNS:
return pht('Key on Wrong Columns');
case self::ISSUE_UNIQUE:
return pht('Key has Wrong Uniqueness');
case self::ISSUE_SUBNOTE:
return pht('Subschemata Have Notices');
case self::ISSUE_SUBWARN:
return pht('Subschemata Have Warnings');
case self::ISSUE_SUBFAIL:
return pht('Subschemata Have Failures');
default:
throw new Exception(pht('Unknown schema issue "%s"!', $issue));
}
}
public static function getIssueDescription($issue) {
switch ($issue) {
case self::ISSUE_MISSING:
return pht('This schema is expected to exist, but does not.');
case self::ISSUE_MISSINGKEY:
return pht('This key is expected to exist, but does not.');
case self::ISSUE_SURPLUS:
return pht('This schema is not expected to exist.');
case self::ISSUE_SURPLUSKEY:
return pht('This key is not expected to exist.');
case self::ISSUE_CHARSET:
return pht('This schema can use a better character set.');
case self::ISSUE_COLLATION:
return pht('This schema can use a better collation.');
case self::ISSUE_COLUMNTYPE:
return pht('This schema can use a better column type.');
case self::ISSUE_NULLABLE:
return pht('This schema has the wrong nullable setting.');
case self::ISSUE_KEYCOLUMNS:
return pht('This schema is on the wrong columns.');
case self::ISSUE_UNIQUE:
return pht('This key has the wrong uniqueness setting.');
case self::ISSUE_SUBNOTE:
return pht('Subschemata have setup notices.');
case self::ISSUE_SUBWARN:
return pht('Subschemata have setup warnings.');
case self::ISSUE_SUBFAIL:
return pht('Subschemata have setup failures.');
default:
throw new Exception(pht('Unknown schema issue "%s"!', $issue));
}
}
public static function getIssueStatus($issue) {
switch ($issue) {
case self::ISSUE_MISSING:
- case self::ISSUE_MISSINGKEY:
case self::ISSUE_SUBFAIL:
- return self::STATUS_FAIL;
case self::ISSUE_SURPLUS:
- case self::ISSUE_SURPLUSKEY:
+ return self::STATUS_FAIL;
case self::ISSUE_SUBWARN:
case self::ISSUE_COLUMNTYPE:
case self::ISSUE_KEYCOLUMNS:
- case self::ISSUE_UNIQUE:
case self::ISSUE_NULLABLE:
return self::STATUS_WARN;
case self::ISSUE_SUBNOTE:
case self::ISSUE_CHARSET:
case self::ISSUE_COLLATION:
+ case self::ISSUE_MISSINGKEY:
+ case self::ISSUE_SURPLUSKEY:
+ case self::ISSUE_UNIQUE:
return self::STATUS_NOTE;
default:
throw new Exception(pht('Unknown schema issue "%s"!', $issue));
}
}
public static function getStatusSeverity($status) {
switch ($status) {
case self::STATUS_FAIL:
return 3;
case self::STATUS_WARN:
return 2;
case self::STATUS_NOTE:
return 1;
case self::STATUS_OKAY:
return 0;
default:
throw new Exception(pht('Unknown schema status "%s"!', $status));
}
}
public static function getStrongestStatus($u, $v) {
$u_sev = self::getStatusSeverity($u);
$v_sev = self::getStatusSeverity($v);
if ($u_sev >= $v_sev) {
return $u;
} else {
return $v;
}
}
}
diff --git a/src/applications/config/storage/PhabricatorConfigEntry.php b/src/applications/config/storage/PhabricatorConfigEntry.php
index 823fe50b00..47801247a2 100644
--- a/src/applications/config/storage/PhabricatorConfigEntry.php
+++ b/src/applications/config/storage/PhabricatorConfigEntry.php
@@ -1,74 +1,75 @@
<?php
final class PhabricatorConfigEntry extends PhabricatorConfigEntryDAO
implements PhabricatorPolicyInterface {
protected $namespace;
protected $configKey;
protected $value;
protected $isDeleted;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'value' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'namespace' => 'text64',
'configKey' => 'text64',
'isDeleted' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'key_name' => array(
'columns' => array('namespace', 'configKey'),
+ 'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorConfigConfigPHIDType::TYPECONST);
}
public static function loadConfigEntry($key) {
$config_entry = id(new PhabricatorConfigEntry())
->loadOneWhere(
'configKey = %s AND namespace = %s',
$key,
'default');
if (!$config_entry) {
$config_entry = id(new PhabricatorConfigEntry())
->setConfigKey($key)
->setNamespace('default');
}
return $config_entry;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_ADMIN;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/conpherence/storage/ConpherenceParticipant.php b/src/applications/conpherence/storage/ConpherenceParticipant.php
index 1310af135a..d8687d2ef8 100644
--- a/src/applications/conpherence/storage/ConpherenceParticipant.php
+++ b/src/applications/conpherence/storage/ConpherenceParticipant.php
@@ -1,55 +1,62 @@
<?php
final class ConpherenceParticipant extends ConpherenceDAO {
protected $participantPHID;
protected $conpherencePHID;
protected $participationStatus;
protected $behindTransactionPHID;
protected $seenMessageCount;
protected $dateTouched;
protected $settings = array();
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'settings' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'participationStatus' => 'uint32',
'dateTouched' => 'epoch',
'seenMessageCount' => 'uint64',
),
self::CONFIG_KEY_SCHEMA => array(
'conpherencePHID' => array(
'columns' => array('conpherencePHID', 'participantPHID'),
+ 'unique' => true,
+ ),
+ 'unreadCount' => array(
+ 'columns' => array('participantPHID', 'participationStatus'),
+ ),
+ 'participationIndex' => array(
+ 'columns' => array('participantPHID', 'dateTouched', 'id'),
),
),
) + parent::getConfiguration();
}
public function getSettings() {
return nonempty($this->settings, array());
}
public function markUpToDate(
ConpherenceThread $conpherence,
ConpherenceTransaction $xaction) {
if (!$this->isUpToDate($conpherence)) {
$this->setParticipationStatus(ConpherenceParticipationStatus::UP_TO_DATE);
$this->setBehindTransactionPHID($xaction->getPHID());
$this->setSeenMessageCount($conpherence->getMessageCount());
$this->save();
}
return $this;
}
private function isUpToDate(ConpherenceThread $conpherence) {
return
($this->getSeenMessageCount() == $conpherence->getMessageCount())
&&
($this->getParticipationStatus() ==
ConpherenceParticipationStatus::UP_TO_DATE);
}
}
diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php
index 162442db46..629ed9b0e5 100644
--- a/src/applications/conpherence/storage/ConpherenceThread.php
+++ b/src/applications/conpherence/storage/ConpherenceThread.php
@@ -1,208 +1,209 @@
<?php
final class ConpherenceThread extends ConpherenceDAO
implements PhabricatorPolicyInterface {
protected $title;
protected $messageCount;
protected $recentParticipantPHIDs = array();
protected $mailKey;
private $participants = self::ATTACHABLE;
private $transactions = self::ATTACHABLE;
private $handles = self::ATTACHABLE;
private $filePHIDs = self::ATTACHABLE;
private $widgetData = self::ATTACHABLE;
private $images = array();
public static function initializeNewThread(PhabricatorUser $sender) {
return id(new ConpherenceThread())
->setMessageCount(0)
->setTitle('');
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'recentParticipantPHIDs' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'title' => 'text255?',
'messageCount' => 'uint64',
'mailKey' => 'text20',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
+ 'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorConpherenceThreadPHIDType::TYPECONST);
}
public function save() {
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
}
public function attachParticipants(array $participants) {
assert_instances_of($participants, 'ConpherenceParticipant');
$this->participants = $participants;
return $this;
}
public function getParticipants() {
return $this->assertAttached($this->participants);
}
public function getParticipant($phid) {
$participants = $this->getParticipants();
return $participants[$phid];
}
public function getParticipantPHIDs() {
$participants = $this->getParticipants();
return array_keys($participants);
}
public function attachHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function getHandles() {
return $this->assertAttached($this->handles);
}
public function attachTransactions(array $transactions) {
assert_instances_of($transactions, 'ConpherenceTransaction');
$this->transactions = $transactions;
return $this;
}
public function getTransactions() {
return $this->assertAttached($this->transactions);
}
public function getTransactionsFrom($begin = 0, $amount = null) {
$length = count($this->transactions);
return array_slice(
$this->getTransactions(),
$length - $begin - $amount,
$amount);
}
public function attachFilePHIDs(array $file_phids) {
$this->filePHIDs = $file_phids;
return $this;
}
public function getFilePHIDs() {
return $this->assertAttached($this->filePHIDs);
}
public function attachWidgetData(array $widget_data) {
$this->widgetData = $widget_data;
return $this;
}
public function getWidgetData() {
return $this->assertAttached($this->widgetData);
}
public function getDisplayData(PhabricatorUser $user) {
$recent_phids = $this->getRecentParticipantPHIDs();
$handles = $this->getHandles();
// luck has little to do with it really; most recent participant who isn't
// the user....
$lucky_phid = null;
$lucky_index = null;
foreach ($recent_phids as $index => $phid) {
if ($phid == $user->getPHID()) {
continue;
}
$lucky_phid = $phid;
break;
}
reset($recent_phids);
if ($lucky_phid) {
$lucky_handle = $handles[$lucky_phid];
// this will be just the user talking to themselves. weirdos.
} else {
$lucky_handle = reset($handles);
}
$title = $js_title = $this->getTitle();
if (!$title) {
$title = $lucky_handle->getName();
$js_title = pht('[No Title]');
}
$img_src = $lucky_handle->getImageURI();
$count = 0;
$final = false;
$subtitle = null;
foreach ($recent_phids as $phid) {
if ($phid == $user->getPHID()) {
continue;
}
$handle = $handles[$phid];
if ($subtitle) {
if ($final) {
$subtitle .= '...';
break;
} else {
$subtitle .= ', ';
}
}
$subtitle .= $handle->getName();
$count++;
$final = $count == 3;
}
$participants = $this->getParticipants();
$user_participation = $participants[$user->getPHID()];
$unread_count = $this->getMessageCount() -
$user_participation->getSeenMessageCount();
return array(
'title' => $title,
'js_title' => $js_title,
'subtitle' => $subtitle,
'unread_count' => $unread_count,
'epoch' => $this->getDateModified(),
'image' => $img_src,
);
}
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_NOONE;
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
// this bad boy isn't even created yet so go nuts $user
if (!$this->getID()) {
return true;
}
$participants = $this->getParticipants();
return isset($participants[$user->getPHID()]);
}
public function describeAutomaticCapability($capability) {
return pht('Participants in a thread can always view and edit it.');
}
}
diff --git a/src/applications/conpherence/storage/ConpherenceTransactionComment.php b/src/applications/conpherence/storage/ConpherenceTransactionComment.php
index 958731e855..50169d7cd3 100644
--- a/src/applications/conpherence/storage/ConpherenceTransactionComment.php
+++ b/src/applications/conpherence/storage/ConpherenceTransactionComment.php
@@ -1,20 +1,29 @@
<?php
final class ConpherenceTransactionComment
extends PhabricatorApplicationTransactionComment {
protected $conpherencePHID;
public function getApplicationTransactionObject() {
return new ConpherenceTransaction();
}
public function getConfiguration() {
$config = parent::getConfiguration();
+
$config[self::CONFIG_COLUMN_SCHEMA] = array(
'conpherencePHID' => 'phid?',
) + $config[self::CONFIG_COLUMN_SCHEMA];
+
+ $config[self::CONFIG_KEY_SCHEMA] = array(
+ 'key_draft' => array(
+ 'columns' => array('authorPHID', 'conpherencePHID', 'transactionPHID'),
+ 'unique' => true,
+ ),
+ ) + $config[self::CONFIG_KEY_SCHEMA];
+
return $config;
}
}
diff --git a/src/applications/daemon/storage/PhabricatorDaemonLog.php b/src/applications/daemon/storage/PhabricatorDaemonLog.php
index a4f89c8477..641ab3aabd 100644
--- a/src/applications/daemon/storage/PhabricatorDaemonLog.php
+++ b/src/applications/daemon/storage/PhabricatorDaemonLog.php
@@ -1,70 +1,78 @@
<?php
final class PhabricatorDaemonLog extends PhabricatorDaemonDAO
implements PhabricatorPolicyInterface {
const STATUS_UNKNOWN = 'unknown';
const STATUS_RUNNING = 'run';
const STATUS_DEAD = 'dead';
const STATUS_WAIT = 'wait';
const STATUS_EXITING = 'exiting';
const STATUS_EXITED = 'exit';
protected $daemon;
protected $host;
protected $pid;
protected $argv;
protected $explicitArgv = array();
protected $envHash;
protected $status;
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'argv' => self::SERIALIZATION_JSON,
'explicitArgv' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'daemon' => 'text255',
'host' => 'text255',
'pid' => 'uint32',
'envHash' => 'bytes40',
'status' => 'text8',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'status' => array(
+ 'columns' => array('status'),
+ ),
+ 'dateCreated' => array(
+ 'columns' => array('dateCreated'),
+ ),
+ ),
) + parent::getConfiguration();
}
public function getExplicitArgv() {
$argv = $this->explicitArgv;
if (!is_array($argv)) {
return array();
}
return $argv;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getPHID() {
return null;
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_ADMIN;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/daemon/storage/PhabricatorDaemonLogEvent.php b/src/applications/daemon/storage/PhabricatorDaemonLogEvent.php
index 6be65dd784..fd1110ae06 100644
--- a/src/applications/daemon/storage/PhabricatorDaemonLogEvent.php
+++ b/src/applications/daemon/storage/PhabricatorDaemonLogEvent.php
@@ -1,20 +1,25 @@
<?php
final class PhabricatorDaemonLogEvent extends PhabricatorDaemonDAO {
protected $logID;
protected $logType;
protected $message;
protected $epoch;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'logType' => 'text4',
'message' => 'text',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'logID' => array(
+ 'columns' => array('logID', 'epoch'),
+ ),
+ ),
) + parent::getConfiguration();
}
}
diff --git a/src/applications/dashboard/storage/PhabricatorDashboardInstall.php b/src/applications/dashboard/storage/PhabricatorDashboardInstall.php
index 0064beb8bd..0f108fbac8 100644
--- a/src/applications/dashboard/storage/PhabricatorDashboardInstall.php
+++ b/src/applications/dashboard/storage/PhabricatorDashboardInstall.php
@@ -1,51 +1,52 @@
<?php
/**
* An install of a dashboard. Examples might be
* - the home page for a user
* - the profile page for a user
* - the profile page for a project
*/
final class PhabricatorDashboardInstall
extends PhabricatorDashboardDAO {
protected $installerPHID;
protected $objectPHID;
protected $applicationClass;
protected $dashboardPHID;
public function getConfiguration() {
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'applicationClass' => 'text64',
),
self::CONFIG_KEY_SCHEMA => array(
'objectPHID' => array(
'columns' => array('objectPHID', 'applicationClass'),
+ 'unique' => true,
),
),
) + parent::getConfiguration();
}
public static function getDashboard(
PhabricatorUser $viewer,
$object_phid,
$application_class) {
$dashboard = null;
$dashboard_install = id(new PhabricatorDashboardInstall())
->loadOneWhere(
'objectPHID = %s AND applicationClass = %s',
$object_phid,
$application_class);
if ($dashboard_install) {
$dashboard = id(new PhabricatorDashboardQuery())
->setViewer($viewer)
->withPHIDs(array($dashboard_install->getDashboardPHID()))
->needPanels(true)
->executeOne();
}
return $dashboard;
}
}
diff --git a/src/applications/doorkeeper/storage/DoorkeeperExternalObject.php b/src/applications/doorkeeper/storage/DoorkeeperExternalObject.php
index ce0a4f5eae..3719462ffb 100644
--- a/src/applications/doorkeeper/storage/DoorkeeperExternalObject.php
+++ b/src/applications/doorkeeper/storage/DoorkeeperExternalObject.php
@@ -1,98 +1,107 @@
<?php
final class DoorkeeperExternalObject extends DoorkeeperDAO
implements PhabricatorPolicyInterface {
protected $objectKey;
protected $applicationType;
protected $applicationDomain;
protected $objectType;
protected $objectID;
protected $objectURI;
protected $importerPHID;
protected $properties = array();
protected $viewPolicy;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'objectKey' => 'bytes12',
'applicationType' => 'text32',
'applicationDomain' => 'text32',
'objectType' => 'text32',
'objectID' => 'text64',
'objectURI' => 'text128?',
'importerPHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_object' => array(
'columns' => array('objectKey'),
+ 'unique' => true,
+ ),
+ 'key_full' => array(
+ 'columns' => array(
+ 'applicationType',
+ 'applicationDomain',
+ 'objectType',
+ 'objectID',
+ ),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_XOBJ);
}
public function getProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function setProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
public function getObjectKey() {
$key = parent::getObjectKey();
if ($key === null) {
$key = $this->getRef()->getObjectKey();
}
return $key;
}
public function getRef() {
return id(new DoorkeeperObjectRef())
->setApplicationType($this->getApplicationType())
->setApplicationDomain($this->getApplicationDomain())
->setObjectType($this->getObjectType())
->setObjectID($this->getObjectID());
}
public function save() {
if (!$this->objectKey) {
$this->objectKey = $this->getObjectKey();
}
return parent::save();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return $this->viewPolicy;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/draft/storage/PhabricatorDraft.php b/src/applications/draft/storage/PhabricatorDraft.php
index e8d3748431..129d999298 100644
--- a/src/applications/draft/storage/PhabricatorDraft.php
+++ b/src/applications/draft/storage/PhabricatorDraft.php
@@ -1,100 +1,101 @@
<?php
final class PhabricatorDraft extends PhabricatorDraftDAO {
protected $authorPHID;
protected $draftKey;
protected $draft;
protected $metadata = array();
private $deleted = false;
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'draftKey' => 'text64',
'draft' => 'text',
),
self::CONFIG_KEY_SCHEMA => array(
'authorPHID' => array(
'columns' => array('authorPHID', 'draftKey'),
+ 'unique' => true,
),
),
) + parent::getConfiguration();
}
public function replaceOrDelete() {
if ($this->draft == '' && !array_filter($this->metadata)) {
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T WHERE authorPHID = %s AND draftKey = %s',
$this->getTableName(),
$this->authorPHID,
$this->draftKey);
$this->deleted = true;
return $this;
}
return parent::replace();
}
protected function didDelete() {
$this->deleted = true;
}
public function isDeleted() {
return $this->deleted;
}
public static function newFromUserAndKey(PhabricatorUser $user, $key) {
if ($user->getPHID() && strlen($key)) {
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
$key);
if ($draft) {
return $draft;
}
}
$draft = new PhabricatorDraft();
if ($user->getPHID()) {
$draft
->setAuthorPHID($user->getPHID())
->setDraftKey($key);
}
return $draft;
}
public static function buildFromRequest(AphrontRequest $request) {
$user = $request->getUser();
if (!$user->getPHID()) {
return null;
}
if (!$request->getStr('__draft__')) {
return null;
}
$draft = id(new PhabricatorDraft())
->setAuthorPHID($user->getPHID())
->setDraftKey($request->getStr('__draft__'));
// If this is a preview, add other data. If not, leave the draft empty so
// that replaceOrDelete() will delete it.
if ($request->isPreviewRequest()) {
$other_data = $request->getPassthroughRequestData();
unset($other_data['comment']);
$draft
->setDraft($request->getStr('comment'))
->setMetadata($other_data);
}
return $draft;
}
}
diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php
index 6a971629e8..b9e79fe820 100644
--- a/src/applications/drydock/storage/DrydockLease.php
+++ b/src/applications/drydock/storage/DrydockLease.php
@@ -1,228 +1,229 @@
<?php
final class DrydockLease extends DrydockDAO
implements PhabricatorPolicyInterface {
protected $resourceID;
protected $resourceType;
protected $until;
protected $ownerPHID;
protected $attributes = array();
protected $status = DrydockLeaseStatus::STATUS_PENDING;
protected $taskID;
private $resource = self::ATTACHABLE;
private $releaseOnDestruction;
/**
* Flag this lease to be released when its destructor is called. This is
* mostly useful if you have a script which acquires, uses, and then releases
* a lease, as you don't need to explicitly handle exceptions to properly
* release the lease.
*/
public function releaseOnDestruction() {
$this->releaseOnDestruction = true;
return $this;
}
public function __destruct() {
if ($this->releaseOnDestruction) {
if ($this->isActive()) {
$this->release();
}
}
}
public function getLeaseName() {
return pht('Lease %d', $this->getID());
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'attributes' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'status' => 'uint32',
'until' => 'epoch?',
'resourceType' => 'text128',
'taskID' => 'id?',
'ownerPHID' => 'phid?',
'resourceID' => 'id?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
+ 'unique' => true,
),
),
) + parent::getConfiguration();
}
public function setAttribute($key, $value) {
$this->attributes[$key] = $value;
return $this;
}
public function getAttribute($key, $default = null) {
return idx($this->attributes, $key, $default);
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(DrydockLeasePHIDType::TYPECONST);
}
public function getInterface($type) {
return $this->getResource()->getInterface($this, $type);
}
public function getResource() {
return $this->assertAttached($this->resource);
}
public function attachResource(DrydockResource $resource = null) {
$this->resource = $resource;
return $this;
}
public function hasAttachedResource() {
return ($this->resource !== null);
}
public function loadResource() {
return id(new DrydockResource())->loadOneWhere(
'id = %d',
$this->getResourceID());
}
public function queueForActivation() {
if ($this->getID()) {
throw new Exception(
'Only new leases may be queued for activation!');
}
$this->setStatus(DrydockLeaseStatus::STATUS_PENDING);
$this->save();
$task = PhabricatorWorker::scheduleTask(
'DrydockAllocatorWorker',
$this->getID());
// NOTE: Scheduling the task might execute it in-process, if we're running
// from a CLI script. Reload the lease to make sure we have the most
// up-to-date information. Normally, this has no effect.
$this->reload();
$this->setTaskID($task->getID());
$this->save();
return $this;
}
public function release() {
$this->assertActive();
$this->setStatus(DrydockLeaseStatus::STATUS_RELEASED);
$this->save();
$this->resource = null;
return $this;
}
public function isActive() {
switch ($this->status) {
case DrydockLeaseStatus::STATUS_ACTIVE:
case DrydockLeaseStatus::STATUS_ACQUIRING:
return true;
}
return false;
}
private function assertActive() {
if (!$this->isActive()) {
throw new Exception(
'Lease is not active! You can not interact with resources through '.
'an inactive lease.');
}
}
public static function waitForLeases(array $leases) {
assert_instances_of($leases, 'DrydockLease');
$task_ids = array_filter(mpull($leases, 'getTaskID'));
PhabricatorWorker::waitForTasks($task_ids);
$unresolved = $leases;
while (true) {
foreach ($unresolved as $key => $lease) {
$lease->reload();
switch ($lease->getStatus()) {
case DrydockLeaseStatus::STATUS_ACTIVE:
unset($unresolved[$key]);
break;
case DrydockLeaseStatus::STATUS_RELEASED:
throw new Exception('Lease has already been released!');
case DrydockLeaseStatus::STATUS_EXPIRED:
throw new Exception('Lease has already expired!');
case DrydockLeaseStatus::STATUS_BROKEN:
throw new Exception('Lease has been broken!');
case DrydockLeaseStatus::STATUS_PENDING:
case DrydockLeaseStatus::STATUS_ACQUIRING:
break;
default:
throw new Exception('Unknown status??');
}
}
if ($unresolved) {
sleep(1);
} else {
break;
}
}
foreach ($leases as $lease) {
$lease->attachResource($lease->loadResource());
}
}
public function waitUntilActive() {
if (!$this->getID()) {
$this->queueForActivation();
}
self::waitForLeases(array($this));
return $this;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
if ($this->getResource()) {
return $this->getResource()->getPolicy($capability);
}
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($this->getResource()) {
return $this->getResource()->hasAutomaticCapability($capability, $viewer);
}
return false;
}
public function describeAutomaticCapability($capability) {
return pht('Leases inherit policies from the resources they lease.');
}
}
diff --git a/src/applications/drydock/storage/DrydockLog.php b/src/applications/drydock/storage/DrydockLog.php
index 1faa6f9bc4..763d531e42 100644
--- a/src/applications/drydock/storage/DrydockLog.php
+++ b/src/applications/drydock/storage/DrydockLog.php
@@ -1,71 +1,82 @@
<?php
final class DrydockLog extends DrydockDAO
implements PhabricatorPolicyInterface {
protected $resourceID;
protected $leaseID;
protected $epoch;
protected $message;
private $resource = self::ATTACHABLE;
private $lease = self::ATTACHABLE;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'resourceID' => 'id?',
'leaseID' => 'id?',
'message' => 'text',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'resourceID' => array(
+ 'columns' => array('resourceID', 'epoch'),
+ ),
+ 'leaseID' => array(
+ 'columns' => array('leaseID', 'epoch'),
+ ),
+ 'epoch' => array(
+ 'columns' => array('epoch'),
+ ),
+ ),
) + parent::getConfiguration();
}
public function attachResource(DrydockResource $resource = null) {
$this->resource = $resource;
return $this;
}
public function getResource() {
return $this->assertAttached($this->resource);
}
public function attachLease(DrydockLease $lease = null) {
$this->lease = $lease;
return $this;
}
public function getLease() {
return $this->assertAttached($this->lease);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
if ($this->getResource()) {
return $this->getResource()->getPolicy($capability);
}
return $this->getLease()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($this->getResource()) {
return $this->getResource()->hasAutomaticCapability($capability, $viewer);
}
return $this->getLease()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return pht('Logs inherit the policy of their resources.');
}
}
diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php
index 0ad17d303f..84b8bc6d69 100644
--- a/src/applications/drydock/storage/DrydockResource.php
+++ b/src/applications/drydock/storage/DrydockResource.php
@@ -1,133 +1,134 @@
<?php
final class DrydockResource extends DrydockDAO
implements PhabricatorPolicyInterface {
protected $id;
protected $phid;
protected $blueprintPHID;
protected $status;
protected $type;
protected $name;
protected $attributes = array();
protected $capabilities = array();
protected $ownerPHID;
private $blueprint;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'attributes' => self::SERIALIZATION_JSON,
'capabilities' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255',
'ownerPHID' => 'phid?',
'status' => 'uint32',
'type' => 'text64',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
+ 'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(DrydockResourcePHIDType::TYPECONST);
}
public function getAttribute($key, $default = null) {
return idx($this->attributes, $key, $default);
}
public function getAttributesForTypeSpec(array $attribute_names) {
return array_select_keys($this->attributes, $attribute_names);
}
public function setAttribute($key, $value) {
$this->attributes[$key] = $value;
return $this;
}
public function getCapability($key, $default = null) {
return idx($this->capbilities, $key, $default);
}
public function getInterface(DrydockLease $lease, $type) {
return $this->getBlueprint()->getInterface($this, $lease, $type);
}
public function getBlueprint() {
// TODO: Policy stuff.
if (empty($this->blueprint)) {
$blueprint = id(new DrydockBlueprint())
->loadOneWhere('phid = %s', $this->blueprintPHID);
$this->blueprint = $blueprint->getImplementation();
}
return $this->blueprint;
}
public function closeResource() {
$this->openTransaction();
$statuses = array(
DrydockLeaseStatus::STATUS_PENDING,
DrydockLeaseStatus::STATUS_ACTIVE,
);
$leases = id(new DrydockLeaseQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withResourceIDs(array($this->getID()))
->withStatuses($statuses)
->execute();
foreach ($leases as $lease) {
switch ($lease->getStatus()) {
case DrydockLeaseStatus::STATUS_PENDING:
$message = pht('Breaking pending lease (resource closing).');
$lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN);
break;
case DrydockLeaseStatus::STATUS_ACTIVE:
$message = pht('Releasing active lease (resource closing).');
$lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED);
break;
}
DrydockBlueprintImplementation::writeLog($this, $lease, $message);
$lease->save();
}
$this->setStatus(DrydockResourceStatus::STATUS_CLOSED);
$this->save();
$this->saveTransaction();
}
/* -( 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;
}
}
diff --git a/src/applications/feed/storage/PhabricatorFeedStoryData.php b/src/applications/feed/storage/PhabricatorFeedStoryData.php
index 161d8fe896..bf0aba84be 100644
--- a/src/applications/feed/storage/PhabricatorFeedStoryData.php
+++ b/src/applications/feed/storage/PhabricatorFeedStoryData.php
@@ -1,67 +1,69 @@
<?php
final class PhabricatorFeedStoryData extends PhabricatorFeedDAO {
protected $phid;
protected $storyType;
protected $storyData;
protected $authorPHID;
protected $chronologicalKey;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'storyData' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'chronologicalKey' => 'uint64',
'storyType' => 'text64',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
+ 'unique' => true,
),
'chronologicalKey' => array(
'columns' => array('chronologicalKey'),
+ 'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_STRY);
}
public function getEpoch() {
if (PHP_INT_SIZE < 8) {
// We're on a 32-bit machine.
if (function_exists('bcadd')) {
// Try to use the 'bc' extension.
return bcdiv($this->chronologicalKey, bcpow(2, 32));
} else {
// Do the math in MySQL. TODO: If we formalize a bc dependency, get
// rid of this.
// See: PhabricatorFeedStoryPublisher::generateChronologicalKey()
$conn_r = id($this->establishConnection('r'));
$result = queryfx_one(
$conn_r,
// Insert the chronologicalKey as a string since longs don't seem to
// be supported by qsprintf and ints get maxed on 32 bit machines.
'SELECT (%s >> 32) as N',
$this->chronologicalKey);
return $result['N'];
}
} else {
return $this->chronologicalKey >> 32;
}
}
public function getValue($key, $default = null) {
return idx($this->storyData, $key, $default);
}
}
diff --git a/src/applications/feed/storage/PhabricatorFeedStoryReference.php b/src/applications/feed/storage/PhabricatorFeedStoryReference.php
index f4b980a4a0..c5740b542d 100644
--- a/src/applications/feed/storage/PhabricatorFeedStoryReference.php
+++ b/src/applications/feed/storage/PhabricatorFeedStoryReference.php
@@ -1,25 +1,29 @@
<?php
final class PhabricatorFeedStoryReference extends PhabricatorFeedDAO {
protected $objectPHID;
protected $chronologicalKey;
public function getConfiguration() {
return array(
self::CONFIG_IDS => self::IDS_MANUAL,
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'chronologicalKey' => 'uint64',
'id' => null,
),
self::CONFIG_KEY_SCHEMA => array(
'PRIMARY' => null,
'objectPHID' => array(
'columns' => array('objectPHID', 'chronologicalKey'),
+ 'unique' => true,
+ ),
+ 'chronologicalKey' => array(
+ 'columns' => array('chronologicalKey'),
),
),
) + parent::getConfiguration();
}
}
diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php
index a6171a4f39..703886f5d4 100644
--- a/src/applications/files/storage/PhabricatorFile.php
+++ b/src/applications/files/storage/PhabricatorFile.php
@@ -1,1158 +1,1171 @@
<?php
/**
* Parameters
* ==========
*
* When creating a new file using a method like @{method:newFromFileData}, these
* parameters are supported:
*
* | name | Human readable filename.
* | authorPHID | User PHID of uploader.
* | ttl | Temporary file lifetime, in seconds.
* | viewPolicy | File visibility policy.
* | isExplicitUpload | Used to show users files they explicitly uploaded.
* | canCDN | Allows the file to be cached and delivered over a CDN.
* | mime-type | Optional, explicit file MIME type.
*
*/
final class PhabricatorFile extends PhabricatorFileDAO
implements
PhabricatorTokenReceiverInterface,
PhabricatorSubscribableInterface,
PhabricatorFlaggableInterface,
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
const ONETIME_TEMPORARY_TOKEN_TYPE = 'file:onetime';
const STORAGE_FORMAT_RAW = 'raw';
const METADATA_IMAGE_WIDTH = 'width';
const METADATA_IMAGE_HEIGHT = 'height';
const METADATA_CAN_CDN = 'canCDN';
protected $name;
protected $mimeType;
protected $byteSize;
protected $authorPHID;
protected $secretKey;
protected $contentHash;
protected $metadata = array();
protected $mailKey;
protected $storageEngine;
protected $storageFormat;
protected $storageHandle;
protected $ttl;
protected $isExplicitUpload = 1;
protected $viewPolicy = PhabricatorPolicies::POLICY_USER;
private $objects = self::ATTACHABLE;
private $objectPHIDs = self::ATTACHABLE;
private $originalFile = self::ATTACHABLE;
public static function initializeNewFile() {
return id(new PhabricatorFile())
->attachOriginalFile(null)
->attachObjects(array())
->attachObjectPHIDs(array());
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255?',
'mimeType' => 'text255?',
- 'byteSize' => 'uint64?',
+ 'byteSize' => 'uint64',
'storageEngine' => 'text32',
'storageFormat' => 'text32',
'storageHandle' => 'text255',
'authorPHID' => 'phid?',
'secretKey' => 'bytes20?',
'contentHash' => 'bytes40?',
'ttl' => 'epoch?',
'isExplicitUpload' => 'bool?',
'mailKey' => 'bytes20',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
+ 'unique' => true,
+ ),
+ 'authorPHID' => array(
+ 'columns' => array('authorPHID'),
+ ),
+ 'contentHash' => array(
+ 'columns' => array('contentHash'),
+ ),
+ 'key_ttl' => array(
+ 'columns' => array('ttl'),
+ ),
+ 'key_dateCreated' => array(
+ 'columns' => array('dateCreated'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorFileFilePHIDType::TYPECONST);
}
public function save() {
if (!$this->getSecretKey()) {
$this->setSecretKey($this->generateSecretKey());
}
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
}
public function getMonogram() {
return 'F'.$this->getID();
}
public static function readUploadedFileData($spec) {
if (!$spec) {
throw new Exception('No file was uploaded!');
}
$err = idx($spec, 'error');
if ($err) {
throw new PhabricatorFileUploadException($err);
}
$tmp_name = idx($spec, 'tmp_name');
$is_valid = @is_uploaded_file($tmp_name);
if (!$is_valid) {
throw new Exception('File is not an uploaded file.');
}
$file_data = Filesystem::readFile($tmp_name);
$file_size = idx($spec, 'size');
if (strlen($file_data) != $file_size) {
throw new Exception('File size disagrees with uploaded size.');
}
self::validateFileSize(strlen($file_data));
return $file_data;
}
public static function newFromPHPUpload($spec, array $params = array()) {
$file_data = self::readUploadedFileData($spec);
$file_name = nonempty(
idx($params, 'name'),
idx($spec, 'name'));
$params = array(
'name' => $file_name,
) + $params;
return self::newFromFileData($file_data, $params);
}
public static function newFromXHRUpload($data, array $params = array()) {
self::validateFileSize(strlen($data));
return self::newFromFileData($data, $params);
}
private static function validateFileSize($size) {
$limit = PhabricatorEnv::getEnvConfig('storage.upload-size-limit');
if (!$limit) {
return;
}
$limit = phutil_parse_bytes($limit);
if ($size > $limit) {
throw new PhabricatorFileUploadException(-1000);
}
}
/**
* Given a block of data, try to load an existing file with the same content
* if one exists. If it does not, build a new file.
*
* This method is generally used when we have some piece of semi-trusted data
* like a diff or a file from a repository that we want to show to the user.
* We can't just dump it out because it may be dangerous for any number of
* reasons; instead, we need to serve it through the File abstraction so it
* ends up on the CDN domain if one is configured and so on. However, if we
* simply wrote a new file every time we'd potentially end up with a lot
* of redundant data in file storage.
*
* To solve these problems, we use file storage as a cache and reuse the
* same file again if we've previously written it.
*
* NOTE: This method unguards writes.
*
* @param string Raw file data.
* @param dict Dictionary of file information.
*/
public static function buildFromFileDataOrHash(
$data,
array $params = array()) {
$file = id(new PhabricatorFile())->loadOneWhere(
'name = %s AND contentHash = %s LIMIT 1',
self::normalizeFileName(idx($params, 'name')),
self::hashFileContent($data));
if (!$file) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$file = PhabricatorFile::newFromFileData($data, $params);
unset($unguarded);
}
return $file;
}
public static function newFileFromContentHash($hash, array $params) {
// Check to see if a file with same contentHash exist
$file = id(new PhabricatorFile())->loadOneWhere(
'contentHash = %s LIMIT 1',
$hash);
if ($file) {
// copy storageEngine, storageHandle, storageFormat
$copy_of_storage_engine = $file->getStorageEngine();
$copy_of_storage_handle = $file->getStorageHandle();
$copy_of_storage_format = $file->getStorageFormat();
$copy_of_byteSize = $file->getByteSize();
$copy_of_mimeType = $file->getMimeType();
$new_file = PhabricatorFile::initializeNewFile();
$new_file->setByteSize($copy_of_byteSize);
$new_file->setContentHash($hash);
$new_file->setStorageEngine($copy_of_storage_engine);
$new_file->setStorageHandle($copy_of_storage_handle);
$new_file->setStorageFormat($copy_of_storage_format);
$new_file->setMimeType($copy_of_mimeType);
$new_file->copyDimensions($file);
$new_file->readPropertiesFromParameters($params);
$new_file->save();
return $new_file;
}
return $file;
}
private static function buildFromFileData($data, array $params = array()) {
if (isset($params['storageEngines'])) {
$engines = $params['storageEngines'];
} else {
$selector = PhabricatorEnv::newObjectFromConfig(
'storage.engine-selector');
$engines = $selector->selectStorageEngines($data, $params);
}
assert_instances_of($engines, 'PhabricatorFileStorageEngine');
if (!$engines) {
throw new Exception('No valid storage engines are available!');
}
$file = PhabricatorFile::initializeNewFile();
$data_handle = null;
$engine_identifier = null;
$exceptions = array();
foreach ($engines as $engine) {
$engine_class = get_class($engine);
try {
list($engine_identifier, $data_handle) = $file->writeToEngine(
$engine,
$data,
$params);
// We stored the file somewhere so stop trying to write it to other
// places.
break;
} catch (PhabricatorFileStorageConfigurationException $ex) {
// If an engine is outright misconfigured (or misimplemented), raise
// that immediately since it probably needs attention.
throw $ex;
} catch (Exception $ex) {
phlog($ex);
// If an engine doesn't work, keep trying all the other valid engines
// in case something else works.
$exceptions[$engine_class] = $ex;
}
}
if (!$data_handle) {
throw new PhutilAggregateException(
'All storage engines failed to write file:',
$exceptions);
}
$file->setByteSize(strlen($data));
$file->setContentHash(self::hashFileContent($data));
$file->setStorageEngine($engine_identifier);
$file->setStorageHandle($data_handle);
// TODO: This is probably YAGNI, but allows for us to do encryption or
// compression later if we want.
$file->setStorageFormat(self::STORAGE_FORMAT_RAW);
$file->readPropertiesFromParameters($params);
if (!$file->getMimeType()) {
$tmp = new TempFile();
Filesystem::writeFile($tmp, $data);
$file->setMimeType(Filesystem::getMimeType($tmp));
}
try {
$file->updateDimensions(false);
} catch (Exception $ex) {
// Do nothing
}
$file->save();
return $file;
}
public static function newFromFileData($data, array $params = array()) {
$hash = self::hashFileContent($data);
$file = self::newFileFromContentHash($hash, $params);
if ($file) {
return $file;
}
return self::buildFromFileData($data, $params);
}
public function migrateToEngine(PhabricatorFileStorageEngine $engine) {
if (!$this->getID() || !$this->getStorageHandle()) {
throw new Exception(
"You can not migrate a file which hasn't yet been saved.");
}
$data = $this->loadFileData();
$params = array(
'name' => $this->getName(),
);
list($new_identifier, $new_handle) = $this->writeToEngine(
$engine,
$data,
$params);
$old_engine = $this->instantiateStorageEngine();
$old_identifier = $this->getStorageEngine();
$old_handle = $this->getStorageHandle();
$this->setStorageEngine($new_identifier);
$this->setStorageHandle($new_handle);
$this->save();
$this->deleteFileDataIfUnused(
$old_engine,
$old_identifier,
$old_handle);
return $this;
}
private function writeToEngine(
PhabricatorFileStorageEngine $engine,
$data,
array $params) {
$engine_class = get_class($engine);
$data_handle = $engine->writeFile($data, $params);
if (!$data_handle || strlen($data_handle) > 255) {
// This indicates an improperly implemented storage engine.
throw new PhabricatorFileStorageConfigurationException(
"Storage engine '{$engine_class}' executed writeFile() but did ".
"not return a valid handle ('{$data_handle}') to the data: it ".
"must be nonempty and no longer than 255 characters.");
}
$engine_identifier = $engine->getEngineIdentifier();
if (!$engine_identifier || strlen($engine_identifier) > 32) {
throw new PhabricatorFileStorageConfigurationException(
"Storage engine '{$engine_class}' returned an improper engine ".
"identifier '{$engine_identifier}': it must be nonempty ".
"and no longer than 32 characters.");
}
return array($engine_identifier, $data_handle);
}
public static function newFromFileDownload($uri, array $params = array()) {
// Make sure we're allowed to make a request first
if (!PhabricatorEnv::getEnvConfig('security.allow-outbound-http')) {
throw new Exception('Outbound HTTP requests are disabled!');
}
$uri = new PhutilURI($uri);
$protocol = $uri->getProtocol();
switch ($protocol) {
case 'http':
case 'https':
break;
default:
// Make sure we are not accessing any file:// URIs or similar.
return null;
}
$timeout = 5;
list($file_data) = id(new HTTPSFuture($uri))
->setTimeout($timeout)
->resolvex();
$params = $params + array(
'name' => basename($uri),
);
return self::newFromFileData($file_data, $params);
}
public static function normalizeFileName($file_name) {
$pattern = "@[\\x00-\\x19#%&+!~'\$\"\/=\\\\?<> ]+@";
$file_name = preg_replace($pattern, '_', $file_name);
$file_name = preg_replace('@_+@', '_', $file_name);
$file_name = trim($file_name, '_');
$disallowed_filenames = array(
'.' => 'dot',
'..' => 'dotdot',
'' => 'file',
);
$file_name = idx($disallowed_filenames, $file_name, $file_name);
return $file_name;
}
public function delete() {
// We want to delete all the rows which mark this file as the transformation
// of some other file (since we're getting rid of it). We also delete all
// the transformations of this file, so that a user who deletes an image
// doesn't need to separately hunt down and delete a bunch of thumbnails and
// resizes of it.
$outbound_xforms = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTransforms(
array(
array(
'originalPHID' => $this->getPHID(),
'transform' => true,
),
))
->execute();
foreach ($outbound_xforms as $outbound_xform) {
$outbound_xform->delete();
}
$inbound_xforms = id(new PhabricatorTransformedFile())->loadAllWhere(
'transformedPHID = %s',
$this->getPHID());
$this->openTransaction();
foreach ($inbound_xforms as $inbound_xform) {
$inbound_xform->delete();
}
$ret = parent::delete();
$this->saveTransaction();
$this->deleteFileDataIfUnused(
$this->instantiateStorageEngine(),
$this->getStorageEngine(),
$this->getStorageHandle());
return $ret;
}
/**
* Destroy stored file data if there are no remaining files which reference
* it.
*/
public function deleteFileDataIfUnused(
PhabricatorFileStorageEngine $engine,
$engine_identifier,
$handle) {
// Check to see if any files are using storage.
$usage = id(new PhabricatorFile())->loadAllWhere(
'storageEngine = %s AND storageHandle = %s LIMIT 1',
$engine_identifier,
$handle);
// If there are no files using the storage, destroy the actual storage.
if (!$usage) {
try {
$engine->deleteFile($handle);
} catch (Exception $ex) {
// In the worst case, we're leaving some data stranded in a storage
// engine, which is not a big deal.
phlog($ex);
}
}
}
public static function hashFileContent($data) {
return sha1($data);
}
public function loadFileData() {
$engine = $this->instantiateStorageEngine();
$data = $engine->readFile($this->getStorageHandle());
switch ($this->getStorageFormat()) {
case self::STORAGE_FORMAT_RAW:
$data = $data;
break;
default:
throw new Exception('Unknown storage format.');
}
return $data;
}
public function getViewURI() {
if (!$this->getPHID()) {
throw new Exception(
'You must save a file before you can generate a view URI.');
}
$name = phutil_escape_uri($this->getName());
$path = '/file/data/'.$this->getSecretKey().'/'.$this->getPHID().'/'.$name;
return PhabricatorEnv::getCDNURI($path);
}
public function getInfoURI() {
return '/'.$this->getMonogram();
}
public function getBestURI() {
if ($this->isViewableInBrowser()) {
return $this->getViewURI();
} else {
return $this->getInfoURI();
}
}
public function getDownloadURI() {
$uri = id(new PhutilURI($this->getViewURI()))
->setQueryParam('download', true);
return (string) $uri;
}
public function getProfileThumbURI() {
$path = '/file/xform/thumb-profile/'.$this->getPHID().'/'
.$this->getSecretKey().'/';
return PhabricatorEnv::getCDNURI($path);
}
public function getThumb60x45URI() {
$path = '/file/xform/thumb-60x45/'.$this->getPHID().'/'
.$this->getSecretKey().'/';
return PhabricatorEnv::getCDNURI($path);
}
public function getThumb160x120URI() {
$path = '/file/xform/thumb-160x120/'.$this->getPHID().'/'
.$this->getSecretKey().'/';
return PhabricatorEnv::getCDNURI($path);
}
public function getPreview100URI() {
$path = '/file/xform/preview-100/'.$this->getPHID().'/'
.$this->getSecretKey().'/';
return PhabricatorEnv::getCDNURI($path);
}
public function getPreview220URI() {
$path = '/file/xform/preview-220/'.$this->getPHID().'/'
.$this->getSecretKey().'/';
return PhabricatorEnv::getCDNURI($path);
}
public function getThumb220x165URI() {
$path = '/file/xform/thumb-220x165/'.$this->getPHID().'/'
.$this->getSecretKey().'/';
return PhabricatorEnv::getCDNURI($path);
}
public function getThumb280x210URI() {
$path = '/file/xform/thumb-280x210/'.$this->getPHID().'/'
.$this->getSecretKey().'/';
return PhabricatorEnv::getCDNURI($path);
}
public function isViewableInBrowser() {
return ($this->getViewableMimeType() !== null);
}
public function isViewableImage() {
if (!$this->isViewableInBrowser()) {
return false;
}
$mime_map = PhabricatorEnv::getEnvConfig('files.image-mime-types');
$mime_type = $this->getMimeType();
return idx($mime_map, $mime_type);
}
public function isAudio() {
if (!$this->isViewableInBrowser()) {
return false;
}
$mime_map = PhabricatorEnv::getEnvConfig('files.audio-mime-types');
$mime_type = $this->getMimeType();
return idx($mime_map, $mime_type);
}
public function isTransformableImage() {
// NOTE: The way the 'gd' extension works in PHP is that you can install it
// with support for only some file types, so it might be able to handle
// PNG but not JPEG. Try to generate thumbnails for whatever we can. Setup
// warns you if you don't have complete support.
$matches = null;
$ok = preg_match(
'@^image/(gif|png|jpe?g)@',
$this->getViewableMimeType(),
$matches);
if (!$ok) {
return false;
}
switch ($matches[1]) {
case 'jpg';
case 'jpeg':
return function_exists('imagejpeg');
break;
case 'png':
return function_exists('imagepng');
break;
case 'gif':
return function_exists('imagegif');
break;
default:
throw new Exception('Unknown type matched as image MIME type.');
}
}
public static function getTransformableImageFormats() {
$supported = array();
if (function_exists('imagejpeg')) {
$supported[] = 'jpg';
}
if (function_exists('imagepng')) {
$supported[] = 'png';
}
if (function_exists('imagegif')) {
$supported[] = 'gif';
}
return $supported;
}
public function instantiateStorageEngine() {
return self::buildEngine($this->getStorageEngine());
}
public static function buildEngine($engine_identifier) {
$engines = self::buildAllEngines();
foreach ($engines as $engine) {
if ($engine->getEngineIdentifier() == $engine_identifier) {
return $engine;
}
}
throw new Exception(
"Storage engine '{$engine_identifier}' could not be located!");
}
public static function buildAllEngines() {
$engines = id(new PhutilSymbolLoader())
->setType('class')
->setConcreteOnly(true)
->setAncestorClass('PhabricatorFileStorageEngine')
->selectAndLoadSymbols();
$results = array();
foreach ($engines as $engine_class) {
$results[] = newv($engine_class['name'], array());
}
return $results;
}
public function getViewableMimeType() {
$mime_map = PhabricatorEnv::getEnvConfig('files.viewable-mime-types');
$mime_type = $this->getMimeType();
$mime_parts = explode(';', $mime_type);
$mime_type = trim(reset($mime_parts));
return idx($mime_map, $mime_type);
}
public function getDisplayIconForMimeType() {
$mime_map = PhabricatorEnv::getEnvConfig('files.icon-mime-types');
$mime_type = $this->getMimeType();
return idx($mime_map, $mime_type, 'docs_file');
}
public function validateSecretKey($key) {
return ($key == $this->getSecretKey());
}
public function generateSecretKey() {
return Filesystem::readRandomCharacters(20);
}
public function updateDimensions($save = true) {
if (!$this->isViewableImage()) {
throw new Exception(
'This file is not a viewable image.');
}
if (!function_exists('imagecreatefromstring')) {
throw new Exception(
'Cannot retrieve image information.');
}
$data = $this->loadFileData();
$img = imagecreatefromstring($data);
if ($img === false) {
throw new Exception(
'Error when decoding image.');
}
$this->metadata[self::METADATA_IMAGE_WIDTH] = imagesx($img);
$this->metadata[self::METADATA_IMAGE_HEIGHT] = imagesy($img);
if ($save) {
$this->save();
}
return $this;
}
public function copyDimensions(PhabricatorFile $file) {
$metadata = $file->getMetadata();
$width = idx($metadata, self::METADATA_IMAGE_WIDTH);
if ($width) {
$this->metadata[self::METADATA_IMAGE_WIDTH] = $width;
}
$height = idx($metadata, self::METADATA_IMAGE_HEIGHT);
if ($height) {
$this->metadata[self::METADATA_IMAGE_HEIGHT] = $height;
}
return $this;
}
/**
* Load (or build) the {@class:PhabricatorFile} objects for builtin file
* resources. The builtin mechanism allows files shipped with Phabricator
* to be treated like normal files so that APIs do not need to special case
* things like default images or deleted files.
*
* Builtins are located in `resources/builtin/` and identified by their
* name.
*
* @param PhabricatorUser Viewing user.
* @param list<string> List of builtin file names.
* @return dict<string, PhabricatorFile> Dictionary of named builtins.
*/
public static function loadBuiltins(PhabricatorUser $user, array $names) {
$specs = array();
foreach ($names as $name) {
$specs[] = array(
'originalPHID' => PhabricatorPHIDConstants::PHID_VOID,
'transform' => 'builtin:'.$name,
);
}
// NOTE: Anyone is allowed to access builtin files.
$files = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTransforms($specs)
->execute();
$files = mpull($files, null, 'getName');
$root = dirname(phutil_get_library_root('phabricator'));
$root = $root.'/resources/builtin/';
$build = array();
foreach ($names as $name) {
if (isset($files[$name])) {
continue;
}
// This is just a sanity check to prevent loading arbitrary files.
if (basename($name) != $name) {
throw new Exception("Invalid builtin name '{$name}'!");
}
$path = $root.$name;
if (!Filesystem::pathExists($path)) {
throw new Exception("Builtin '{$path}' does not exist!");
}
$data = Filesystem::readFile($path);
$params = array(
'name' => $name,
'ttl' => time() + (60 * 60 * 24 * 7),
);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$file = PhabricatorFile::newFromFileData($data, $params);
$xform = id(new PhabricatorTransformedFile())
->setOriginalPHID(PhabricatorPHIDConstants::PHID_VOID)
->setTransform('builtin:'.$name)
->setTransformedPHID($file->getPHID())
->save();
unset($unguarded);
$file->attachObjectPHIDs(array());
$file->attachObjects(array());
$files[$name] = $file;
}
return $files;
}
/**
* Convenience wrapper for @{method:loadBuiltins}.
*
* @param PhabricatorUser Viewing user.
* @param string Single builtin name to load.
* @return PhabricatorFile Corresponding builtin file.
*/
public static function loadBuiltin(PhabricatorUser $user, $name) {
return idx(self::loadBuiltins($user, array($name)), $name);
}
public function getObjects() {
return $this->assertAttached($this->objects);
}
public function attachObjects(array $objects) {
$this->objects = $objects;
return $this;
}
public function getObjectPHIDs() {
return $this->assertAttached($this->objectPHIDs);
}
public function attachObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
public function getOriginalFile() {
return $this->assertAttached($this->originalFile);
}
public function attachOriginalFile(PhabricatorFile $file = null) {
$this->originalFile = $file;
return $this;
}
public function getImageHeight() {
if (!$this->isViewableImage()) {
return null;
}
return idx($this->metadata, self::METADATA_IMAGE_HEIGHT);
}
public function getImageWidth() {
if (!$this->isViewableImage()) {
return null;
}
return idx($this->metadata, self::METADATA_IMAGE_WIDTH);
}
public function getCanCDN() {
if (!$this->isViewableImage()) {
return false;
}
return idx($this->metadata, self::METADATA_CAN_CDN);
}
public function setCanCDN($can_cdn) {
$this->metadata[self::METADATA_CAN_CDN] = $can_cdn ? 1 : 0;
return $this;
}
protected function generateOneTimeToken() {
$key = Filesystem::readRandomCharacters(16);
// Save the new secret.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$token = id(new PhabricatorAuthTemporaryToken())
->setObjectPHID($this->getPHID())
->setTokenType(self::ONETIME_TEMPORARY_TOKEN_TYPE)
->setTokenExpires(time() + phutil_units('1 hour in seconds'))
->setTokenCode(PhabricatorHash::digest($key))
->save();
unset($unguarded);
return $key;
}
public function validateOneTimeToken($token_code) {
$token = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withObjectPHIDs(array($this->getPHID()))
->withTokenTypes(array(self::ONETIME_TEMPORARY_TOKEN_TYPE))
->withExpired(false)
->withTokenCodes(array(PhabricatorHash::digest($token_code)))
->executeOne();
return $token;
}
/** Get the CDN uri for this file
* This will generate a one-time-use token if
* security.alternate_file_domain is set in the config.
*/
public function getCDNURIWithToken() {
if (!$this->getPHID()) {
throw new Exception(
'You must save a file before you can generate a CDN URI.');
}
$name = phutil_escape_uri($this->getName());
$path = '/file/data'
.'/'.$this->getSecretKey()
.'/'.$this->getPHID()
.'/'.$this->generateOneTimeToken()
.'/'.$name;
return PhabricatorEnv::getCDNURI($path);
}
/**
* Write the policy edge between this file and some object.
*
* @param phid Object PHID to attach to.
* @return this
*/
public function attachToObject($phid) {
$edge_type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_FILE;
id(new PhabricatorEdgeEditor())
->addEdge($phid, $edge_type, $this->getPHID())
->save();
return $this;
}
/**
* Remove the policy edge between this file and some object.
*
* @param phid Object PHID to detach from.
* @return this
*/
public function detachFromObject($phid) {
$edge_type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_FILE;
id(new PhabricatorEdgeEditor())
->removeEdge($phid, $edge_type, $this->getPHID())
->save();
return $this;
}
/**
* Configure a newly created file object according to specified parameters.
*
* This method is called both when creating a file from fresh data, and
* when creating a new file which reuses existing storage.
*
* @param map<string, wild> Bag of parameters, see @{class:PhabricatorFile}
* for documentation.
* @return this
*/
private function readPropertiesFromParameters(array $params) {
$file_name = idx($params, 'name');
$file_name = self::normalizeFileName($file_name);
$this->setName($file_name);
$author_phid = idx($params, 'authorPHID');
$this->setAuthorPHID($author_phid);
$file_ttl = idx($params, 'ttl');
$this->setTtl($file_ttl);
$view_policy = idx($params, 'viewPolicy');
if ($view_policy) {
$this->setViewPolicy($params['viewPolicy']);
}
$is_explicit = (idx($params, 'isExplicitUpload') ? 1 : 0);
$this->setIsExplicitUpload($is_explicit);
$can_cdn = idx($params, 'canCDN');
if ($can_cdn) {
$this->setCanCDN(true);
}
$mime_type = idx($params, 'mime-type');
if ($mime_type) {
$this->setMimeType($mime_type);
}
return $this;
}
public function getRedirectResponse() {
$uri = $this->getBestURI();
// TODO: This is a bit iffy. Sometimes, getBestURI() returns a CDN URI
// (if the file is a viewable image) and sometimes a local URI (if not).
// For now, just detect which one we got and configure the response
// appropriately. In the long run, if this endpoint is served from a CDN
// domain, we can't issue a local redirect to an info URI (which is not
// present on the CDN domain). We probably never actually issue local
// redirects here anyway, since we only ever transform viewable images
// right now.
$is_external = strlen(id(new PhutilURI($uri))->getDomain());
return id(new AphrontRedirectResponse())
->setIsExternal($is_external)
->setURI($uri);
}
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
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 PhabricatorPolicies::POLICY_NOONE;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
$viewer_phid = $viewer->getPHID();
if ($viewer_phid) {
if ($this->getAuthorPHID() == $viewer_phid) {
return true;
}
}
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
// If you can see the file this file is a transform of, you can see
// this file.
if ($this->getOriginalFile()) {
return true;
}
// If you can see any object this file is attached to, you can see
// the file.
return (count($this->getObjects()) > 0);
}
return false;
}
public function describeAutomaticCapability($capability) {
$out = array();
$out[] = pht('The user who uploaded a file can always view and edit it.');
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$out[] = pht(
'Files attached to objects are visible to users who can view '.
'those objects.');
$out[] = pht(
'Thumbnails are visible only to users who can view the original '.
'file.');
break;
}
return $out;
}
/* -( PhabricatorSubscribableInterface Implementation )-------------------- */
public function isAutomaticallySubscribed($phid) {
return ($this->authorPHID == $phid);
}
public function shouldShowSubscribersProperty() {
return true;
}
public function shouldAllowSubscription($phid) {
return true;
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
return array(
$this->getAuthorPHID(),
);
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$this->saveTransaction();
}
}
diff --git a/src/applications/files/storage/PhabricatorFileTransactionComment.php b/src/applications/files/storage/PhabricatorFileTransactionComment.php
index 57fa7d7a33..f1a2211f7a 100644
--- a/src/applications/files/storage/PhabricatorFileTransactionComment.php
+++ b/src/applications/files/storage/PhabricatorFileTransactionComment.php
@@ -1,25 +1,26 @@
<?php
final class PhabricatorFileTransactionComment
extends PhabricatorApplicationTransactionComment {
public function getApplicationTransactionObject() {
return new PhabricatorFileTransaction();
}
public function shouldUseMarkupCache($field) {
// Only cache submitted comments.
return ($this->getTransactionPHID() != null);
}
public 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/files/storage/PhabricatorTransformedFile.php b/src/applications/files/storage/PhabricatorTransformedFile.php
index 52dde89add..5b0f0e2f1d 100644
--- a/src/applications/files/storage/PhabricatorTransformedFile.php
+++ b/src/applications/files/storage/PhabricatorTransformedFile.php
@@ -1,22 +1,26 @@
<?php
final class PhabricatorTransformedFile extends PhabricatorFileDAO {
protected $originalPHID;
protected $transform;
protected $transformedPHID;
public function getConfiguration() {
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'transform' => 'text255',
),
self::CONFIG_KEY_SCHEMA => array(
'originalPHID' => array(
'columns' => array('originalPHID', 'transform'),
+ 'unique' => true,
+ ),
+ 'transformedPHID' => array(
+ 'columns' => array('transformedPHID'),
),
),
) + parent::getConfiguration();
}
}
diff --git a/src/applications/flag/storage/PhabricatorFlag.php b/src/applications/flag/storage/PhabricatorFlag.php
index 0ef56a916c..535b9a9e8e 100644
--- a/src/applications/flag/storage/PhabricatorFlag.php
+++ b/src/applications/flag/storage/PhabricatorFlag.php
@@ -1,72 +1,76 @@
<?php
final class PhabricatorFlag extends PhabricatorFlagDAO
implements PhabricatorPolicyInterface {
protected $ownerPHID;
protected $type;
protected $objectPHID;
protected $reasonPHID;
protected $color = PhabricatorFlagColor::COLOR_BLUE;
protected $note;
private $handle = self::ATTACHABLE;
private $object = self::ATTACHABLE;
public function getConfiguration() {
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'type' => 'text4',
'color' => 'uint32',
'note' => 'text',
),
self::CONFIG_KEY_SCHEMA => array(
'ownerPHID' => array(
'columns' => array('ownerPHID', 'type', 'objectPHID'),
+ 'unique' => true,
+ ),
+ 'objectPHID' => array(
+ 'columns' => array('objectPHID'),
),
),
) + parent::getConfiguration();
}
public function getObject() {
return $this->assertAttached($this->object);
}
public function attachObject($object) {
$this->object = $object;
return $this;
}
public function getHandle() {
return $this->assertAttached($this->handle);
}
public function attachHandle(PhabricatorObjectHandle $handle) {
$this->handle = $handle;
return $this;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_NOONE;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return ($viewer->getPHID() == $this->getOwnerPHID());
}
public function describeAutomaticCapability($capability) {
return pht('Flags are private. Only you can view or edit your flags.');
}
}
diff --git a/src/applications/fund/storage/FundBacker.php b/src/applications/fund/storage/FundBacker.php
index 25b9765f01..ad34bbc9d8 100644
--- a/src/applications/fund/storage/FundBacker.php
+++ b/src/applications/fund/storage/FundBacker.php
@@ -1,114 +1,122 @@
<?php
final class FundBacker extends FundDAO
implements
PhabricatorPolicyInterface,
PhabricatorApplicationTransactionInterface {
protected $initiativePHID;
protected $backerPHID;
protected $amountInCents;
protected $status;
protected $properties = array();
private $initiative = self::ATTACHABLE;
const STATUS_NEW = 'new';
const STATUS_IN_CART = 'in-cart';
public static function initializeNewBacker(PhabricatorUser $actor) {
return id(new FundBacker())
->setBackerPHID($actor->getPHID())
->setStatus(self::STATUS_NEW);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'status' => 'text32',
'amountInCents' => 'uint32',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_initiative' => array(
+ 'columns' => array('initiativePHID'),
+ ),
+ 'key_backer' => array(
+ 'columns' => array('backerPHID'),
+ ),
+ ),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(FundBackerPHIDType::TYPECONST);
}
protected function didReadData() {
// The payment processing code is strict about types.
$this->amountInCents = (int)$this->amountInCents;
}
public function getProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function setProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
public function getInitiative() {
return $this->assertAttached($this->initiative);
}
public function attachInitiative(FundInitiative $initiative = null) {
$this->initiative = $initiative;
return $this;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
// If we have the initiative, use the initiative's policy.
// Otherwise, return NOONE. This allows the backer to continue seeing
// a backer even if they're no longer allowed to see the initiative.
$initiative = $this->getInitiative();
if ($initiative) {
return $initiative->getPolicy($capability);
}
return PhabricatorPolicies::POLICY_NOONE;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return ($viewer->getPHID() == $this->getBackerPHID());
}
public function describeAutomaticCapability($capability) {
return pht('A backer can always see what they have backed.');
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new FundBackerEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new FundBackerTransaction();
}
}
diff --git a/src/applications/fund/storage/FundInitiative.php b/src/applications/fund/storage/FundInitiative.php
index 88d9ccea05..e0cb4b5fa8 100644
--- a/src/applications/fund/storage/FundInitiative.php
+++ b/src/applications/fund/storage/FundInitiative.php
@@ -1,163 +1,171 @@
<?php
final class FundInitiative extends FundDAO
implements
PhabricatorPolicyInterface,
PhabricatorProjectInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorSubscribableInterface,
PhabricatorMentionableInterface,
PhabricatorFlaggableInterface,
PhabricatorTokenReceiverInterface,
PhabricatorDestructibleInterface {
protected $name;
protected $ownerPHID;
protected $description;
protected $viewPolicy;
protected $editPolicy;
protected $status;
private $projectPHIDs = self::ATTACHABLE;
const STATUS_OPEN = 'open';
const STATUS_CLOSED = 'closed';
public static function getStatusNameMap() {
return array(
self::STATUS_OPEN => pht('Open'),
self::STATUS_CLOSED => pht('Closed'),
);
}
public static function initializeNewInitiative(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorFundApplication'))
->executeOne();
$view_policy = $app->getPolicy(FundDefaultViewCapability::CAPABILITY);
return id(new FundInitiative())
->setOwnerPHID($actor->getPHID())
->setViewPolicy($view_policy)
->setEditPolicy($actor->getPHID())
->setStatus(self::STATUS_OPEN);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255',
'description' => 'text',
'status' => 'text32',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_status' => array(
+ 'columns' => array('status'),
+ ),
+ 'key_owner' => array(
+ 'columns' => array('ownerPHID'),
+ ),
+ ),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(FundInitiativePHIDType::TYPECONST);
}
public function getMonogram() {
return 'I'.$this->getID();
}
public function getProjectPHIDs() {
return $this->assertAttached($this->projectPHIDs);
}
public function attachProjectPHIDs(array $phids) {
$this->projectPHIDs = $phids;
return $this;
}
public function isClosed() {
return ($this->getStatus() == self::STATUS_CLOSED);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
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 $viewer) {
return ($viewer->getPHID() == $this->getOwnerPHID());
}
public function describeAutomaticCapability($capability) {
return pht(
'The owner of an initiative can always view and edit it.');
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new FundInitiativeEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new FundInitiativeTransaction();
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
return ($phid == $this->getOwnerPHID());
}
public function shouldShowSubscribersProperty() {
return true;
}
public function shouldAllowSubscription($phid) {
return true;
}
/* -( PhabricatorTokenRecevierInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
return array(
$this->getOwnerPHID(),
);
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$this->saveTransaction();
}
}
diff --git a/src/applications/herald/storage/transcript/HeraldTranscript.php b/src/applications/herald/storage/transcript/HeraldTranscript.php
index 06b6bc6345..cafae6a424 100644
--- a/src/applications/herald/storage/transcript/HeraldTranscript.php
+++ b/src/applications/herald/storage/transcript/HeraldTranscript.php
@@ -1,232 +1,233 @@
<?php
final class HeraldTranscript extends HeraldDAO
implements
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
protected $objectTranscript;
protected $ruleTranscripts = array();
protected $conditionTranscripts = array();
protected $applyTranscripts = array();
protected $time;
protected $host;
protected $duration;
protected $objectPHID;
protected $dryRun;
+ protected $garbageCollected = 0;
const TABLE_SAVED_HEADER = 'herald_savedheader';
public function getXHeraldRulesHeader() {
$ids = array();
foreach ($this->applyTranscripts as $xscript) {
if ($xscript->getApplied()) {
if ($xscript->getRuleID()) {
$ids[] = $xscript->getRuleID();
}
}
}
if (!$ids) {
return 'none';
}
// A rule may have multiple effects, which will cause it to be listed
// multiple times.
$ids = array_unique($ids);
foreach ($ids as $k => $id) {
$ids[$k] = '<'.$id.'>';
}
return implode(', ', $ids);
}
public static function saveXHeraldRulesHeader($phid, $header) {
// Combine any existing header with the new header, listing all rules
// which have ever triggered for this object.
$header = self::combineXHeraldRulesHeaders(
self::loadXHeraldRulesHeader($phid),
$header);
queryfx(
id(new HeraldTranscript())->establishConnection('w'),
'INSERT INTO %T (phid, header) VALUES (%s, %s)
ON DUPLICATE KEY UPDATE header = VALUES(header)',
self::TABLE_SAVED_HEADER,
$phid,
$header);
return $header;
}
private static function combineXHeraldRulesHeaders($u, $v) {
$u = preg_split('/[, ]+/', $u);
$v = preg_split('/[, ]+/', $v);
$combined = array_unique(array_filter(array_merge($u, $v)));
return implode(', ', $combined);
}
public static function loadXHeraldRulesHeader($phid) {
$header = queryfx_one(
id(new HeraldTranscript())->establishConnection('r'),
'SELECT * FROM %T WHERE phid = %s',
self::TABLE_SAVED_HEADER,
$phid);
if ($header) {
return idx($header, 'header');
}
return null;
}
protected function getConfiguration() {
// Ugh. Too much of a mess to deal with.
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_SERIALIZATION => array(
'objectTranscript' => self::SERIALIZATION_PHP,
'ruleTranscripts' => self::SERIALIZATION_PHP,
'conditionTranscripts' => self::SERIALIZATION_PHP,
'applyTranscripts' => self::SERIALIZATION_PHP,
),
self::CONFIG_BINARY => array(
'objectTranscript' => true,
'ruleTranscripts' => true,
'conditionTranscripts' => true,
'applyTranscripts' => true,
),
self::CONFIG_COLUMN_SCHEMA => array(
'time' => 'epoch',
'host' => 'text255',
'duration' => 'double',
'dryRun' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'objectPHID' => array(
'columns' => array('objectPHID'),
),
'garbageCollected' => array(
'columns' => array('garbageCollected', 'time'),
),
),
) + parent::getConfiguration();
}
public function __construct() {
$this->time = time();
$this->host = php_uname('n');
}
public function addApplyTranscript(HeraldApplyTranscript $transcript) {
$this->applyTranscripts[] = $transcript;
return $this;
}
public function getApplyTranscripts() {
return nonempty($this->applyTranscripts, array());
}
public function setDuration($duration) {
$this->duration = $duration;
return $this;
}
public function setObjectTranscript(HeraldObjectTranscript $transcript) {
$this->objectTranscript = $transcript;
return $this;
}
public function getObjectTranscript() {
return $this->objectTranscript;
}
public function addRuleTranscript(HeraldRuleTranscript $transcript) {
$this->ruleTranscripts[$transcript->getRuleID()] = $transcript;
return $this;
}
public function discardDetails() {
$this->applyTranscripts = null;
$this->ruleTranscripts = null;
$this->objectTranscript = null;
$this->conditionTranscripts = null;
}
public function getRuleTranscripts() {
return nonempty($this->ruleTranscripts, array());
}
public function addConditionTranscript(
HeraldConditionTranscript $transcript) {
$rule_id = $transcript->getRuleID();
$cond_id = $transcript->getConditionID();
$this->conditionTranscripts[$rule_id][$cond_id] = $transcript;
return $this;
}
public function getConditionTranscriptsForRule($rule_id) {
return idx($this->conditionTranscripts, $rule_id, array());
}
public function getMetadataMap() {
return array(
'Run At Epoch' => date('F jS, g:i:s A', $this->time),
'Run On Host' => $this->host,
'Run Duration' => (int)(1000 * $this->duration).' ms',
);
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID('HLXS');
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return PhabricatorPolicies::POLICY_USER;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return pht(
'To view a transcript, you must be able to view the object the '.
'transcript is about.');
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$this->saveTransaction();
}
}
diff --git a/src/applications/legalpad/storage/LegalpadDocument.php b/src/applications/legalpad/storage/LegalpadDocument.php
index 03285c1e3a..ca3b035cb0 100644
--- a/src/applications/legalpad/storage/LegalpadDocument.php
+++ b/src/applications/legalpad/storage/LegalpadDocument.php
@@ -1,238 +1,243 @@
<?php
final class LegalpadDocument extends LegalpadDAO
implements
PhabricatorPolicyInterface,
PhabricatorSubscribableInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorDestructibleInterface {
protected $title;
protected $contributorCount;
protected $recentContributorPHIDs = array();
protected $creatorPHID;
protected $versions;
protected $documentBodyPHID;
protected $viewPolicy;
protected $editPolicy;
protected $mailKey;
protected $signatureType;
protected $preamble;
const SIGNATURE_TYPE_INDIVIDUAL = 'user';
const SIGNATURE_TYPE_CORPORATION = 'corp';
private $documentBody = self::ATTACHABLE;
private $contributors = self::ATTACHABLE;
private $signatures = self::ATTACHABLE;
private $userSignatures = array();
public static function initializeNewDocument(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorLegalpadApplication'))
->executeOne();
$view_policy = $app->getPolicy(LegalpadDefaultViewCapability::CAPABILITY);
$edit_policy = $app->getPolicy(LegalpadDefaultEditCapability::CAPABILITY);
return id(new LegalpadDocument())
->setVersions(0)
->setCreatorPHID($actor->getPHID())
->setContributorCount(0)
->setRecentContributorPHIDs(array())
->attachSignatures(array())
->setSignatureType(self::SIGNATURE_TYPE_INDIVIDUAL)
->setPreamble('')
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'recentContributorPHIDs' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'title' => 'text255',
'contributorCount' => 'uint32',
'versions' => 'uint32',
'mailKey' => 'bytes20',
'signatureType' => 'text4',
'preamble' => 'text',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_creator' => array(
+ 'columns' => array('creatorPHID', 'dateModified'),
+ ),
+ ),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorLegalpadDocumentPHIDType::TYPECONST);
}
public function getDocumentBody() {
return $this->assertAttached($this->documentBody);
}
public function attachDocumentBody(LegalpadDocumentBody $body) {
$this->documentBody = $body;
return $this;
}
public function getContributors() {
return $this->assertAttached($this->contributors);
}
public function attachContributors(array $contributors) {
$this->contributors = $contributors;
return $this;
}
public function getSignatures() {
return $this->assertAttached($this->signatures);
}
public function attachSignatures(array $signatures) {
$this->signatures = $signatures;
return $this;
}
public function save() {
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
}
public function getMonogram() {
return 'L'.$this->getID();
}
public function getUserSignature($phid) {
return $this->assertAttachedKey($this->userSignatures, $phid);
}
public function attachUserSignature(
$user_phid,
LegalpadDocumentSignature $signature = null) {
$this->userSignatures[$user_phid] = $signature;
return $this;
}
public static function getSignatureTypeMap() {
return array(
self::SIGNATURE_TYPE_INDIVIDUAL => pht('Individuals'),
self::SIGNATURE_TYPE_CORPORATION => pht('Corporations'),
);
}
public function getSignatureTypeName() {
$type = $this->getSignatureType();
return idx(self::getSignatureTypeMap(), $type, $type);
}
public function getSignatureTypeIcon() {
$type = $this->getSignatureType();
$map = array(
self::SIGNATURE_TYPE_INDIVIDUAL => 'fa-user grey',
self::SIGNATURE_TYPE_CORPORATION => 'fa-building-o grey',
);
return idx($map, $type, 'fa-user grey');
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
return ($this->creatorPHID == $phid);
}
public function shouldShowSubscribersProperty() {
return true;
}
public function shouldAllowSubscription($phid) {
return true;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$policy = $this->viewPolicy;
break;
case PhabricatorPolicyCapability::CAN_EDIT:
$policy = $this->editPolicy;
break;
default:
$policy = PhabricatorPolicies::POLICY_NOONE;
break;
}
return $policy;
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
return ($user->getPHID() == $this->getCreatorPHID());
}
public function describeAutomaticCapability($capability) {
return pht(
'The author of a document can always view and edit it.');
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new LegalpadDocumentEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new LegalpadTransaction();
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$bodies = id(new LegalpadDocumentBody())->loadAllWhere(
'documentPHID = %s',
$this->getPHID());
foreach ($bodies as $body) {
$body->delete();
}
$signatures = id(new LegalpadDocumentSignature())->loadAllWhere(
'documentPHID = %s',
$this->getPHID());
foreach ($signatures as $signature) {
$signature->delete();
}
$this->saveTransaction();
}
}
diff --git a/src/applications/legalpad/storage/LegalpadDocumentBody.php b/src/applications/legalpad/storage/LegalpadDocumentBody.php
index 97a1eb1183..a4800f2192 100644
--- a/src/applications/legalpad/storage/LegalpadDocumentBody.php
+++ b/src/applications/legalpad/storage/LegalpadDocumentBody.php
@@ -1,79 +1,80 @@
<?php
final class LegalpadDocumentBody extends LegalpadDAO
implements
PhabricatorMarkupInterface {
const MARKUP_FIELD_TEXT = 'markup:text ';
protected $phid;
protected $creatorPHID;
protected $documentPHID;
protected $version;
protected $title;
protected $text;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'version' => 'uint32',
'title' => 'text255',
'text' => 'text?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_document' => array(
'columns' => array('documentPHID', 'version'),
+ 'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_LEGB);
}
/* -( PhabricatorMarkupInterface )----------------------------------------- */
public function getMarkupFieldKey($field) {
$hash = PhabricatorHash::digest($this->getMarkupText($field));
return 'LEGB:'.$hash;
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newMarkupEngine(array());
}
public function getMarkupText($field) {
switch ($field) {
case self::MARKUP_FIELD_TEXT:
$text = $this->getText();
break;
case self::MARKUP_FIELD_TITLE:
$text = $this->getTitle();
break;
default:
throw new Exception('Unknown field: '.$field);
break;
}
return $text;
}
public function didMarkupText($field, $output, PhutilMarkupEngine $engine) {
require_celerity_resource('phabricator-remarkup-css');
return phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$output);
}
public function shouldUseMarkupCache($field) {
return (bool)$this->getID();
}
}
diff --git a/src/applications/legalpad/storage/LegalpadDocumentSignature.php b/src/applications/legalpad/storage/LegalpadDocumentSignature.php
index 1a7d7dc824..e247073c6d 100644
--- a/src/applications/legalpad/storage/LegalpadDocumentSignature.php
+++ b/src/applications/legalpad/storage/LegalpadDocumentSignature.php
@@ -1,89 +1,100 @@
<?php
final class LegalpadDocumentSignature
extends LegalpadDAO
implements PhabricatorPolicyInterface {
const VERIFIED = 0;
const UNVERIFIED = 1;
protected $documentPHID;
protected $documentVersion;
protected $signatureType;
protected $signerPHID;
protected $signerName;
protected $signerEmail;
protected $signatureData = array();
protected $verified;
protected $isExemption = 0;
protected $exemptionPHID;
protected $secretKey;
private $document = self::ATTACHABLE;
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'signatureData' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'documentVersion' => 'uint32',
'signatureType' => 'text4',
'signerPHID' => 'phid?',
'signerName' => 'text255',
'signerEmail' => 'text255',
'secretKey' => 'bytes20',
'verified' => 'bool?',
'isExemption' => 'bool',
'exemptionPHID' => 'phid?',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_signer' => array(
+ 'columns' => array('signerPHID', 'dateModified'),
+ ),
+ 'secretKey' => array(
+ 'columns' => array('secretKey'),
+ ),
+ 'key_document' => array(
+ 'columns' => array('documentPHID', 'signerPHID', 'documentVersion'),
+ ),
+ ),
) + parent::getConfiguration();
}
public function save() {
if (!$this->getSecretKey()) {
$this->setSecretKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
}
public function isVerified() {
return ($this->getVerified() != self::UNVERIFIED);
}
public function getDocument() {
return $this->assertAttached($this->document);
}
public function attachDocument(LegalpadDocument $document) {
$this->document = $document;
return $this;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getDocument()->getPolicy(
PhabricatorPolicyCapability::CAN_EDIT);
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return ($viewer->getPHID() == $this->getSignerPHID());
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/legalpad/storage/LegalpadTransactionComment.php b/src/applications/legalpad/storage/LegalpadTransactionComment.php
index 800303009e..d8315ddaad 100644
--- a/src/applications/legalpad/storage/LegalpadTransactionComment.php
+++ b/src/applications/legalpad/storage/LegalpadTransactionComment.php
@@ -1,40 +1,41 @@
<?php
final class LegalpadTransactionComment
extends PhabricatorApplicationTransactionComment {
protected $documentID;
protected $lineNumber;
protected $lineLength;
protected $fixedState;
protected $hasReplies = 0;
protected $replyToCommentPHID;
public function getApplicationTransactionObject() {
return new LegalpadTransaction();
}
public function shouldUseMarkupCache($field) {
// Only cache submitted comments.
return ($this->getTransactionPHID() != null);
}
public function getConfiguration() {
$config = parent::getConfiguration();
$config[self::CONFIG_COLUMN_SCHEMA] = array(
'documentID' => 'id?',
'lineNumber' => 'uint32',
'lineLength' => 'uint32',
'fixedState' => 'text12?',
'hasReplies' => 'bool',
'replyToCommentPHID' => 'phid?',
) + $config[self::CONFIG_COLUMN_SCHEMA];
$config[self::CONFIG_KEY_SCHEMA] = array(
'key_draft' => array(
'columns' => array('authorPHID', 'documentID', 'transactionPHID'),
+ 'unique' => true,
),
) + $config[self::CONFIG_KEY_SCHEMA];
return $config;
}
}
diff --git a/src/applications/macro/storage/PhabricatorFileImageMacro.php b/src/applications/macro/storage/PhabricatorFileImageMacro.php
index 1ea5ac3704..61f0000779 100644
--- a/src/applications/macro/storage/PhabricatorFileImageMacro.php
+++ b/src/applications/macro/storage/PhabricatorFileImageMacro.php
@@ -1,129 +1,136 @@
<?php
final class PhabricatorFileImageMacro extends PhabricatorFileDAO
implements
PhabricatorSubscribableInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorFlaggableInterface,
PhabricatorPolicyInterface {
protected $authorPHID;
protected $filePHID;
protected $name;
protected $isDisabled = 0;
protected $audioPHID;
protected $audioBehavior = self::AUDIO_BEHAVIOR_NONE;
protected $mailKey;
private $file = self::ATTACHABLE;
private $audio = self::ATTACHABLE;
const AUDIO_BEHAVIOR_NONE = 'audio:none';
const AUDIO_BEHAVIOR_ONCE = 'audio:once';
const AUDIO_BEHAVIOR_LOOP = 'audio:loop';
public function attachFile(PhabricatorFile $file) {
$this->file = $file;
return $this;
}
public function getFile() {
return $this->assertAttached($this->file);
}
public function attachAudio(PhabricatorFile $audio = null) {
$this->audio = $audio;
return $this;
}
public function getAudio() {
return $this->assertAttached($this->audio);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255',
'authorPHID' => 'phid?',
'isDisabled' => 'bool',
'audioPHID' => 'phid?',
'audioBehavior' => 'text64',
'mailKey' => 'bytes20',
),
self::CONFIG_KEY_SCHEMA => array(
'name' => array(
'columns' => array('name'),
+ 'unique' => true,
+ ),
+ 'key_disabled' => array(
+ 'columns' => array('isDisabled'),
+ ),
+ 'key_dateCreated' => array(
+ 'columns' => array('dateCreated'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorMacroMacroPHIDType::TYPECONST);
}
public function save() {
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorMacroEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorMacroTransaction();
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
return false;
}
public function shouldShowSubscribersProperty() {
return true;
}
public function shouldAllowSubscription($phid) {
return true;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/notification/storage/PhabricatorFeedStoryNotification.php b/src/applications/notification/storage/PhabricatorFeedStoryNotification.php
index dd500bb0d3..6018d02f8d 100644
--- a/src/applications/notification/storage/PhabricatorFeedStoryNotification.php
+++ b/src/applications/notification/storage/PhabricatorFeedStoryNotification.php
@@ -1,65 +1,69 @@
<?php
final class PhabricatorFeedStoryNotification extends PhabricatorFeedDAO {
protected $userPHID;
protected $primaryObjectPHID;
protected $chronologicalKey;
protected $hasViewed;
public function getConfiguration() {
return array(
self::CONFIG_IDS => self::IDS_MANUAL,
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'chronologicalKey' => 'uint64',
'hasViewed' => 'bool',
'id' => null,
),
self::CONFIG_KEY_SCHEMA => array(
'PRIMARY' => null,
'userPHID' => array(
'columns' => array('userPHID', 'chronologicalKey'),
+ 'unique' => true,
+ ),
+ 'userPHID_2' => array(
+ 'columns' => array('userPHID', 'hasViewed', 'primaryObjectPHID'),
),
),
) + parent::getConfiguration();
}
static public function updateObjectNotificationViews(
PhabricatorUser $user,
$object_phid) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$notification_table = new PhabricatorFeedStoryNotification();
$conn = $notification_table->establishConnection('w');
queryfx(
$conn,
'UPDATE %T
SET hasViewed = 1
WHERE userPHID = %s
AND primaryObjectPHID = %s
AND hasViewed = 0',
$notification_table->getTableName(),
$user->getPHID(),
$object_phid);
unset($unguarded);
}
public function countUnread(PhabricatorUser $user) {
$conn = $this->establishConnection('r');
$data = queryfx_one(
$conn,
'SELECT COUNT(*) as count
FROM %T
WHERE userPHID = %s AND hasViewed = 0',
$this->getTableName(),
$user->getPHID());
return $data['count'];
}
}
diff --git a/src/applications/nuance/storage/NuanceRequestorSource.php b/src/applications/nuance/storage/NuanceRequestorSource.php
index ea23bcc891..6c607a643c 100644
--- a/src/applications/nuance/storage/NuanceRequestorSource.php
+++ b/src/applications/nuance/storage/NuanceRequestorSource.php
@@ -1,18 +1,34 @@
<?php
final class NuanceRequestorSource
extends NuanceDAO {
protected $requestorPHID;
protected $sourcePHID;
+ protected $sourceKey;
protected $data;
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'data' => self::SERIALIZATION_JSON,
),
+ self::CONFIG_COLUMN_SCHEMA => array(
+ 'sourceKey' => 'text128',
+ ),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_source_key' => array(
+ 'columns' => array('sourcePHID', 'sourceKey'),
+ 'unique' => true,
+ ),
+ 'key_requestor' => array(
+ 'columns' => array('requestorPHID', 'id'),
+ ),
+ 'key_source' => array(
+ 'columns' => array('sourcePHID', 'id'),
+ ),
+ ),
) + parent::getConfiguration();
}
}
diff --git a/src/applications/pholio/storage/PholioTransactionComment.php b/src/applications/pholio/storage/PholioTransactionComment.php
index d5693e8a90..926b74eb7d 100644
--- a/src/applications/pholio/storage/PholioTransactionComment.php
+++ b/src/applications/pholio/storage/PholioTransactionComment.php
@@ -1,54 +1,55 @@
<?php
final class PholioTransactionComment
extends PhabricatorApplicationTransactionComment {
protected $imageID;
protected $x;
protected $y;
protected $width;
protected $height;
protected $content;
public function getApplicationTransactionObject() {
return new PholioTransaction();
}
public function getConfiguration() {
$config = parent::getConfiguration();
$config[self::CONFIG_COLUMN_SCHEMA] = array(
'imageID' => 'id?',
'x' => 'uint32?',
'y' => 'uint32?',
'width' => 'uint32?',
'height' => 'uint32?',
) + $config[self::CONFIG_COLUMN_SCHEMA];
$config[self::CONFIG_KEY_SCHEMA] = array(
'key_draft' => array(
'columns' => array('authorPHID', 'imageID', 'transactionPHID'),
+ 'unique' => true,
),
) + $config[self::CONFIG_KEY_SCHEMA];
return $config;
}
public function toDictionary() {
return array(
'id' => $this->getID(),
'phid' => $this->getPHID(),
'transactionPHID' => $this->getTransactionPHID(),
'x' => $this->getX(),
'y' => $this->getY(),
'width' => $this->getWidth(),
'height' => $this->getHeight(),
);
}
public function shouldUseMarkupCache($field) {
// Only cache submitted comments.
return ($this->getTransactionPHID() != null);
}
}
diff --git a/src/applications/slowvote/storage/PhabricatorSlowvoteChoice.php b/src/applications/slowvote/storage/PhabricatorSlowvoteChoice.php
index 3afaf910f5..c325b8ddc7 100644
--- a/src/applications/slowvote/storage/PhabricatorSlowvoteChoice.php
+++ b/src/applications/slowvote/storage/PhabricatorSlowvoteChoice.php
@@ -1,9 +1,22 @@
<?php
final class PhabricatorSlowvoteChoice extends PhabricatorSlowvoteDAO {
protected $pollID;
protected $optionID;
protected $authorPHID;
+ public function getConfiguration() {
+ return array(
+ self::CONFIG_KEY_SCHEMA => array(
+ 'pollID' => array(
+ 'columns' => array('pollID'),
+ ),
+ 'authorPHID' => array(
+ 'columns' => array('authorPHID'),
+ ),
+ ),
+ ) + parent::getConfiguration();
+ }
+
}
diff --git a/src/applications/slowvote/storage/PhabricatorSlowvoteOption.php b/src/applications/slowvote/storage/PhabricatorSlowvoteOption.php
index 97893632dc..bac8ab5332 100644
--- a/src/applications/slowvote/storage/PhabricatorSlowvoteOption.php
+++ b/src/applications/slowvote/storage/PhabricatorSlowvoteOption.php
@@ -1,16 +1,21 @@
<?php
final class PhabricatorSlowvoteOption extends PhabricatorSlowvoteDAO {
protected $pollID;
protected $name;
public function getConfiguration() {
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'pollID' => array(
+ 'columns' => array('pollID'),
+ ),
+ ),
) + parent::getConfiguration();
}
}
diff --git a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
index 587ce1959e..6b677da2bb 100644
--- a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
+++ b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
@@ -1,179 +1,180 @@
<?php
final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO
implements
PhabricatorPolicyInterface,
PhabricatorSubscribableInterface,
PhabricatorFlaggableInterface,
PhabricatorTokenReceiverInterface,
PhabricatorProjectInterface,
PhabricatorDestructibleInterface {
const RESPONSES_VISIBLE = 0;
const RESPONSES_VOTERS = 1;
const RESPONSES_OWNER = 2;
const METHOD_PLURALITY = 0;
const METHOD_APPROVAL = 1;
protected $question;
protected $description;
protected $authorPHID;
protected $responseVisibility;
protected $shuffle;
protected $method;
protected $viewPolicy;
protected $isClosed = 0;
private $options = self::ATTACHABLE;
private $choices = self::ATTACHABLE;
private $viewerChoices = self::ATTACHABLE;
public static function initializeNewPoll(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorSlowvoteApplication'))
->executeOne();
$view_policy = $app->getPolicy(
PhabricatorSlowvoteDefaultViewCapability::CAPABILITY);
return id(new PhabricatorSlowvotePoll())
->setAuthorPHID($actor->getPHID())
->setViewPolicy($view_policy);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'question' => 'text255',
'responseVisibility' => 'uint32',
'shuffle' => 'uint32',
'method' => 'uint32',
'description' => 'text',
'isClosed' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
+ 'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorSlowvotePollPHIDType::TYPECONST);
}
public function getOptions() {
return $this->assertAttached($this->options);
}
public function attachOptions(array $options) {
assert_instances_of($options, 'PhabricatorSlowvoteOption');
$this->options = $options;
return $this;
}
public function getChoices() {
return $this->assertAttached($this->choices);
}
public function attachChoices(array $choices) {
assert_instances_of($choices, 'PhabricatorSlowvoteChoice');
$this->choices = $choices;
return $this;
}
public function getViewerChoices(PhabricatorUser $viewer) {
return $this->assertAttachedKey($this->viewerChoices, $viewer->getPHID());
}
public function attachViewerChoices(PhabricatorUser $viewer, array $choices) {
if ($this->viewerChoices === self::ATTACHABLE) {
$this->viewerChoices = array();
}
assert_instances_of($choices, 'PhabricatorSlowvoteChoice');
$this->viewerChoices[$viewer->getPHID()] = $choices;
return $this;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->viewPolicy;
case PhabricatorPolicyCapability::CAN_EDIT:
return PhabricatorPolicies::POLICY_NOONE;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return ($viewer->getPHID() == $this->getAuthorPHID());
}
public function describeAutomaticCapability($capability) {
return pht('The author of a poll can always view and edit it.');
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
return ($phid == $this->getAuthorPHID());
}
public function shouldShowSubscribersProperty() {
return true;
}
public function shouldAllowSubscription($phid) {
return true;
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
return array($this->getAuthorPHID());
}
/* -( PhabricatorDestructableInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere(
'pollID = %d',
$this->getID());
foreach ($choices as $choice) {
$choice->delete();
}
$options = id(new PhabricatorSlowvoteOption())->loadAllWhere(
'pollID = %d',
$this->getID());
foreach ($options as $option) {
$option->delete();
}
$this->delete();
$this->saveTransaction();
}
}
diff --git a/src/applications/system/storage/PhabricatorSystemActionLog.php b/src/applications/system/storage/PhabricatorSystemActionLog.php
index 9b49759c00..438806a685 100644
--- a/src/applications/system/storage/PhabricatorSystemActionLog.php
+++ b/src/applications/system/storage/PhabricatorSystemActionLog.php
@@ -1,28 +1,36 @@
<?php
final class PhabricatorSystemActionLog extends PhabricatorSystemDAO {
protected $actorHash;
protected $actorIdentity;
protected $action;
protected $score;
protected $epoch;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'actorHash' => 'bytes12',
'actorIdentity' => 'text255',
'action' => 'text32',
'score' => 'double',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_epoch' => array(
+ 'columns' => array('epoch'),
+ ),
+ 'key_action' => array(
+ 'columns' => array('actorHash', 'action', 'epoch'),
+ ),
+ ),
) + parent::getConfiguration();
}
public function setActorIdentity($identity) {
$this->setActorHash(PhabricatorHash::digestForIndex($identity));
return parent::setActorIdentity($identity);
}
}
diff --git a/src/applications/system/storage/PhabricatorSystemDestructionLog.php b/src/applications/system/storage/PhabricatorSystemDestructionLog.php
index fca4e04705..aca41a043c 100644
--- a/src/applications/system/storage/PhabricatorSystemDestructionLog.php
+++ b/src/applications/system/storage/PhabricatorSystemDestructionLog.php
@@ -1,23 +1,28 @@
<?php
final class PhabricatorSystemDestructionLog extends PhabricatorSystemDAO {
protected $objectClass;
protected $rootLogID;
protected $objectPHID;
protected $objectMonogram;
protected $epoch;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'objectClass' => 'text128',
'rootLogID' => 'id?',
'objectPHID' => 'phid?',
'objectMonogram' => 'text64?',
),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_epoch' => array(
+ 'columns' => array('epoch'),
+ ),
+ ),
) + parent::getConfiguration();
}
}
diff --git a/src/applications/tokens/storage/PhabricatorTokenCount.php b/src/applications/tokens/storage/PhabricatorTokenCount.php
index 6e057b76b1..c4be4407f0 100644
--- a/src/applications/tokens/storage/PhabricatorTokenCount.php
+++ b/src/applications/tokens/storage/PhabricatorTokenCount.php
@@ -1,23 +1,27 @@
<?php
final class PhabricatorTokenCount extends PhabricatorTokenDAO {
protected $objectPHID;
protected $tokenCount;
public function getConfiguration() {
return array(
self::CONFIG_IDS => self::IDS_MANUAL,
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'tokenCount' => 'uint32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_objectPHID' => array(
'columns' => array('objectPHID'),
+ 'unique' => true,
+ ),
+ 'key_count' => array(
+ 'columns' => array('tokenCount'),
),
),
) + parent::getConfiguration();
}
}
diff --git a/src/applications/tokens/storage/PhabricatorTokenGiven.php b/src/applications/tokens/storage/PhabricatorTokenGiven.php
index f6a1b0153d..5be2044551 100644
--- a/src/applications/tokens/storage/PhabricatorTokenGiven.php
+++ b/src/applications/tokens/storage/PhabricatorTokenGiven.php
@@ -1,74 +1,81 @@
<?php
final class PhabricatorTokenGiven extends PhabricatorTokenDAO
implements PhabricatorPolicyInterface {
protected $authorPHID;
protected $objectPHID;
protected $tokenPHID;
private $object = self::ATTACHABLE;
public function getConfiguration() {
return array(
self::CONFIG_KEY_SCHEMA => array(
'key_all' => array(
'columns' => array('objectPHID', 'authorPHID'),
+ 'unique' => true,
+ ),
+ 'key_author' => array(
+ 'columns' => array('authorPHID'),
+ ),
+ 'key_token' => array(
+ 'columns' => array('tokenPHID'),
),
),
) + parent::getConfiguration();
}
public function attachObject(PhabricatorTokenReceiverInterface $object) {
$this->object = $object;
return $this;
}
public function getObject() {
return $this->assertAttached($this->object);
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getObject()->getPolicy($capability);
default:
return PhabricatorPolicies::POLICY_NOONE;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getObject()->hasAutomaticCapability(
$capability,
$user);
default:
if ($user->getPHID() == $this->authorPHID) {
return true;
}
return false;
}
}
public function describeAutomaticCapability($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return pht(
'A token inherits the policies of the object it is awarded to.');
case PhabricatorPolicyCapability::CAN_EDIT:
return pht(
'The user who gave a token can always edit it.');
}
return null;
}
}
diff --git a/src/applications/xhprof/storage/PhabricatorXHProfSample.php b/src/applications/xhprof/storage/PhabricatorXHProfSample.php
index b0e56a7962..8d13a76534 100644
--- a/src/applications/xhprof/storage/PhabricatorXHProfSample.php
+++ b/src/applications/xhprof/storage/PhabricatorXHProfSample.php
@@ -1,31 +1,32 @@
<?php
final class PhabricatorXHProfSample extends PhabricatorXHProfDAO {
protected $filePHID;
protected $usTotal;
protected $sampleRate;
protected $hostname;
protected $requestPath;
protected $controller;
protected $userPHID;
public function getConfiguration() {
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'sampleRate' => 'uint32',
'usTotal' => 'uint64',
'hostname' => 'text255?',
'requestPath' => 'text255?',
'controller' => 'text255?',
'userPHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'filePHID' => array(
'columns' => array('filePHID'),
+ 'unique' => true,
),
),
) + parent::getConfiguration();
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Mar 16, 9:44 AM (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
962556
Default Alt Text
(162 KB)

Event Timeline