mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-18 11:30:55 +01:00
Improve ruleset for generating project hashtags
Summary: Ref T9551. We currently use the same logic for generating project hashtags and Phriction slugs, but should be a little more conservative with project hashtags. Stop them from generating with stuff that won't parse in a "Reviewers:" field or generally in commments (commas, colons, etc). Test Plan: Created a bunch of projects with nonsense in them and saw them generate pretty reasonable hashtags. {F873456} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9551 Differential Revision: https://secure.phabricator.com/D14261
This commit is contained in:
parent
3ff5ca789a
commit
cd8be8106b
8 changed files with 78 additions and 48 deletions
|
@ -24,12 +24,14 @@ foreach (new LiskMigrationIterator($table) as $doc) {
|
||||||
$prefix = 'projects/';
|
$prefix = 'projects/';
|
||||||
if (($slug != $prefix) && (strncmp($slug, $prefix, strlen($prefix)) === 0)) {
|
if (($slug != $prefix) && (strncmp($slug, $prefix, strlen($prefix)) === 0)) {
|
||||||
$parts = explode('/', $slug);
|
$parts = explode('/', $slug);
|
||||||
$project_slug = $parts[1].'/';
|
|
||||||
|
$project_slug = $parts[1];
|
||||||
|
$project_slug = PhabricatorSlug::normalizeProjectSlug($project_slug);
|
||||||
|
|
||||||
$project_slugs = array($project_slug);
|
$project_slugs = array($project_slug);
|
||||||
$project = id(new PhabricatorProjectQuery())
|
$project = id(new PhabricatorProjectQuery())
|
||||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
->withPhrictionSlugs($project_slugs)
|
->withSlugs($project_slugs)
|
||||||
->executeOne();
|
->executeOne();
|
||||||
|
|
||||||
if ($project) {
|
if ($project) {
|
||||||
|
|
|
@ -10,13 +10,14 @@ $projects = $table->loadAll();
|
||||||
$slug_map = array();
|
$slug_map = array();
|
||||||
|
|
||||||
foreach ($projects as $project) {
|
foreach ($projects as $project) {
|
||||||
$project->setPhrictionSlug($project->getName());
|
$slug = PhabricatorSlug::normalizeProjectSlug($project->getName());
|
||||||
$slug = $project->getPhrictionSlug();
|
|
||||||
if ($slug == '/') {
|
if (!strlen($slug)) {
|
||||||
$project_id = $project->getID();
|
$project_id = $project->getID();
|
||||||
echo pht("Project #%d doesn't have a meaningful name...", $project_id)."\n";
|
echo pht("Project #%d doesn't have a meaningful name...", $project_id)."\n";
|
||||||
$project->setName(trim(pht('Unnamed Project %s', $project->getName())));
|
$project->setName(trim(pht('Unnamed Project %s', $project->getName())));
|
||||||
}
|
}
|
||||||
|
|
||||||
$slug_map[$slug][] = $project->getID();
|
$slug_map[$slug][] = $project->getID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,8 +48,8 @@ while ($update) {
|
||||||
foreach ($update as $key => $project) {
|
foreach ($update as $key => $project) {
|
||||||
$id = $project->getID();
|
$id = $project->getID();
|
||||||
$name = $project->getName();
|
$name = $project->getName();
|
||||||
$project->setPhrictionSlug($name);
|
|
||||||
$slug = $project->getPhrictionSlug();
|
$slug = PhabricatorSlug::normalizeProjectSlug($name).'/';
|
||||||
|
|
||||||
echo pht("Updating project #%d '%s' (%s)... ", $id, $name, $slug);
|
echo pht("Updating project #%d '%s' (%s)... ", $id, $name, $slug);
|
||||||
try {
|
try {
|
||||||
|
@ -87,8 +88,8 @@ function rename_project($project, $projects) {
|
||||||
$suffix = 2;
|
$suffix = 2;
|
||||||
while (true) {
|
while (true) {
|
||||||
$new_name = $project->getName().' ('.$suffix.')';
|
$new_name = $project->getName().' ('.$suffix.')';
|
||||||
$project->setPhrictionSlug($new_name);
|
|
||||||
$new_slug = $project->getPhrictionSlug();
|
$new_slug = PhabricatorSlug::normalizeProjectSlug($new_name).'/';
|
||||||
|
|
||||||
$okay = true;
|
$okay = true;
|
||||||
foreach ($projects as $other) {
|
foreach ($projects as $other) {
|
||||||
|
|
|
@ -112,7 +112,7 @@ final class ProjectQueryConduitAPIMethod extends ProjectConduitAPIMethod {
|
||||||
$slug_map = array();
|
$slug_map = array();
|
||||||
if ($slugs) {
|
if ($slugs) {
|
||||||
foreach ($slugs as $slug) {
|
foreach ($slugs as $slug) {
|
||||||
$normal = rtrim(PhabricatorSlug::normalize($slug), '/');
|
$normal = PhabricatorSlug::normalizeProjectSlug($slug);
|
||||||
foreach ($projects as $project) {
|
foreach ($projects as $project) {
|
||||||
if (in_array($normal, $project['slugs'])) {
|
if (in_array($normal, $project['slugs'])) {
|
||||||
$slug_map[$slug] = $project['phid'];
|
$slug_map[$slug] = $project['phid'];
|
||||||
|
|
|
@ -81,9 +81,9 @@ final class PhabricatorProjectTransactionEditor
|
||||||
|
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case PhabricatorProjectTransaction::TYPE_NAME:
|
case PhabricatorProjectTransaction::TYPE_NAME:
|
||||||
$object->setName($xaction->getNewValue());
|
$name = $xaction->getNewValue();
|
||||||
// TODO - this is really "setPrimarySlug"
|
$object->setName($name);
|
||||||
$object->setPhrictionSlug($xaction->getNewValue());
|
$object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($name));
|
||||||
return;
|
return;
|
||||||
case PhabricatorProjectTransaction::TYPE_SLUGS:
|
case PhabricatorProjectTransaction::TYPE_SLUGS:
|
||||||
return;
|
return;
|
||||||
|
@ -265,9 +265,8 @@ final class PhabricatorProjectTransactionEditor
|
||||||
$errors[] = $error;
|
$errors[] = $error;
|
||||||
}
|
}
|
||||||
|
|
||||||
$slug_builder = clone $object;
|
$slug = PhabricatorSlug::normalizeProjectSlug($name);
|
||||||
$slug_builder->setPhrictionSlug($name);
|
|
||||||
$slug = $slug_builder->getPrimarySlug();
|
|
||||||
$slug_used_already = id(new PhabricatorProjectSlug())
|
$slug_used_already = id(new PhabricatorProjectSlug())
|
||||||
->loadOneWhere('slug = %s', $slug);
|
->loadOneWhere('slug = %s', $slug);
|
||||||
if ($slug_used_already &&
|
if ($slug_used_already &&
|
||||||
|
|
|
@ -7,7 +7,6 @@ final class PhabricatorProjectQuery
|
||||||
private $phids;
|
private $phids;
|
||||||
private $memberPHIDs;
|
private $memberPHIDs;
|
||||||
private $slugs;
|
private $slugs;
|
||||||
private $phrictionSlugs;
|
|
||||||
private $names;
|
private $names;
|
||||||
private $nameTokens;
|
private $nameTokens;
|
||||||
private $icons;
|
private $icons;
|
||||||
|
@ -50,11 +49,6 @@ final class PhabricatorProjectQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function withPhrictionSlugs(array $slugs) {
|
|
||||||
$this->phrictionSlugs = $slugs;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withNames(array $names) {
|
public function withNames(array $names) {
|
||||||
$this->names = $names;
|
$this->names = $names;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -308,13 +302,6 @@ final class PhabricatorProjectQuery
|
||||||
$this->slugs);
|
$this->slugs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->phrictionSlugs !== null) {
|
|
||||||
$where[] = qsprintf(
|
|
||||||
$conn,
|
|
||||||
'phrictionSlug IN (%Ls)',
|
|
||||||
$this->phrictionSlugs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->names !== null) {
|
if ($this->names !== null) {
|
||||||
$where[] = qsprintf(
|
$where[] = qsprintf(
|
||||||
$conn,
|
$conn,
|
||||||
|
|
|
@ -189,24 +189,11 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
return $this->assertAttached($this->memberPHIDs);
|
return $this->assertAttached($this->memberPHIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPhrictionSlug($slug) {
|
public function setPrimarySlug($slug) {
|
||||||
|
$this->phrictionSlug = $slug.'/';
|
||||||
// NOTE: We're doing a little magic here and stripping out '/' so that
|
|
||||||
// project pages always appear at top level under projects/ even if the
|
|
||||||
// display name is "Hack / Slash" or similar (it will become
|
|
||||||
// 'hack_slash' instead of 'hack/slash').
|
|
||||||
|
|
||||||
$slug = str_replace('/', ' ', $slug);
|
|
||||||
$slug = PhabricatorSlug::normalize($slug);
|
|
||||||
$this->phrictionSlug = $slug;
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFullPhrictionSlug() {
|
|
||||||
$slug = $this->getPhrictionSlug();
|
|
||||||
return 'projects/'.$slug;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO - once we sever project => phriction automagicalness,
|
// TODO - once we sever project => phriction automagicalness,
|
||||||
// migrate getPhrictionSlug to have no trailing slash and be called
|
// migrate getPhrictionSlug to have no trailing slash and be called
|
||||||
// getPrimarySlug
|
// getPrimarySlug
|
||||||
|
|
|
@ -4,21 +4,54 @@ final class PhabricatorSlug extends Phobject {
|
||||||
|
|
||||||
public static function normalizeProjectSlug($slug) {
|
public static function normalizeProjectSlug($slug) {
|
||||||
$slug = str_replace('/', ' ', $slug);
|
$slug = str_replace('/', ' ', $slug);
|
||||||
$slug = self::normalize($slug);
|
$slug = self::normalize($slug, $hashtag = true);
|
||||||
return rtrim($slug, '/');
|
return rtrim($slug, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function normalize($slug) {
|
public static function normalize($slug, $hashtag = false) {
|
||||||
$slug = preg_replace('@/+@', '/', $slug);
|
$slug = preg_replace('@/+@', '/', $slug);
|
||||||
$slug = trim($slug, '/');
|
$slug = trim($slug, '/');
|
||||||
$slug = phutil_utf8_strtolower($slug);
|
$slug = phutil_utf8_strtolower($slug);
|
||||||
$slug = preg_replace("@[\\x00-\\x19#%&+=\\\\?<> ]+@", '_', $slug);
|
|
||||||
|
$ban =
|
||||||
|
// Ban control characters since users can't type them and they create
|
||||||
|
// various other problems with parsing and rendering.
|
||||||
|
"\\x00-\\x19".
|
||||||
|
|
||||||
|
// Ban characters with special meanings in URIs (and spaces), since we
|
||||||
|
// want slugs to produce nice URIs.
|
||||||
|
"#%&+=?".
|
||||||
|
" ".
|
||||||
|
|
||||||
|
// Ban backslashes and various brackets for parsing and URI quality.
|
||||||
|
"\\\\".
|
||||||
|
"<>{}\\[\\]".
|
||||||
|
|
||||||
|
// Ban single and double quotes since they can mess up URIs.
|
||||||
|
"'".
|
||||||
|
'"';
|
||||||
|
|
||||||
|
// In hashtag mode (used for Project hashtags), ban additional characters
|
||||||
|
// which cause parsing problems.
|
||||||
|
if ($hashtag) {
|
||||||
|
$ban .= '`~!@$^*,:;(|)';
|
||||||
|
}
|
||||||
|
|
||||||
|
$slug = preg_replace('(['.$ban.']+)', '_', $slug);
|
||||||
$slug = preg_replace('@_+@', '_', $slug);
|
$slug = preg_replace('@_+@', '_', $slug);
|
||||||
|
|
||||||
|
$parts = explode('/', $slug);
|
||||||
|
|
||||||
|
// Convert "_-_" into "-". This is a little nicer for inputs with
|
||||||
|
// hyphens used as internal separators, and turns an input like "A - B"
|
||||||
|
// into "a-b" instead of "a_-_b";
|
||||||
|
foreach ($parts as $key => $part) {
|
||||||
|
$parts[$key] = str_replace('_-_', '-', $part);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove leading and trailing underscores from each component, if the
|
// Remove leading and trailing underscores from each component, if the
|
||||||
// component has not been reduced to a single underscore. For example, "a?"
|
// component has not been reduced to a single underscore. For example, "a?"
|
||||||
// converts to "a", but "??" converts to "_".
|
// converts to "a", but "??" converts to "_".
|
||||||
$parts = explode('/', $slug);
|
|
||||||
foreach ($parts as $key => $part) {
|
foreach ($parts as $key => $part) {
|
||||||
if ($part != '_') {
|
if ($part != '_') {
|
||||||
$parts[$key] = trim($part, '_');
|
$parts[$key] = trim($part, '_');
|
||||||
|
|
|
@ -34,6 +34,9 @@ final class PhabricatorSlugTestCase extends PhabricatorTestCase {
|
||||||
'a/??/c' => 'a/_/c/',
|
'a/??/c' => 'a/_/c/',
|
||||||
'a/?b/c' => 'a/b/c/',
|
'a/?b/c' => 'a/b/c/',
|
||||||
'a/b?/c' => 'a/b/c/',
|
'a/b?/c' => 'a/b/c/',
|
||||||
|
'a - b' => 'a-b/',
|
||||||
|
'a[b]' => 'a_b/',
|
||||||
|
'ab!' => 'ab!/',
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($slugs as $slug => $normal) {
|
foreach ($slugs as $slug => $normal) {
|
||||||
|
@ -44,6 +47,24 @@ final class PhabricatorSlugTestCase extends PhabricatorTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testProjectSlugs() {
|
||||||
|
$slugs = array(
|
||||||
|
'a:b' => 'a_b',
|
||||||
|
'a!b' => 'a_b',
|
||||||
|
'a - b' => 'a-b',
|
||||||
|
'' => '',
|
||||||
|
'Demonology: HSA (Hexes, Signs, Alchemy)' =>
|
||||||
|
'demonology_hsa_hexes_signs_alchemy',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($slugs as $slug => $normal) {
|
||||||
|
$this->assertEqual(
|
||||||
|
$normal,
|
||||||
|
PhabricatorSlug::normalizeProjectSlug($slug),
|
||||||
|
pht('Hashtag normalization of "%s"', $slug));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function testSlugAncestry() {
|
public function testSlugAncestry() {
|
||||||
$slugs = array(
|
$slugs = array(
|
||||||
'/' => array(),
|
'/' => array(),
|
||||||
|
|
Loading…
Reference in a new issue