| 16 | | class MailTemplatesCopy extends BaseController |
| 17 | | { |
| 18 | | |
| 19 | | |
| 20 | | |
| 21 | | #[\Override] |
| 22 | | public function readResponse( |
| 23 | | RequestInterface $request, |
| 24 | | ResponseInterface $response, |
| 25 | | array $args |
| 26 | | ): ResponseInterface { |
| 27 | | $workstation = $this->readAuthorizedWorkstation(); |
| 28 | | $queryParameters = $this->readQueryParameters($request); |
| 29 | | $scopeList = $this->readScopeList($workstation); |
| 30 | | |
| 31 | | $input = (array) ($request->getParsedBody() ?? []); |
| 32 | | $isPostRequest = strtoupper($request->getMethod()) === 'POST'; |
| 33 | | |
| 34 | | $sourceScopeId = $this->resolveSourceScopeId( |
| 35 | | $input, |
| 36 | | $queryParameters['sourceScopeId'], |
| 37 | | $isPostRequest, |
| 38 | | $workstation, |
| 39 | | $scopeList |
| 40 | | ); |
| 41 | | |
| 42 | | [ |
| 43 | | $selectedSourceScope, |
| 44 | | $formError, |
| 45 | | ] = $this->resolveSourceScope( |
| 46 | | $sourceScopeId, |
| 47 | | $isPostRequest, |
| 48 | | $scopeList |
| 49 | | ); |
| 50 | | |
| 51 | | $customTemplates = $this->readCustomTemplates( |
| 52 | | $selectedSourceScope |
| 53 | | ); |
| 54 | | |
| 55 | | $sourceTemplateId = null; |
| 56 | | $selectedTargetScopeIds = []; |
| 57 | | |
| 58 | | if ($isPostRequest) { |
| 59 | | $postResult = $this->processPostRequest( |
| 60 | | $input, |
| 61 | | $sourceScopeId, |
| 62 | | $selectedSourceScope, |
| 63 | | $scopeList, |
| 64 | | $customTemplates, |
| 65 | | $formError |
| 66 | | ); |
| 67 | | |
| 68 | | if ($postResult['response'] instanceof ResponseInterface) { |
| 69 | | return $postResult['response']; |
| 70 | | } |
| 71 | | |
| 72 | | $sourceTemplateId = |
| 73 | | $postResult['sourceTemplateId']; |
| 74 | | |
| 75 | | $selectedTargetScopeIds = |
| 76 | | $postResult['selectedTargetScopeIds']; |
| 77 | | |
| 78 | | $formError = $postResult['formError']; |
| 79 | | } |
| 80 | | |
| 81 | | return Render::withHtml( |
| 82 | | $response, |
| 83 | | 'page/mailtemplatesCopy.twig', |
| 84 | | [ |
| 85 | | 'title' => 'E-Mail-Template kopieren', |
| 86 | | 'pageTitle' => 'E-Mail-Template kopieren', |
| 87 | | 'menuActive' => 'mailtemplatesCopy', |
| 88 | | 'workstation' => $workstation, |
| 89 | | 'scopeList' => $scopeList, |
| 90 | | 'sourceScopeId' => $sourceScopeId, |
| 91 | | 'selectedSourceScope' => $selectedSourceScope, |
| 92 | | 'customTemplates' => $customTemplates, |
| 93 | | 'selectedSourceTemplateId' => $sourceTemplateId, |
| 94 | | 'selectedTargetScopeIds' => |
| 95 | | $selectedTargetScopeIds, |
| 96 | | 'formError' => $formError, |
| 97 | | 'success' => $queryParameters['success'], |
| 98 | | 'copiedCount' => |
| 99 | | $queryParameters['copiedCount'], |
| 100 | | 'targetScopeOptions' => |
| 101 | | $this->buildTargetScopeOptions( |
| 102 | | $workstation, |
| 103 | | $scopeList, |
| 104 | | $selectedSourceScope |
| 105 | | ), |
| 106 | | ] |
| 107 | | ); |
| 108 | | } |
| 109 | | |
| 110 | | private function readAuthorizedWorkstation(): Workstation |
| 111 | | { |
| 112 | | $workstation = \App::$http |
| 113 | | ->readGetResult( |
| 114 | | '/workstation/', |
| 115 | | ['resolveReferences' => 3] |
| 116 | | ) |
| 117 | | ->getEntity(); |
| 118 | | |
| 119 | | if ( |
| 120 | | !$workstation |
| 121 | | ->getUseraccount() |
| 122 | | ->hasPermissions(['mailtemplates']) |
| 123 | | ) { |
| 124 | | throw new UserAccountMissingRights(); |
| 125 | | } |
| 126 | | |
| 127 | | return $workstation; |
| 128 | | } |
| 129 | | |
| 130 | | private function readQueryParameters( |
| 131 | | RequestInterface $request |
| 132 | | ): array { |
| 133 | | $validator = $request->getAttribute('validator'); |
| 134 | | |
| 135 | | return [ |
| 136 | | 'success' => $validator |
| 137 | | ->getParameter('success') |
| 138 | | ->isString() |
| 139 | | ->getValue(), |
| 140 | | 'copiedCount' => (int) $validator |
| 141 | | ->getParameter('copiedCount') |
| 142 | | ->isNumber() |
| 143 | | ->setDefault(0) |
| 144 | | ->getValue(), |
| 145 | | 'sourceScopeId' => $validator |
| 146 | | ->getParameter('sourceScopeId') |
| 147 | | ->isNumber() |
| 148 | | ->getValue(), |
| 149 | | ]; |
| 150 | | } |
| 151 | | |
| 152 | | private function readScopeList(Workstation $workstation): ScopeList |
| 153 | | { |
| 154 | | |
| 155 | | |
| 156 | | |
| 157 | | |
| 158 | | $scopeList = $workstation |
| 159 | | ->getUseraccount() |
| 160 | | ->getDepartmentList() |
| 161 | | ->getUniqueScopeList() |
| 162 | | ->sortByContactName(); |
| 163 | | |
| 164 | | return $this->withScopesThatHaveProvider($scopeList); |
| 165 | | } |
| 166 | | |
| 167 | | private function withScopesThatHaveProvider( |
| 168 | | ScopeList $scopeList |
| 169 | | ): ScopeList { |
| 170 | | $authorizedScopeList = new ScopeList(); |
| 171 | | |
| 172 | | foreach ($scopeList as $scope) { |
| 173 | | if ($this->readProviderId($scope) === '') { |
| 174 | | continue; |
| 175 | | } |
| 176 | | |
| 177 | | $authorizedScopeList->addEntity($scope); |
| 178 | | } |
| 179 | | |
| 180 | | return $authorizedScopeList; |
| 181 | | } |
| 182 | | |
| 183 | | private function readProviderId(Scope $scope): string |
| 184 | | { |
| 185 | | try { |
| 186 | | $providerId = (string) $scope->getProviderId(); |
| 187 | | } catch (ScopeMissingProvider) { |
| 188 | | return ''; |
| 189 | | } |
| 190 | | |
| 191 | | if ($providerId === '' || $providerId === '0') { |
| 192 | | return ''; |
| 193 | | } |
| 194 | | |
| 195 | | return $providerId; |
| 196 | | } |
| 197 | | |
| 198 | | private function resolveSourceScopeId( |
| 199 | | array $input, |
| 200 | | mixed $requestedSourceScopeId, |
| 201 | | bool $isPostRequest, |
| 202 | | Workstation $workstation, |
| 203 | | ScopeList $scopeList |
| 204 | | ): ?int { |
| 205 | | $sourceScopeValue = $isPostRequest |
| 206 | | ? ($input['sourceScopeId'] ?? null) |
| 207 | | : $requestedSourceScopeId; |
| 208 | | |
| 209 | | $sourceScopeId = $this->readPositiveInt( |
| 210 | | $sourceScopeValue |
| 211 | | ); |
| 212 | | |
| 213 | | if ($sourceScopeId !== null || $isPostRequest) { |
| 214 | | return $sourceScopeId; |
| 215 | | } |
| 216 | | |
| 217 | | |
| 218 | | |
| 219 | | |
| 220 | | |
| 221 | | $currentScopeId = $this->readPositiveInt( |
| 222 | | $workstation->getScope()->getId() |
| 223 | | ); |
| 224 | | |
| 225 | | if ( |
| 226 | | $currentScopeId !== null |
| 227 | | && $scopeList->hasEntity($currentScopeId) |
| 228 | | ) { |
| 229 | | return $currentScopeId; |
| 230 | | } |
| 231 | | |
| 232 | | return null; |
| 233 | | } |
| 234 | | |
| 235 | | private function resolveSourceScope( |
| 236 | | ?int $sourceScopeId, |
| 237 | | bool $isPostRequest, |
| 238 | | ScopeList $scopeList |
| 239 | | ): array { |
| 240 | | if ($sourceScopeId === null) { |
| 241 | | $formError = $isPostRequest |
| 242 | | ? 'Bitte wählen Sie einen gültigen ' |
| 243 | | . 'Quellstandort aus.' |
| 244 | | : null; |
| 245 | | |
| 246 | | return [null, $formError]; |
| 247 | | } |
| 248 | | |
| 249 | | $selectedSourceScope = $scopeList->getEntity( |
| 250 | | $sourceScopeId |
| 251 | | ); |
| 252 | | |
| 253 | | if ($selectedSourceScope === null) { |
| 254 | | return [ |
| 255 | | null, |
| 256 | | 'Der ausgewählte Quellstandort wurde nicht ' |
| 257 | | . 'gefunden oder darf nicht verwendet werden.', |
| 258 | | ]; |
| 259 | | } |
| 260 | | |
| 261 | | return [$selectedSourceScope, null]; |
| 262 | | } |
| 263 | | |
| 264 | | private function readCustomTemplates( |
| 265 | | ?Scope $selectedSourceScope |
| 266 | | ): MailtemplateList { |
| 267 | | $customTemplates = new MailtemplateList(); |
| 268 | | |
| 269 | | if ($selectedSourceScope === null) { |
| 270 | | return $customTemplates; |
| 271 | | } |
| 272 | | |
| 273 | | $providerId = $this->readProviderId( |
| 274 | | $selectedSourceScope |
| 275 | | ); |
| 276 | | |
| 277 | | if ($providerId === '') { |
| 278 | | return $customTemplates; |
| 279 | | } |
| 280 | | |
| 281 | | $loadedTemplates = \App::$http |
| 282 | | ->readGetResult( |
| 283 | | '/custom-mailtemplates/' . $providerId . '/' |
| 284 | | ) |
| 285 | | ->getCollection(); |
| 286 | | |
| 287 | | if ($loadedTemplates === null) { |
| 288 | | return $customTemplates; |
| 289 | | } |
| 290 | | |
| 291 | | $loadedTemplates->prioritizeByName([ |
| 292 | | 'mail_preconfirmed.twig', |
| 293 | | 'mail_confirmation.twig', |
| 294 | | 'mail_reminder.twig', |
| 295 | | 'mail_delete.twig', |
| 296 | | ]); |
| 297 | | |
| 298 | | return $loadedTemplates; |
| 299 | | } |
| 300 | | |
| 301 | | private function processPostRequest( |
| 302 | | array $input, |
| 303 | | ?int $sourceScopeId, |
| 304 | | ?Scope $selectedSourceScope, |
| 305 | | ScopeList $scopeList, |
| 306 | | MailtemplateList $customTemplates, |
| 307 | | ?string $formError |
| 308 | | ): array { |
| 309 | | $sourceTemplateId = $this->readPositiveInt( |
| 310 | | $input['sourceTemplateId'] ?? null |
| 311 | | ); |
| 312 | | |
| 313 | | $normalizedTargetScopeIds = |
| 314 | | $this->readTargetScopeIds( |
| 315 | | $input['targetScopeIds'] ?? null |
| 316 | | ); |
| 317 | | |
| 318 | | $result = [ |
| 319 | | 'sourceTemplateId' => $sourceTemplateId, |
| 320 | | 'selectedTargetScopeIds' => |
| 321 | | $normalizedTargetScopeIds ?? [], |
| 322 | | 'formError' => $formError, |
| 323 | | 'response' => null, |
| 324 | | ]; |
| 325 | | |
| 326 | | if ($result['formError'] !== null) { |
| 327 | | return $result; |
| 328 | | } |
| 329 | | |
| 330 | | if ( |
| 331 | | $sourceScopeId === null |
| 332 | | || $selectedSourceScope === null |
| 333 | | ) { |
| 334 | | $result['formError'] = |
| 335 | | 'Bitte wählen Sie einen gültigen ' |
| 336 | | . 'Quellstandort aus.'; |
| 337 | | |
| 338 | | return $result; |
| 339 | | } |
| 340 | | |
| 341 | | if ( |
| 342 | | $sourceTemplateId === null |
| 343 | | || !$customTemplates->hasEntity( |
| 344 | | $sourceTemplateId |
| 345 | | ) |
| 346 | | ) { |
| 347 | | $result['formError'] = |
| 348 | | 'Bitte wählen Sie ein angepasstes ' |
| 349 | | . 'E-Mail-Template des Quellstandorts aus.'; |
| 350 | | |
| 351 | | return $result; |
| 352 | | } |
| 353 | | |
| 354 | | if ( |
| 355 | | $normalizedTargetScopeIds === null |
| 356 | | || $normalizedTargetScopeIds === [] |
| 357 | | ) { |
| 358 | | $result['formError'] = |
| 359 | | 'Bitte wählen Sie mindestens einen ' |
| 360 | | . 'Zielstandort aus.'; |
| 361 | | |
| 362 | | return $result; |
| 363 | | } |
| 364 | | |
| 365 | | $targetScopeError = $this->validateTargetScopes( |
| 366 | | $normalizedTargetScopeIds, |
| 367 | | $selectedSourceScope, |
| 368 | | $scopeList |
| 369 | | ); |
| 370 | | |
| 371 | | if ($targetScopeError !== null) { |
| 372 | | $result['formError'] = $targetScopeError; |
| 373 | | |
| 374 | | return $result; |
| 375 | | } |
| 376 | | |
| 377 | | $copyResult = $this->copyTemplate( |
| 378 | | $sourceScopeId, |
| 379 | | $sourceTemplateId, |
| 380 | | $normalizedTargetScopeIds, |
| 381 | | $scopeList |
| 382 | | ); |
| 383 | | |
| 384 | | if ($copyResult instanceof ResponseInterface) { |
| 385 | | $result['response'] = $copyResult; |
| 386 | | } else { |
| 387 | | $result['formError'] = $copyResult; |
| 388 | | } |
| 389 | | |
| 390 | | return $result; |
| 391 | | } |
| 392 | | |
| 393 | | private function validateTargetScopes( |
| 394 | | array $targetScopeIds, |
| 395 | | Scope $selectedSourceScope, |
| 396 | | ScopeList $scopeList |
| 397 | | ): ?string { |
| 398 | | foreach ($targetScopeIds as $targetScopeId) { |
| 399 | | $targetScope = $scopeList->getEntity( |
| 400 | | $targetScopeId |
| 401 | | ); |
| 402 | | |
| 403 | | if ($targetScope === null) { |
| 404 | | return |
| 405 | | 'Mindestens ein ausgewählter Zielstandort ' |
| 406 | | . 'wurde nicht gefunden oder darf nicht ' |
| 407 | | . 'verwendet werden.'; |
| 408 | | } |
| 409 | | |
| 410 | | $isSourceScope = |
| 411 | | $targetScopeId |
| 412 | | === $selectedSourceScope->getId(); |
| 413 | | |
| 414 | | if ($isSourceScope) { |
| 415 | | return |
| 416 | | 'Der Quellstandort darf nicht gleichzeitig ' |
| 417 | | . 'als Zielstandort ausgewählt werden.'; |
| 418 | | } |
| 419 | | |
| 420 | | $hasSameProvider = |
| 421 | | $this->readProviderId($targetScope) |
| 422 | | === |
| 423 | | $this->readProviderId($selectedSourceScope); |
| 424 | | |
| 425 | | if ($hasSameProvider) { |
| 426 | | return |
| 427 | | 'Der ausgewählte Zielstandort verwendet ' |
| 428 | | . 'denselben Provider wie der Quellstandort ' |
| 429 | | . 'und kann daher nicht als Zielstandort ' |
| 430 | | . 'verwendet werden.'; |
| 431 | | } |
| 432 | | } |
| 433 | | |
| 434 | | return null; |
| 435 | | } |
| 436 | | |
| 437 | | private function copyTemplate( |
| 438 | | int $sourceScopeId, |
| 439 | | int $sourceTemplateId, |
| 440 | | array $targetScopeIds, |
| 441 | | ScopeList $scopeList |
| 442 | | ): ResponseInterface|string { |
| 443 | | try { |
| 444 | | $copiedTemplate = \App::$http |
| 445 | | ->readPostResult( |
| 446 | | '/mailtemplates/copy/', |
| 447 | | [ |
| 448 | | 'sourceScopeId' => $sourceScopeId, |
| 449 | | 'sourceTemplateId' => |
| 450 | | $sourceTemplateId, |
| 451 | | 'targetScopeIds' => $targetScopeIds, |
| 452 | | ] |
| 453 | | ) |
| 454 | | ->getEntity(); |
| 455 | | } catch (Exception $exception) { |
| 456 | | $backendMessage = trim( |
| 457 | | (string) $exception->originalMessage |
| 458 | | ); |
| 459 | | |
| 460 | | if ($backendMessage !== '') { |
| 461 | | return $backendMessage; |
| 462 | | } |
| 463 | | |
| 464 | | return |
| 465 | | 'Beim Kopieren des E-Mail-Templates ist ein ' |
| 466 | | . 'Fehler aufgetreten.'; |
| 467 | | } |
| 468 | | |
| 469 | | if ($copiedTemplate === null) { |
| 470 | | return |
| 471 | | 'Das E-Mail-Template konnte nicht kopiert ' |
| 472 | | . 'werden.'; |
| 473 | | } |
| 474 | | |
| 475 | | return Render::redirect( |
| 476 | | 'mailtemplatesCopy', |
| 477 | | [], |
| 478 | | [ |
| 479 | | 'sourceScopeId' => $sourceScopeId, |
| 480 | | 'success' => 'mailtemplates_copied', |
| 481 | | 'copiedCount' => $this->countUniqueProviders( |
| 482 | | $targetScopeIds, |
| 483 | | $scopeList |
| 484 | | ), |
| 485 | | ] |
| 486 | | ); |
| 487 | | } |
| 488 | | |
| 489 | | private function readPositiveInt(mixed $value): ?int |
| 490 | | { |
| 491 | | if ( |
| 492 | | (!is_int($value) && !is_string($value)) |
| 493 | | || is_bool($value) |
| 494 | | ) { |
| 495 | | return null; |
| 496 | | } |
| 497 | | |
| 498 | | $validatedValue = filter_var( |
| 499 | | $value, |
| 500 | | FILTER_VALIDATE_INT |
| 501 | | ); |
| 502 | | |
| 503 | | if ( |
| 504 | | $validatedValue === false |
| 505 | | || $validatedValue < 1 |
| 506 | | ) { |
| 507 | | return null; |
| 508 | | } |
| 509 | | |
| 510 | | return (int) $validatedValue; |
| 511 | | } |
| 512 | | |
| 513 | | private function readTargetScopeIds( |
| 514 | | mixed $value |
| 515 | | ): ?array { |
| 516 | | if (!is_array($value)) { |
| 517 | | return null; |
| 518 | | } |
| 519 | | |
| 520 | | $scopeIds = []; |
| 521 | | |
| 522 | | foreach ($value as $scopeId) { |
| 523 | | $validatedScopeId = $this->readPositiveInt( |
| 524 | | $scopeId |
| 525 | | ); |
| 526 | | |
| 527 | | if ($validatedScopeId === null) { |
| 528 | | return null; |
| 529 | | } |
| 530 | | |
| 531 | | $scopeIds[] = $validatedScopeId; |
| 532 | | } |
| 533 | | |
| 534 | | return array_values(array_unique($scopeIds)); |
| 535 | | } |
| 536 | | |
| 537 | | private function countUniqueProviders( |
| 538 | | array $targetScopeIds, |
| 539 | | ScopeList $scopeList |
| 540 | | ): int { |
| 541 | | $providerIds = []; |
| 542 | | |
| 543 | | foreach ($targetScopeIds as $targetScopeId) { |
| 544 | | $targetScope = $scopeList->getEntity( |
| 545 | | $targetScopeId |
| 546 | | ); |
| 547 | | |
| 548 | | if ($targetScope === null) { |
| 549 | | continue; |
| 550 | | } |
| 551 | | |
| 552 | | $providerId = $this->readProviderId($targetScope); |
| 553 | | |
| 554 | | if ($providerId !== '') { |
| 555 | | $providerIds[] = $providerId; |
| 556 | | } |
| 557 | | } |
| 558 | | |
| 559 | | return count(array_unique($providerIds)); |
| 560 | | } |
| 561 | | |
| 562 | | |
| 563 | | |
| 564 | | |
| 565 | | private function buildTargetScopeOptions( |
| 566 | | Workstation $workstation, |
| 567 | | ScopeList $scopeList, |
| 568 | | ?Scope $selectedSourceScope |
| 569 | | ): array { |
| 570 | | if ($selectedSourceScope === null) { |
| 571 | | return []; |
| 572 | | } |
| 573 | | |
| 574 | | $departments = $workstation |
| 575 | | ->getUseraccount() |
| 576 | | ->getDepartmentList() |
| 577 | | ->withMatchingScopes($scopeList); |
| 578 | | |
| 579 | | $seenProviderIds = []; |
| 580 | | $targetScopeOptions = []; |
| 581 | | |
| 582 | | foreach ($departments as $department) { |
| 583 | | $departmentScopeOptions = []; |
| 584 | | |
| 585 | | foreach ($department->scopes as $scope) { |
| 586 | | $providerId = $this->readProviderId($scope); |
| 587 | | |
| 588 | | if ( |
| 589 | | !$this->isEligibleCopyTarget( |
| 590 | | $scope, |
| 591 | | $selectedSourceScope |
| 592 | | ) |
| 593 | | || isset($seenProviderIds[$providerId]) |
| 594 | | ) { |
| 595 | | continue; |
| 596 | | } |
| 597 | | |
| 598 | | $seenProviderIds[$providerId] = true; |
| 599 | | $departmentScopeOptions[] = [ |
| 600 | | 'value' => (int) $scope->getId(), |
| 601 | | 'name' => $this->formatScopeName($scope), |
| 602 | | ]; |
| 603 | | } |
| 604 | | |
| 605 | | if ($departmentScopeOptions === []) { |
| 606 | | continue; |
| 607 | | } |
| 608 | | |
| 609 | | usort( |
| 610 | | $departmentScopeOptions, |
| 611 | | static function (array $left, array $right): int { |
| 612 | | return strnatcasecmp( |
| 613 | | (string) $left['name'], |
| 614 | | (string) $right['name'] |
| 615 | | ); |
| 616 | | } |
| 617 | | ); |
| 618 | | |
| 619 | | $targetScopeOptions[] = [ |
| 620 | | 'name' => (string) $department->name, |
| 621 | | 'options' => $departmentScopeOptions, |
| 622 | | ]; |
| 623 | | } |
| 624 | | |
| 625 | | return $targetScopeOptions; |
| 626 | | } |
| 627 | | |
| 628 | | private function isEligibleCopyTarget( |
| 629 | | Scope $scope, |
| 630 | | Scope $selectedSourceScope |
| 631 | | ): bool { |
| 632 | | if ( |
| 633 | | (int) $scope->getId() |
| 634 | | === (int) $selectedSourceScope->getId() |
| 635 | | ) { |
| 636 | | return false; |
| 637 | | } |
| 638 | | |
| 639 | | $scopeProviderId = $this->readProviderId($scope); |
| 640 | | $sourceProviderId = $this->readProviderId( |
| 641 | | $selectedSourceScope |
| 642 | | ); |
| 643 | | |
| 644 | | if ($scopeProviderId === '' || $sourceProviderId === '') { |
| 645 | | return false; |
| 646 | | } |
| 647 | | |
| 648 | | return $scopeProviderId !== $sourceProviderId; |
| 649 | | } |
| 650 | | |
| 651 | | private function formatScopeName(Scope $scope): string |
| 652 | | { |
| 653 | | $contactName = trim((string) $scope->getName()); |
| 654 | | $shortName = trim((string) $scope->getShortName()); |
| 655 | | |
| 656 | | if ($contactName !== '') { |
| 657 | | return trim($contactName . ' ' . $shortName); |
| 658 | | } |
| 659 | | |
| 660 | | return sprintf( |
| 661 | | '(Standort gelöscht, ehemals: %s-%s)', |
| 662 | | (string) ($scope->provider['source'] ?? ''), |
| 663 | | $this->readProviderId($scope) |
| 664 | | ); |
| 665 | | } |
| 666 | | } |