diff --git a/src/applications/almanac/storage/AlmanacDevice.php b/src/applications/almanac/storage/AlmanacDevice.php index 19ee3f89ff..d0994174b3 100644 --- a/src/applications/almanac/storage/AlmanacDevice.php +++ b/src/applications/almanac/storage/AlmanacDevice.php @@ -1,253 +1,253 @@ <?php final class AlmanacDevice extends AlmanacDAO implements PhabricatorPolicyInterface, PhabricatorCustomFieldInterface, PhabricatorApplicationTransactionInterface, PhabricatorProjectInterface, PhabricatorSSHPublicKeyInterface, AlmanacPropertyInterface, PhabricatorDestructibleInterface { protected $name; protected $nameIndex; protected $mailKey; protected $viewPolicy; protected $editPolicy; protected $isLocked; private $customFields = self::ATTACHABLE; private $almanacProperties = self::ATTACHABLE; public static function initializeNewDevice() { return id(new AlmanacDevice()) ->setViewPolicy(PhabricatorPolicies::POLICY_USER) ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN) ->attachAlmanacProperties(array()) ->setIsLocked(0); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text128', 'nameIndex' => 'bytes12', 'mailKey' => 'bytes20', 'isLocked' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_name' => array( 'columns' => array('nameIndex'), 'unique' => true, ), 'key_nametext' => array( 'columns' => array('name'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID(AlmanacDevicePHIDType::TYPECONST); } public function save() { AlmanacNames::validateServiceOrDeviceName($this->getName()); $this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); if (!$this->mailKey) { $this->mailKey = Filesystem::readRandomCharacters(20); } return parent::save(); } public function getURI() { return '/almanac/device/view/'.$this->getName().'/'; } /** * Find locked services which are bound to this device, updating the device * lock flag if necessary. * * @return list<phid> List of locking service PHIDs. */ public function rebuildDeviceLocks() { $services = id(new AlmanacServiceQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withDevicePHIDs(array($this->getPHID())) ->withLocked(true) ->execute(); $locked = (bool)count($services); if ($locked != $this->getIsLocked()) { $this->setIsLocked((int)$locked); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); queryfx( $this->establishConnection('w'), 'UPDATE %T SET isLocked = %d WHERE id = %d', $this->getTableName(), $this->getIsLocked(), $this->getID()); unset($unguarded); } return $this; } /* -( AlmanacPropertyInterface )------------------------------------------- */ public function attachAlmanacProperties(array $properties) { assert_instances_of($properties, 'AlmanacProperty'); $this->almanacProperties = mpull($properties, null, 'getFieldName'); return $this; } public function getAlmanacProperties() { return $this->assertAttached($this->almanacProperties); } public function hasAlmanacProperty($key) { $this->assertAttached($this->almanacProperties); return isset($this->almanacProperties[$key]); } public function getAlmanacProperty($key) { return $this->assertAttachedKey($this->almanacProperties, $key); } public function getAlmanacPropertyValue($key, $default = null) { if ($this->hasAlmanacProperty($key)) { return $this->getAlmanacProperty($key)->getFieldValue(); } else { return $default; } } public function getAlmanacPropertyFieldSpecifications() { return array(); } /* -( 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: if ($this->getIsLocked()) { return PhabricatorPolicies::POLICY_NOONE; } else { return $this->getEditPolicy(); } } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } public function describeAutomaticCapability($capability) { if ($capability === PhabricatorPolicyCapability::CAN_EDIT) { if ($this->getIsLocked()) { return pht( 'This device is bound to a locked service, so it can not '. 'be edited.'); } } return null; } /* -( PhabricatorCustomFieldInterface )------------------------------------ */ public function getCustomFieldSpecificationForRole($role) { return array(); } public function getCustomFieldBaseClass() { return 'AlmanacCustomField'; } public function getCustomFields() { return $this->assertAttached($this->customFields); } public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { $this->customFields = $fields; return $this; } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new AlmanacDeviceEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new AlmanacDeviceTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorSSHPublicKeyInterface )----------------------------------- */ public function getSSHPublicKeyManagementURI(PhabricatorUser $viewer) { return $this->getURI(); } public function getSSHKeyDefaultName() { return $this->getName(); } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $interfaces = id(new AlmanacInterfaceQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($engine->getViewer()) ->withDevicePHIDs(array($this->getPHID())) ->execute(); foreach ($interfaces as $interface) { $engine->destroyObject($interface); } $this->delete(); } } diff --git a/src/applications/almanac/storage/AlmanacInterface.php b/src/applications/almanac/storage/AlmanacInterface.php index 5ab892cef7..729e95aa9a 100644 --- a/src/applications/almanac/storage/AlmanacInterface.php +++ b/src/applications/almanac/storage/AlmanacInterface.php @@ -1,132 +1,132 @@ <?php final class AlmanacInterface extends AlmanacDAO implements PhabricatorPolicyInterface, PhabricatorDestructibleInterface { protected $devicePHID; protected $networkPHID; protected $address; protected $port; private $device = self::ATTACHABLE; private $network = self::ATTACHABLE; public static function initializeNewInterface() { return id(new AlmanacInterface()); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'address' => 'text64', 'port' => 'uint32', ), self::CONFIG_KEY_SCHEMA => array( 'key_location' => array( 'columns' => array('networkPHID', 'address', 'port'), ), 'key_device' => array( 'columns' => array('devicePHID'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( AlmanacInterfacePHIDType::TYPECONST); } public function getDevice() { return $this->assertAttached($this->device); } public function attachDevice(AlmanacDevice $device) { $this->device = $device; return $this; } public function getNetwork() { return $this->assertAttached($this->network); } public function attachNetwork(AlmanacNetwork $network) { $this->network = $network; return $this; } public function toAddress() { return AlmanacAddress::newFromParts( $this->getNetworkPHID(), $this->getAddress(), $this->getPort()); } public function getAddressHash() { return $this->toAddress()->toHash(); } public function renderDisplayAddress() { return $this->getAddress().':'.$this->getPort(); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return $this->getDevice()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getDevice()->hasAutomaticCapability($capability, $viewer); } public function describeAutomaticCapability($capability) { $notes = array( pht('An interface inherits the policies of the device it belongs to.'), pht( 'You must be able to view the network an interface resides on to '. 'view the interface.'), ); if ($capability === PhabricatorPolicyCapability::CAN_EDIT) { if ($this->getDevice()->getIsLocked()) { $notes[] = pht( 'The device for this interface is locked, so it can not be edited.'); } } return $notes; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $bindings = id(new AlmanacBindingQuery()) - ->setViewer($this->getViewer()) + ->setViewer($engine->getViewer()) ->withInterfacePHIDs(array($this->getPHID())) ->execute(); foreach ($bindings as $binding) { $engine->destroyObject($binding); } $this->delete(); } } diff --git a/src/applications/almanac/storage/AlmanacNetwork.php b/src/applications/almanac/storage/AlmanacNetwork.php index 92d4b8fa47..a623248fd9 100644 --- a/src/applications/almanac/storage/AlmanacNetwork.php +++ b/src/applications/almanac/storage/AlmanacNetwork.php @@ -1,117 +1,117 @@ <?php final class AlmanacNetwork extends AlmanacDAO implements PhabricatorApplicationTransactionInterface, PhabricatorPolicyInterface, PhabricatorDestructibleInterface { protected $name; protected $mailKey; protected $viewPolicy; protected $editPolicy; public static function initializeNewNetwork() { return id(new AlmanacNetwork()) ->setViewPolicy(PhabricatorPolicies::POLICY_USER) ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text128', 'mailKey' => 'bytes20', ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID(AlmanacNetworkPHIDType::TYPECONST); } public function save() { if (!$this->mailKey) { $this->mailKey = Filesystem::readRandomCharacters(20); } return parent::save(); } public function getURI() { return '/almanac/network/'.$this->getID().'/'; } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new AlmanacNetworkEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new AlmanacNetworkTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( 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 false; } public function describeAutomaticCapability($capability) { return null; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $interfaces = id(new AlmanacInterfaceQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($engine->getViewer()) ->withNetworkPHIDs(array($this->getPHID())) ->execute(); foreach ($interfaces as $interface) { $engine->destroyObject($interface); } $this->delete(); } } diff --git a/src/applications/almanac/storage/AlmanacService.php b/src/applications/almanac/storage/AlmanacService.php index 36c7761e22..b351f0d3fc 100644 --- a/src/applications/almanac/storage/AlmanacService.php +++ b/src/applications/almanac/storage/AlmanacService.php @@ -1,234 +1,234 @@ <?php final class AlmanacService extends AlmanacDAO implements PhabricatorPolicyInterface, PhabricatorCustomFieldInterface, PhabricatorApplicationTransactionInterface, PhabricatorProjectInterface, AlmanacPropertyInterface, PhabricatorDestructibleInterface { protected $name; protected $nameIndex; protected $mailKey; protected $viewPolicy; protected $editPolicy; protected $serviceClass; protected $isLocked; private $customFields = self::ATTACHABLE; private $almanacProperties = self::ATTACHABLE; private $bindings = self::ATTACHABLE; private $serviceType = self::ATTACHABLE; public static function initializeNewService() { return id(new AlmanacService()) ->setViewPolicy(PhabricatorPolicies::POLICY_USER) ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN) ->attachAlmanacProperties(array()) ->setIsLocked(0); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text128', 'nameIndex' => 'bytes12', 'mailKey' => 'bytes20', 'serviceClass' => 'text64', 'isLocked' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_name' => array( 'columns' => array('nameIndex'), 'unique' => true, ), 'key_nametext' => array( 'columns' => array('name'), ), 'key_class' => array( 'columns' => array('serviceClass'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID(AlmanacServicePHIDType::TYPECONST); } public function save() { AlmanacNames::validateServiceOrDeviceName($this->getName()); $this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); if (!$this->mailKey) { $this->mailKey = Filesystem::readRandomCharacters(20); } return parent::save(); } public function getURI() { return '/almanac/service/view/'.$this->getName().'/'; } public function getBindings() { return $this->assertAttached($this->bindings); } public function attachBindings(array $bindings) { $this->bindings = $bindings; return $this; } public function getServiceType() { return $this->assertAttached($this->serviceType); } public function attachServiceType(AlmanacServiceType $type) { $this->serviceType = $type; return $this; } /* -( AlmanacPropertyInterface )------------------------------------------- */ public function attachAlmanacProperties(array $properties) { assert_instances_of($properties, 'AlmanacProperty'); $this->almanacProperties = mpull($properties, null, 'getFieldName'); return $this; } public function getAlmanacProperties() { return $this->assertAttached($this->almanacProperties); } public function hasAlmanacProperty($key) { $this->assertAttached($this->almanacProperties); return isset($this->almanacProperties[$key]); } public function getAlmanacProperty($key) { return $this->assertAttachedKey($this->almanacProperties, $key); } public function getAlmanacPropertyValue($key, $default = null) { if ($this->hasAlmanacProperty($key)) { return $this->getAlmanacProperty($key)->getFieldValue(); } else { return $default; } } public function getAlmanacPropertyFieldSpecifications() { return $this->getServiceType()->getFieldSpecifications(); } /* -( 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: if ($this->getIsLocked()) { return PhabricatorPolicies::POLICY_NOONE; } else { return $this->getEditPolicy(); } } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } public function describeAutomaticCapability($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_EDIT: if ($this->getIsLocked()) { return pht('This service is locked and can not be edited.'); } break; } return null; } /* -( PhabricatorCustomFieldInterface )------------------------------------ */ public function getCustomFieldSpecificationForRole($role) { return array(); } public function getCustomFieldBaseClass() { return 'AlmanacCustomField'; } public function getCustomFields() { return $this->assertAttached($this->customFields); } public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { $this->customFields = $fields; return $this; } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new AlmanacServiceEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new AlmanacServiceTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $bindings = id(new AlmanacBindingQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($engine->getViewer()) ->withServicePHIDs(array($this->getPHID())) ->execute(); foreach ($bindings as $binding) { $engine->destroyObject($binding); } $this->delete(); } } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 74f3ff34d7..e6baa678e9 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -1,581 +1,581 @@ <?php final class DifferentialRevision extends DifferentialDAO implements PhabricatorTokenReceiverInterface, PhabricatorPolicyInterface, PhabricatorFlaggableInterface, PhrequentTrackableInterface, HarbormasterBuildableInterface, PhabricatorSubscribableInterface, PhabricatorCustomFieldInterface, PhabricatorApplicationTransactionInterface, PhabricatorMentionableInterface, PhabricatorDestructibleInterface, PhabricatorProjectInterface { protected $title = ''; protected $originalTitle; protected $status; protected $summary = ''; protected $testPlan = ''; protected $authorPHID; protected $lastReviewerPHID; protected $lineCount = 0; protected $attached = array(); protected $mailKey; protected $branchName; protected $arcanistProjectPHID; protected $repositoryPHID; protected $viewPolicy = PhabricatorPolicies::POLICY_USER; protected $editPolicy = PhabricatorPolicies::POLICY_USER; private $relationships = self::ATTACHABLE; private $commits = self::ATTACHABLE; private $activeDiff = self::ATTACHABLE; private $diffIDs = self::ATTACHABLE; private $hashes = self::ATTACHABLE; private $repository = self::ATTACHABLE; private $reviewerStatus = self::ATTACHABLE; private $customFields = self::ATTACHABLE; private $drafts = array(); private $flags = array(); const TABLE_COMMIT = 'differential_commit'; const RELATION_REVIEWER = 'revw'; const RELATION_SUBSCRIBED = 'subd'; public static function initializeNewRevision(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) ->withClasses(array('PhabricatorDifferentialApplication')) ->executeOne(); $view_policy = $app->getPolicy( DifferentialDefaultViewCapability::CAPABILITY); return id(new DifferentialRevision()) ->setViewPolicy($view_policy) ->setAuthorPHID($actor->getPHID()) ->attachRelationships(array()) ->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'attached' => self::SERIALIZATION_JSON, 'unsubscribed' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'title' => 'text255', 'originalTitle' => 'text255', 'status' => 'text32', 'summary' => 'text', 'testPlan' => 'text', 'authorPHID' => 'phid?', 'lastReviewerPHID' => 'phid?', 'lineCount' => 'uint32?', 'mailKey' => 'bytes40', 'branchName' => 'text255?', 'arcanistProjectPHID' => 'phid?', 'repositoryPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, 'phid' => array( 'columns' => array('phid'), 'unique' => true, ), 'authorPHID' => array( 'columns' => array('authorPHID', 'status'), ), 'repositoryPHID' => array( 'columns' => array('repositoryPHID'), ), ), ) + parent::getConfiguration(); } public function getMonogram() { $id = $this->getID(); return "D{$id}"; } public function setTitle($title) { $this->title = $title; if (!$this->getID()) { $this->originalTitle = $title; } return $this; } public function loadIDsByCommitPHIDs($phids) { if (!$phids) { return array(); } $revision_ids = queryfx_all( $this->establishConnection('r'), 'SELECT * FROM %T WHERE commitPHID IN (%Ls)', self::TABLE_COMMIT, $phids); return ipull($revision_ids, 'revisionID', 'commitPHID'); } public function loadCommitPHIDs() { if (!$this->getID()) { return ($this->commits = array()); } $commits = queryfx_all( $this->establishConnection('r'), 'SELECT commitPHID FROM %T WHERE revisionID = %d', self::TABLE_COMMIT, $this->getID()); $commits = ipull($commits, 'commitPHID'); return ($this->commits = $commits); } public function getCommitPHIDs() { return $this->assertAttached($this->commits); } public function getActiveDiff() { // TODO: Because it's currently technically possible to create a revision // without an associated diff, we allow an attached-but-null active diff. // It would be good to get rid of this once we make diff-attaching // transactional. return $this->assertAttached($this->activeDiff); } public function attachActiveDiff($diff) { $this->activeDiff = $diff; return $this; } public function getDiffIDs() { return $this->assertAttached($this->diffIDs); } public function attachDiffIDs(array $ids) { rsort($ids); $this->diffIDs = array_values($ids); return $this; } public function attachCommitPHIDs(array $phids) { $this->commits = array_values($phids); return $this; } public function getAttachedPHIDs($type) { return array_keys(idx($this->attached, $type, array())); } public function setAttachedPHIDs($type, array $phids) { $this->attached[$type] = array_fill_keys($phids, array()); return $this; } public function generatePHID() { return PhabricatorPHID::generateNewPHID( DifferentialRevisionPHIDType::TYPECONST); } public function loadActiveDiff() { return id(new DifferentialDiff())->loadOneWhere( 'revisionID = %d ORDER BY id DESC LIMIT 1', $this->getID()); } public function save() { if (!$this->getMailKey()) { $this->mailKey = Filesystem::readRandomCharacters(40); } return parent::save(); } public function loadRelationships() { if (!$this->getID()) { $this->relationships = array(); return; } $data = array(); $subscriber_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $this->getPHID(), PhabricatorObjectHasSubscriberEdgeType::EDGECONST); $subscriber_phids = array_reverse($subscriber_phids); foreach ($subscriber_phids as $phid) { $data[] = array( 'relation' => self::RELATION_SUBSCRIBED, 'objectPHID' => $phid, 'reasonPHID' => null, ); } $reviewer_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $this->getPHID(), DifferentialRevisionHasReviewerEdgeType::EDGECONST); $reviewer_phids = array_reverse($reviewer_phids); foreach ($reviewer_phids as $phid) { $data[] = array( 'relation' => self::RELATION_REVIEWER, 'objectPHID' => $phid, 'reasonPHID' => null, ); } return $this->attachRelationships($data); } public function attachRelationships(array $relationships) { $this->relationships = igroup($relationships, 'relation'); return $this; } public function getReviewers() { return $this->getRelatedPHIDs(self::RELATION_REVIEWER); } public function getCCPHIDs() { return $this->getRelatedPHIDs(self::RELATION_SUBSCRIBED); } private function getRelatedPHIDs($relation) { $this->assertAttached($this->relationships); return ipull($this->getRawRelations($relation), 'objectPHID'); } public function getRawRelations($relation) { return idx($this->relationships, $relation, array()); } public function getPrimaryReviewer() { $reviewers = $this->getReviewers(); $last = $this->lastReviewerPHID; if (!$last || !in_array($last, $reviewers)) { return head($this->getReviewers()); } return $last; } public function getHashes() { return $this->assertAttached($this->hashes); } public function attachHashes(array $hashes) { $this->hashes = $hashes; return $this; } public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $user) { // A revision's author (which effectively means "owner" after we added // commandeering) can always view and edit it. $author_phid = $this->getAuthorPHID(); if ($author_phid) { if ($user->getPHID() == $author_phid) { return true; } } return false; } public function describeAutomaticCapability($capability) { $description = array( pht('The owner of a revision can always view and edit it.'), ); switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: $description[] = pht( "A revision's reviewers can always view it."); $description[] = pht( 'If a revision belongs to a repository, other users must be able '. 'to view the repository in order to view the revision.'); break; } return $description; } public function getUsersToNotifyOfTokenGiven() { return array( $this->getAuthorPHID(), ); } public function getReviewerStatus() { return $this->assertAttached($this->reviewerStatus); } public function attachReviewerStatus(array $reviewers) { assert_instances_of($reviewers, 'DifferentialReviewer'); $this->reviewerStatus = $reviewers; return $this; } public function getRepository() { return $this->assertAttached($this->repository); } public function attachRepository(PhabricatorRepository $repository = null) { $this->repository = $repository; return $this; } public function isClosed() { return DifferentialRevisionStatus::isClosedStatus($this->getStatus()); } public function getFlag(PhabricatorUser $viewer) { return $this->assertAttachedKey($this->flags, $viewer->getPHID()); } public function attachFlag( PhabricatorUser $viewer, PhabricatorFlag $flag = null) { $this->flags[$viewer->getPHID()] = $flag; return $this; } public function getDrafts(PhabricatorUser $viewer) { return $this->assertAttachedKey($this->drafts, $viewer->getPHID()); } public function attachDrafts(PhabricatorUser $viewer, array $drafts) { $this->drafts[$viewer->getPHID()] = $drafts; return $this; } /* -( HarbormasterBuildableInterface )------------------------------------- */ public function getHarbormasterBuildablePHID() { return $this->loadActiveDiff()->getPHID(); } public function getHarbormasterContainerPHID() { return $this->getPHID(); } public function getBuildVariables() { return array(); } public function getAvailableBuildVariables() { return array(); } /* -( PhabricatorSubscribableInterface )----------------------------------- */ public function isAutomaticallySubscribed($phid) { if ($phid == $this->getAuthorPHID()) { return true; } // TODO: This only happens when adding or removing CCs, and is safe from a // policy perspective, but the subscription pathway should have some // opportunity to load this data properly. For now, this is the only case // where implicit subscription is not an intrinsic property of the object. if ($this->reviewerStatus == self::ATTACHABLE) { $reviewers = id(new DifferentialRevisionQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs(array($this->getPHID())) ->needReviewerStatus(true) ->executeOne() ->getReviewerStatus(); } else { $reviewers = $this->getReviewerStatus(); } foreach ($reviewers as $reviewer) { if ($reviewer->getReviewerPHID() == $phid) { return true; } } return false; } public function shouldShowSubscribersProperty() { return true; } public function shouldAllowSubscription($phid) { return true; } /* -( PhabricatorCustomFieldInterface )------------------------------------ */ public function getCustomFieldSpecificationForRole($role) { return PhabricatorEnv::getEnvConfig('differential.fields'); } public function getCustomFieldBaseClass() { return 'DifferentialCustomField'; } public function getCustomFields() { return $this->assertAttached($this->customFields); } public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { $this->customFields = $fields; return $this; } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new DifferentialTransactionEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new DifferentialTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { $viewer = $request->getViewer(); $render_data = $timeline->getRenderData(); $left = $request->getInt('left', idx($render_data, 'left')); $right = $request->getInt('right', idx($render_data, 'right')); $diffs = id(new DifferentialDiffQuery()) ->setViewer($request->getUser()) ->withIDs(array($left, $right)) ->execute(); $diffs = mpull($diffs, null, 'getID'); $left_diff = $diffs[$left]; $right_diff = $diffs[$right]; $old_ids = $request->getStr('old', idx($render_data, 'old')); $new_ids = $request->getStr('new', idx($render_data, 'new')); $old_ids = array_filter(explode(',', $old_ids)); $new_ids = array_filter(explode(',', $new_ids)); $type_inline = DifferentialTransaction::TYPE_INLINE; $changeset_ids = array_merge($old_ids, $new_ids); $inlines = array(); foreach ($timeline->getTransactions() as $xaction) { if ($xaction->getTransactionType() == $type_inline) { $inlines[] = $xaction->getComment(); $changeset_ids[] = $xaction->getComment()->getChangesetID(); } } if ($changeset_ids) { $changesets = id(new DifferentialChangesetQuery()) ->setViewer($request->getUser()) ->withIDs($changeset_ids) ->execute(); $changesets = mpull($changesets, null, 'getID'); } else { $changesets = array(); } foreach ($inlines as $key => $inline) { $inlines[$key] = DifferentialInlineComment::newFromModernComment( $inline); } $query = id(new DifferentialInlineCommentQuery()) ->setViewer($viewer); // NOTE: This is a bit sketchy: this method adjusts the inlines as a // side effect, which means it will ultimately adjust the transaction // comments and affect timeline rendering. $query->adjustInlinesForChangesets( $inlines, array_select_keys($changesets, $old_ids), array_select_keys($changesets, $new_ids), $this); return $timeline ->setChangesets($changesets) ->setRevision($this) ->setLeftDiff($left_diff) ->setRightDiff($right_diff); } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $diffs = id(new DifferentialDiffQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($engine->getViewer()) ->withRevisionIDs(array($this->getID())) ->execute(); foreach ($diffs as $diff) { $engine->destroyObject($diff); } $conn_w = $this->establishConnection('w'); queryfx( $conn_w, 'DELETE FROM %T WHERE revisionID = %d', self::TABLE_COMMIT, $this->getID()); // we have to do paths a little differentally as they do not have // an id or phid column for delete() to act on $dummy_path = new DifferentialAffectedPath(); queryfx( $conn_w, 'DELETE FROM %T WHERE revisionID = %d', $dummy_path->getTableName(), $this->getID()); $this->delete(); $this->saveTransaction(); } } diff --git a/src/applications/diviner/storage/DivinerLiveBook.php b/src/applications/diviner/storage/DivinerLiveBook.php index 68a6f8a0ec..ef7a9bb277 100644 --- a/src/applications/diviner/storage/DivinerLiveBook.php +++ b/src/applications/diviner/storage/DivinerLiveBook.php @@ -1,108 +1,108 @@ <?php final class DivinerLiveBook extends DivinerDAO implements PhabricatorPolicyInterface, PhabricatorDestructibleInterface { protected $name; protected $viewPolicy; protected $configurationData = array(); protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'configurationData' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text64', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, 'phid' => array( 'columns' => array('phid'), 'unique' => true, ), 'name' => array( 'columns' => array('name'), 'unique' => true, ), ), ) + parent::getConfiguration(); } public function getConfig($key, $default = null) { return idx($this->configurationData, $key, $default); } public function setConfig($key, $value) { $this->configurationData[$key] = $value; return $this; } public function generatePHID() { return PhabricatorPHID::generateNewPHID( DivinerBookPHIDType::TYPECONST); } public function getTitle() { return $this->getConfig('title', $this->getName()); } public function getShortTitle() { return $this->getConfig('short', $this->getTitle()); } public function getPreface() { return $this->getConfig('preface'); } public function getGroupName($group) { $groups = $this->getConfig('groups', array()); $spec = idx($groups, $group, array()); return idx($spec, 'name', $group); } /* -( 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; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $atoms = id(new DivinerAtomQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($engine->getViewer()) ->withBookPHIDs(array($this->getPHID())) ->withIncludeGhosts(true) ->withIncludeUndocumentable(true) ->execute(); foreach ($atoms as $atom) { $engine->destroyObject($atom); } $this->delete(); $this->saveTransaction(); } } diff --git a/src/applications/files/controller/PhabricatorFileTransformController.php b/src/applications/files/controller/PhabricatorFileTransformController.php index 5062fe76fc..0e7e2b73d7 100644 --- a/src/applications/files/controller/PhabricatorFileTransformController.php +++ b/src/applications/files/controller/PhabricatorFileTransformController.php @@ -1,124 +1,124 @@ <?php final class PhabricatorFileTransformController extends PhabricatorFileController { public function shouldRequireLogin() { return false; } public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); // NOTE: This is a public/CDN endpoint, and permission to see files is // controlled by knowing the secret key, not by authentication. $is_regenerate = $request->getBool('regenerate'); $source_phid = $request->getURIData('phid'); $file = id(new PhabricatorFileQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs(array($source_phid)) ->executeOne(); if (!$file) { return new Aphront404Response(); } $secret_key = $request->getURIData('key'); if (!$file->validateSecretKey($secret_key)) { return new Aphront403Response(); } $transform = $request->getURIData('transform'); $xform = id(new PhabricatorTransformedFile()) ->loadOneWhere( 'originalPHID = %s AND transform = %s', $source_phid, $transform); if ($xform) { if ($is_regenerate) { $this->destroyTransform($xform); } else { return $this->buildTransformedFileResponse($xform); } } $xforms = PhabricatorFileTransform::getAllTransforms(); if (!isset($xforms[$transform])) { return new Aphront404Response(); } $xform = $xforms[$transform]; // We're essentially just building a cache here and don't need CSRF // protection. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $xformed_file = null; if ($xform->canApplyTransform($file)) { try { $xformed_file = $xforms[$transform]->applyTransform($file); } catch (Exception $ex) { // In normal transform mode, we ignore failures and generate a // default transform below. If we're explicitly regenerating the // thumbnail, rethrow the exception. if ($is_regenerate) { throw $ex; } } } if (!$xformed_file) { $xformed_file = $xform->getDefaultTransform($file); } if (!$xformed_file) { return new Aphront400Response(); } $xform = id(new PhabricatorTransformedFile()) ->setOriginalPHID($source_phid) ->setTransform($transform) ->setTransformedPHID($xformed_file->getPHID()) ->save(); return $this->buildTransformedFileResponse($xform); } private function buildTransformedFileResponse( PhabricatorTransformedFile $xform) { $file = id(new PhabricatorFileQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs(array($xform->getTransformedPHID())) ->executeOne(); if (!$file) { return new Aphront404Response(); } // TODO: We could just delegate to the file view controller instead, // which would save the client a roundtrip, but is slightly more complex. return $file->getRedirectResponse(); } private function destroyTransform(PhabricatorTransformedFile $xform) { + $engine = new PhabricatorDestructionEngine(); $file = id(new PhabricatorFileQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($engine->getViewer()) ->withPHIDs(array($xform->getTransformedPHID())) ->executeOne(); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); if (!$file) { $xform->delete(); } else { - $engine = new PhabricatorDestructionEngine(); $engine->destroyObject($file); } unset($unguarded); } } diff --git a/src/applications/files/storage/PhabricatorFileChunk.php b/src/applications/files/storage/PhabricatorFileChunk.php index b69a421fed..eaa10acd63 100644 --- a/src/applications/files/storage/PhabricatorFileChunk.php +++ b/src/applications/files/storage/PhabricatorFileChunk.php @@ -1,105 +1,105 @@ <?php final class PhabricatorFileChunk extends PhabricatorFileDAO implements PhabricatorPolicyInterface, PhabricatorDestructibleInterface { protected $chunkHandle; protected $byteStart; protected $byteEnd; protected $dataFilePHID; private $dataFile = self::ATTACHABLE; protected function getConfiguration() { return array( self::CONFIG_TIMESTAMPS => false, self::CONFIG_COLUMN_SCHEMA => array( 'chunkHandle' => 'bytes12', 'byteStart' => 'uint64', 'byteEnd' => 'uint64', 'dataFilePHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_file' => array( 'columns' => array('chunkHandle', 'byteStart', 'byteEnd'), ), 'key_data' => array( 'columns' => array('dataFilePHID'), ), ), ) + parent::getConfiguration(); } public static function newChunkHandle() { $seed = Filesystem::readRandomBytes(64); return PhabricatorHash::digestForIndex($seed); } public static function initializeNewChunk($handle, $start, $end) { return id(new PhabricatorFileChunk()) ->setChunkHandle($handle) ->setByteStart($start) ->setByteEnd($end); } public function attachDataFile(PhabricatorFile $file = null) { $this->dataFile = $file; return $this; } public function getDataFile() { return $this->assertAttached($this->dataFile); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { // These objects are low-level and only accessed through the storage // engine, so policies are mostly just in place to let us use the common // query infrastructure. return PhabricatorPolicies::getMostOpenPolicy(); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } public function describeAutomaticCapability($capability) { return null; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $data_phid = $this->getDataFilePHID(); if ($data_phid) { $data_file = id(new PhabricatorFileQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($engine->getViewer()) ->withPHIDs(array($data_phid)) ->executeOne(); if ($data_file) { $engine->destroyObject($data_file); } } $this->delete(); } } diff --git a/src/applications/paste/storage/PhabricatorPaste.php b/src/applications/paste/storage/PhabricatorPaste.php index 9eb88a77b4..3a0ae754e7 100644 --- a/src/applications/paste/storage/PhabricatorPaste.php +++ b/src/applications/paste/storage/PhabricatorPaste.php @@ -1,209 +1,209 @@ <?php final class PhabricatorPaste extends PhabricatorPasteDAO implements PhabricatorSubscribableInterface, PhabricatorTokenReceiverInterface, PhabricatorFlaggableInterface, PhabricatorMentionableInterface, PhabricatorPolicyInterface, PhabricatorProjectInterface, PhabricatorDestructibleInterface, PhabricatorApplicationTransactionInterface { protected $title; protected $authorPHID; protected $filePHID; protected $language; protected $parentPHID; protected $viewPolicy; protected $editPolicy; protected $mailKey; private $content = self::ATTACHABLE; private $rawContent = self::ATTACHABLE; public static function initializeNewPaste(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) ->withClasses(array('PhabricatorPasteApplication')) ->executeOne(); $view_policy = $app->getPolicy(PasteDefaultViewCapability::CAPABILITY); $edit_policy = $app->getPolicy(PasteDefaultEditCapability::CAPABILITY); return id(new PhabricatorPaste()) ->setTitle('') ->setAuthorPHID($actor->getPHID()) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy); } public function getURI() { return '/P'.$this->getID(); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'title' => 'text255', 'language' => 'text64', 'mailKey' => 'bytes20', 'parentPHID' => 'phid?', // T6203/NULLABILITY // Pastes should always have a view policy. 'viewPolicy' => 'policy?', ), self::CONFIG_KEY_SCHEMA => array( 'parentPHID' => array( 'columns' => array('parentPHID'), ), 'authorPHID' => array( 'columns' => array('authorPHID'), ), 'key_dateCreated' => array( 'columns' => array('dateCreated'), ), 'key_language' => array( 'columns' => array('language'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPastePastePHIDType::TYPECONST); } public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } public function getFullName() { $title = $this->getTitle(); if (!$title) { $title = pht('(An Untitled Masterwork)'); } return 'P'.$this->getID().' '.$title; } public function getContent() { return $this->assertAttached($this->content); } public function attachContent($content) { $this->content = $content; return $this; } public function getRawContent() { return $this->assertAttached($this->rawContent); } public function attachRawContent($raw_content) { $this->rawContent = $raw_content; return $this; } /* -( PhabricatorSubscribableInterface )----------------------------------- */ 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(), ); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { return $this->viewPolicy; } else if ($capability == PhabricatorPolicyCapability::CAN_EDIT) { return $this->editPolicy; } return PhabricatorPolicies::POLICY_NOONE; } public function hasAutomaticCapability($capability, PhabricatorUser $user) { return ($user->getPHID() == $this->getAuthorPHID()); } public function describeAutomaticCapability($capability) { return pht('The author of a paste can always view and edit it.'); } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { if ($this->filePHID) { $file = id(new PhabricatorFileQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($engine->getViewer()) ->withPHIDs(array($this->filePHID)) ->executeOne(); if ($file) { $engine->destroyObject($file); } } $this->delete(); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhabricatorPasteEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new PhabricatorPasteTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } } diff --git a/src/applications/system/engine/PhabricatorDestructionEngine.php b/src/applications/system/engine/PhabricatorDestructionEngine.php index b55b336052..c5b813169c 100644 --- a/src/applications/system/engine/PhabricatorDestructionEngine.php +++ b/src/applications/system/engine/PhabricatorDestructionEngine.php @@ -1,124 +1,128 @@ <?php final class PhabricatorDestructionEngine extends Phobject { private $rootLogID; + public function getViewer() { + return PhabricatorUser::getOmnipotentUser(); + } + public function destroyObject(PhabricatorDestructibleInterface $object) { $log = id(new PhabricatorSystemDestructionLog()) ->setEpoch(time()) ->setObjectClass(get_class($object)); if ($this->rootLogID) { $log->setRootLogID($this->rootLogID); } $object_phid = null; if (method_exists($object, 'getPHID')) { try { $object_phid = $object->getPHID(); $log->setObjectPHID($object_phid); } catch (Exception $ex) { // Ignore. } } if (method_exists($object, 'getMonogram')) { try { $log->setObjectMonogram($object->getMonogram()); } catch (Exception $ex) { // Ignore. } } $log->save(); if (!$this->rootLogID) { $this->rootLogID = $log->getID(); } $object->destroyObjectPermanently($this); if ($object_phid) { $this->destroyEdges($object_phid); if ($object instanceof PhabricatorApplicationTransactionInterface) { $template = $object->getApplicationTransactionTemplate(); $this->destroyTransactions($template, $object_phid); } $this->destroyWorkerTasks($object_phid); $this->destroyNotifications($object_phid); } // Nuke any Herald transcripts of the object, because they may contain // field data. // TODO: Define an interface so we don't have to do this for transactions // and other objects with no Herald adapters? $transcripts = id(new HeraldTranscript())->loadAllWhere( 'objectPHID = %s', $object_phid); foreach ($transcripts as $transcript) { $transcript->destroyObjectPermanently($this); } // TODO: Remove stuff from search indexes? // TODO: PhabricatorFlaggableInterface // TODO: PhabricatorTokenReceiverInterface } private function destroyEdges($src_phid) { try { $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($src_phid)) ->execute(); } catch (Exception $ex) { // This is (presumably) a "no edges for this PHID type" exception. return; } $editor = new PhabricatorEdgeEditor(); foreach ($edges as $type => $type_edges) { foreach ($type_edges as $src => $src_edges) { foreach ($src_edges as $dst => $edge) { $editor->removeEdge($edge['src'], $edge['type'], $edge['dst']); } } } $editor->save(); } private function destroyTransactions( PhabricatorApplicationTransaction $template, $object_phid) { $xactions = $template->loadAllWhere('objectPHID = %s', $object_phid); foreach ($xactions as $xaction) { $this->destroyObject($xaction); } } private function destroyWorkerTasks($object_phid) { $tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere( 'objectPHID = %s', $object_phid); foreach ($tasks as $task) { $task->archiveTask( PhabricatorWorkerArchiveTask::RESULT_CANCELLED, 0); } } private function destroyNotifications($object_phid) { $notifications = id(new PhabricatorFeedStoryNotification())->loadAllWhere( 'primaryObjectPHID = %s', $object_phid); foreach ($notifications as $notification) { $notification->delete(); } } }