Path: blob/master/src/applications/differential/storage/DifferentialRevision.php
12256 views
<?php12final class DifferentialRevision extends DifferentialDAO3implements4PhabricatorTokenReceiverInterface,5PhabricatorPolicyInterface,6PhabricatorExtendedPolicyInterface,7PhabricatorFlaggableInterface,8PhrequentTrackableInterface,9HarbormasterBuildableInterface,10PhabricatorSubscribableInterface,11PhabricatorCustomFieldInterface,12PhabricatorApplicationTransactionInterface,13PhabricatorTimelineInterface,14PhabricatorMentionableInterface,15PhabricatorDestructibleInterface,16PhabricatorProjectInterface,17PhabricatorFulltextInterface,18PhabricatorFerretInterface,19PhabricatorConduitResultInterface,20PhabricatorDraftInterface {2122protected $title = '';23protected $status;2425protected $summary = '';26protected $testPlan = '';2728protected $authorPHID;29protected $lastReviewerPHID;3031protected $lineCount = 0;32protected $attached = array();3334protected $mailKey;35protected $branchName;36protected $repositoryPHID;37protected $activeDiffPHID;3839protected $viewPolicy = PhabricatorPolicies::POLICY_USER;40protected $editPolicy = PhabricatorPolicies::POLICY_USER;41protected $properties = array();4243private $commitPHIDs = self::ATTACHABLE;44private $activeDiff = self::ATTACHABLE;45private $diffIDs = self::ATTACHABLE;46private $hashes = self::ATTACHABLE;47private $repository = self::ATTACHABLE;4849private $reviewerStatus = self::ATTACHABLE;50private $customFields = self::ATTACHABLE;51private $drafts = array();52private $flags = array();53private $forceMap = array();5455const RELATION_REVIEWER = 'revw';56const RELATION_SUBSCRIBED = 'subd';5758const PROPERTY_CLOSED_FROM_ACCEPTED = 'wasAcceptedBeforeClose';59const PROPERTY_DRAFT_HOLD = 'draft.hold';60const PROPERTY_SHOULD_BROADCAST = 'draft.broadcast';61const PROPERTY_LINES_ADDED = 'lines.added';62const PROPERTY_LINES_REMOVED = 'lines.removed';63const PROPERTY_BUILDABLES = 'buildables';64const PROPERTY_WRONG_BUILDS = 'wrong.builds';6566public static function initializeNewRevision(PhabricatorUser $actor) {67$app = id(new PhabricatorApplicationQuery())68->setViewer($actor)69->withClasses(array('PhabricatorDifferentialApplication'))70->executeOne();7172$view_policy = $app->getPolicy(73DifferentialDefaultViewCapability::CAPABILITY);7475$initial_state = DifferentialRevisionStatus::DRAFT;76$should_broadcast = false;7778return id(new DifferentialRevision())79->setViewPolicy($view_policy)80->setAuthorPHID($actor->getPHID())81->attachRepository(null)82->attachActiveDiff(null)83->attachReviewers(array())84->setModernRevisionStatus($initial_state)85->setShouldBroadcast($should_broadcast);86}8788protected function getConfiguration() {89return array(90self::CONFIG_AUX_PHID => true,91self::CONFIG_SERIALIZATION => array(92'attached' => self::SERIALIZATION_JSON,93'unsubscribed' => self::SERIALIZATION_JSON,94'properties' => self::SERIALIZATION_JSON,95),96self::CONFIG_COLUMN_SCHEMA => array(97'title' => 'text255',98'status' => 'text32',99'summary' => 'text',100'testPlan' => 'text',101'authorPHID' => 'phid?',102'lastReviewerPHID' => 'phid?',103'lineCount' => 'uint32?',104'mailKey' => 'bytes40',105'branchName' => 'text255?',106'repositoryPHID' => 'phid?',107),108self::CONFIG_KEY_SCHEMA => array(109'authorPHID' => array(110'columns' => array('authorPHID', 'status'),111),112'repositoryPHID' => array(113'columns' => array('repositoryPHID'),114),115// If you (or a project you are a member of) is reviewing a significant116// fraction of the revisions on an install, the result set of open117// revisions may be smaller than the result set of revisions where you118// are a reviewer. In these cases, this key is better than keys on the119// edge table.120'key_status' => array(121'columns' => array('status', 'phid'),122),123'key_modified' => array(124'columns' => array('dateModified'),125),126),127) + parent::getConfiguration();128}129130public function setProperty($key, $value) {131$this->properties[$key] = $value;132return $this;133}134135public function getProperty($key, $default = null) {136return idx($this->properties, $key, $default);137}138139public function hasRevisionProperty($key) {140return array_key_exists($key, $this->properties);141}142143public function getMonogram() {144$id = $this->getID();145return "D{$id}";146}147148public function getURI() {149return '/'.$this->getMonogram();150}151152public function getCommitPHIDs() {153return $this->assertAttached($this->commitPHIDs);154}155156public function getActiveDiff() {157// TODO: Because it's currently technically possible to create a revision158// without an associated diff, we allow an attached-but-null active diff.159// It would be good to get rid of this once we make diff-attaching160// transactional.161162return $this->assertAttached($this->activeDiff);163}164165public function attachActiveDiff($diff) {166$this->activeDiff = $diff;167return $this;168}169170public function getDiffIDs() {171return $this->assertAttached($this->diffIDs);172}173174public function attachDiffIDs(array $ids) {175rsort($ids);176$this->diffIDs = array_values($ids);177return $this;178}179180public function attachCommitPHIDs(array $phids) {181$this->commitPHIDs = $phids;182return $this;183}184185public function getAttachedPHIDs($type) {186return array_keys(idx($this->attached, $type, array()));187}188189public function setAttachedPHIDs($type, array $phids) {190$this->attached[$type] = array_fill_keys($phids, array());191return $this;192}193194public function generatePHID() {195return PhabricatorPHID::generateNewPHID(196DifferentialRevisionPHIDType::TYPECONST);197}198199public function loadActiveDiff() {200return id(new DifferentialDiff())->loadOneWhere(201'revisionID = %d ORDER BY id DESC LIMIT 1',202$this->getID());203}204205public function save() {206if (!$this->getMailKey()) {207$this->mailKey = Filesystem::readRandomCharacters(40);208}209return parent::save();210}211212public function getHashes() {213return $this->assertAttached($this->hashes);214}215216public function attachHashes(array $hashes) {217$this->hashes = $hashes;218return $this;219}220221public function canReviewerForceAccept(222PhabricatorUser $viewer,223DifferentialReviewer $reviewer) {224225if (!$reviewer->isPackage()) {226return false;227}228229$map = $this->getReviewerForceAcceptMap($viewer);230if (!$map) {231return false;232}233234if (isset($map[$reviewer->getReviewerPHID()])) {235return true;236}237238return false;239}240241private function getReviewerForceAcceptMap(PhabricatorUser $viewer) {242$fragment = $viewer->getCacheFragment();243244if (!array_key_exists($fragment, $this->forceMap)) {245$map = $this->newReviewerForceAcceptMap($viewer);246$this->forceMap[$fragment] = $map;247}248249return $this->forceMap[$fragment];250}251252private function newReviewerForceAcceptMap(PhabricatorUser $viewer) {253$diff = $this->getActiveDiff();254if (!$diff) {255return null;256}257258$repository_phid = $diff->getRepositoryPHID();259if (!$repository_phid) {260return null;261}262263$paths = array();264265try {266$changesets = $diff->getChangesets();267} catch (Exception $ex) {268$changesets = id(new DifferentialChangesetQuery())269->setViewer($viewer)270->withDiffs(array($diff))271->execute();272}273274foreach ($changesets as $changeset) {275$paths[] = $changeset->getOwnersFilename();276}277278if (!$paths) {279return null;280}281282$reviewer_phids = array();283foreach ($this->getReviewers() as $reviewer) {284if (!$reviewer->isPackage()) {285continue;286}287288$reviewer_phids[] = $reviewer->getReviewerPHID();289}290291if (!$reviewer_phids) {292return null;293}294295// Load all the reviewing packages which have control over some of the296// paths in the change. These are packages which the actor may be able297// to force-accept on behalf of.298$control_query = id(new PhabricatorOwnersPackageQuery())299->setViewer($viewer)300->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE))301->withPHIDs($reviewer_phids)302->withControl($repository_phid, $paths);303$control_packages = $control_query->execute();304if (!$control_packages) {305return null;306}307308// Load all the packages which have potential control over some of the309// paths in the change and are owned by the actor. These are packages310// which the actor may be able to use their authority over to gain the311// ability to force-accept for other packages. This query doesn't apply312// dominion rules yet, and we'll bypass those rules later on.313314// See T13657. We ignore "watcher" packages which don't grant their owners315// permission to force accept anything.316317$authority_query = id(new PhabricatorOwnersPackageQuery())318->setViewer($viewer)319->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE))320->withAuthorityModes(321array(322PhabricatorOwnersPackage::AUTHORITY_STRONG,323))324->withAuthorityPHIDs(array($viewer->getPHID()))325->withControl($repository_phid, $paths);326$authority_packages = $authority_query->execute();327if (!$authority_packages) {328return null;329}330$authority_packages = mpull($authority_packages, null, 'getPHID');331332// Build a map from each path in the revision to the reviewer packages333// which control it.334$control_map = array();335foreach ($paths as $path) {336$control_packages = $control_query->getControllingPackagesForPath(337$repository_phid,338$path);339340// Remove packages which the viewer has authority over. We don't need341// to check these for force-accept because they can just accept them342// normally.343$control_packages = mpull($control_packages, null, 'getPHID');344foreach ($control_packages as $phid => $control_package) {345if (isset($authority_packages[$phid])) {346unset($control_packages[$phid]);347}348}349350if (!$control_packages) {351continue;352}353354$control_map[$path] = $control_packages;355}356357if (!$control_map) {358return null;359}360361// From here on out, we only care about paths which we have at least one362// controlling package for.363$paths = array_keys($control_map);364365// Now, build a map from each path to the packages which would control it366// if there were no dominion rules.367$authority_map = array();368foreach ($paths as $path) {369$authority_packages = $authority_query->getControllingPackagesForPath(370$repository_phid,371$path,372$ignore_dominion = true);373374$authority_map[$path] = mpull($authority_packages, null, 'getPHID');375}376377// For each path, find the most general package that the viewer has378// authority over. For example, we'll prefer a package that owns "/" to a379// package that owns "/src/".380$force_map = array();381foreach ($authority_map as $path => $package_map) {382$path_fragments = PhabricatorOwnersPackage::splitPath($path);383$fragment_count = count($path_fragments);384385// Find the package that we have authority over which has the most386// general match for this path.387$best_match = null;388$best_package = null;389foreach ($package_map as $package_phid => $package) {390$package_paths = $package->getPathsForRepository($repository_phid);391foreach ($package_paths as $package_path) {392393// NOTE: A strength of 0 means "no match". A strength of 1 means394// that we matched "/", so we can not possibly find another stronger395// match.396397$strength = $package_path->getPathMatchStrength(398$path_fragments,399$fragment_count);400if (!$strength) {401continue;402}403404if ($strength < $best_match || !$best_package) {405$best_match = $strength;406$best_package = $package;407if ($strength == 1) {408break 2;409}410}411}412}413414if ($best_package) {415$force_map[$path] = array(416'strength' => $best_match,417'package' => $best_package,418);419}420}421422// For each path which the viewer owns a package for, find other packages423// which that authority can be used to force-accept. Once we find a way to424// force-accept a package, we don't need to keep looking.425$has_control = array();426foreach ($force_map as $path => $spec) {427$path_fragments = PhabricatorOwnersPackage::splitPath($path);428$fragment_count = count($path_fragments);429430$authority_strength = $spec['strength'];431432$control_packages = $control_map[$path];433foreach ($control_packages as $control_phid => $control_package) {434if (isset($has_control[$control_phid])) {435continue;436}437438$control_paths = $control_package->getPathsForRepository(439$repository_phid);440foreach ($control_paths as $control_path) {441$strength = $control_path->getPathMatchStrength(442$path_fragments,443$fragment_count);444445if (!$strength) {446continue;447}448449if ($strength > $authority_strength) {450$authority = $spec['package'];451$has_control[$control_phid] = array(452'authority' => $authority,453'phid' => $authority->getPHID(),454);455break;456}457}458}459}460461// Return a map from packages which may be force accepted to the packages462// which permit that forced acceptance.463return ipull($has_control, 'phid');464}465466467/* -( PhabricatorPolicyInterface )----------------------------------------- */468469470public function getCapabilities() {471return array(472PhabricatorPolicyCapability::CAN_VIEW,473PhabricatorPolicyCapability::CAN_EDIT,474);475}476477public function getPolicy($capability) {478switch ($capability) {479case PhabricatorPolicyCapability::CAN_VIEW:480return $this->getViewPolicy();481case PhabricatorPolicyCapability::CAN_EDIT:482return $this->getEditPolicy();483}484}485486public function hasAutomaticCapability($capability, PhabricatorUser $user) {487// A revision's author (which effectively means "owner" after we added488// commandeering) can always view and edit it.489$author_phid = $this->getAuthorPHID();490if ($author_phid) {491if ($user->getPHID() == $author_phid) {492return true;493}494}495496return false;497}498499public function describeAutomaticCapability($capability) {500$description = array(501pht('The owner of a revision can always view and edit it.'),502);503504switch ($capability) {505case PhabricatorPolicyCapability::CAN_VIEW:506$description[] = pht(507'If a revision belongs to a repository, other users must be able '.508'to view the repository in order to view the revision.');509break;510}511512return $description;513}514515516/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */517518519public function getExtendedPolicy($capability, PhabricatorUser $viewer) {520$extended = array();521522switch ($capability) {523case PhabricatorPolicyCapability::CAN_VIEW:524$repository_phid = $this->getRepositoryPHID();525$repository = $this->getRepository();526527// Try to use the object if we have it, since it will save us some528// data fetching later on. In some cases, we might not have it.529$repository_ref = nonempty($repository, $repository_phid);530if ($repository_ref) {531$extended[] = array(532$repository_ref,533PhabricatorPolicyCapability::CAN_VIEW,534);535}536break;537}538539return $extended;540}541542543/* -( PhabricatorTokenReceiverInterface )---------------------------------- */544545546public function getUsersToNotifyOfTokenGiven() {547return array(548$this->getAuthorPHID(),549);550}551552public function getReviewers() {553return $this->assertAttached($this->reviewerStatus);554}555556public function attachReviewers(array $reviewers) {557assert_instances_of($reviewers, 'DifferentialReviewer');558$reviewers = mpull($reviewers, null, 'getReviewerPHID');559$this->reviewerStatus = $reviewers;560return $this;561}562563public function hasAttachedReviewers() {564return ($this->reviewerStatus !== self::ATTACHABLE);565}566567public function getReviewerPHIDs() {568$reviewers = $this->getReviewers();569return mpull($reviewers, 'getReviewerPHID');570}571572public function getReviewerPHIDsForEdit() {573$reviewers = $this->getReviewers();574575$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;576577$value = array();578foreach ($reviewers as $reviewer) {579$phid = $reviewer->getReviewerPHID();580if ($reviewer->getReviewerStatus() == $status_blocking) {581$value[] = 'blocking('.$phid.')';582} else {583$value[] = $phid;584}585}586587return $value;588}589590public function getRepository() {591return $this->assertAttached($this->repository);592}593594public function attachRepository(PhabricatorRepository $repository = null) {595$this->repository = $repository;596return $this;597}598599public function setModernRevisionStatus($status) {600return $this->setStatus($status);601}602603public function getModernRevisionStatus() {604return $this->getStatus();605}606607public function getLegacyRevisionStatus() {608return $this->getStatusObject()->getLegacyKey();609}610611public function isClosed() {612return $this->getStatusObject()->isClosedStatus();613}614615public function isAbandoned() {616return $this->getStatusObject()->isAbandoned();617}618619public function isAccepted() {620return $this->getStatusObject()->isAccepted();621}622623public function isNeedsReview() {624return $this->getStatusObject()->isNeedsReview();625}626627public function isNeedsRevision() {628return $this->getStatusObject()->isNeedsRevision();629}630631public function isChangePlanned() {632return $this->getStatusObject()->isChangePlanned();633}634635public function isPublished() {636return $this->getStatusObject()->isPublished();637}638639public function isDraft() {640return $this->getStatusObject()->isDraft();641}642643public function getStatusIcon() {644return $this->getStatusObject()->getIcon();645}646647public function getStatusDisplayName() {648return $this->getStatusObject()->getDisplayName();649}650651public function getStatusIconColor() {652return $this->getStatusObject()->getIconColor();653}654655public function getStatusTagColor() {656return $this->getStatusObject()->getTagColor();657}658659public function getStatusObject() {660$status = $this->getStatus();661return DifferentialRevisionStatus::newForStatus($status);662}663664public function getFlag(PhabricatorUser $viewer) {665return $this->assertAttachedKey($this->flags, $viewer->getPHID());666}667668public function attachFlag(669PhabricatorUser $viewer,670PhabricatorFlag $flag = null) {671$this->flags[$viewer->getPHID()] = $flag;672return $this;673}674675public function getHasDraft(PhabricatorUser $viewer) {676return $this->assertAttachedKey($this->drafts, $viewer->getCacheFragment());677}678679public function attachHasDraft(PhabricatorUser $viewer, $has_draft) {680$this->drafts[$viewer->getCacheFragment()] = $has_draft;681return $this;682}683684public function getHoldAsDraft() {685return $this->getProperty(self::PROPERTY_DRAFT_HOLD, false);686}687688public function setHoldAsDraft($hold) {689return $this->setProperty(self::PROPERTY_DRAFT_HOLD, $hold);690}691692public function getShouldBroadcast() {693return $this->getProperty(self::PROPERTY_SHOULD_BROADCAST, true);694}695696public function setShouldBroadcast($should_broadcast) {697return $this->setProperty(698self::PROPERTY_SHOULD_BROADCAST,699$should_broadcast);700}701702public function setAddedLineCount($count) {703return $this->setProperty(self::PROPERTY_LINES_ADDED, $count);704}705706public function getAddedLineCount() {707return $this->getProperty(self::PROPERTY_LINES_ADDED);708}709710public function setRemovedLineCount($count) {711return $this->setProperty(self::PROPERTY_LINES_REMOVED, $count);712}713714public function getRemovedLineCount() {715return $this->getProperty(self::PROPERTY_LINES_REMOVED);716}717718public function hasLineCounts() {719// This data was not populated on older revisions, so it may not be720// present on all revisions.721return isset($this->properties[self::PROPERTY_LINES_ADDED]);722}723724public function getRevisionScaleGlyphs() {725$add = $this->getAddedLineCount();726$rem = $this->getRemovedLineCount();727$all = ($add + $rem);728729if (!$all) {730return ' ';731}732733$map = array(73420 => 2,73550 => 3,736150 => 4,737375 => 5,7381000 => 6,7392500 => 7,740);741742$n = 1;743foreach ($map as $size => $count) {744if ($size <= $all) {745$n = $count;746} else {747break;748}749}750751$add_n = (int)ceil(($add / $all) * $n);752$rem_n = (int)ceil(($rem / $all) * $n);753754while ($add_n + $rem_n > $n) {755if ($add_n > 1) {756$add_n--;757} else {758$rem_n--;759}760}761762return763str_repeat('+', $add_n).764str_repeat('-', $rem_n).765str_repeat(' ', (7 - $n));766}767768public function getBuildableStatus($phid) {769$buildables = $this->getProperty(self::PROPERTY_BUILDABLES);770if (!is_array($buildables)) {771$buildables = array();772}773774$buildable = idx($buildables, $phid);775if (!is_array($buildable)) {776$buildable = array();777}778779return idx($buildable, 'status');780}781782public function setBuildableStatus($phid, $status) {783$buildables = $this->getProperty(self::PROPERTY_BUILDABLES);784if (!is_array($buildables)) {785$buildables = array();786}787788$buildable = idx($buildables, $phid);789if (!is_array($buildable)) {790$buildable = array();791}792793$buildable['status'] = $status;794795$buildables[$phid] = $buildable;796797return $this->setProperty(self::PROPERTY_BUILDABLES, $buildables);798}799800public function newBuildableStatus(PhabricatorUser $viewer, $phid) {801// For Differential, we're ignoring autobuilds (local lint and unit)802// when computing build status. Differential only cares about remote803// builds when making publishing and undrafting decisions.804805$builds = $this->loadImpactfulBuildsForBuildablePHIDs(806$viewer,807array($phid));808809return $this->newBuildableStatusForBuilds($builds);810}811812public function newBuildableStatusForBuilds(array $builds) {813// If we have nothing but passing builds, the buildable passes.814if (!$builds) {815return HarbormasterBuildableStatus::STATUS_PASSED;816}817818// If we have any completed, non-passing builds, the buildable fails.819foreach ($builds as $build) {820if ($build->isComplete()) {821return HarbormasterBuildableStatus::STATUS_FAILED;822}823}824825// Otherwise, we're still waiting for the build to pass or fail.826return null;827}828829public function loadImpactfulBuilds(PhabricatorUser $viewer) {830$diff = $this->getActiveDiff();831832// NOTE: We can't use `withContainerPHIDs()` here because the container833// update in Harbormaster is not synchronous.834$buildables = id(new HarbormasterBuildableQuery())835->setViewer($viewer)836->withBuildablePHIDs(array($diff->getPHID()))837->withManualBuildables(false)838->execute();839if (!$buildables) {840return array();841}842843return $this->loadImpactfulBuildsForBuildablePHIDs(844$viewer,845mpull($buildables, 'getPHID'));846}847848private function loadImpactfulBuildsForBuildablePHIDs(849PhabricatorUser $viewer,850array $phids) {851852$builds = id(new HarbormasterBuildQuery())853->setViewer($viewer)854->withBuildablePHIDs($phids)855->withAutobuilds(false)856->withBuildStatuses(857array(858HarbormasterBuildStatus::STATUS_INACTIVE,859HarbormasterBuildStatus::STATUS_PENDING,860HarbormasterBuildStatus::STATUS_BUILDING,861HarbormasterBuildStatus::STATUS_FAILED,862HarbormasterBuildStatus::STATUS_ABORTED,863HarbormasterBuildStatus::STATUS_ERROR,864HarbormasterBuildStatus::STATUS_PAUSED,865HarbormasterBuildStatus::STATUS_DEADLOCKED,866))867->execute();868869// Filter builds based on the "Hold Drafts" behavior of their associated870// build plans.871872$hold_drafts = HarbormasterBuildPlanBehavior::BEHAVIOR_DRAFTS;873$behavior = HarbormasterBuildPlanBehavior::getBehavior($hold_drafts);874875$key_never = HarbormasterBuildPlanBehavior::DRAFTS_NEVER;876$key_building = HarbormasterBuildPlanBehavior::DRAFTS_IF_BUILDING;877878foreach ($builds as $key => $build) {879$plan = $build->getBuildPlan();880881// See T13526. If the viewer can't see the build plan, pretend it has882// generic options. This is often wrong, but "often wrong" is better than883// "fatal".884if ($plan) {885$hold_key = $behavior->getPlanOption($plan)->getKey();886887$hold_never = ($hold_key === $key_never);888$hold_building = ($hold_key === $key_building);889} else {890$hold_never = false;891$hold_building = false;892}893894// If the build "Never" holds drafts from promoting, we don't care what895// the status is.896if ($hold_never) {897unset($builds[$key]);898continue;899}900901// If the build holds drafts from promoting "While Building", we only902// care about the status until it completes.903if ($hold_building) {904if ($build->isComplete()) {905unset($builds[$key]);906continue;907}908}909}910911return $builds;912}913914915/* -( HarbormasterBuildableInterface )------------------------------------- */916917918public function getHarbormasterBuildableDisplayPHID() {919return $this->getHarbormasterContainerPHID();920}921922public function getHarbormasterBuildablePHID() {923return $this->loadActiveDiff()->getPHID();924}925926public function getHarbormasterContainerPHID() {927return $this->getPHID();928}929930public function getBuildVariables() {931return array();932}933934public function getAvailableBuildVariables() {935return array();936}937938public function newBuildableEngine() {939return new DifferentialBuildableEngine();940}941942943/* -( PhabricatorSubscribableInterface )----------------------------------- */944945946public function isAutomaticallySubscribed($phid) {947if ($phid == $this->getAuthorPHID()) {948return true;949}950951// TODO: This only happens when adding or removing CCs, and is safe from a952// policy perspective, but the subscription pathway should have some953// opportunity to load this data properly. For now, this is the only case954// where implicit subscription is not an intrinsic property of the object.955if ($this->reviewerStatus == self::ATTACHABLE) {956$reviewers = id(new DifferentialRevisionQuery())957->setViewer(PhabricatorUser::getOmnipotentUser())958->withPHIDs(array($this->getPHID()))959->needReviewers(true)960->executeOne()961->getReviewers();962} else {963$reviewers = $this->getReviewers();964}965966foreach ($reviewers as $reviewer) {967if ($reviewer->getReviewerPHID() !== $phid) {968continue;969}970971if ($reviewer->isResigned()) {972continue;973}974975return true;976}977978return false;979}980981982/* -( PhabricatorCustomFieldInterface )------------------------------------ */983984985public function getCustomFieldSpecificationForRole($role) {986return PhabricatorEnv::getEnvConfig('differential.fields');987}988989public function getCustomFieldBaseClass() {990return 'DifferentialCustomField';991}992993public function getCustomFields() {994return $this->assertAttached($this->customFields);995}996997public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {998$this->customFields = $fields;999return $this;1000}100110021003/* -( PhabricatorApplicationTransactionInterface )------------------------- */100410051006public function getApplicationTransactionEditor() {1007return new DifferentialTransactionEditor();1008}10091010public function getApplicationTransactionTemplate() {1011return new DifferentialTransaction();1012}101310141015/* -( PhabricatorDestructibleInterface )----------------------------------- */101610171018public function destroyObjectPermanently(1019PhabricatorDestructionEngine $engine) {10201021$viewer = $engine->getViewer();10221023$this->openTransaction();1024$diffs = id(new DifferentialDiffQuery())1025->setViewer($viewer)1026->withRevisionIDs(array($this->getID()))1027->execute();1028foreach ($diffs as $diff) {1029$engine->destroyObject($diff);1030}10311032id(new DifferentialAffectedPathEngine())1033->setRevision($this)1034->destroyAffectedPaths();10351036$viewstate_query = id(new DifferentialViewStateQuery())1037->setViewer($viewer)1038->withObjectPHIDs(array($this->getPHID()));1039$viewstates = new PhabricatorQueryIterator($viewstate_query);1040foreach ($viewstates as $viewstate) {1041$viewstate->delete();1042}10431044$this->delete();1045$this->saveTransaction();1046}104710481049/* -( PhabricatorFulltextInterface )--------------------------------------- */105010511052public function newFulltextEngine() {1053return new DifferentialRevisionFulltextEngine();1054}105510561057/* -( PhabricatorFerretInterface )----------------------------------------- */105810591060public function newFerretEngine() {1061return new DifferentialRevisionFerretEngine();1062}106310641065/* -( PhabricatorConduitResultInterface )---------------------------------- */106610671068public function getFieldSpecificationsForConduit() {1069return array(1070id(new PhabricatorConduitSearchFieldSpecification())1071->setKey('title')1072->setType('string')1073->setDescription(pht('The revision title.')),1074id(new PhabricatorConduitSearchFieldSpecification())1075->setKey('uri')1076->setType('uri')1077->setDescription(pht('View URI for the revision.')),1078id(new PhabricatorConduitSearchFieldSpecification())1079->setKey('authorPHID')1080->setType('phid')1081->setDescription(pht('Revision author PHID.')),1082id(new PhabricatorConduitSearchFieldSpecification())1083->setKey('status')1084->setType('map<string, wild>')1085->setDescription(pht('Information about revision status.')),1086id(new PhabricatorConduitSearchFieldSpecification())1087->setKey('repositoryPHID')1088->setType('phid?')1089->setDescription(pht('Revision repository PHID.')),1090id(new PhabricatorConduitSearchFieldSpecification())1091->setKey('diffPHID')1092->setType('phid')1093->setDescription(pht('Active diff PHID.')),1094id(new PhabricatorConduitSearchFieldSpecification())1095->setKey('summary')1096->setType('string')1097->setDescription(pht('Revision summary.')),1098id(new PhabricatorConduitSearchFieldSpecification())1099->setKey('testPlan')1100->setType('string')1101->setDescription(pht('Revision test plan.')),1102id(new PhabricatorConduitSearchFieldSpecification())1103->setKey('isDraft')1104->setType('bool')1105->setDescription(1106pht(1107'True if this revision is in any draft state, and thus not '.1108'notifying reviewers and subscribers about changes.')),1109id(new PhabricatorConduitSearchFieldSpecification())1110->setKey('holdAsDraft')1111->setType('bool')1112->setDescription(1113pht(1114'True if this revision is being held as a draft. It will not be '.1115'automatically submitted for review even if tests pass.')),1116);1117}11181119public function getFieldValuesForConduit() {1120$status = $this->getStatusObject();1121$status_info = array(1122'value' => $status->getKey(),1123'name' => $status->getDisplayName(),1124'closed' => $status->isClosedStatus(),1125'color.ansi' => $status->getANSIColor(),1126);11271128return array(1129'title' => $this->getTitle(),1130'uri' => PhabricatorEnv::getURI($this->getURI()),1131'authorPHID' => $this->getAuthorPHID(),1132'status' => $status_info,1133'repositoryPHID' => $this->getRepositoryPHID(),1134'diffPHID' => $this->getActiveDiffPHID(),1135'summary' => $this->getSummary(),1136'testPlan' => $this->getTestPlan(),1137'isDraft' => !$this->getShouldBroadcast(),1138'holdAsDraft' => (bool)$this->getHoldAsDraft(),1139);1140}11411142public function getConduitSearchAttachments() {1143return array(1144id(new DifferentialReviewersSearchEngineAttachment())1145->setAttachmentKey('reviewers'),1146);1147}114811491150/* -( PhabricatorDraftInterface )------------------------------------------ */115111521153public function newDraftEngine() {1154return new DifferentialRevisionDraftEngine();1155}115611571158/* -( PhabricatorTimelineInterface )--------------------------------------- */115911601161public function newTimelineEngine() {1162return new DifferentialRevisionTimelineEngine();1163}116411651166}116711681169