[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 final class PhabricatorProjectTransactionEditor 4 extends PhabricatorApplicationTransactionEditor { 5 6 public function getEditorApplicationClass() { 7 return 'PhabricatorProjectApplication'; 8 } 9 10 public function getEditorObjectsDescription() { 11 return pht('Projects'); 12 } 13 14 public function getTransactionTypes() { 15 $types = parent::getTransactionTypes(); 16 17 $types[] = PhabricatorTransactions::TYPE_EDGE; 18 $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; 19 $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 20 $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; 21 22 $types[] = PhabricatorProjectTransaction::TYPE_NAME; 23 $types[] = PhabricatorProjectTransaction::TYPE_SLUGS; 24 $types[] = PhabricatorProjectTransaction::TYPE_STATUS; 25 $types[] = PhabricatorProjectTransaction::TYPE_IMAGE; 26 $types[] = PhabricatorProjectTransaction::TYPE_ICON; 27 $types[] = PhabricatorProjectTransaction::TYPE_COLOR; 28 $types[] = PhabricatorProjectTransaction::TYPE_LOCKED; 29 30 return $types; 31 } 32 33 protected function getCustomTransactionOldValue( 34 PhabricatorLiskDAO $object, 35 PhabricatorApplicationTransaction $xaction) { 36 37 switch ($xaction->getTransactionType()) { 38 case PhabricatorProjectTransaction::TYPE_NAME: 39 return $object->getName(); 40 case PhabricatorProjectTransaction::TYPE_SLUGS: 41 $slugs = $object->getSlugs(); 42 $slugs = mpull($slugs, 'getSlug', 'getSlug'); 43 unset($slugs[$object->getPrimarySlug()]); 44 return $slugs; 45 case PhabricatorProjectTransaction::TYPE_STATUS: 46 return $object->getStatus(); 47 case PhabricatorProjectTransaction::TYPE_IMAGE: 48 return $object->getProfileImagePHID(); 49 case PhabricatorProjectTransaction::TYPE_ICON: 50 return $object->getIcon(); 51 case PhabricatorProjectTransaction::TYPE_COLOR: 52 return $object->getColor(); 53 case PhabricatorProjectTransaction::TYPE_LOCKED: 54 return (int) $object->getIsMembershipLocked(); 55 } 56 57 return parent::getCustomTransactionOldValue($object, $xaction); 58 } 59 60 protected function getCustomTransactionNewValue( 61 PhabricatorLiskDAO $object, 62 PhabricatorApplicationTransaction $xaction) { 63 64 switch ($xaction->getTransactionType()) { 65 case PhabricatorProjectTransaction::TYPE_NAME: 66 case PhabricatorProjectTransaction::TYPE_SLUGS: 67 case PhabricatorProjectTransaction::TYPE_STATUS: 68 case PhabricatorProjectTransaction::TYPE_IMAGE: 69 case PhabricatorProjectTransaction::TYPE_ICON: 70 case PhabricatorProjectTransaction::TYPE_COLOR: 71 case PhabricatorProjectTransaction::TYPE_LOCKED: 72 return $xaction->getNewValue(); 73 } 74 75 return parent::getCustomTransactionNewValue($object, $xaction); 76 } 77 78 protected function applyCustomInternalTransaction( 79 PhabricatorLiskDAO $object, 80 PhabricatorApplicationTransaction $xaction) { 81 82 switch ($xaction->getTransactionType()) { 83 case PhabricatorProjectTransaction::TYPE_NAME: 84 $object->setName($xaction->getNewValue()); 85 $object->setPhrictionSlug($xaction->getNewValue()); 86 return; 87 case PhabricatorProjectTransaction::TYPE_SLUGS: 88 return; 89 case PhabricatorProjectTransaction::TYPE_STATUS: 90 $object->setStatus($xaction->getNewValue()); 91 return; 92 case PhabricatorProjectTransaction::TYPE_IMAGE: 93 $object->setProfileImagePHID($xaction->getNewValue()); 94 return; 95 case PhabricatorProjectTransaction::TYPE_ICON: 96 $object->setIcon($xaction->getNewValue()); 97 return; 98 case PhabricatorProjectTransaction::TYPE_COLOR: 99 $object->setColor($xaction->getNewValue()); 100 return; 101 case PhabricatorProjectTransaction::TYPE_LOCKED: 102 $object->setIsMembershipLocked($xaction->getNewValue()); 103 return; 104 case PhabricatorTransactions::TYPE_EDGE: 105 return; 106 case PhabricatorTransactions::TYPE_VIEW_POLICY: 107 $object->setViewPolicy($xaction->getNewValue()); 108 return; 109 case PhabricatorTransactions::TYPE_EDIT_POLICY: 110 $object->setEditPolicy($xaction->getNewValue()); 111 return; 112 case PhabricatorTransactions::TYPE_JOIN_POLICY: 113 $object->setJoinPolicy($xaction->getNewValue()); 114 return; 115 } 116 117 return parent::applyCustomInternalTransaction($object, $xaction); 118 } 119 120 protected function applyCustomExternalTransaction( 121 PhabricatorLiskDAO $object, 122 PhabricatorApplicationTransaction $xaction) { 123 124 $old = $xaction->getOldValue(); 125 $new = $xaction->getNewValue(); 126 127 switch ($xaction->getTransactionType()) { 128 case PhabricatorProjectTransaction::TYPE_NAME: 129 // First, remove the old and new slugs. Removing the old slug is 130 // important when changing the project's capitalization or punctuation. 131 // Removing the new slug is important when changing the project's name 132 // so that one of its secondary slugs is now the primary slug. 133 if ($old !== null) { 134 $this->removeSlug($object, $old); 135 } 136 $this->removeSlug($object, $new); 137 138 $new_slug = id(new PhabricatorProjectSlug()) 139 ->setSlug($object->getPrimarySlug()) 140 ->setProjectPHID($object->getPHID()) 141 ->save(); 142 143 return; 144 case PhabricatorProjectTransaction::TYPE_SLUGS: 145 $old = $xaction->getOldValue(); 146 $new = $xaction->getNewValue(); 147 $add = array_diff($new, $old); 148 $rem = array_diff($old, $new); 149 150 if ($add) { 151 $add_slug_template = id(new PhabricatorProjectSlug()) 152 ->setProjectPHID($object->getPHID()); 153 foreach ($add as $add_slug_str) { 154 $add_slug = id(clone $add_slug_template) 155 ->setSlug($add_slug_str) 156 ->save(); 157 } 158 } 159 if ($rem) { 160 $rem_slugs = id(new PhabricatorProjectSlug()) 161 ->loadAllWhere('slug IN (%Ls)', $rem); 162 foreach ($rem_slugs as $rem_slug) { 163 $rem_slug->delete(); 164 } 165 } 166 167 return; 168 case PhabricatorTransactions::TYPE_VIEW_POLICY: 169 case PhabricatorTransactions::TYPE_EDIT_POLICY: 170 case PhabricatorTransactions::TYPE_JOIN_POLICY: 171 case PhabricatorProjectTransaction::TYPE_STATUS: 172 case PhabricatorProjectTransaction::TYPE_IMAGE: 173 case PhabricatorProjectTransaction::TYPE_ICON: 174 case PhabricatorProjectTransaction::TYPE_COLOR: 175 case PhabricatorProjectTransaction::TYPE_LOCKED: 176 return; 177 case PhabricatorTransactions::TYPE_EDGE: 178 $edge_type = $xaction->getMetadataValue('edge:type'); 179 switch ($edge_type) { 180 case PhabricatorEdgeConfig::TYPE_PROJ_MEMBER: 181 case PhabricatorEdgeConfig::TYPE_OBJECT_HAS_WATCHER: 182 $old = $xaction->getOldValue(); 183 $new = $xaction->getNewValue(); 184 185 // When adding members or watchers, we add subscriptions. 186 $add = array_keys(array_diff_key($new, $old)); 187 188 // When removing members, we remove their subscription too. 189 // When unwatching, we leave subscriptions, since it's fine to be 190 // subscribed to a project but not be a member of it. 191 if ($edge_type == PhabricatorEdgeConfig::TYPE_PROJ_MEMBER) { 192 $rem = array_keys(array_diff_key($old, $new)); 193 } else { 194 $rem = array(); 195 } 196 197 // NOTE: The subscribe is "explicit" because there's no implicit 198 // unsubscribe, so Join -> Leave -> Join doesn't resubscribe you 199 // if we use an implicit subscribe, even though you never willfully 200 // unsubscribed. Not sure if adding implicit unsubscribe (which 201 // would not write the unsubscribe row) is justified to deal with 202 // this, which is a fairly weird edge case and pretty arguable both 203 // ways. 204 205 // Subscriptions caused by watches should also clearly be explicit, 206 // and that case is unambiguous. 207 208 id(new PhabricatorSubscriptionsEditor()) 209 ->setActor($this->requireActor()) 210 ->setObject($object) 211 ->subscribeExplicit($add) 212 ->unsubscribe($rem) 213 ->save(); 214 215 if ($rem) { 216 // When removing members, also remove any watches on the project. 217 $edge_editor = new PhabricatorEdgeEditor(); 218 foreach ($rem as $rem_phid) { 219 $edge_editor->removeEdge( 220 $object->getPHID(), 221 PhabricatorEdgeConfig::TYPE_OBJECT_HAS_WATCHER, 222 $rem_phid); 223 } 224 $edge_editor->save(); 225 } 226 break; 227 } 228 return; 229 } 230 231 return parent::applyCustomExternalTransaction($object, $xaction); 232 } 233 234 protected function validateTransaction( 235 PhabricatorLiskDAO $object, 236 $type, 237 array $xactions) { 238 239 $errors = parent::validateTransaction($object, $type, $xactions); 240 241 switch ($type) { 242 case PhabricatorProjectTransaction::TYPE_NAME: 243 $missing = $this->validateIsEmptyTextField( 244 $object->getName(), 245 $xactions); 246 247 if ($missing) { 248 $error = new PhabricatorApplicationTransactionValidationError( 249 $type, 250 pht('Required'), 251 pht('Project name is required.'), 252 nonempty(last($xactions), null)); 253 254 $error->setIsMissingFieldError(true); 255 $errors[] = $error; 256 } 257 258 if (!$xactions) { 259 break; 260 } 261 262 $name = last($xactions)->getNewValue(); 263 $name_used_already = id(new PhabricatorProjectQuery()) 264 ->setViewer($this->getActor()) 265 ->withNames(array($name)) 266 ->executeOne(); 267 if ($name_used_already && 268 ($name_used_already->getPHID() != $object->getPHID())) { 269 $error = new PhabricatorApplicationTransactionValidationError( 270 $type, 271 pht('Duplicate'), 272 pht('Project name is already used.'), 273 nonempty(last($xactions), null)); 274 $errors[] = $error; 275 } 276 277 $slug_builder = clone $object; 278 $slug_builder->setPhrictionSlug($name); 279 $slug = $slug_builder->getPrimarySlug(); 280 $slug_used_already = id(new PhabricatorProjectSlug()) 281 ->loadOneWhere('slug = %s', $slug); 282 if ($slug_used_already && 283 $slug_used_already->getProjectPHID() != $object->getPHID()) { 284 $error = new PhabricatorApplicationTransactionValidationError( 285 $type, 286 pht('Duplicate'), 287 pht('Project name can not be used due to hashtag collision.'), 288 nonempty(last($xactions), null)); 289 $errors[] = $error; 290 } 291 break; 292 case PhabricatorProjectTransaction::TYPE_SLUGS: 293 if (!$xactions) { 294 break; 295 } 296 297 $slug_xaction = last($xactions); 298 $new = $slug_xaction->getNewValue(); 299 300 if ($new) { 301 $slugs_used_already = id(new PhabricatorProjectSlug()) 302 ->loadAllWhere('slug IN (%Ls)', $new); 303 } else { 304 // The project doesn't have any extra slugs. 305 $slugs_used_already = array(); 306 } 307 308 $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID'); 309 foreach ($slugs_used_already as $project_phid => $used_slugs) { 310 $used_slug_strs = mpull($used_slugs, 'getSlug'); 311 if ($project_phid == $object->getPHID()) { 312 if (in_array($object->getPrimarySlug(), $used_slug_strs)) { 313 $error = new PhabricatorApplicationTransactionValidationError( 314 $type, 315 pht('Invalid'), 316 pht( 317 'Project hashtag %s is already the primary hashtag.', 318 $object->getPrimarySlug()), 319 $slug_xaction); 320 $errors[] = $error; 321 } 322 continue; 323 } 324 325 $error = new PhabricatorApplicationTransactionValidationError( 326 $type, 327 pht('Invalid'), 328 pht( 329 '%d project hashtag(s) are already used: %s', 330 count($used_slug_strs), 331 implode(', ', $used_slug_strs)), 332 $slug_xaction); 333 $errors[] = $error; 334 } 335 336 break; 337 338 } 339 340 return $errors; 341 } 342 343 344 protected function requireCapabilities( 345 PhabricatorLiskDAO $object, 346 PhabricatorApplicationTransaction $xaction) { 347 348 switch ($xaction->getTransactionType()) { 349 case PhabricatorProjectTransaction::TYPE_NAME: 350 case PhabricatorProjectTransaction::TYPE_STATUS: 351 case PhabricatorProjectTransaction::TYPE_IMAGE: 352 case PhabricatorProjectTransaction::TYPE_ICON: 353 case PhabricatorProjectTransaction::TYPE_COLOR: 354 PhabricatorPolicyFilter::requireCapability( 355 $this->requireActor(), 356 $object, 357 PhabricatorPolicyCapability::CAN_EDIT); 358 return; 359 case PhabricatorProjectTransaction::TYPE_LOCKED: 360 PhabricatorPolicyFilter::requireCapability( 361 $this->requireActor(), 362 newv($this->getEditorApplicationClass(), array()), 363 ProjectCanLockProjectsCapability::CAPABILITY); 364 return; 365 case PhabricatorTransactions::TYPE_EDGE: 366 switch ($xaction->getMetadataValue('edge:type')) { 367 case PhabricatorEdgeConfig::TYPE_PROJ_MEMBER: 368 $old = $xaction->getOldValue(); 369 $new = $xaction->getNewValue(); 370 371 $add = array_keys(array_diff_key($new, $old)); 372 $rem = array_keys(array_diff_key($old, $new)); 373 374 $actor_phid = $this->requireActor()->getPHID(); 375 376 $is_join = (($add === array($actor_phid)) && !$rem); 377 $is_leave = (($rem === array($actor_phid)) && !$add); 378 379 if ($is_join) { 380 // You need CAN_JOIN to join a project. 381 PhabricatorPolicyFilter::requireCapability( 382 $this->requireActor(), 383 $object, 384 PhabricatorPolicyCapability::CAN_JOIN); 385 } else if ($is_leave) { 386 // You usually don't need any capabilities to leave a project. 387 if ($object->getIsMembershipLocked()) { 388 // you must be able to edit though to leave locked projects 389 PhabricatorPolicyFilter::requireCapability( 390 $this->requireActor(), 391 $object, 392 PhabricatorPolicyCapability::CAN_EDIT); 393 } 394 } else { 395 // You need CAN_EDIT to change members other than yourself. 396 PhabricatorPolicyFilter::requireCapability( 397 $this->requireActor(), 398 $object, 399 PhabricatorPolicyCapability::CAN_EDIT); 400 } 401 return; 402 } 403 break; 404 } 405 406 return parent::requireCapabilities($object, $xaction); 407 } 408 409 protected function supportsSearch() { 410 return true; 411 } 412 413 protected function extractFilePHIDsFromCustomTransaction( 414 PhabricatorLiskDAO $object, 415 PhabricatorApplicationTransaction $xaction) { 416 417 switch ($xaction->getTransactionType()) { 418 case PhabricatorProjectTransaction::TYPE_IMAGE: 419 $new = $xaction->getNewValue(); 420 if ($new) { 421 return array($new); 422 } 423 break; 424 } 425 426 return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); 427 } 428 429 private function removeSlug( 430 PhabricatorLiskDAO $object, 431 $name) { 432 433 $object = (clone $object); 434 $object->setPhrictionSlug($name); 435 $slug = $object->getPrimarySlug(); 436 437 $slug_object = id(new PhabricatorProjectSlug())->loadOneWhere( 438 'slug = %s', 439 $slug); 440 441 if (!$slug_object) { 442 return; 443 } 444 445 if ($slug_object->getProjectPHID() != $object->getPHID()) { 446 throw new Exception( 447 pht('Trying to remove slug owned by another project!')); 448 } 449 450 $slug_object->delete(); 451 } 452 453 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |