@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+78 -48
+4 -2
resources/sql/autopatches/20141107.phriction.policy.2.php
··· 24 24 $prefix = 'projects/'; 25 25 if (($slug != $prefix) && (strncmp($slug, $prefix, strlen($prefix)) === 0)) { 26 26 $parts = explode('/', $slug); 27 - $project_slug = $parts[1].'/'; 27 + 28 + $project_slug = $parts[1]; 29 + $project_slug = PhabricatorSlug::normalizeProjectSlug($project_slug); 28 30 29 31 $project_slugs = array($project_slug); 30 32 $project = id(new PhabricatorProjectQuery()) 31 33 ->setViewer(PhabricatorUser::getOmnipotentUser()) 32 - ->withPhrictionSlugs($project_slugs) 34 + ->withSlugs($project_slugs) 33 35 ->executeOne(); 34 36 35 37 if ($project) {
+8 -7
resources/sql/patches/090.forceuniqueprojectnames.php
··· 10 10 $slug_map = array(); 11 11 12 12 foreach ($projects as $project) { 13 - $project->setPhrictionSlug($project->getName()); 14 - $slug = $project->getPhrictionSlug(); 15 - if ($slug == '/') { 13 + $slug = PhabricatorSlug::normalizeProjectSlug($project->getName()); 14 + 15 + if (!strlen($slug)) { 16 16 $project_id = $project->getID(); 17 17 echo pht("Project #%d doesn't have a meaningful name...", $project_id)."\n"; 18 18 $project->setName(trim(pht('Unnamed Project %s', $project->getName()))); 19 19 } 20 + 20 21 $slug_map[$slug][] = $project->getID(); 21 22 } 22 23 ··· 47 48 foreach ($update as $key => $project) { 48 49 $id = $project->getID(); 49 50 $name = $project->getName(); 50 - $project->setPhrictionSlug($name); 51 - $slug = $project->getPhrictionSlug(); 51 + 52 + $slug = PhabricatorSlug::normalizeProjectSlug($name).'/'; 52 53 53 54 echo pht("Updating project #%d '%s' (%s)... ", $id, $name, $slug); 54 55 try { ··· 87 88 $suffix = 2; 88 89 while (true) { 89 90 $new_name = $project->getName().' ('.$suffix.')'; 90 - $project->setPhrictionSlug($new_name); 91 - $new_slug = $project->getPhrictionSlug(); 91 + 92 + $new_slug = PhabricatorSlug::normalizeProjectSlug($new_name).'/'; 92 93 93 94 $okay = true; 94 95 foreach ($projects as $other) {
+1 -1
src/applications/project/conduit/ProjectQueryConduitAPIMethod.php
··· 112 112 $slug_map = array(); 113 113 if ($slugs) { 114 114 foreach ($slugs as $slug) { 115 - $normal = rtrim(PhabricatorSlug::normalize($slug), '/'); 115 + $normal = PhabricatorSlug::normalizeProjectSlug($slug); 116 116 foreach ($projects as $project) { 117 117 if (in_array($normal, $project['slugs'])) { 118 118 $slug_map[$slug] = $project['phid'];
+5 -6
src/applications/project/editor/PhabricatorProjectTransactionEditor.php
··· 81 81 82 82 switch ($xaction->getTransactionType()) { 83 83 case PhabricatorProjectTransaction::TYPE_NAME: 84 - $object->setName($xaction->getNewValue()); 85 - // TODO - this is really "setPrimarySlug" 86 - $object->setPhrictionSlug($xaction->getNewValue()); 84 + $name = $xaction->getNewValue(); 85 + $object->setName($name); 86 + $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($name)); 87 87 return; 88 88 case PhabricatorProjectTransaction::TYPE_SLUGS: 89 89 return; ··· 265 265 $errors[] = $error; 266 266 } 267 267 268 - $slug_builder = clone $object; 269 - $slug_builder->setPhrictionSlug($name); 270 - $slug = $slug_builder->getPrimarySlug(); 268 + $slug = PhabricatorSlug::normalizeProjectSlug($name); 269 + 271 270 $slug_used_already = id(new PhabricatorProjectSlug()) 272 271 ->loadOneWhere('slug = %s', $slug); 273 272 if ($slug_used_already &&
-13
src/applications/project/query/PhabricatorProjectQuery.php
··· 7 7 private $phids; 8 8 private $memberPHIDs; 9 9 private $slugs; 10 - private $phrictionSlugs; 11 10 private $names; 12 11 private $nameTokens; 13 12 private $icons; ··· 47 46 48 47 public function withSlugs(array $slugs) { 49 48 $this->slugs = $slugs; 50 - return $this; 51 - } 52 - 53 - public function withPhrictionSlugs(array $slugs) { 54 - $this->phrictionSlugs = $slugs; 55 49 return $this; 56 50 } 57 51 ··· 306 300 $conn, 307 301 'slug.slug IN (%Ls)', 308 302 $this->slugs); 309 - } 310 - 311 - if ($this->phrictionSlugs !== null) { 312 - $where[] = qsprintf( 313 - $conn, 314 - 'phrictionSlug IN (%Ls)', 315 - $this->phrictionSlugs); 316 303 } 317 304 318 305 if ($this->names !== null) {
+2 -15
src/applications/project/storage/PhabricatorProject.php
··· 189 189 return $this->assertAttached($this->memberPHIDs); 190 190 } 191 191 192 - public function setPhrictionSlug($slug) { 193 - 194 - // NOTE: We're doing a little magic here and stripping out '/' so that 195 - // project pages always appear at top level under projects/ even if the 196 - // display name is "Hack / Slash" or similar (it will become 197 - // 'hack_slash' instead of 'hack/slash'). 198 - 199 - $slug = str_replace('/', ' ', $slug); 200 - $slug = PhabricatorSlug::normalize($slug); 201 - $this->phrictionSlug = $slug; 192 + public function setPrimarySlug($slug) { 193 + $this->phrictionSlug = $slug.'/'; 202 194 return $this; 203 - } 204 - 205 - public function getFullPhrictionSlug() { 206 - $slug = $this->getPhrictionSlug(); 207 - return 'projects/'.$slug; 208 195 } 209 196 210 197 // TODO - once we sever project => phriction automagicalness,
+37 -4
src/infrastructure/util/PhabricatorSlug.php
··· 4 4 5 5 public static function normalizeProjectSlug($slug) { 6 6 $slug = str_replace('/', ' ', $slug); 7 - $slug = self::normalize($slug); 7 + $slug = self::normalize($slug, $hashtag = true); 8 8 return rtrim($slug, '/'); 9 9 } 10 10 11 - public static function normalize($slug) { 11 + public static function normalize($slug, $hashtag = false) { 12 12 $slug = preg_replace('@/+@', '/', $slug); 13 13 $slug = trim($slug, '/'); 14 14 $slug = phutil_utf8_strtolower($slug); 15 - $slug = preg_replace("@[\\x00-\\x19#%&+=\\\\?<> ]+@", '_', $slug); 15 + 16 + $ban = 17 + // Ban control characters since users can't type them and they create 18 + // various other problems with parsing and rendering. 19 + "\\x00-\\x19". 20 + 21 + // Ban characters with special meanings in URIs (and spaces), since we 22 + // want slugs to produce nice URIs. 23 + "#%&+=?". 24 + " ". 25 + 26 + // Ban backslashes and various brackets for parsing and URI quality. 27 + "\\\\". 28 + "<>{}\\[\\]". 29 + 30 + // Ban single and double quotes since they can mess up URIs. 31 + "'". 32 + '"'; 33 + 34 + // In hashtag mode (used for Project hashtags), ban additional characters 35 + // which cause parsing problems. 36 + if ($hashtag) { 37 + $ban .= '`~!@$^*,:;(|)'; 38 + } 39 + 40 + $slug = preg_replace('(['.$ban.']+)', '_', $slug); 16 41 $slug = preg_replace('@_+@', '_', $slug); 17 42 43 + $parts = explode('/', $slug); 44 + 45 + // Convert "_-_" into "-". This is a little nicer for inputs with 46 + // hyphens used as internal separators, and turns an input like "A - B" 47 + // into "a-b" instead of "a_-_b"; 48 + foreach ($parts as $key => $part) { 49 + $parts[$key] = str_replace('_-_', '-', $part); 50 + } 51 + 18 52 // Remove leading and trailing underscores from each component, if the 19 53 // component has not been reduced to a single underscore. For example, "a?" 20 54 // converts to "a", but "??" converts to "_". 21 - $parts = explode('/', $slug); 22 55 foreach ($parts as $key => $part) { 23 56 if ($part != '_') { 24 57 $parts[$key] = trim($part, '_');
+21
src/infrastructure/util/__tests__/PhabricatorSlugTestCase.php
··· 34 34 'a/??/c' => 'a/_/c/', 35 35 'a/?b/c' => 'a/b/c/', 36 36 'a/b?/c' => 'a/b/c/', 37 + 'a - b' => 'a-b/', 38 + 'a[b]' => 'a_b/', 39 + 'ab!' => 'ab!/', 37 40 ); 38 41 39 42 foreach ($slugs as $slug => $normal) { ··· 41 44 $normal, 42 45 PhabricatorSlug::normalize($slug), 43 46 pht("Normalization of '%s'", $slug)); 47 + } 48 + } 49 + 50 + public function testProjectSlugs() { 51 + $slugs = array( 52 + 'a:b' => 'a_b', 53 + 'a!b' => 'a_b', 54 + 'a - b' => 'a-b', 55 + '' => '', 56 + 'Demonology: HSA (Hexes, Signs, Alchemy)' => 57 + 'demonology_hsa_hexes_signs_alchemy', 58 + ); 59 + 60 + foreach ($slugs as $slug => $normal) { 61 + $this->assertEqual( 62 + $normal, 63 + PhabricatorSlug::normalizeProjectSlug($slug), 64 + pht('Hashtag normalization of "%s"', $slug)); 44 65 } 45 66 } 46 67