protected function validateTransaction(PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhabricatorProjectTransaction::TYPE_NAME: $missing = $this->validateIsEmptyTextField($object->getName(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError($type, pht('Required'), pht('Project name is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } if (!$xactions) { break; } if ($this->getIsMilestone()) { break; } $name = last($xactions)->getNewValue(); if (!PhabricatorSlug::isValidProjectSlug($name)) { $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Project names must contain at least one letter or number.'), last($xactions)); break; } $slug = PhabricatorSlug::normalizeProjectSlug($name); $slug_used_already = id(new PhabricatorProjectSlug())->loadOneWhere('slug = %s', $slug); if ($slug_used_already && $slug_used_already->getProjectPHID() != $object->getPHID()) { $error = new PhabricatorApplicationTransactionValidationError($type, pht('Duplicate'), pht('Project name generates the same hashtag ("%s") as another ' . 'existing project. Choose a unique name.', '#' . $slug), nonempty(last($xactions), null)); $errors[] = $error; } break; case PhabricatorProjectTransaction::TYPE_SLUGS: if (!$xactions) { break; } $slug_xaction = last($xactions); $new = $slug_xaction->getNewValue(); $invalid = array(); foreach ($new as $slug) { if (!PhabricatorSlug::isValidProjectSlug($slug)) { $invalid[] = $slug; } } if ($invalid) { $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Hashtags must contain at least one letter or number. %s ' . 'project hashtag(s) are invalid: %s.', phutil_count($invalid), implode(', ', $invalid)), $slug_xaction); break; } $new = $this->normalizeSlugs($new); if ($new) { $slugs_used_already = id(new PhabricatorProjectSlug())->loadAllWhere('slug IN (%Ls)', $new); } else { // The project doesn't have any extra slugs. $slugs_used_already = array(); } $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID'); foreach ($slugs_used_already as $project_phid => $used_slugs) { if ($project_phid == $object->getPHID()) { continue; } $used_slug_strs = mpull($used_slugs, 'getSlug'); $error = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('%s project hashtag(s) are already used by other projects: %s.', phutil_count($used_slug_strs), implode(', ', $used_slug_strs)), $slug_xaction); $errors[] = $error; } break; case PhabricatorProjectTransaction::TYPE_PARENT: case PhabricatorProjectTransaction::TYPE_MILESTONE: if (!$xactions) { break; } $xaction = last($xactions); $parent_phid = $xaction->getNewValue(); if (!$parent_phid) { continue; } if (!$this->getIsNewObject()) { $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('You can only set a parent or milestone project when creating a ' . 'project for the first time.'), $xaction); break; } $projects = id(new PhabricatorProjectQuery())->setViewer($this->requireActor())->withPHIDs(array($parent_phid))->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->execute(); if (!$projects) { $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Parent or milestone project PHID ("%s") must be the PHID of a ' . 'valid, visible project which you have permission to edit.', $parent_phid), $xaction); break; } $project = head($projects); if ($project->isMilestone()) { $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Parent or milestone project PHID ("%s") must not be a ' . 'milestone. Milestones may not have subprojects or milestones.', $parent_phid), $xaction); break; } $limit = PhabricatorProject::getProjectDepthLimit(); if ($project->getProjectDepth() >= $limit - 1) { $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('You can not create a subproject or mielstone under this parent ' . 'because it would nest projects too deeply. The maximum ' . 'nesting depth of projects is %s.', new PhutilNumber($limit)), $xaction); break; } $object->attachParentProject($project); break; } return $errors; }