Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

324 linhas
10KB

  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Controllers;
  4. use App\Models\Campaign;
  5. use App\Repositories\CampaignAuditRepository;
  6. use App\Repositories\CampaignRepository;
  7. use App\Repositories\CampaignTypeRepository;
  8. use App\ViewModels\CampaignViewModel;
  9. use Core\Controller;
  10. use Core\Request;
  11. use Core\Response;
  12. use Core\Validator;
  13. class CampaignController extends Controller
  14. {
  15. public function index(): Response
  16. {
  17. $request = Request::capture();
  18. $model = new CampaignViewModel();
  19. $model->saved = $request->input('saved') === '1';
  20. $model->deleted = $request->input('deleted') === '1';
  21. return $this->view('campaigns.index', [
  22. 'model' => $model,
  23. 'pageTitle' => $model->title,
  24. ]);
  25. }
  26. public function data(): Response
  27. {
  28. $rows = $this->repo()->allWithType();
  29. $data = array_map(static function (array $row): array {
  30. $attrValues = [];
  31. $campaignTypeAttributes = [];
  32. if (!empty($row['attribute_values'])) {
  33. $attrValues = json_decode((string) $row['attribute_values'], true) ?? [];
  34. }
  35. if (!empty($row['campaign_type_attributes'])) {
  36. $campaignTypeAttributes = json_decode((string) $row['campaign_type_attributes'], true) ?? [];
  37. }
  38. $summary = implode(', ', array_map(
  39. static fn($k, $v) => "{$k}: {$v}",
  40. array_keys($attrValues),
  41. array_values($attrValues)
  42. ));
  43. return [
  44. 'id' => (int) $row['id'],
  45. 'campaign_type_id' => (int) $row['campaign_type_id'],
  46. 'campaign_type_name' => (string) $row['campaign_type_name'],
  47. 'campaign_type_attributes' => $campaignTypeAttributes,
  48. 'attribute_values' => $attrValues,
  49. 'attributes_summary' => $summary,
  50. 'created_at' => (string) $row['created_at'],
  51. ];
  52. }, $rows);
  53. return $this->json($data);
  54. }
  55. public function create(): Response
  56. {
  57. $model = new CampaignViewModel();
  58. $model->title = 'New Campaign';
  59. $model->campaignTypes = $this->loadCampaignTypes();
  60. return $this->view('campaigns.create', [
  61. 'model' => $model,
  62. 'pageTitle' => $model->title,
  63. ]);
  64. }
  65. public function store(): Response
  66. {
  67. $request = Request::capture();
  68. $model = new CampaignViewModel();
  69. $model->title = 'New Campaign';
  70. $model->campaignTypes = $this->loadCampaignTypes();
  71. [$form, $errors] = $this->validateForm($request, $model->campaignTypes);
  72. if (!empty($errors)) {
  73. $model->form = $form;
  74. $model->errors = $errors;
  75. return $this->view('campaigns.create', [
  76. 'model' => $model,
  77. 'pageTitle' => $model->title,
  78. ]);
  79. }
  80. $campaign = new Campaign();
  81. $campaign->campaignTypeId = (int) $form['campaign_type_id'];
  82. $campaign->attributeValues = $form['attribute_values'];
  83. $this->repo()->create($campaign);
  84. // Audit: I — query back the inserted row to capture the generated id.
  85. $inserted = $this->repo()->findLatestByType($campaign->campaignTypeId);
  86. if ($inserted !== null) {
  87. $this->auditRepo()->log(
  88. (int) $inserted['id'],
  89. 'I',
  90. $this->toAuditFields($inserted),
  91. $this->currentUsername()
  92. );
  93. }
  94. return $this->redirect('/campaigns?saved=1');
  95. }
  96. public function edit(string $id): Response
  97. {
  98. $row = $this->repo()->findWithType((int) $id);
  99. if ($row === null) {
  100. return $this->redirect('/campaigns');
  101. }
  102. $storedValues = [];
  103. if (!empty($row['attribute_values'])) {
  104. $storedValues = json_decode((string) $row['attribute_values'], true) ?? [];
  105. }
  106. $model = new CampaignViewModel();
  107. $model->title = 'Edit Campaign';
  108. $model->campaign = $row;
  109. $model->saved = Request::capture()->input('saved') === '1';
  110. $model->campaignTypes = $this->loadCampaignTypes();
  111. $model->form = [
  112. 'campaign_type_id' => (int) $row['campaign_type_id'],
  113. 'attribute_values' => $storedValues,
  114. ];
  115. return $this->view('campaigns.edit', [
  116. 'model' => $model,
  117. 'pageTitle' => $model->title,
  118. ]);
  119. }
  120. public function update(string $id): Response
  121. {
  122. $before = $this->repo()->findWithType((int) $id);
  123. if ($before === null) {
  124. return $this->redirect('/campaigns');
  125. }
  126. $request = Request::capture();
  127. $model = new CampaignViewModel();
  128. $model->title = 'Edit Campaign';
  129. $model->campaign = $before;
  130. $model->campaignTypes = $this->loadCampaignTypes();
  131. [$form, $errors] = $this->validateForm($request, $model->campaignTypes);
  132. if (!empty($errors)) {
  133. $model->form = $form;
  134. $model->errors = $errors;
  135. return $this->view('campaigns.edit', [
  136. 'model' => $model,
  137. 'pageTitle' => $model->title,
  138. ]);
  139. }
  140. $campaign = new Campaign();
  141. $campaign->id = (int) $id;
  142. $campaign->campaignTypeId = (int) $form['campaign_type_id'];
  143. $campaign->attributeValues = $form['attribute_values'];
  144. $this->repo()->update($campaign);
  145. // Audit: U — capture before and after snapshots.
  146. $after = $this->repo()->findWithType((int) $id);
  147. $this->auditRepo()->log(
  148. (int) $id,
  149. 'U',
  150. [
  151. 'before' => $this->toAuditFields($before),
  152. 'after' => $this->toAuditFields($after ?? []),
  153. ],
  154. $this->currentUsername()
  155. );
  156. return $this->redirect('/campaigns/' . $id . '/edit?saved=1');
  157. }
  158. public function destroy(string $id): Response
  159. {
  160. $row = $this->repo()->find((int) $id);
  161. if ($row !== null) {
  162. $this->repo()->delete((int) $id);
  163. // Audit: D — snapshot of the row at the moment of deletion.
  164. $this->auditRepo()->log(
  165. (int) $row['id'],
  166. 'D',
  167. $this->toAuditFields($row),
  168. $this->currentUsername()
  169. );
  170. }
  171. return $this->redirect('/campaigns?deleted=1');
  172. }
  173. // ── Helpers ───────────────────────────────────────────────────────────────
  174. /**
  175. * @return list<array{id: int, name: string, attributes: list<array{name: string, type: string}>}>
  176. */
  177. private function loadCampaignTypes(): array
  178. {
  179. return array_map(static function (array $type): array {
  180. return [
  181. 'id' => (int) $type['id'],
  182. 'name' => (string) $type['name'],
  183. 'attributes' => json_decode((string) ($type['attributes'] ?? '[]'), true) ?? [],
  184. ];
  185. }, $this->ctRepo()->allOrderedByName());
  186. }
  187. /**
  188. * @param list<array{id: int, name: string, attributes: list<array{name: string, type: string}> }> $types
  189. * @return list<array{name: string, type: string}>
  190. */
  191. private function attributesForType(int $typeId, array $types): array
  192. {
  193. foreach ($types as $type) {
  194. if ($type['id'] === $typeId) {
  195. return $type['attributes'];
  196. }
  197. }
  198. return [];
  199. }
  200. /**
  201. * @param list<array{id: int, name: string, attributes: list<array{name: string, type: string}> }> $campaignTypes
  202. * @return array{0: array{campaign_type_id: int|string, attribute_values: array<string, string>}, 1: array<string, list<string>>}
  203. */
  204. private function validateForm(Request $request, array $campaignTypes): array
  205. {
  206. $campaignTypeId = (int) $request->input('campaign_type_id', 0);
  207. $submittedValues = (array) ($request->input('attribute_values') ?? []);
  208. $errors = [];
  209. if (!verify_csrf_token((string) $request->input('_token', ''))) {
  210. $errors['_token'][] = 'Your form session expired. Please refresh the page and try again.';
  211. }
  212. if ($campaignTypeId === 0) {
  213. $errors['campaign_type_id'][] = 'Please select a campaign type.';
  214. }
  215. // Build attribute values — only keep keys that belong to the selected type.
  216. $typeAttributes = $this->attributesForType($campaignTypeId, $campaignTypes);
  217. $attributeValues = [];
  218. foreach ($typeAttributes as $attr) {
  219. $attributeValues[$attr['name']] = trim((string) ($submittedValues[$attr['name']] ?? ''));
  220. }
  221. $form = [
  222. 'campaign_type_id' => $campaignTypeId,
  223. 'attribute_values' => $attributeValues,
  224. ];
  225. return [$form, $errors];
  226. }
  227. /**
  228. * @param array<string, mixed> $row
  229. * @return array<string, mixed>
  230. */
  231. private function toAuditFields(array $row): array
  232. {
  233. $attrValues = [];
  234. if (!empty($row['attribute_values'])) {
  235. $raw = $row['attribute_values'];
  236. $attrValues = is_string($raw) ? (json_decode($raw, true) ?? []) : (array) $raw;
  237. }
  238. return [
  239. 'campaign_type_id' => (int) ($row['campaign_type_id'] ?? 0),
  240. 'campaign_type_name' => (string) ($row['campaign_type_name'] ?? ''),
  241. 'attribute_values' => $attrValues,
  242. 'created_at' => (string) ($row['created_at'] ?? ''),
  243. 'updated_at' => (string) ($row['updated_at'] ?? ''),
  244. ];
  245. }
  246. private function currentUsername(): string
  247. {
  248. return auth()->user()?->username ?? 'system';
  249. }
  250. private function repo(): CampaignRepository
  251. {
  252. return new CampaignRepository(database());
  253. }
  254. private function auditRepo(): CampaignAuditRepository
  255. {
  256. return new CampaignAuditRepository(database());
  257. }
  258. private function ctRepo(): CampaignTypeRepository
  259. {
  260. return new CampaignTypeRepository(database());
  261. }
  262. }

Powered by TurnKey Linux.