protected function buildCustomEditFields($object)
    {
        $paths_help = pht(<<<EOTEXT
When updating the paths for a package, pass a list of dictionaries like
this as the `value` for the transaction:

```lang=json, name="Example Paths Value"
[
  {
    "repositoryPHID": "PHID-REPO-1234",
    "path": "/path/to/directory/",
    "excluded": false
  },
  {
    "repositoryPHID": "PHID-REPO-1234",
    "path": "/another/example/path/",
    "excluded": false
  }
]
```

This transaction will set the paths to the list you provide, overwriting any
previous paths.

Generally, you will call `owners.search` first to get a list of current paths
(which are provided in the same format), make changes, then update them by
applying a transaction of this type.
EOTEXT
);
        $autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
        $autoreview_map = ipull($autoreview_map, 'name');
        $dominion_map = PhabricatorOwnersPackage::getDominionOptionsMap();
        $dominion_map = ipull($dominion_map, 'name');
        return array(id(new PhabricatorTextEditField())->setKey('name')->setLabel(pht('Name'))->setDescription(pht('Name of the package.'))->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_NAME)->setIsRequired(true)->setValue($object->getName()), id(new PhabricatorDatasourceEditField())->setKey('owners')->setLabel(pht('Owners'))->setDescription(pht('Users and projects which own the package.'))->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_OWNERS)->setDatasource(new PhabricatorProjectOrUserDatasource())->setIsCopyable(true)->setValue($object->getOwnerPHIDs()), id(new PhabricatorSelectEditField())->setKey('dominion')->setLabel(pht('Dominion'))->setDescription(pht('Change package dominion rules.'))->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_DOMINION)->setIsCopyable(true)->setValue($object->getDominion())->setOptions($dominion_map), id(new PhabricatorSelectEditField())->setKey('autoReview')->setLabel(pht('Auto Review'))->setDescription(pht('Automatically trigger reviews for commits affecting files in ' . 'this package.'))->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW)->setIsCopyable(true)->setValue($object->getAutoReview())->setOptions($autoreview_map), id(new PhabricatorSelectEditField())->setKey('auditing')->setLabel(pht('Auditing'))->setDescription(pht('Automatically trigger audits for commits affecting files in ' . 'this package.'))->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_AUDITING)->setIsCopyable(true)->setValue($object->getAuditingEnabled())->setOptions(array('' => pht('Disabled'), '1' => pht('Enabled'))), id(new PhabricatorRemarkupEditField())->setKey('description')->setLabel(pht('Description'))->setDescription(pht('Human-readable description of the package.'))->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION)->setValue($object->getDescription()), id(new PhabricatorSelectEditField())->setKey('status')->setLabel(pht('Status'))->setDescription(pht('Archive or enable the package.'))->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_STATUS)->setIsConduitOnly(true)->setValue($object->getStatus())->setOptions($object->getStatusNameMap()), id(new PhabricatorConduitEditField())->setKey('paths.set')->setLabel(pht('Paths'))->setIsConduitOnly(true)->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_PATHS)->setConduitDescription(pht('Overwrite existing package paths with new paths.'))->setConduitTypeDescription(pht('List of dictionaries, each describing a path.'))->setConduitDocumentation($paths_help));
    }
 public function getTitle()
 {
     $map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
     $map = ipull($map, 'name');
     $old = $this->getOldValue();
     $new = $this->getNewValue();
     $old = idx($map, $old, $old);
     $new = idx($map, $new, $new);
     return pht('%s adjusted autoreview from %s to %s.', $this->renderAuthor(), $this->renderValue($old), $this->renderValue($new));
 }
 private function buildPackageDetailView(PhabricatorOwnersPackage $package, PhabricatorCustomFieldList $field_list)
 {
     $viewer = $this->getViewer();
     $view = id(new PHUIPropertyListView())->setUser($viewer);
     $owners = $package->getOwners();
     if ($owners) {
         $owner_list = $viewer->renderHandleList(mpull($owners, 'getUserPHID'));
     } else {
         $owner_list = phutil_tag('em', array(), pht('None'));
     }
     $view->addProperty(pht('Owners'), $owner_list);
     $dominion = $package->getDominion();
     $dominion_map = PhabricatorOwnersPackage::getDominionOptionsMap();
     $spec = idx($dominion_map, $dominion, array());
     $name = idx($spec, 'short', $dominion);
     $view->addProperty(pht('Dominion'), $name);
     $auto = $package->getAutoReview();
     $autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
     $spec = idx($autoreview_map, $auto, array());
     $name = idx($spec, 'name', $auto);
     $view->addProperty(pht('Auto Review'), $name);
     if ($package->getAuditingEnabled()) {
         $auditing = pht('Enabled');
     } else {
         $auditing = pht('Disabled');
     }
     $view->addProperty(pht('Auditing'), $auditing);
     $description = $package->getDescription();
     if (strlen($description)) {
         $description = new PHUIRemarkupView($viewer, $description);
         $view->addSectionHeader(pht('Description'));
         $view->addTextContent($description);
     }
     $field_list->appendFieldsToPropertyList($package, $viewer, $view);
     return $view;
 }
 public function getTitle()
 {
     $old = $this->getOldValue();
     $new = $this->getNewValue();
     $author_phid = $this->getAuthorPHID();
     switch ($this->getTransactionType()) {
         case PhabricatorTransactions::TYPE_CREATE:
             return pht('%s created this package.', $this->renderHandleLink($author_phid));
         case self::TYPE_NAME:
             if ($old === null) {
                 return pht('%s created this package.', $this->renderHandleLink($author_phid));
             } else {
                 return pht('%s renamed this package from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new);
             }
         case self::TYPE_OWNERS:
             $add = array_diff($new, $old);
             $rem = array_diff($old, $new);
             if ($add && !$rem) {
                 return pht('%s added %s owner(s): %s.', $this->renderHandleLink($author_phid), count($add), $this->renderHandleList($add));
             } else {
                 if ($rem && !$add) {
                     return pht('%s removed %s owner(s): %s.', $this->renderHandleLink($author_phid), count($rem), $this->renderHandleList($rem));
                 } else {
                     return pht('%s changed %s package owner(s), added %s: %s; removed %s: %s.', $this->renderHandleLink($author_phid), count($add) + count($rem), count($add), $this->renderHandleList($add), count($rem), $this->renderHandleList($rem));
                 }
             }
         case self::TYPE_AUDITING:
             if ($new) {
                 return pht('%s enabled auditing for this package.', $this->renderHandleLink($author_phid));
             } else {
                 return pht('%s disabled auditing for this package.', $this->renderHandleLink($author_phid));
             }
         case self::TYPE_DESCRIPTION:
             return pht('%s updated the description for this package.', $this->renderHandleLink($author_phid));
         case self::TYPE_PATHS:
             // TODO: Flesh this out.
             return pht('%s updated paths for this package.', $this->renderHandleLink($author_phid));
         case self::TYPE_STATUS:
             if ($new == PhabricatorOwnersPackage::STATUS_ACTIVE) {
                 return pht('%s activated this package.', $this->renderHandleLink($author_phid));
             } else {
                 if ($new == PhabricatorOwnersPackage::STATUS_ARCHIVED) {
                     return pht('%s archived this package.', $this->renderHandleLink($author_phid));
                 }
             }
         case self::TYPE_AUTOREVIEW:
             $map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
             $map = ipull($map, 'name');
             $old = idx($map, $old, $old);
             $new = idx($map, $new, $new);
             return pht('%s adjusted autoreview from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new);
         case self::TYPE_DOMINION:
             $map = PhabricatorOwnersPackage::getDominionOptionsMap();
             $map = ipull($map, 'short');
             $old = idx($map, $old, $old);
             $new = idx($map, $new, $new);
             return pht('%s adjusted package dominion rules from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new);
     }
     return parent::getTitle();
 }
 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;
             }
             foreach ($xactions as $xaction) {
                 $new = $xaction->getNewValue();
                 if (preg_match('([,!])', $new)) {
                     $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Package names may not contain commas (",") or exclamation ' . 'marks ("!"). These characters are ambiguous when package ' . 'names are parsed from the command line.'), $xaction);
                 }
             }
             break;
         case PhabricatorOwnersPackageTransaction::TYPE_AUTOREVIEW:
             $map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
             foreach ($xactions as $xaction) {
                 $new = $xaction->getNewValue();
                 if (empty($map[$new])) {
                     $valid = array_keys($map);
                     $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Autoreview setting "%s" is not valid. ' . 'Valid settings are: %s.', $new, implode(', ', $valid)), $xaction);
                 }
             }
             break;
         case PhabricatorOwnersPackageTransaction::TYPE_DOMINION:
             $map = PhabricatorOwnersPackage::getDominionOptionsMap();
             foreach ($xactions as $xaction) {
                 $new = $xaction->getNewValue();
                 if (empty($map[$new])) {
                     $valid = array_keys($map);
                     $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Dominion setting "%s" is not valid. ' . 'Valid settings are: %s.', $new, implode(', ', $valid)), $xaction);
                 }
             }
             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;
 }