| Total Complexity | 50 |
| Total Lines | 434 |
| Duplicated Lines | 0 % |
| Changes | 6 | ||
| Bugs | 0 | Features | 0 |
Complex classes like AddToCampaignHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use AddToCampaignHandler, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 52 | class AddToCampaignHandler |
||
| 53 | { |
||
| 54 | use Injectable; |
||
| 55 | |||
| 56 | /** |
||
| 57 | * Parent controller for this form |
||
| 58 | * |
||
| 59 | * @var Controller |
||
| 60 | */ |
||
| 61 | protected $controller; |
||
| 62 | |||
| 63 | /** |
||
| 64 | * The submitted form data |
||
| 65 | * |
||
| 66 | * @var array |
||
| 67 | */ |
||
| 68 | protected $data; |
||
| 69 | |||
| 70 | /** |
||
| 71 | * Form name to use |
||
| 72 | * |
||
| 73 | * @var string |
||
| 74 | */ |
||
| 75 | protected $name; |
||
| 76 | |||
| 77 | /** |
||
| 78 | * AddToCampaignHandler constructor. |
||
| 79 | * |
||
| 80 | * @param Controller $controller Controller for this form |
||
| 81 | * @param array|DataObject $data The data submitted as part of that form |
||
| 82 | * @param string $name Form name |
||
| 83 | */ |
||
| 84 | public function __construct($controller = null, $data = [], $name = 'AddToCampaignForm') |
||
| 85 | { |
||
| 86 | $this->controller = $controller; |
||
| 87 | if ($data instanceof DataObject) { |
||
| 88 | $data = $data->toMap(); |
||
| 89 | } |
||
| 90 | $this->data = $data; |
||
| 91 | $this->name = $name; |
||
| 92 | } |
||
| 93 | |||
| 94 | /** |
||
| 95 | * Perform the action. Either returns a Form or performs the action, as per the class doc |
||
| 96 | * |
||
| 97 | * @return DBHTMLText|HTTPResponse |
||
| 98 | */ |
||
| 99 | public function handle() |
||
| 100 | { |
||
| 101 | Deprecation::notice('5.0', 'handle() will be removed. Use addToCampaign or Form directly'); |
||
| 102 | $object = $this->getObject($this->data['ID'], $this->data['ClassName']); |
||
| 103 | |||
| 104 | if (empty($this->data['Campaign'])) { |
||
| 105 | return $this->Form($object)->forTemplate(); |
||
| 106 | } else { |
||
| 107 | return $this->addToCampaign($object, $this->data['Campaign']); |
||
| 108 | } |
||
| 109 | } |
||
| 110 | |||
| 111 | /** |
||
| 112 | * Get what ChangeSets are available for an item to be added to by this user |
||
| 113 | * |
||
| 114 | * @return ArrayList|ChangeSet[] |
||
| 115 | */ |
||
| 116 | protected function getAvailableChangeSets() |
||
| 117 | { |
||
| 118 | return ChangeSet::get() |
||
| 119 | ->filter([ |
||
| 120 | 'State' => ChangeSet::STATE_OPEN, |
||
| 121 | 'IsInferred' => 0 |
||
| 122 | ]) |
||
| 123 | ->filterByCallback(function ($item) { |
||
| 124 | /** @var ChangeSet $item */ |
||
| 125 | return $item->canView(); |
||
| 126 | }); |
||
| 127 | } |
||
| 128 | |||
| 129 | /** |
||
| 130 | * Get changesets that a given object is already in |
||
| 131 | * |
||
| 132 | * @param DataObject |
||
| 133 | * @return ArrayList[ChangeSet] |
||
|
|
|||
| 134 | */ |
||
| 135 | protected function getInChangeSets($object) |
||
| 148 | } |
||
| 149 | |||
| 150 | /** |
||
| 151 | * Safely get a DataObject from a client-supplied ID and ClassName, checking: argument |
||
| 152 | * validity; existence; and canView permissions. |
||
| 153 | * |
||
| 154 | * @param int $id The ID of the DataObject |
||
| 155 | * @param string $class The Class of the DataObject |
||
| 156 | * @return DataObject The referenced DataObject |
||
| 157 | * @throws HTTPResponse_Exception |
||
| 158 | */ |
||
| 159 | protected function getObject($id, $class) |
||
| 198 | } |
||
| 199 | |||
| 200 | /** |
||
| 201 | * Builds a Form that mirrors the parent editForm, but with an extra field to collect the ChangeSet ID |
||
| 202 | * |
||
| 203 | * @param DataObject $object The object we're going to be adding to whichever ChangeSet is chosen |
||
| 204 | * @return Form |
||
| 205 | */ |
||
| 206 | public function Form($object) |
||
| 281 | } |
||
| 282 | |||
| 283 | /** |
||
| 284 | * Performs the actual action of adding the object to the ChangeSet, once the ChangeSet ID is known |
||
| 285 | * |
||
| 286 | * @param DataObject $object The object to add to the ChangeSet |
||
| 287 | * @param array|int $data Post data for this campaign form, or the ID of the campaign to add to |
||
| 288 | * @return HTTPResponse |
||
| 289 | * @throws ValidationException |
||
| 290 | */ |
||
| 291 | public function addToCampaign($object, $data) |
||
| 292 | { |
||
| 293 | // Extract $campaignID from $data |
||
| 294 | $campaignID = $this->getOrCreateCampaign($data); |
||
| 295 | |||
| 296 | /** @var ChangeSet $changeSet */ |
||
| 297 | $changeSet = ChangeSet::get()->byID($campaignID); |
||
| 298 | |||
| 299 | if (!$changeSet) { |
||
| 300 | throw new ValidationException(_t( |
||
| 301 | __CLASS__ . '.ErrorNotFound', |
||
| 302 | 'That {Type} couldn\'t be found', |
||
| 303 | ['Type' => 'Campaign'] |
||
| 304 | )); |
||
| 305 | } |
||
| 306 | |||
| 307 | if (!$changeSet->canEdit()) { |
||
| 308 | throw new ValidationException(_t( |
||
| 309 | __CLASS__ . '.ErrorCampaignPermissionDenied', |
||
| 310 | 'It seems you don\'t have the necessary permissions to add {ObjectTitle} to {CampaignTitle}', |
||
| 311 | [ |
||
| 312 | 'ObjectTitle' => $object->Title, |
||
| 313 | 'CampaignTitle' => $changeSet->Title |
||
| 314 | ] |
||
| 315 | )); |
||
| 316 | } |
||
| 317 | |||
| 318 | $changeSet->addObject($object); |
||
| 319 | |||
| 320 | $childObjects = $object->findRelatedObjects('cascade_add_to_campaign'); |
||
| 321 | if ($childObjects) { |
||
| 322 | foreach ($childObjects as $childObject) { |
||
| 323 | $changeSet->addObject($childObject); |
||
| 324 | } |
||
| 325 | } |
||
| 326 | |||
| 327 | $request = $this->controller->getRequest(); |
||
| 328 | $message = _t( |
||
| 329 | __CLASS__ . '.Success', |
||
| 330 | 'Successfully added <strong>{ObjectTitle}</strong> to <strong>{CampaignTitle}</strong>', |
||
| 331 | [ |
||
| 332 | 'ObjectTitle' => Convert::raw2xml($object->Title), |
||
| 333 | 'CampaignTitle' => Convert::raw2xml($changeSet->Title) |
||
| 334 | ] |
||
| 335 | ); |
||
| 336 | if ($request->getHeader('X-Formschema-Request')) { |
||
| 337 | return $message; |
||
| 338 | } elseif (Director::is_ajax()) { |
||
| 339 | $response = new HTTPResponse($message, 200); |
||
| 340 | |||
| 341 | $response->addHeader('Content-Type', 'text/html; charset=utf-8'); |
||
| 342 | return $response; |
||
| 343 | } else { |
||
| 344 | return $this->controller->redirectBack(); |
||
| 345 | } |
||
| 346 | } |
||
| 347 | |||
| 348 | /** |
||
| 349 | * Get descriptive alert to display at the top of the form |
||
| 350 | * |
||
| 351 | * @param ArrayList $inChangeSets List of changesets this item exists in |
||
| 352 | * @param ArrayList $candidateChangeSets List of changesets this item could be added to |
||
| 353 | * @param bool $canCreate |
||
| 354 | * @return string |
||
| 355 | */ |
||
| 356 | protected function getFormAlert($inChangeSets, $candidateChangeSets, $canCreate) |
||
| 357 | { |
||
| 358 | // In a subset of changesets |
||
| 359 | if ($inChangeSets->count() > 0 && $candidateChangeSets->count() > 0) { |
||
| 360 | return sprintf( |
||
| 361 | '<div class="alert alert-info"><strong>%s</strong><br/>%s</div>', |
||
| 362 | _t( |
||
| 363 | __CLASS__ . '.AddToCampaignInChangsetLabel', |
||
| 364 | 'Heads up, this item is already in campaign(s):' |
||
| 365 | ), |
||
| 366 | Convert::raw2xml(implode(', ', $inChangeSets->column('Name'))) |
||
| 367 | ); |
||
| 368 | } |
||
| 369 | |||
| 370 | // In all changesets |
||
| 371 | if ($inChangeSets->count() > 0) { |
||
| 372 | return sprintf( |
||
| 373 | '<div class="alert alert-info"><strong>%s</strong><br/>%s</div>', |
||
| 374 | _t( |
||
| 375 | __CLASS__ . '.AddToCampaignInChangsetLabelAll', |
||
| 376 | 'Heads up, this item is already in ALL campaign(s):' |
||
| 377 | ), |
||
| 378 | Convert::raw2xml(implode(', ', $inChangeSets->column('Name'))) |
||
| 379 | ); |
||
| 380 | } |
||
| 381 | |||
| 382 | // Create only |
||
| 383 | if ($candidateChangeSets->count() === 0 && $canCreate) { |
||
| 384 | return sprintf( |
||
| 385 | '<div class="alert alert-info">%s</div>', |
||
| 386 | _t( |
||
| 387 | __CLASS__ . '.NO_CAMPAIGNS', |
||
| 388 | "You currently don't have any campaigns. " |
||
| 389 | . "You can edit campaign details later in the Campaigns section." |
||
| 390 | ) |
||
| 391 | ); |
||
| 392 | } |
||
| 393 | |||
| 394 | // Can't select or create |
||
| 395 | if ($candidateChangeSets->count() === 0 && !$canCreate) { |
||
| 396 | return sprintf( |
||
| 397 | '<div class="alert alert-warning">%s</div>', |
||
| 398 | _t( |
||
| 399 | __CLASS__ . '.NO_CREATE', |
||
| 400 | "Oh no! You currently don't have any campaigns created. " |
||
| 401 | . "Your current login does not have privileges to create campaigns. " |
||
| 402 | . "Campaigns can only be created by users with Campaigns section rights." |
||
| 403 | ) |
||
| 404 | ); |
||
| 405 | } |
||
| 406 | return null; |
||
| 407 | } |
||
| 408 | |||
| 409 | /** |
||
| 410 | * Find or build campaign from posted data |
||
| 411 | * |
||
| 412 | * @param array|int $data |
||
| 413 | * @return int |
||
| 414 | * @throws ValidationException |
||
| 415 | */ |
||
| 416 | protected function getOrCreateCampaign($data) |
||
| 417 | { |
||
| 418 | // Create new campaign if selected |
||
| 419 | if (is_array($data) && !empty($data['AddNewSelect']) // Explicitly click "Add to a new campaign" |
||
| 420 | || (is_array($data) && !isset($data['Campaign']) && isset($data['NewTitle'])) // This is the only option |
||
| 421 | ) { |
||
| 422 | // Permission |
||
| 423 | if (!ChangeSet::singleton()->canCreate()) { |
||
| 424 | throw $this->validationResult( |
||
| 425 | _t(__CLASS__ . '.CREATE_DENIED', 'You do not have permission to create campaigns') |
||
| 426 | ); |
||
| 427 | } |
||
| 428 | |||
| 429 | // Check title is valid |
||
| 430 | $title = $data['NewTitle']; |
||
| 431 | if (empty($title)) { |
||
| 432 | throw $this->validationResult( |
||
| 433 | _t(__CLASS__ . '.MISSING_TITLE', 'Campaign name is required'), |
||
| 434 | 'NewTitle' |
||
| 435 | ); |
||
| 436 | } |
||
| 437 | |||
| 438 | // Prevent duplicates |
||
| 439 | $hasExistingName = Changeset::get() |
||
| 440 | ->filter('Name:nocase', $title) |
||
| 441 | ->count() > 0; |
||
| 442 | |||
| 443 | if ($hasExistingName) { |
||
| 444 | throw $this->validationResult( |
||
| 445 | _t( |
||
| 446 | 'SilverStripe\\CampaignAdmin\\CampaignAdmin.ERROR_DUPLICATE_NAME', |
||
| 447 | 'Name "{Name}" already exists', |
||
| 448 | ['Name' => $title] |
||
| 449 | ), |
||
| 450 | 'NewTitle' |
||
| 451 | ); |
||
| 452 | } |
||
| 453 | |||
| 454 | // Create and return |
||
| 455 | $campaign = ChangeSet::create(); |
||
| 456 | $campaign->Name = $title; |
||
| 457 | $campaign->write(); |
||
| 458 | return $campaign->ID; |
||
| 459 | } |
||
| 460 | |||
| 461 | // Get selected campaign ID |
||
| 462 | $campaignID = null; |
||
| 463 | if (is_array($data) && !empty($data['Campaign'])) { |
||
| 464 | $campaignID = $data['Campaign']; |
||
| 465 | } elseif (is_numeric($data)) { |
||
| 466 | $campaignID = (int)$data; |
||
| 467 | } |
||
| 468 | if (empty($campaignID)) { |
||
| 469 | throw $this->validationResult(_t(__CLASS__ . '.NONE_SELECTED', 'No campaign selected')); |
||
| 470 | } |
||
| 471 | return $campaignID; |
||
| 472 | } |
||
| 473 | |||
| 474 | /** |
||
| 475 | * Raise validation error |
||
| 476 | * |
||
| 477 | * @param string $message |
||
| 478 | * @param string $field |
||
| 479 | * @return ValidationException |
||
| 480 | */ |
||
| 481 | protected function validationResult($message, $field = null) |
||
| 486 | } |
||
| 487 | } |
||
| 488 |