|
- <?php
-
- declare(strict_types=1);
-
- namespace App\Controllers;
-
- use App\Models\Campaign;
- use App\Repositories\CampaignAuditRepository;
- use App\Repositories\CampaignRepository;
- use App\Repositories\CampaignTypeRepository;
- use App\ViewModels\CampaignViewModel;
- use Core\Controller;
- use Core\Request;
- use Core\Response;
- use Core\Validator;
-
- class CampaignController extends Controller
- {
- public function index(): Response
- {
- $request = Request::capture();
- $model = new CampaignViewModel();
- $model->saved = $request->input('saved') === '1';
- $model->deleted = $request->input('deleted') === '1';
-
- return $this->view('campaigns.index', [
- 'model' => $model,
- 'pageTitle' => $model->title,
- ]);
- }
-
- public function data(): Response
- {
- $rows = $this->repo()->allWithType();
-
- $data = array_map(static function (array $row): array {
- $attrValues = [];
- $campaignTypeAttributes = [];
-
- if (!empty($row['attribute_values'])) {
- $attrValues = json_decode((string) $row['attribute_values'], true) ?? [];
- }
-
- if (!empty($row['campaign_type_attributes'])) {
- $campaignTypeAttributes = json_decode((string) $row['campaign_type_attributes'], true) ?? [];
- }
-
- $summary = implode(', ', array_map(
- static fn($k, $v) => "{$k}: {$v}",
- array_keys($attrValues),
- array_values($attrValues)
- ));
-
- return [
- 'id' => (int) $row['id'],
- 'campaign_type_id' => (int) $row['campaign_type_id'],
- 'campaign_type_name' => (string) $row['campaign_type_name'],
- 'campaign_type_attributes' => $campaignTypeAttributes,
- 'attribute_values' => $attrValues,
- 'attributes_summary' => $summary,
- 'created_at' => (string) $row['created_at'],
- ];
- }, $rows);
-
- return $this->json($data);
- }
-
- public function create(): Response
- {
- $model = new CampaignViewModel();
- $model->title = 'New Campaign';
- $model->campaignTypes = $this->loadCampaignTypes();
-
- return $this->view('campaigns.create', [
- 'model' => $model,
- 'pageTitle' => $model->title,
- ]);
- }
-
- public function store(): Response
- {
- $request = Request::capture();
- $model = new CampaignViewModel();
- $model->title = 'New Campaign';
- $model->campaignTypes = $this->loadCampaignTypes();
-
- [$form, $errors] = $this->validateForm($request, $model->campaignTypes);
-
- if (!empty($errors)) {
- $model->form = $form;
- $model->errors = $errors;
-
- return $this->view('campaigns.create', [
- 'model' => $model,
- 'pageTitle' => $model->title,
- ]);
- }
-
- $campaign = new Campaign();
- $campaign->campaignTypeId = (int) $form['campaign_type_id'];
- $campaign->attributeValues = $form['attribute_values'];
-
- $this->repo()->create($campaign);
-
- // Audit: I — query back the inserted row to capture the generated id.
- $inserted = $this->repo()->findLatestByType($campaign->campaignTypeId);
- if ($inserted !== null) {
- $this->auditRepo()->log(
- (int) $inserted['id'],
- 'I',
- $this->toAuditFields($inserted),
- $this->currentUsername()
- );
- }
-
- return $this->redirect('/campaigns?saved=1');
- }
-
- public function edit(string $id): Response
- {
- $row = $this->repo()->findWithType((int) $id);
-
- if ($row === null) {
- return $this->redirect('/campaigns');
- }
-
- $storedValues = [];
-
- if (!empty($row['attribute_values'])) {
- $storedValues = json_decode((string) $row['attribute_values'], true) ?? [];
- }
-
- $model = new CampaignViewModel();
- $model->title = 'Edit Campaign';
- $model->campaign = $row;
- $model->saved = Request::capture()->input('saved') === '1';
- $model->campaignTypes = $this->loadCampaignTypes();
- $model->form = [
- 'campaign_type_id' => (int) $row['campaign_type_id'],
- 'attribute_values' => $storedValues,
- ];
-
- return $this->view('campaigns.edit', [
- 'model' => $model,
- 'pageTitle' => $model->title,
- ]);
- }
-
- public function update(string $id): Response
- {
- $before = $this->repo()->findWithType((int) $id);
-
- if ($before === null) {
- return $this->redirect('/campaigns');
- }
-
- $request = Request::capture();
- $model = new CampaignViewModel();
- $model->title = 'Edit Campaign';
- $model->campaign = $before;
- $model->campaignTypes = $this->loadCampaignTypes();
-
- [$form, $errors] = $this->validateForm($request, $model->campaignTypes);
-
- if (!empty($errors)) {
- $model->form = $form;
- $model->errors = $errors;
-
- return $this->view('campaigns.edit', [
- 'model' => $model,
- 'pageTitle' => $model->title,
- ]);
- }
-
- $campaign = new Campaign();
- $campaign->id = (int) $id;
- $campaign->campaignTypeId = (int) $form['campaign_type_id'];
- $campaign->attributeValues = $form['attribute_values'];
-
- $this->repo()->update($campaign);
-
- // Audit: U — capture before and after snapshots.
- $after = $this->repo()->findWithType((int) $id);
- $this->auditRepo()->log(
- (int) $id,
- 'U',
- [
- 'before' => $this->toAuditFields($before),
- 'after' => $this->toAuditFields($after ?? []),
- ],
- $this->currentUsername()
- );
-
- return $this->redirect('/campaigns/' . $id . '/edit?saved=1');
- }
-
- public function destroy(string $id): Response
- {
- $row = $this->repo()->find((int) $id);
-
- if ($row !== null) {
- $this->repo()->delete((int) $id);
-
- // Audit: D — snapshot of the row at the moment of deletion.
- $this->auditRepo()->log(
- (int) $row['id'],
- 'D',
- $this->toAuditFields($row),
- $this->currentUsername()
- );
- }
-
- return $this->redirect('/campaigns?deleted=1');
- }
-
- // ── Helpers ───────────────────────────────────────────────────────────────
-
- /**
- * @return list<array{id: int, name: string, attributes: list<array{name: string, type: string}>}>
- */
- private function loadCampaignTypes(): array
- {
- return array_map(static function (array $type): array {
- return [
- 'id' => (int) $type['id'],
- 'name' => (string) $type['name'],
- 'attributes' => json_decode((string) ($type['attributes'] ?? '[]'), true) ?? [],
- ];
- }, $this->ctRepo()->allOrderedByName());
- }
-
- /**
- * @param list<array{id: int, name: string, attributes: list<array{name: string, type: string}> }> $types
- * @return list<array{name: string, type: string}>
- */
- private function attributesForType(int $typeId, array $types): array
- {
- foreach ($types as $type) {
- if ($type['id'] === $typeId) {
- return $type['attributes'];
- }
- }
-
- return [];
- }
-
- /**
- * @param list<array{id: int, name: string, attributes: list<array{name: string, type: string}> }> $campaignTypes
- * @return array{0: array{campaign_type_id: int|string, attribute_values: array<string, string>}, 1: array<string, list<string>>}
- */
- private function validateForm(Request $request, array $campaignTypes): array
- {
- $campaignTypeId = (int) $request->input('campaign_type_id', 0);
- $submittedValues = (array) ($request->input('attribute_values') ?? []);
-
- $errors = [];
-
- if (!verify_csrf_token((string) $request->input('_token', ''))) {
- $errors['_token'][] = 'Your form session expired. Please refresh the page and try again.';
- }
-
- if ($campaignTypeId === 0) {
- $errors['campaign_type_id'][] = 'Please select a campaign type.';
- }
-
- // Build attribute values — only keep keys that belong to the selected type.
- $typeAttributes = $this->attributesForType($campaignTypeId, $campaignTypes);
- $attributeValues = [];
-
- foreach ($typeAttributes as $attr) {
- $attributeValues[$attr['name']] = trim((string) ($submittedValues[$attr['name']] ?? ''));
- }
-
- $form = [
- 'campaign_type_id' => $campaignTypeId,
- 'attribute_values' => $attributeValues,
- ];
-
- return [$form, $errors];
- }
-
- /**
- * @param array<string, mixed> $row
- * @return array<string, mixed>
- */
- private function toAuditFields(array $row): array
- {
- $attrValues = [];
-
- if (!empty($row['attribute_values'])) {
- $raw = $row['attribute_values'];
- $attrValues = is_string($raw) ? (json_decode($raw, true) ?? []) : (array) $raw;
- }
-
- return [
- 'campaign_type_id' => (int) ($row['campaign_type_id'] ?? 0),
- 'campaign_type_name' => (string) ($row['campaign_type_name'] ?? ''),
- 'attribute_values' => $attrValues,
- 'created_at' => (string) ($row['created_at'] ?? ''),
- 'updated_at' => (string) ($row['updated_at'] ?? ''),
- ];
- }
-
- private function currentUsername(): string
- {
- return auth()->user()?->username ?? 'system';
- }
-
- private function repo(): CampaignRepository
- {
- return new CampaignRepository(database());
- }
-
- private function auditRepo(): CampaignAuditRepository
- {
- return new CampaignAuditRepository(database());
- }
-
- private function ctRepo(): CampaignTypeRepository
- {
- return new CampaignTypeRepository(database());
- }
- }
|