public function renderChangeDetails(PhabricatorUser $viewer)
 {
     switch ($this->getTransactionType()) {
         case self::TYPE_DESCRIPTION:
             $old = $this->getOldValue();
             $new = $this->getNewValue();
             return $this->renderTextCorpusChangeDetails($viewer, $old, $new);
         case self::TYPE_PATHS:
             $old = $this->getOldValue();
             $new = $this->getNewValue();
             $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new);
             list($rem, $add) = $diffs;
             $rows = array();
             foreach ($rem as $ref) {
                 $rows[] = array('class' => 'diff-removed', 'change' => '-') + $ref;
             }
             foreach ($add as $ref) {
                 $rows[] = array('class' => 'diff-added', 'change' => '+') + $ref;
             }
             $rowc = array();
             foreach ($rows as $key => $row) {
                 $rowc[] = $row['class'];
                 $rows[$key] = array($row['change'], $row['excluded'] ? pht('Exclude') : pht('Include'), $viewer->renderHandle($row['repositoryPHID']), $row['path']);
             }
             $table = id(new AphrontTableView($rows))->setRowClasses($rowc)->setHeaders(array(null, pht('Type'), pht('Repository'), pht('Path')))->setColumnClasses(array(null, null, null, 'wide'));
             return $table;
     }
     return parent::renderChangeDetails($viewer);
 }
 protected function validateTransaction(PhabricatorLiskDAO $object, $type, array $xactions)
 {
     $errors = parent::validateTransaction($object, $type, $xactions);
     switch ($type) {
         case PhabricatorOwnersPackageTransaction::TYPE_NAME:
             $missing = $this->validateIsEmptyTextField($object->getName(), $xactions);
             if ($missing) {
                 $error = new PhabricatorApplicationTransactionValidationError($type, pht('Required'), pht('Package name is required.'), nonempty(last($xactions), null));
                 $error->setIsMissingFieldError(true);
                 $errors[] = $error;
             }
             break;
         case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
             if (!$xactions) {
                 continue;
             }
             $old = mpull($object->getPaths(), 'getRef');
             foreach ($xactions as $xaction) {
                 $new = $xaction->getNewValue();
                 // Check that we have a list of paths.
                 if (!is_array($new)) {
                     $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Path specification must be a list of paths.'), $xaction);
                     continue;
                 }
                 // Check that each item in the list is formatted properly.
                 $type_exception = null;
                 foreach ($new as $key => $value) {
                     try {
                         PhutilTypeSpec::checkMap($value, array('repositoryPHID' => 'string', 'path' => 'string', 'excluded' => 'optional wild'));
                     } catch (PhutilTypeCheckException $ex) {
                         $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Path specification list contains invalid value ' . 'in key "%s": %s.', $key, $ex->getMessage()), $xaction);
                         $type_exception = $ex;
                     }
                 }
                 if ($type_exception) {
                     continue;
                 }
                 // Check that any new paths reference legitimate repositories which
                 // the viewer has permission to see.
                 list($rem, $add) = PhabricatorOwnersPath::getTransactionValueChanges($old, $new);
                 if ($add) {
                     $repository_phids = ipull($add, 'repositoryPHID');
                     $repositories = id(new PhabricatorRepositoryQuery())->setViewer($this->getActor())->withPHIDs($repository_phids)->execute();
                     $repositories = mpull($repositories, null, 'getPHID');
                     foreach ($add as $ref) {
                         $repository_phid = $ref['repositoryPHID'];
                         if (isset($repositories[$repository_phid])) {
                             continue;
                         }
                         $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Path specification list references repository PHID "%s", ' . 'but that is not a valid, visible repository.', $repository_phid));
                     }
                 }
             }
             break;
     }
     return $errors;
 }
 protected function applyCustomExternalTransaction(PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction)
 {
     switch ($xaction->getTransactionType()) {
         case PhabricatorOwnersPackageTransaction::TYPE_NAME:
         case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY:
         case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION:
         case PhabricatorOwnersPackageTransaction::TYPE_AUDITING:
             return;
         case PhabricatorOwnersPackageTransaction::TYPE_OWNERS:
             $old = $xaction->getOldValue();
             $new = $xaction->getNewValue();
             // TODO: needOwners this
             $owners = $object->loadOwners();
             $owners = mpull($owners, null, 'getUserPHID');
             $rem = array_diff($old, $new);
             foreach ($rem as $phid) {
                 if (isset($owners[$phid])) {
                     $owners[$phid]->delete();
                     unset($owners[$phid]);
                 }
             }
             $add = array_diff($new, $old);
             foreach ($add as $phid) {
                 $owners[$phid] = id(new PhabricatorOwnersOwner())->setPackageID($object->getID())->setUserPHID($phid)->save();
             }
             // TODO: Attach owners here
             return;
         case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
             $old = $xaction->getOldValue();
             $new = $xaction->getNewValue();
             // TODO: needPaths this
             $paths = $object->loadPaths();
             $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new);
             list($rem, $add) = $diffs;
             $set = PhabricatorOwnersPath::getSetFromTransactionValue($rem);
             foreach ($paths as $path) {
                 $ref = $path->getRef();
                 if (PhabricatorOwnersPath::isRefInSet($ref, $set)) {
                     $path->delete();
                 }
             }
             foreach ($add as $ref) {
                 $path = PhabricatorOwnersPath::newFromRef($ref)->setPackageID($object->getID())->save();
             }
             return;
     }
     return parent::applyCustomExternalTransaction($object, $xaction);
 }
 public function newChangeDetailView()
 {
     $old = $this->getOldValue();
     $new = $this->getNewValue();
     $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new);
     list($rem, $add) = $diffs;
     $rows = array();
     foreach ($rem as $ref) {
         $rows[] = array('class' => 'diff-removed', 'change' => '-') + $ref;
     }
     foreach ($add as $ref) {
         $rows[] = array('class' => 'diff-added', 'change' => '+') + $ref;
     }
     $rowc = array();
     foreach ($rows as $key => $row) {
         $rowc[] = $row['class'];
         $rows[$key] = array($row['change'], $row['excluded'] ? pht('Exclude') : pht('Include'), $this->renderHandle($row['repositoryPHID']), $row['path']);
     }
     $table = id(new AphrontTableView($rows))->setViewer($this->getViewer())->setRowClasses($rowc)->setHeaders(array(null, pht('Type'), pht('Repository'), pht('Path')))->setColumnClasses(array(null, null, null, 'wide'));
     return $table;
 }