@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.

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

+181 -157
+2
src/__phutil_library_map__.php
··· 3666 3666 'PhabricatorProjectSilenceController' => 'applications/project/controller/PhabricatorProjectSilenceController.php', 3667 3667 'PhabricatorProjectSilencedEdgeType' => 'applications/project/edge/PhabricatorProjectSilencedEdgeType.php', 3668 3668 'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php', 3669 + 'PhabricatorProjectSlugsTransaction' => 'applications/project/xaction/PhabricatorProjectSlugsTransaction.php', 3669 3670 'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php', 3670 3671 'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php', 3671 3672 'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php', ··· 9077 9078 'PhabricatorProjectSilenceController' => 'PhabricatorProjectController', 9078 9079 'PhabricatorProjectSilencedEdgeType' => 'PhabricatorEdgeType', 9079 9080 'PhabricatorProjectSlug' => 'PhabricatorProjectDAO', 9081 + 'PhabricatorProjectSlugsTransaction' => 'PhabricatorProjectTransactionType', 9080 9082 'PhabricatorProjectStandardCustomField' => array( 9081 9083 'PhabricatorProjectCustomField', 9082 9084 'PhabricatorStandardCustomFieldInterface',
+6 -6
src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
··· 362 362 363 363 $xactions = array(); 364 364 $xactions[] = id(new PhabricatorProjectTransaction()) 365 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) 365 + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) 366 366 ->setNewValue(array($name)); 367 367 $this->applyTransactions($project, $user, $xactions); 368 368 ··· 386 386 ->setNewValue($name2); 387 387 388 388 $xactions[] = id(new PhabricatorProjectTransaction()) 389 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) 389 + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) 390 390 ->setNewValue(array($name2)); 391 391 392 392 $this->applyTransactions($project, $user, $xactions); ··· 416 416 $xactions = array(); 417 417 418 418 $xactions[] = id(new PhabricatorProjectTransaction()) 419 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) 419 + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) 420 420 ->setNewValue(array($input, $input)); 421 421 422 422 $this->applyTransactions($project, $user, $xactions); ··· 448 448 $xactions = array(); 449 449 450 450 $xactions[] = id(new PhabricatorProjectTransaction()) 451 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) 451 + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) 452 452 ->setNewValue(array($input)); 453 453 454 454 $this->applyTransactions($project, $user, $xactions); ··· 474 474 $xactions = array(); 475 475 476 476 $xactions[] = id(new PhabricatorProjectTransaction()) 477 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) 477 + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) 478 478 ->setNewValue(array($input)); 479 479 480 480 $caught = null; ··· 605 605 ->setNewValue($name); 606 606 607 607 $xactions[] = id(new PhabricatorProjectTransaction()) 608 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) 608 + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) 609 609 ->setNewValue(array($slug)); 610 610 611 611 $this->applyTransactions($project, $user, $xactions);
+2 -1
src/applications/project/conduit/ProjectCreateConduitAPIMethod.php
··· 64 64 65 65 if ($request->getValue('tags')) { 66 66 $xactions[] = id(new PhabricatorProjectTransaction()) 67 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) 67 + ->setTransactionType( 68 + PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) 68 69 ->setNewValue($request->getValue('tags')); 69 70 } 70 71
+2 -83
src/applications/project/editor/PhabricatorProjectTransactionEditor.php
··· 30 30 $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 31 31 $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; 32 32 33 - $types[] = PhabricatorProjectTransaction::TYPE_SLUGS; 34 33 $types[] = PhabricatorProjectTransaction::TYPE_STATUS; 35 34 $types[] = PhabricatorProjectTransaction::TYPE_IMAGE; 36 35 $types[] = PhabricatorProjectTransaction::TYPE_ICON; ··· 51 50 PhabricatorApplicationTransaction $xaction) { 52 51 53 52 switch ($xaction->getTransactionType()) { 54 - case PhabricatorProjectTransaction::TYPE_SLUGS: 55 - $slugs = $object->getSlugs(); 56 - $slugs = mpull($slugs, 'getSlug', 'getSlug'); 57 - unset($slugs[$object->getPrimarySlug()]); 58 - return array_keys($slugs); 59 53 case PhabricatorProjectTransaction::TYPE_STATUS: 60 54 return $object->getStatus(); 61 55 case PhabricatorProjectTransaction::TYPE_IMAGE: ··· 105 99 return null; 106 100 } 107 101 return $value; 108 - case PhabricatorProjectTransaction::TYPE_SLUGS: 109 - return $this->normalizeSlugs($xaction->getNewValue()); 110 102 } 111 103 112 104 return parent::getCustomTransactionNewValue($object, $xaction); ··· 117 109 PhabricatorApplicationTransaction $xaction) { 118 110 119 111 switch ($xaction->getTransactionType()) { 120 - case PhabricatorProjectTransaction::TYPE_SLUGS: 121 - return; 122 112 case PhabricatorProjectTransaction::TYPE_STATUS: 123 113 $object->setStatus($xaction->getNewValue()); 124 114 return; ··· 167 157 $new = $xaction->getNewValue(); 168 158 169 159 switch ($xaction->getTransactionType()) { 170 - case PhabricatorProjectTransaction::TYPE_SLUGS: 171 - $old = $xaction->getOldValue(); 172 - $new = $xaction->getNewValue(); 173 - $add = array_diff($new, $old); 174 - $rem = array_diff($old, $new); 175 - 176 - foreach ($add as $slug) { 177 - $this->addSlug($object, $slug, true); 178 - } 179 - 180 - $this->removeSlugs($object, $rem); 181 - return; 182 160 case PhabricatorProjectTransaction::TYPE_STATUS: 183 161 case PhabricatorProjectTransaction::TYPE_IMAGE: 184 162 case PhabricatorProjectTransaction::TYPE_ICON: ··· 278 256 $errors = parent::validateTransaction($object, $type, $xactions); 279 257 280 258 switch ($type) { 281 - case PhabricatorProjectTransaction::TYPE_SLUGS: 282 - if (!$xactions) { 283 - break; 284 - } 285 - 286 - $slug_xaction = last($xactions); 287 - 288 - $new = $slug_xaction->getNewValue(); 289 - 290 - $invalid = array(); 291 - foreach ($new as $slug) { 292 - if (!PhabricatorSlug::isValidProjectSlug($slug)) { 293 - $invalid[] = $slug; 294 - } 295 - } 296 - 297 - if ($invalid) { 298 - $errors[] = new PhabricatorApplicationTransactionValidationError( 299 - $type, 300 - pht('Invalid'), 301 - pht( 302 - 'Hashtags must contain at least one letter or number. %s '. 303 - 'project hashtag(s) are invalid: %s.', 304 - phutil_count($invalid), 305 - implode(', ', $invalid)), 306 - $slug_xaction); 307 - break; 308 - } 309 - 310 - $new = $this->normalizeSlugs($new); 311 - 312 - if ($new) { 313 - $slugs_used_already = id(new PhabricatorProjectSlug()) 314 - ->loadAllWhere('slug IN (%Ls)', $new); 315 - } else { 316 - // The project doesn't have any extra slugs. 317 - $slugs_used_already = array(); 318 - } 319 - 320 - $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID'); 321 - foreach ($slugs_used_already as $project_phid => $used_slugs) { 322 - if ($project_phid == $object->getPHID()) { 323 - continue; 324 - } 325 - 326 - $used_slug_strs = mpull($used_slugs, 'getSlug'); 327 - 328 - $error = new PhabricatorApplicationTransactionValidationError( 329 - $type, 330 - pht('Invalid'), 331 - pht( 332 - '%s project hashtag(s) are already used by other projects: %s.', 333 - phutil_count($used_slug_strs), 334 - implode(', ', $used_slug_strs)), 335 - $slug_xaction); 336 - $errors[] = $error; 337 - } 338 - 339 - break; 340 259 case PhabricatorProjectTransaction::TYPE_PARENT: 341 260 case PhabricatorProjectTransaction::TYPE_MILESTONE: 342 261 if (!$xactions) { ··· 674 593 ->save(); 675 594 } 676 595 677 - private function removeSlugs(PhabricatorProject $project, array $slugs) { 596 + public function removeSlugs(PhabricatorProject $project, array $slugs) { 678 597 if (!$slugs) { 679 598 return; 680 599 } ··· 696 615 } 697 616 } 698 617 699 - private function normalizeSlugs(array $slugs) { 618 + public function normalizeSlugs(array $slugs) { 700 619 foreach ($slugs as $key => $slug) { 701 620 $slugs[$key] = PhabricatorSlug::normalizeProjectSlug($slug); 702 621 }
+2 -1
src/applications/project/engine/PhabricatorProjectEditEngine.php
··· 262 262 id(new PhabricatorStringListEditField()) 263 263 ->setKey('slugs') 264 264 ->setLabel(pht('Additional Hashtags')) 265 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) 265 + ->setTransactionType( 266 + PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) 266 267 ->setDescription(pht('Additional project slugs.')) 267 268 ->setConduitDescription(pht('Change project slugs.')) 268 269 ->setConduitTypeDescription(pht('New list of slugs.'))
+2 -66
src/applications/project/storage/PhabricatorProjectTransaction.php
··· 3 3 final class PhabricatorProjectTransaction 4 4 extends PhabricatorModularTransaction { 5 5 6 - const TYPE_SLUGS = 'project:slugs'; 7 6 const TYPE_STATUS = 'project:status'; 8 7 const TYPE_IMAGE = 'project:image'; 9 8 const TYPE_ICON = 'project:icon'; ··· 134 133 return 'fa-photo'; 135 134 case self::TYPE_MEMBERS: 136 135 return 'fa-user'; 137 - case self::TYPE_SLUGS: 138 - return 'fa-tag'; 139 136 } 140 137 return parent::getIcon(); 141 138 } ··· 212 209 } 213 210 break; 214 211 215 - case self::TYPE_SLUGS: 216 - $add = array_diff($new, $old); 217 - $rem = array_diff($old, $new); 218 - 219 - if ($add && $rem) { 220 - return pht( 221 - '%s changed project hashtag(s), added %d: %s; removed %d: %s.', 222 - $author_handle, 223 - count($add), 224 - $this->renderSlugList($add), 225 - count($rem), 226 - $this->renderSlugList($rem)); 227 - } else if ($add) { 228 - return pht( 229 - '%s added %d project hashtag(s): %s.', 230 - $author_handle, 231 - count($add), 232 - $this->renderSlugList($add)); 233 - } else if ($rem) { 234 - return pht( 235 - '%s removed %d project hashtag(s): %s.', 236 - $author_handle, 237 - count($rem), 238 - $this->renderSlugList($rem)); 239 - } 240 - break; 241 - 242 212 case self::TYPE_MEMBERS: 243 213 $add = array_diff($new, $old); 244 214 $rem = array_diff($old, $new); ··· 380 350 $author_handle, 381 351 $object_handle); 382 352 } 383 - 384 - case self::TYPE_SLUGS: 385 - $add = array_diff($new, $old); 386 - $rem = array_diff($old, $new); 387 - 388 - if ($add && $rem) { 389 - return pht( 390 - '%s changed %s hashtag(s), added %d: %s; removed %d: %s.', 391 - $author_handle, 392 - $object_handle, 393 - count($add), 394 - $this->renderSlugList($add), 395 - count($rem), 396 - $this->renderSlugList($rem)); 397 - } else if ($add) { 398 - return pht( 399 - '%s added %d %s hashtag(s): %s.', 400 - $author_handle, 401 - count($add), 402 - $object_handle, 403 - $this->renderSlugList($add)); 404 - } else if ($rem) { 405 - return pht( 406 - '%s removed %d %s hashtag(s): %s.', 407 - $author_handle, 408 - count($rem), 409 - $object_handle, 410 - $this->renderSlugList($rem)); 411 - } 412 - 413 353 } 414 354 415 355 return parent::getTitleForFeed(); ··· 418 358 public function getMailTags() { 419 359 $tags = array(); 420 360 switch ($this->getTransactionType()) { 421 - case self::TYPE_NAME: 422 - case self::TYPE_SLUGS: 361 + case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: 362 + case PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE: 423 363 case self::TYPE_IMAGE: 424 364 case self::TYPE_ICON: 425 365 case self::TYPE_COLOR: ··· 445 385 break; 446 386 } 447 387 return $tags; 448 - } 449 - 450 - private function renderSlugList($slugs) { 451 - return implode(', ', $slugs); 452 388 } 453 389 454 390 }
+165
src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectSlugsTransaction 4 + extends PhabricatorProjectTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'project:slugs'; 7 + 8 + public function generateOldValue($object) { 9 + $slugs = $object->getSlugs(); 10 + $slugs = mpull($slugs, 'getSlug', 'getSlug'); 11 + unset($slugs[$object->getPrimarySlug()]); 12 + return array_keys($slugs); 13 + } 14 + 15 + public function generateNewValue($object, $value) { 16 + return $this->getEditor()->normalizeSlugs($value); 17 + } 18 + 19 + public function applyInternalEffects($object, $value) { 20 + return; 21 + } 22 + 23 + public function applyExternalEffects($object, $value) { 24 + $old = $this->getOldValue(); 25 + $new = $value; 26 + $add = array_diff($new, $old); 27 + $rem = array_diff($old, $new); 28 + 29 + foreach ($add as $slug) { 30 + $this->getEditor()->addSlug($object, $slug, true); 31 + } 32 + 33 + $this->getEditor()->removeSlugs($object, $rem); 34 + } 35 + 36 + public function getTitle() { 37 + $old = $this->getOldValue(); 38 + $new = $this->getNewValue(); 39 + 40 + $add = array_diff($new, $old); 41 + $rem = array_diff($old, $new); 42 + 43 + if ($add && $rem) { 44 + return pht( 45 + '%s changed project hashtag(s), added %d: %s; removed %d: %s.', 46 + $this->renderAuthor(), 47 + count($add), 48 + $this->renderSlugList($add), 49 + count($rem), 50 + $this->renderSlugList($rem)); 51 + } else if ($add) { 52 + return pht( 53 + '%s added %d project hashtag(s): %s.', 54 + $this->renderAuthor(), 55 + count($add), 56 + $this->renderSlugList($add)); 57 + } else if ($rem) { 58 + return pht( 59 + '%s removed %d project hashtag(s): %s.', 60 + $this->renderAuthor(), 61 + count($rem), 62 + $this->renderSlugList($rem)); 63 + } 64 + break; 65 + } 66 + 67 + public function getTitleForFeed() { 68 + $old = $this->getOldValue(); 69 + $new = $this->getNewValue(); 70 + 71 + $add = array_diff($new, $old); 72 + $rem = array_diff($old, $new); 73 + 74 + if ($add && $rem) { 75 + return pht( 76 + '%s changed %s hashtag(s), added %d: %s; removed %d: %s.', 77 + $this->renderAuthor(), 78 + $this->renderObject(), 79 + count($add), 80 + $this->renderSlugList($add), 81 + count($rem), 82 + $this->renderSlugList($rem)); 83 + } else if ($add) { 84 + return pht( 85 + '%s added %d %s hashtag(s): %s.', 86 + $this->renderAuthor(), 87 + count($add), 88 + $this->renderObject(), 89 + $this->renderSlugList($add)); 90 + } else if ($rem) { 91 + return pht( 92 + '%s removed %d %s hashtag(s): %s.', 93 + $this->renderAuthor(), 94 + count($rem), 95 + $this->renderObject(), 96 + $this->renderSlugList($rem)); 97 + } 98 + } 99 + 100 + public function getIcon() { 101 + return 'fa-tag'; 102 + } 103 + 104 + public function validateTransactions($object, array $xactions) { 105 + $errors = array(); 106 + 107 + if (!$xactions) { 108 + return $errors; 109 + } 110 + 111 + $slug_xaction = last($xactions); 112 + 113 + $new = $slug_xaction->getNewValue(); 114 + 115 + $invalid = array(); 116 + foreach ($new as $slug) { 117 + if (!PhabricatorSlug::isValidProjectSlug($slug)) { 118 + $invalid[] = $slug; 119 + } 120 + } 121 + 122 + if ($invalid) { 123 + $errors[] = $this->newInvalidError( 124 + pht( 125 + 'Hashtags must contain at least one letter or number. %s '. 126 + 'project hashtag(s) are invalid: %s.', 127 + phutil_count($invalid), 128 + implode(', ', $invalid))); 129 + 130 + return $errors; 131 + } 132 + 133 + $new = $this->getEditor()->normalizeSlugs($new); 134 + 135 + if ($new) { 136 + $slugs_used_already = id(new PhabricatorProjectSlug()) 137 + ->loadAllWhere('slug IN (%Ls)', $new); 138 + } else { 139 + // The project doesn't have any extra slugs. 140 + $slugs_used_already = array(); 141 + } 142 + 143 + $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID'); 144 + foreach ($slugs_used_already as $project_phid => $used_slugs) { 145 + if ($project_phid == $object->getPHID()) { 146 + continue; 147 + } 148 + 149 + $used_slug_strs = mpull($used_slugs, 'getSlug'); 150 + 151 + $errors[] = $this->newInvalidError( 152 + pht( 153 + '%s project hashtag(s) are already used by other projects: %s.', 154 + phutil_count($used_slug_strs), 155 + implode(', ', $used_slug_strs))); 156 + } 157 + 158 + return $errors; 159 + } 160 + 161 + private function renderSlugList($slugs) { 162 + return implode(', ', $slugs); 163 + } 164 + 165 + }