mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-27 01:02:42 +01:00
Migrate Project slugs to modular transactions
Test Plan: Unit tests all pass. Added/removed/altered some project hashtags and observed expected transactions in timeline. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D17952
This commit is contained in:
parent
1599c56217
commit
91eb22cb3a
7 changed files with 181 additions and 157 deletions
|
@ -3666,6 +3666,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectSilenceController' => 'applications/project/controller/PhabricatorProjectSilenceController.php',
|
'PhabricatorProjectSilenceController' => 'applications/project/controller/PhabricatorProjectSilenceController.php',
|
||||||
'PhabricatorProjectSilencedEdgeType' => 'applications/project/edge/PhabricatorProjectSilencedEdgeType.php',
|
'PhabricatorProjectSilencedEdgeType' => 'applications/project/edge/PhabricatorProjectSilencedEdgeType.php',
|
||||||
'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php',
|
'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php',
|
||||||
|
'PhabricatorProjectSlugsTransaction' => 'applications/project/xaction/PhabricatorProjectSlugsTransaction.php',
|
||||||
'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php',
|
'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php',
|
||||||
'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php',
|
'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php',
|
||||||
'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php',
|
'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php',
|
||||||
|
@ -9077,6 +9078,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectSilenceController' => 'PhabricatorProjectController',
|
'PhabricatorProjectSilenceController' => 'PhabricatorProjectController',
|
||||||
'PhabricatorProjectSilencedEdgeType' => 'PhabricatorEdgeType',
|
'PhabricatorProjectSilencedEdgeType' => 'PhabricatorEdgeType',
|
||||||
'PhabricatorProjectSlug' => 'PhabricatorProjectDAO',
|
'PhabricatorProjectSlug' => 'PhabricatorProjectDAO',
|
||||||
|
'PhabricatorProjectSlugsTransaction' => 'PhabricatorProjectTransactionType',
|
||||||
'PhabricatorProjectStandardCustomField' => array(
|
'PhabricatorProjectStandardCustomField' => array(
|
||||||
'PhabricatorProjectCustomField',
|
'PhabricatorProjectCustomField',
|
||||||
'PhabricatorStandardCustomFieldInterface',
|
'PhabricatorStandardCustomFieldInterface',
|
||||||
|
|
|
@ -362,7 +362,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
$xactions = array();
|
$xactions = array();
|
||||||
$xactions[] = id(new PhabricatorProjectTransaction())
|
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||||
->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
|
->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE)
|
||||||
->setNewValue(array($name));
|
->setNewValue(array($name));
|
||||||
$this->applyTransactions($project, $user, $xactions);
|
$this->applyTransactions($project, $user, $xactions);
|
||||||
|
|
||||||
|
@ -386,7 +386,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
|
||||||
->setNewValue($name2);
|
->setNewValue($name2);
|
||||||
|
|
||||||
$xactions[] = id(new PhabricatorProjectTransaction())
|
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||||
->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
|
->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE)
|
||||||
->setNewValue(array($name2));
|
->setNewValue(array($name2));
|
||||||
|
|
||||||
$this->applyTransactions($project, $user, $xactions);
|
$this->applyTransactions($project, $user, $xactions);
|
||||||
|
@ -416,7 +416,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
|
||||||
$xactions = array();
|
$xactions = array();
|
||||||
|
|
||||||
$xactions[] = id(new PhabricatorProjectTransaction())
|
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||||
->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
|
->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE)
|
||||||
->setNewValue(array($input, $input));
|
->setNewValue(array($input, $input));
|
||||||
|
|
||||||
$this->applyTransactions($project, $user, $xactions);
|
$this->applyTransactions($project, $user, $xactions);
|
||||||
|
@ -448,7 +448,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
|
||||||
$xactions = array();
|
$xactions = array();
|
||||||
|
|
||||||
$xactions[] = id(new PhabricatorProjectTransaction())
|
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||||
->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
|
->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE)
|
||||||
->setNewValue(array($input));
|
->setNewValue(array($input));
|
||||||
|
|
||||||
$this->applyTransactions($project, $user, $xactions);
|
$this->applyTransactions($project, $user, $xactions);
|
||||||
|
@ -474,7 +474,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
|
||||||
$xactions = array();
|
$xactions = array();
|
||||||
|
|
||||||
$xactions[] = id(new PhabricatorProjectTransaction())
|
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||||
->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
|
->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE)
|
||||||
->setNewValue(array($input));
|
->setNewValue(array($input));
|
||||||
|
|
||||||
$caught = null;
|
$caught = null;
|
||||||
|
@ -605,7 +605,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
|
||||||
->setNewValue($name);
|
->setNewValue($name);
|
||||||
|
|
||||||
$xactions[] = id(new PhabricatorProjectTransaction())
|
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||||
->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
|
->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE)
|
||||||
->setNewValue(array($slug));
|
->setNewValue(array($slug));
|
||||||
|
|
||||||
$this->applyTransactions($project, $user, $xactions);
|
$this->applyTransactions($project, $user, $xactions);
|
||||||
|
|
|
@ -64,7 +64,8 @@ final class ProjectCreateConduitAPIMethod extends ProjectConduitAPIMethod {
|
||||||
|
|
||||||
if ($request->getValue('tags')) {
|
if ($request->getValue('tags')) {
|
||||||
$xactions[] = id(new PhabricatorProjectTransaction())
|
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||||
->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
|
->setTransactionType(
|
||||||
|
PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE)
|
||||||
->setNewValue($request->getValue('tags'));
|
->setNewValue($request->getValue('tags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ final class PhabricatorProjectTransactionEditor
|
||||||
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||||
$types[] = PhabricatorTransactions::TYPE_JOIN_POLICY;
|
$types[] = PhabricatorTransactions::TYPE_JOIN_POLICY;
|
||||||
|
|
||||||
$types[] = PhabricatorProjectTransaction::TYPE_SLUGS;
|
|
||||||
$types[] = PhabricatorProjectTransaction::TYPE_STATUS;
|
$types[] = PhabricatorProjectTransaction::TYPE_STATUS;
|
||||||
$types[] = PhabricatorProjectTransaction::TYPE_IMAGE;
|
$types[] = PhabricatorProjectTransaction::TYPE_IMAGE;
|
||||||
$types[] = PhabricatorProjectTransaction::TYPE_ICON;
|
$types[] = PhabricatorProjectTransaction::TYPE_ICON;
|
||||||
|
@ -51,11 +50,6 @@ final class PhabricatorProjectTransactionEditor
|
||||||
PhabricatorApplicationTransaction $xaction) {
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case PhabricatorProjectTransaction::TYPE_SLUGS:
|
|
||||||
$slugs = $object->getSlugs();
|
|
||||||
$slugs = mpull($slugs, 'getSlug', 'getSlug');
|
|
||||||
unset($slugs[$object->getPrimarySlug()]);
|
|
||||||
return array_keys($slugs);
|
|
||||||
case PhabricatorProjectTransaction::TYPE_STATUS:
|
case PhabricatorProjectTransaction::TYPE_STATUS:
|
||||||
return $object->getStatus();
|
return $object->getStatus();
|
||||||
case PhabricatorProjectTransaction::TYPE_IMAGE:
|
case PhabricatorProjectTransaction::TYPE_IMAGE:
|
||||||
|
@ -105,8 +99,6 @@ final class PhabricatorProjectTransactionEditor
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return $value;
|
return $value;
|
||||||
case PhabricatorProjectTransaction::TYPE_SLUGS:
|
|
||||||
return $this->normalizeSlugs($xaction->getNewValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::getCustomTransactionNewValue($object, $xaction);
|
return parent::getCustomTransactionNewValue($object, $xaction);
|
||||||
|
@ -117,8 +109,6 @@ final class PhabricatorProjectTransactionEditor
|
||||||
PhabricatorApplicationTransaction $xaction) {
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case PhabricatorProjectTransaction::TYPE_SLUGS:
|
|
||||||
return;
|
|
||||||
case PhabricatorProjectTransaction::TYPE_STATUS:
|
case PhabricatorProjectTransaction::TYPE_STATUS:
|
||||||
$object->setStatus($xaction->getNewValue());
|
$object->setStatus($xaction->getNewValue());
|
||||||
return;
|
return;
|
||||||
|
@ -167,18 +157,6 @@ final class PhabricatorProjectTransactionEditor
|
||||||
$new = $xaction->getNewValue();
|
$new = $xaction->getNewValue();
|
||||||
|
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case PhabricatorProjectTransaction::TYPE_SLUGS:
|
|
||||||
$old = $xaction->getOldValue();
|
|
||||||
$new = $xaction->getNewValue();
|
|
||||||
$add = array_diff($new, $old);
|
|
||||||
$rem = array_diff($old, $new);
|
|
||||||
|
|
||||||
foreach ($add as $slug) {
|
|
||||||
$this->addSlug($object, $slug, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->removeSlugs($object, $rem);
|
|
||||||
return;
|
|
||||||
case PhabricatorProjectTransaction::TYPE_STATUS:
|
case PhabricatorProjectTransaction::TYPE_STATUS:
|
||||||
case PhabricatorProjectTransaction::TYPE_IMAGE:
|
case PhabricatorProjectTransaction::TYPE_IMAGE:
|
||||||
case PhabricatorProjectTransaction::TYPE_ICON:
|
case PhabricatorProjectTransaction::TYPE_ICON:
|
||||||
|
@ -278,65 +256,6 @@ final class PhabricatorProjectTransactionEditor
|
||||||
$errors = parent::validateTransaction($object, $type, $xactions);
|
$errors = parent::validateTransaction($object, $type, $xactions);
|
||||||
|
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
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_PARENT:
|
||||||
case PhabricatorProjectTransaction::TYPE_MILESTONE:
|
case PhabricatorProjectTransaction::TYPE_MILESTONE:
|
||||||
if (!$xactions) {
|
if (!$xactions) {
|
||||||
|
@ -674,7 +593,7 @@ final class PhabricatorProjectTransactionEditor
|
||||||
->save();
|
->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function removeSlugs(PhabricatorProject $project, array $slugs) {
|
public function removeSlugs(PhabricatorProject $project, array $slugs) {
|
||||||
if (!$slugs) {
|
if (!$slugs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -696,7 +615,7 @@ final class PhabricatorProjectTransactionEditor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function normalizeSlugs(array $slugs) {
|
public function normalizeSlugs(array $slugs) {
|
||||||
foreach ($slugs as $key => $slug) {
|
foreach ($slugs as $key => $slug) {
|
||||||
$slugs[$key] = PhabricatorSlug::normalizeProjectSlug($slug);
|
$slugs[$key] = PhabricatorSlug::normalizeProjectSlug($slug);
|
||||||
}
|
}
|
||||||
|
|
|
@ -262,7 +262,8 @@ final class PhabricatorProjectEditEngine
|
||||||
id(new PhabricatorStringListEditField())
|
id(new PhabricatorStringListEditField())
|
||||||
->setKey('slugs')
|
->setKey('slugs')
|
||||||
->setLabel(pht('Additional Hashtags'))
|
->setLabel(pht('Additional Hashtags'))
|
||||||
->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
|
->setTransactionType(
|
||||||
|
PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE)
|
||||||
->setDescription(pht('Additional project slugs.'))
|
->setDescription(pht('Additional project slugs.'))
|
||||||
->setConduitDescription(pht('Change project slugs.'))
|
->setConduitDescription(pht('Change project slugs.'))
|
||||||
->setConduitTypeDescription(pht('New list of slugs.'))
|
->setConduitTypeDescription(pht('New list of slugs.'))
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
final class PhabricatorProjectTransaction
|
final class PhabricatorProjectTransaction
|
||||||
extends PhabricatorModularTransaction {
|
extends PhabricatorModularTransaction {
|
||||||
|
|
||||||
const TYPE_SLUGS = 'project:slugs';
|
|
||||||
const TYPE_STATUS = 'project:status';
|
const TYPE_STATUS = 'project:status';
|
||||||
const TYPE_IMAGE = 'project:image';
|
const TYPE_IMAGE = 'project:image';
|
||||||
const TYPE_ICON = 'project:icon';
|
const TYPE_ICON = 'project:icon';
|
||||||
|
@ -134,8 +133,6 @@ final class PhabricatorProjectTransaction
|
||||||
return 'fa-photo';
|
return 'fa-photo';
|
||||||
case self::TYPE_MEMBERS:
|
case self::TYPE_MEMBERS:
|
||||||
return 'fa-user';
|
return 'fa-user';
|
||||||
case self::TYPE_SLUGS:
|
|
||||||
return 'fa-tag';
|
|
||||||
}
|
}
|
||||||
return parent::getIcon();
|
return parent::getIcon();
|
||||||
}
|
}
|
||||||
|
@ -212,33 +209,6 @@ final class PhabricatorProjectTransaction
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case self::TYPE_SLUGS:
|
|
||||||
$add = array_diff($new, $old);
|
|
||||||
$rem = array_diff($old, $new);
|
|
||||||
|
|
||||||
if ($add && $rem) {
|
|
||||||
return pht(
|
|
||||||
'%s changed project hashtag(s), added %d: %s; removed %d: %s.',
|
|
||||||
$author_handle,
|
|
||||||
count($add),
|
|
||||||
$this->renderSlugList($add),
|
|
||||||
count($rem),
|
|
||||||
$this->renderSlugList($rem));
|
|
||||||
} else if ($add) {
|
|
||||||
return pht(
|
|
||||||
'%s added %d project hashtag(s): %s.',
|
|
||||||
$author_handle,
|
|
||||||
count($add),
|
|
||||||
$this->renderSlugList($add));
|
|
||||||
} else if ($rem) {
|
|
||||||
return pht(
|
|
||||||
'%s removed %d project hashtag(s): %s.',
|
|
||||||
$author_handle,
|
|
||||||
count($rem),
|
|
||||||
$this->renderSlugList($rem));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case self::TYPE_MEMBERS:
|
case self::TYPE_MEMBERS:
|
||||||
$add = array_diff($new, $old);
|
$add = array_diff($new, $old);
|
||||||
$rem = array_diff($old, $new);
|
$rem = array_diff($old, $new);
|
||||||
|
@ -380,36 +350,6 @@ final class PhabricatorProjectTransaction
|
||||||
$author_handle,
|
$author_handle,
|
||||||
$object_handle);
|
$object_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
case self::TYPE_SLUGS:
|
|
||||||
$add = array_diff($new, $old);
|
|
||||||
$rem = array_diff($old, $new);
|
|
||||||
|
|
||||||
if ($add && $rem) {
|
|
||||||
return pht(
|
|
||||||
'%s changed %s hashtag(s), added %d: %s; removed %d: %s.',
|
|
||||||
$author_handle,
|
|
||||||
$object_handle,
|
|
||||||
count($add),
|
|
||||||
$this->renderSlugList($add),
|
|
||||||
count($rem),
|
|
||||||
$this->renderSlugList($rem));
|
|
||||||
} else if ($add) {
|
|
||||||
return pht(
|
|
||||||
'%s added %d %s hashtag(s): %s.',
|
|
||||||
$author_handle,
|
|
||||||
count($add),
|
|
||||||
$object_handle,
|
|
||||||
$this->renderSlugList($add));
|
|
||||||
} else if ($rem) {
|
|
||||||
return pht(
|
|
||||||
'%s removed %d %s hashtag(s): %s.',
|
|
||||||
$author_handle,
|
|
||||||
count($rem),
|
|
||||||
$object_handle,
|
|
||||||
$this->renderSlugList($rem));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::getTitleForFeed();
|
return parent::getTitleForFeed();
|
||||||
|
@ -418,8 +358,8 @@ final class PhabricatorProjectTransaction
|
||||||
public function getMailTags() {
|
public function getMailTags() {
|
||||||
$tags = array();
|
$tags = array();
|
||||||
switch ($this->getTransactionType()) {
|
switch ($this->getTransactionType()) {
|
||||||
case self::TYPE_NAME:
|
case PhabricatorProjectNameTransaction::TRANSACTIONTYPE:
|
||||||
case self::TYPE_SLUGS:
|
case PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE:
|
||||||
case self::TYPE_IMAGE:
|
case self::TYPE_IMAGE:
|
||||||
case self::TYPE_ICON:
|
case self::TYPE_ICON:
|
||||||
case self::TYPE_COLOR:
|
case self::TYPE_COLOR:
|
||||||
|
@ -447,8 +387,4 @@ final class PhabricatorProjectTransaction
|
||||||
return $tags;
|
return $tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function renderSlugList($slugs) {
|
|
||||||
return implode(', ', $slugs);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorProjectSlugsTransaction
|
||||||
|
extends PhabricatorProjectTransactionType {
|
||||||
|
|
||||||
|
const TRANSACTIONTYPE = 'project:slugs';
|
||||||
|
|
||||||
|
public function generateOldValue($object) {
|
||||||
|
$slugs = $object->getSlugs();
|
||||||
|
$slugs = mpull($slugs, 'getSlug', 'getSlug');
|
||||||
|
unset($slugs[$object->getPrimarySlug()]);
|
||||||
|
return array_keys($slugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateNewValue($object, $value) {
|
||||||
|
return $this->getEditor()->normalizeSlugs($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyInternalEffects($object, $value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyExternalEffects($object, $value) {
|
||||||
|
$old = $this->getOldValue();
|
||||||
|
$new = $value;
|
||||||
|
$add = array_diff($new, $old);
|
||||||
|
$rem = array_diff($old, $new);
|
||||||
|
|
||||||
|
foreach ($add as $slug) {
|
||||||
|
$this->getEditor()->addSlug($object, $slug, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->getEditor()->removeSlugs($object, $rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle() {
|
||||||
|
$old = $this->getOldValue();
|
||||||
|
$new = $this->getNewValue();
|
||||||
|
|
||||||
|
$add = array_diff($new, $old);
|
||||||
|
$rem = array_diff($old, $new);
|
||||||
|
|
||||||
|
if ($add && $rem) {
|
||||||
|
return pht(
|
||||||
|
'%s changed project hashtag(s), added %d: %s; removed %d: %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
count($add),
|
||||||
|
$this->renderSlugList($add),
|
||||||
|
count($rem),
|
||||||
|
$this->renderSlugList($rem));
|
||||||
|
} else if ($add) {
|
||||||
|
return pht(
|
||||||
|
'%s added %d project hashtag(s): %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
count($add),
|
||||||
|
$this->renderSlugList($add));
|
||||||
|
} else if ($rem) {
|
||||||
|
return pht(
|
||||||
|
'%s removed %d project hashtag(s): %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
count($rem),
|
||||||
|
$this->renderSlugList($rem));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitleForFeed() {
|
||||||
|
$old = $this->getOldValue();
|
||||||
|
$new = $this->getNewValue();
|
||||||
|
|
||||||
|
$add = array_diff($new, $old);
|
||||||
|
$rem = array_diff($old, $new);
|
||||||
|
|
||||||
|
if ($add && $rem) {
|
||||||
|
return pht(
|
||||||
|
'%s changed %s hashtag(s), added %d: %s; removed %d: %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
$this->renderObject(),
|
||||||
|
count($add),
|
||||||
|
$this->renderSlugList($add),
|
||||||
|
count($rem),
|
||||||
|
$this->renderSlugList($rem));
|
||||||
|
} else if ($add) {
|
||||||
|
return pht(
|
||||||
|
'%s added %d %s hashtag(s): %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
count($add),
|
||||||
|
$this->renderObject(),
|
||||||
|
$this->renderSlugList($add));
|
||||||
|
} else if ($rem) {
|
||||||
|
return pht(
|
||||||
|
'%s removed %d %s hashtag(s): %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
count($rem),
|
||||||
|
$this->renderObject(),
|
||||||
|
$this->renderSlugList($rem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIcon() {
|
||||||
|
return 'fa-tag';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateTransactions($object, array $xactions) {
|
||||||
|
$errors = array();
|
||||||
|
|
||||||
|
if (!$xactions) {
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
$slug_xaction = last($xactions);
|
||||||
|
|
||||||
|
$new = $slug_xaction->getNewValue();
|
||||||
|
|
||||||
|
$invalid = array();
|
||||||
|
foreach ($new as $slug) {
|
||||||
|
if (!PhabricatorSlug::isValidProjectSlug($slug)) {
|
||||||
|
$invalid[] = $slug;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($invalid) {
|
||||||
|
$errors[] = $this->newInvalidError(
|
||||||
|
pht(
|
||||||
|
'Hashtags must contain at least one letter or number. %s '.
|
||||||
|
'project hashtag(s) are invalid: %s.',
|
||||||
|
phutil_count($invalid),
|
||||||
|
implode(', ', $invalid)));
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = $this->getEditor()->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');
|
||||||
|
|
||||||
|
$errors[] = $this->newInvalidError(
|
||||||
|
pht(
|
||||||
|
'%s project hashtag(s) are already used by other projects: %s.',
|
||||||
|
phutil_count($used_slug_strs),
|
||||||
|
implode(', ', $used_slug_strs)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderSlugList($slugs) {
|
||||||
|
return implode(', ', $slugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue