@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 names to modular transactions

Summary: Also changes access modifiers on `PhabricatorProjectTransactionEditor` and sets up `storage` for `applyExternalEffects`.

Test Plan: Created new projects, attempted to create without name, with too long of a name, and with a name that conflicts with other projects and observed expected errors.

Reviewers: #blessed_reviewers, epriestley

Reviewed By: #blessed_reviewers, epriestley

Subscribers: epriestley

Maniphest Tasks: T12673

Differential Revision: https://secure.phabricator.com/D17947

+142 -118
+1 -1
resources/sql/patches/20131020.pxactionmig.php
··· 32 32 $project_phid = $project_row['phid']; 33 33 34 34 $type_map = array( 35 - 'name' => PhabricatorProjectTransaction::TYPE_NAME, 35 + 'name' => PhabricatorProjectNameTransaction::TRANSACTIONTYPE, 36 36 'members' => PhabricatorProjectTransaction::TYPE_MEMBERS, 37 37 'status' => PhabricatorProjectTransaction::TYPE_STATUS, 38 38 'canview' => PhabricatorTransactions::TYPE_VIEW_POLICY,
+5 -1
src/__phutil_library_map__.php
··· 3644 3644 'PhabricatorProjectMenuItemController' => 'applications/project/controller/PhabricatorProjectMenuItemController.php', 3645 3645 'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php', 3646 3646 'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php', 3647 + 'PhabricatorProjectNameTransaction' => 'applications/project/xaction/PhabricatorProjectNameTransaction.php', 3647 3648 'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php', 3648 3649 'PhabricatorProjectObjectHasProjectEdgeType' => 'applications/project/edge/PhabricatorProjectObjectHasProjectEdgeType.php', 3649 3650 'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php', ··· 3674 3675 'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php', 3675 3676 'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php', 3676 3677 'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php', 3678 + 'PhabricatorProjectTransactionType' => 'applications/project/xaction/PhabricatorProjectTransactionType.php', 3677 3679 'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php', 3678 3680 'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php', 3679 3681 'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php', ··· 9053 9055 'PhabricatorProjectMenuItemController' => 'PhabricatorProjectController', 9054 9056 'PhabricatorProjectMoveController' => 'PhabricatorProjectController', 9055 9057 'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar', 9058 + 'PhabricatorProjectNameTransaction' => 'PhabricatorProjectTransactionType', 9056 9059 'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource', 9057 9060 'PhabricatorProjectObjectHasProjectEdgeType' => 'PhabricatorEdgeType', 9058 9061 'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', ··· 9083 9086 'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController', 9084 9087 'PhabricatorProjectSubprojectsProfileMenuItem' => 'PhabricatorProfileMenuItem', 9085 9088 'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator', 9086 - 'PhabricatorProjectTransaction' => 'PhabricatorApplicationTransaction', 9089 + 'PhabricatorProjectTransaction' => 'PhabricatorModularTransaction', 9087 9090 'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 9088 9091 'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 9092 + 'PhabricatorProjectTransactionType' => 'PhabricatorModularTransactionType', 9089 9093 'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener', 9090 9094 'PhabricatorProjectUpdateController' => 'PhabricatorProjectController', 9091 9095 'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
+7 -6
src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
··· 356 356 357 357 $xactions = array(); 358 358 $xactions[] = id(new PhabricatorProjectTransaction()) 359 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) 359 + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) 360 360 ->setNewValue($name); 361 361 $this->applyTransactions($project, $user, $xactions); 362 362 ··· 382 382 $xactions = array(); 383 383 384 384 $xactions[] = id(new PhabricatorProjectTransaction()) 385 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) 385 + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) 386 386 ->setNewValue($name2); 387 387 388 388 $xactions[] = id(new PhabricatorProjectTransaction()) ··· 503 503 $xactions = array(); 504 504 505 505 $xactions[] = id(new PhabricatorProjectTransaction()) 506 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) 506 + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) 507 507 ->setNewValue($name); 508 508 509 509 $xactions[] = id(new PhabricatorProjectTransaction()) ··· 601 601 $xactions = array(); 602 602 603 603 $xactions[] = id(new PhabricatorProjectTransaction()) 604 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) 604 + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) 605 605 ->setNewValue($name); 606 606 607 607 $xactions[] = id(new PhabricatorProjectTransaction()) ··· 1290 1290 $new_name = $proj->getName().' '.mt_rand(); 1291 1291 1292 1292 $xaction = new PhabricatorProjectTransaction(); 1293 - $xaction->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME); 1293 + $xaction->setTransactionType( 1294 + PhabricatorProjectNameTransaction::TRANSACTIONTYPE); 1294 1295 $xaction->setNewValue($new_name); 1295 1296 1296 1297 $this->applyTransactions($proj, $user, array($xaction)); ··· 1440 1441 $xactions = array(); 1441 1442 1442 1443 $xactions[] = id(new PhabricatorProjectTransaction()) 1443 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) 1444 + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) 1444 1445 ->setNewValue($name); 1445 1446 1446 1447 if ($parent) {
+1 -1
src/applications/project/conduit/ProjectCreateConduitAPIMethod.php
··· 42 42 $user); 43 43 44 44 $project = PhabricatorProject::initializeNewProject($user); 45 - $type_name = PhabricatorProjectTransaction::TYPE_NAME; 45 + $type_name = PhabricatorProjectNameTransaction::TRANSACTIONTYPE; 46 46 $members = $request->getValue('members'); 47 47 $xactions = array(); 48 48
+3 -77
src/applications/project/editor/PhabricatorProjectTransactionEditor.php
··· 10 10 return $this; 11 11 } 12 12 13 - private function getIsMilestone() { 13 + public function getIsMilestone() { 14 14 return $this->isMilestone; 15 15 } 16 16 ··· 30 30 $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 31 31 $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; 32 32 33 - $types[] = PhabricatorProjectTransaction::TYPE_NAME; 34 33 $types[] = PhabricatorProjectTransaction::TYPE_SLUGS; 35 34 $types[] = PhabricatorProjectTransaction::TYPE_STATUS; 36 35 $types[] = PhabricatorProjectTransaction::TYPE_IMAGE; ··· 52 51 PhabricatorApplicationTransaction $xaction) { 53 52 54 53 switch ($xaction->getTransactionType()) { 55 - case PhabricatorProjectTransaction::TYPE_NAME: 56 - return $object->getName(); 57 54 case PhabricatorProjectTransaction::TYPE_SLUGS: 58 55 $slugs = $object->getSlugs(); 59 56 $slugs = mpull($slugs, 'getSlug', 'getSlug'); ··· 90 87 PhabricatorApplicationTransaction $xaction) { 91 88 92 89 switch ($xaction->getTransactionType()) { 93 - case PhabricatorProjectTransaction::TYPE_NAME: 94 90 case PhabricatorProjectTransaction::TYPE_STATUS: 95 91 case PhabricatorProjectTransaction::TYPE_IMAGE: 96 92 case PhabricatorProjectTransaction::TYPE_ICON: ··· 121 117 PhabricatorApplicationTransaction $xaction) { 122 118 123 119 switch ($xaction->getTransactionType()) { 124 - case PhabricatorProjectTransaction::TYPE_NAME: 125 - $name = $xaction->getNewValue(); 126 - $object->setName($name); 127 - if (!$this->getIsMilestone()) { 128 - $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($name)); 129 - } 130 - return; 131 120 case PhabricatorProjectTransaction::TYPE_SLUGS: 132 121 return; 133 122 case PhabricatorProjectTransaction::TYPE_STATUS: ··· 178 167 $new = $xaction->getNewValue(); 179 168 180 169 switch ($xaction->getTransactionType()) { 181 - case PhabricatorProjectTransaction::TYPE_NAME: 182 - // First, add the old name as a secondary slug; this is helpful 183 - // for renames and generally a good thing to do. 184 - if (!$this->getIsMilestone()) { 185 - if ($old !== null) { 186 - $this->addSlug($object, $old, false); 187 - } 188 - $this->addSlug($object, $new, false); 189 - } 190 - return; 191 170 case PhabricatorProjectTransaction::TYPE_SLUGS: 192 171 $old = $xaction->getOldValue(); 193 172 $new = $xaction->getNewValue(); ··· 299 278 $errors = parent::validateTransaction($object, $type, $xactions); 300 279 301 280 switch ($type) { 302 - case PhabricatorProjectTransaction::TYPE_NAME: 303 - $missing = $this->validateIsEmptyTextField( 304 - $object->getName(), 305 - $xactions); 306 - 307 - if ($missing) { 308 - $error = new PhabricatorApplicationTransactionValidationError( 309 - $type, 310 - pht('Required'), 311 - pht('Project name is required.'), 312 - nonempty(last($xactions), null)); 313 - 314 - $error->setIsMissingFieldError(true); 315 - $errors[] = $error; 316 - } 317 - 318 - if (!$xactions) { 319 - break; 320 - } 321 - 322 - if ($this->getIsMilestone()) { 323 - break; 324 - } 325 - 326 - $name = last($xactions)->getNewValue(); 327 - 328 - if (!PhabricatorSlug::isValidProjectSlug($name)) { 329 - $errors[] = new PhabricatorApplicationTransactionValidationError( 330 - $type, 331 - pht('Invalid'), 332 - pht( 333 - 'Project names must contain at least one letter or number.'), 334 - last($xactions)); 335 - break; 336 - } 337 - 338 - $slug = PhabricatorSlug::normalizeProjectSlug($name); 339 - 340 - $slug_used_already = id(new PhabricatorProjectSlug()) 341 - ->loadOneWhere('slug = %s', $slug); 342 - if ($slug_used_already && 343 - $slug_used_already->getProjectPHID() != $object->getPHID()) { 344 - $error = new PhabricatorApplicationTransactionValidationError( 345 - $type, 346 - pht('Duplicate'), 347 - pht( 348 - 'Project name generates the same hashtag ("%s") as another '. 349 - 'existing project. Choose a unique name.', 350 - '#'.$slug), 351 - nonempty(last($xactions), null)); 352 - $errors[] = $error; 353 - } 354 - break; 355 281 case PhabricatorProjectTransaction::TYPE_SLUGS: 356 282 if (!$xactions) { 357 283 break; ··· 497 423 PhabricatorApplicationTransaction $xaction) { 498 424 499 425 switch ($xaction->getTransactionType()) { 500 - case PhabricatorProjectTransaction::TYPE_NAME: 426 + case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: 501 427 case PhabricatorProjectTransaction::TYPE_STATUS: 502 428 case PhabricatorProjectTransaction::TYPE_IMAGE: 503 429 case PhabricatorProjectTransaction::TYPE_ICON: ··· 717 643 return parent::applyFinalEffects($object, $xactions); 718 644 } 719 645 720 - private function addSlug(PhabricatorProject $project, $slug, $force) { 646 + public function addSlug(PhabricatorProject $project, $slug, $force) { 721 647 $slug = PhabricatorSlug::normalizeProjectSlug($slug); 722 648 $table = new PhabricatorProjectSlug(); 723 649 $project_phid = $project->getPHID();
+1 -1
src/applications/project/engine/PhabricatorProjectEditEngine.php
··· 235 235 id(new PhabricatorTextEditField()) 236 236 ->setKey('name') 237 237 ->setLabel(pht('Name')) 238 - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) 238 + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) 239 239 ->setIsRequired(true) 240 240 ->setDescription(pht('Project name.')) 241 241 ->setConduitDescription(pht('Rename the project'))
+1 -1
src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php
··· 16 16 $xactions = array(); 17 17 18 18 $xactions[] = $this->newTransaction( 19 - PhabricatorProjectTransaction::TYPE_NAME, 19 + PhabricatorProjectNameTransaction::TRANSACTIONTYPE, 20 20 $this->newProjectTitle()); 21 21 22 22 $xactions[] = $this->newTransaction(
+5 -30
src/applications/project/storage/PhabricatorProjectTransaction.php
··· 1 1 <?php 2 2 3 3 final class PhabricatorProjectTransaction 4 - extends PhabricatorApplicationTransaction { 4 + extends PhabricatorModularTransaction { 5 5 6 - const TYPE_NAME = 'project:name'; 7 6 const TYPE_SLUGS = 'project:slugs'; 8 7 const TYPE_STATUS = 'project:status'; 9 8 const TYPE_IMAGE = 'project:image'; ··· 31 30 32 31 public function getApplicationTransactionType() { 33 32 return PhabricatorProjectProjectPHIDType::TYPECONST; 33 + } 34 + 35 + public function getBaseTransactionClass() { 36 + return 'PhabricatorProjectTransactionType'; 34 37 } 35 38 36 39 public function getRequiredHandlePHIDs() { ··· 149 152 '%s created this project.', 150 153 $this->renderHandleLink($author_phid)); 151 154 152 - case self::TYPE_NAME: 153 - if ($old === null) { 154 - return pht( 155 - '%s created this project.', 156 - $author_handle); 157 - } else { 158 - return pht( 159 - '%s renamed this project from "%s" to "%s".', 160 - $author_handle, 161 - $old, 162 - $new); 163 - } 164 - break; 165 - 166 155 case self::TYPE_STATUS: 167 156 if ($old == 0) { 168 157 return pht( ··· 329 318 $new = $this->getNewValue(); 330 319 331 320 switch ($this->getTransactionType()) { 332 - case self::TYPE_NAME: 333 - if ($old === null) { 334 - return pht( 335 - '%s created %s.', 336 - $author_handle, 337 - $object_handle); 338 - } else { 339 - return pht( 340 - '%s renamed %s from "%s" to "%s".', 341 - $author_handle, 342 - $object_handle, 343 - $old, 344 - $new); 345 - } 346 321 case self::TYPE_STATUS: 347 322 if ($old == 0) { 348 323 return pht(
+112
src/applications/project/xaction/PhabricatorProjectNameTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectNameTransaction 4 + extends PhabricatorProjectTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'project:name'; 7 + 8 + public function generateOldValue($object) { 9 + return $object->getName(); 10 + } 11 + 12 + public function applyInternalEffects($object, $value) { 13 + $object->setName($value); 14 + if (!$this->getEditor()->getIsMilestone()) { 15 + $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($value)); 16 + } 17 + } 18 + 19 + public function applyExternalEffects($object, $value) { 20 + $old = $this->getOldValue(); 21 + 22 + // First, add the old name as a secondary slug; this is helpful 23 + // for renames and generally a good thing to do. 24 + if (!$this->getEditor()->getIsMilestone()) { 25 + if ($old !== null) { 26 + $this->getEditor()->addSlug($object, $old, false); 27 + } 28 + $this->getEditor()->addSlug($object, $value, false); 29 + } 30 + return; 31 + } 32 + 33 + public function getTitle() { 34 + $old = $this->getOldValue(); 35 + if ($old === null) { 36 + return pht( 37 + '%s created this project.', 38 + $this->renderAuthor()); 39 + } else { 40 + return pht( 41 + '%s renamed this project from %s to %s.', 42 + $this->renderAuthor(), 43 + $this->renderOldValue(), 44 + $this->renderNewValue()); 45 + } 46 + } 47 + 48 + public function getTitleForFeed() { 49 + $old = $this->getOldValue(); 50 + if ($old === null) { 51 + return pht( 52 + '%s created %s.', 53 + $this->renderAuthor(), 54 + $this->renderObject()); 55 + } else { 56 + return pht( 57 + '%s renamed %s from %s to %s.', 58 + $this->renderAuthor(), 59 + $this->renderObject(), 60 + $this->renderOldValue(), 61 + $this->renderNewValue()); 62 + } 63 + } 64 + 65 + public function validateTransactions($object, array $xactions) { 66 + $errors = array(); 67 + 68 + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { 69 + $errors[] = $this->newRequiredError(pht('Projects must have a name.')); 70 + } 71 + 72 + $max_length = $object->getColumnMaximumByteLength('name'); 73 + foreach ($xactions as $xaction) { 74 + $new_value = $xaction->getNewValue(); 75 + $new_length = strlen($new_value); 76 + if ($new_length > $max_length) { 77 + $errors[] = $this->newInvalidError( 78 + pht( 79 + 'Project names must not be longer than %s character(s).', 80 + new PhutilNumber($max_length))); 81 + } 82 + } 83 + 84 + if ($this->getEditor()->getIsMilestone() || !$xactions) { 85 + return $errors; 86 + } 87 + 88 + $name = last($xactions)->getNewValue(); 89 + 90 + if (!PhabricatorSlug::isValidProjectSlug($name)) { 91 + $errors[] = $this->newInvalidError( 92 + pht('Project names must contain at least one letter or number.')); 93 + } 94 + 95 + $slug = PhabricatorSlug::normalizeProjectSlug($name); 96 + 97 + $slug_used_already = id(new PhabricatorProjectSlug()) 98 + ->loadOneWhere('slug = %s', $slug); 99 + if ($slug_used_already && 100 + $slug_used_already->getProjectPHID() != $object->getPHID()) { 101 + 102 + $errors[] = $this->newInvalidError( 103 + pht( 104 + 'Project name generates the same hashtag ("%s") as another '. 105 + 'existing project. Choose a unique name.', 106 + '#'.$slug)); 107 + } 108 + 109 + return $errors; 110 + } 111 + 112 + }
+4
src/applications/project/xaction/PhabricatorProjectTransactionType.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorProjectTransactionType 4 + extends PhabricatorModularTransactionType {}
+2
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 592 592 593 593 $xtype = $this->getModularTransactionType($type); 594 594 if ($xtype) { 595 + $xtype = clone $xtype; 596 + $xtype->setStorage($xaction); 595 597 return $xtype->applyExternalEffects($object, $xaction->getNewValue()); 596 598 } 597 599