Path: blob/master/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php
12256 views
<?php12abstract class DifferentialRevisionReviewTransaction3extends DifferentialRevisionActionTransaction {45protected function getRevisionActionGroupKey() {6return DifferentialRevisionEditEngine::ACTIONGROUP_REVIEW;7}89public function generateNewValue($object, $value) {10if (!is_array($value)) {11return true;12}1314// If the list of options is the same as the default list, just treat this15// as a "take the default action" transaction.16$viewer = $this->getActor();17list($options, $default) = $this->getActionOptions($viewer, $object);1819// Remove reviewers which aren't actionable. In the case of "Accept", we20// may allow the transaction to proceed with some reviewers who have21// already accepted, to avoid race conditions where two reviewers fill22// out the form at the same time and accept on behalf of the same package.23// It's okay for these reviewers to survive validation, but they should24// not survive beyond this point.25$value = array_fuse($value);26$value = array_intersect($value, array_keys($options));27$value = array_values($value);2829sort($default);30sort($value);3132if ($default === $value) {33return true;34}3536return $value;37}3839protected function isViewerAnyReviewer(40DifferentialRevision $revision,41PhabricatorUser $viewer) {42return ($this->getViewerReviewerStatus($revision, $viewer) !== null);43}4445protected function isViewerAnyAuthority(46DifferentialRevision $revision,47PhabricatorUser $viewer) {4849$reviewers = $revision->getReviewers();50foreach ($revision->getReviewers() as $reviewer) {51if ($reviewer->hasAuthority($viewer)) {52return true;53}54}5556return false;57}5859protected function isViewerFullyAccepted(60DifferentialRevision $revision,61PhabricatorUser $viewer) {62return $this->isViewerReviewerStatusFully(63$revision,64$viewer,65DifferentialReviewerStatus::STATUS_ACCEPTED);66}6768protected function isViewerFullyRejected(69DifferentialRevision $revision,70PhabricatorUser $viewer) {71return $this->isViewerReviewerStatusFully(72$revision,73$viewer,74DifferentialReviewerStatus::STATUS_REJECTED);75}7677protected function getViewerReviewerStatus(78DifferentialRevision $revision,79PhabricatorUser $viewer) {8081if (!$viewer->getPHID()) {82return null;83}8485foreach ($revision->getReviewers() as $reviewer) {86if ($reviewer->getReviewerPHID() != $viewer->getPHID()) {87continue;88}8990return $reviewer->getReviewerStatus();91}9293return null;94}9596private function isViewerReviewerStatusFully(97DifferentialRevision $revision,98PhabricatorUser $viewer,99$require_status) {100101// If the user themselves is not a reviewer, the reviews they have102// authority over can not all be in any set of states since their own103// personal review has no state.104$status = $this->getViewerReviewerStatus($revision, $viewer);105if ($status === null) {106return false;107}108109$active_phid = $this->getActiveDiffPHID($revision);110111$status_accepted = DifferentialReviewerStatus::STATUS_ACCEPTED;112$status_rejected = DifferentialReviewerStatus::STATUS_REJECTED;113114$is_accepted = ($require_status == $status_accepted);115$is_rejected = ($require_status == $status_rejected);116117// Otherwise, check that all reviews they have authority over are in118// the desired set of states.119foreach ($revision->getReviewers() as $reviewer) {120if (!$reviewer->hasAuthority($viewer)) {121$can_force = false;122123if ($is_accepted) {124if ($revision->canReviewerForceAccept($viewer, $reviewer)) {125$can_force = true;126}127}128129if (!$can_force) {130continue;131}132}133134$status = $reviewer->getReviewerStatus();135if ($status != $require_status) {136return false;137}138139// Here, we're primarily testing if we can remove a void on the review.140if ($is_accepted) {141if (!$reviewer->isAccepted($active_phid)) {142return false;143}144}145146if ($is_rejected) {147if (!$reviewer->isRejected($active_phid)) {148return false;149}150}151152// This is a broader check to see if we can update the diff where the153// last action occurred.154if ($reviewer->getLastActionDiffPHID() != $active_phid) {155return false;156}157}158159return true;160}161162protected function applyReviewerEffect(163DifferentialRevision $revision,164PhabricatorUser $viewer,165$value,166$status,167array $reviewer_options = array()) {168169PhutilTypeSpec::checkMap(170$reviewer_options,171array(172'sticky' => 'optional bool',173));174175$map = array();176177// When you accept or reject, you may accept or reject on behalf of all178// reviewers you have authority for. When you resign, you only affect179// yourself.180$with_authority = ($status != DifferentialReviewerStatus::STATUS_RESIGNED);181$with_force = ($status == DifferentialReviewerStatus::STATUS_ACCEPTED);182183if ($with_authority) {184foreach ($revision->getReviewers() as $reviewer) {185if (!$reviewer->hasAuthority($viewer)) {186if (!$with_force) {187continue;188}189190if (!$revision->canReviewerForceAccept($viewer, $reviewer)) {191continue;192}193}194195$map[$reviewer->getReviewerPHID()] = $status;196}197}198199// In all cases, you affect yourself.200$map[$viewer->getPHID()] = $status;201202// If we're applying an "accept the defaults" transaction, and this203// transaction type uses checkboxes, replace the value with the list of204// defaults.205if (!is_array($value)) {206list($options, $default) = $this->getActionOptions($viewer, $revision);207if ($options) {208$value = $default;209}210}211212// If we have a specific list of reviewers to act on, usually because the213// user has submitted a specific list of reviewers to act as by214// unchecking some checkboxes under "Accept", only affect those reviewers.215if (is_array($value)) {216$map = array_select_keys($map, $value);217}218219// Now, do the new write.220221if ($map) {222$diff = $this->getEditor()->getActiveDiff($revision);223if ($diff) {224$diff_phid = $diff->getPHID();225} else {226$diff_phid = null;227}228229$table = new DifferentialReviewer();230$src_phid = $revision->getPHID();231232$reviewers = $table->loadAllWhere(233'revisionPHID = %s AND reviewerPHID IN (%Ls)',234$src_phid,235array_keys($map));236$reviewers = mpull($reviewers, null, 'getReviewerPHID');237238foreach (array_keys($map) as $dst_phid) {239$reviewer = idx($reviewers, $dst_phid);240if (!$reviewer) {241$reviewer = id(new DifferentialReviewer())242->setRevisionPHID($src_phid)243->setReviewerPHID($dst_phid);244}245246$old_status = $reviewer->getReviewerStatus();247$reviewer->setReviewerStatus($status);248249if ($diff_phid) {250$reviewer->setLastActionDiffPHID($diff_phid);251}252253if ($old_status !== $status) {254$reviewer->setLastActorPHID($this->getActingAsPHID());255}256257// Clear any outstanding void on this reviewer. A void may be placed258// by the author using "Request Review" when a reviewer has already259// accepted.260$reviewer->setVoidedPHID(null);261262$reviewer->setOption('sticky', idx($reviewer_options, 'sticky'));263264try {265$reviewer->save();266} catch (AphrontDuplicateKeyQueryException $ex) {267// At least for now, just ignore it if we lost a race.268}269}270}271272}273274}275276277