serviceFactory = $serviceFactory;
$this->wp = $wp;
}
public function register(): void
{
$this->wp->addAction('admin_menu', [$this, 'registerMenu']);
$this->wp->addAction('admin_post_feca_mailshots_pdf_assets_api', [$this, 'handleApi']);
$this->wp->addAction('admin_post_feca_mailshots_pdf_assets_ui_save', [$this, 'handleUiSave']);
$this->wp->addAction('admin_post_feca_mailshots_pdf_assets_ui_delete', [$this, 'handleUiDelete']);
}
public function registerMenu(): void
{
$this->wp->addSubmenuPage('feca-mailshot', 'PDF Assets', 'PDF Assets', self::CAPABILITY, self::PAGE_SLUG, [$this, 'render']);
}
public function render(): void
{
if (!$this->wp->currentUserCan(self::CAPABILITY)) {
echo 'Permission denied';
return;
}
$items = $this->service()->list();
$draft = $this->consumeOptionArray(self::DRAFT_OPTION_KEY);
$editId = (int) ($this->wp->requestParam('edit_id', '0') ?? '0');
if ($editId <= 0 && is_array($draft) && isset($draft['id'])) {
$editId = (int) $draft['id'];
}
$editItem = null;
foreach ($items as $row) {
if ((int) ($row['id'] ?? 0) === $editId) {
$editItem = $row;
break;
}
}
$result = $this->consumeOptionArray(self::RESULT_OPTION_KEY);
$action = htmlspecialchars($this->wp->adminUrl('admin-post.php'));
$name = (string) ($editItem['name'] ?? '');
$fileName = (string) ($editItem['file_name'] ?? '');
$width = (string) ($editItem['width_mm'] ?? '210');
$height = (string) ($editItem['height_mm'] ?? '297');
$just = (string) ($editItem['justification'] ?? 'in-place');
$base64 = '';
if (is_array($draft)) {
$name = (string) ($draft['name'] ?? $name);
$fileName = (string) ($draft['file_name'] ?? $fileName);
$width = (string) ($draft['width_mm'] ?? $width);
$height = (string) ($draft['height_mm'] ?? $height);
$just = (string) ($draft['justification'] ?? $just);
$base64 = (string) ($draft['file_bytes_base64'] ?? '');
}
echo '
PDF Assets
';
if ($result !== null) {
$ok = !empty($result['ok']);
$bg = $ok ? '#f1f8e9' : '#ffebee';
$border = $ok ? '#8bc34a' : '#ef9a9a';
$title = $ok ? 'PDF asset saved.' : 'PDF asset action failed.';
echo '
';
echo '
' . htmlspecialchars($title) . '';
if (!empty($result['errors']) && is_array($result['errors'])) {
echo '
' . htmlspecialchars(implode('; ', $result['errors'])) . '
';
}
echo '
';
}
echo '
';
echo '
';
echo '
';
echo '
';
echo '
';
echo '
';
echo '
Existing PDF Assets
';
echo '
| ID | Name | File | Size (mm) | Justification | Bytes | Actions |
';
foreach ($items as $row) {
$id = (int) ($row['id'] ?? 0);
$editUrl = $this->wp->adminUrl('admin.php?page=feca-mailshots-pdf-assets&edit_id=' . $id);
echo '';
echo '| ' . $id . ' | ';
echo '' . htmlspecialchars((string) ($row['name'] ?? '')) . ' | ';
echo '' . htmlspecialchars((string) ($row['file_name'] ?? '')) . ' | ';
echo '' . htmlspecialchars((string) ($row['width_mm'] ?? '')) . ' x ' . htmlspecialchars((string) ($row['height_mm'] ?? '')) . ' | ';
echo '' . htmlspecialchars((string) ($row['justification'] ?? '')) . ' | ';
echo '' . htmlspecialchars((string) ($row['byte_size'] ?? '')) . ' | ';
echo 'Edit ';
echo ' | ';
echo '
';
}
if ($items === []) {
echo '| No PDF assets found. |
';
}
echo '
';
echo $this->modalEditorScript(
'pdf-editor-modal',
'pdf-open-new',
'pdf-close-editor',
'pdf-editor-form',
'pdf-editor-id',
['pdf_name', 'pdf_file_name', 'pdf_width', 'pdf_height', 'pdf_base64'],
$editId > 0,
is_array($draft),
$result !== null,
!empty($result['ok']),
'pdf_just',
'in-place',
'pdf_file_upload',
'pdf_file_name'
);
echo '
';
}
public function handleUiSave(): void
{
if (!$this->enforceMutationGuardOrJson(self::CAPABILITY, self::NONCE_ACTION)) {
return;
}
$id = $this->requestOptionalInt('id');
$uploadedBase64 = $this->uploadedFileToBase64('file_upload');
$base64 = $uploadedBase64 !== null ? $uploadedBase64 : $this->requestString('file_bytes_base64', '');
$payload = $this->pdfAssetPayload($base64);
$result = $this->service()->save($id, $payload);
$this->persistResultAndDraft(self::RESULT_OPTION_KEY, self::DRAFT_OPTION_KEY, $result, ['id' => $id] + $payload);
$editId = $id;
if (!empty($result['ok']) && isset($result['id'])) {
$editId = (int) $result['id'];
}
$this->redirectToAdminPage(self::PAGE_SLUG, $editId);
}
public function handleUiDelete(): void
{
if (!$this->enforceMutationGuardOrJson(self::CAPABILITY, self::NONCE_ACTION)) {
return;
}
$id = $this->requestInt('id', 0);
$result = ['ok' => true];
try {
if ($id > 0) {
$this->service()->delete($id);
}
} catch (\Throwable $e) {
$result = ['ok' => false, 'errors' => [$e->getMessage()]];
}
$this->wp->updateOption(self::RESULT_OPTION_KEY, $result);
$this->redirectToAdminPage(self::PAGE_SLUG);
}
public function handleApi(): void
{
if (!$this->enforceCapabilityOrJson(self::CAPABILITY)) {
return;
}
$op = $this->requestString('op', 'list');
try {
$this->handleBasicCrudApi(
$op,
fn(): array => ['ok' => true, 'items' => $this->service()->list()],
function (): array {
if (!$this->mutationGuardValid(self::CAPABILITY, self::NONCE_ACTION)) {
return ['ok' => false, 'error' => 'Mutation guard rejected request.'];
}
$id = $this->requestOptionalInt('id');
$payload = $this->pdfAssetPayload($this->requestString('file_bytes_base64', ''));
return $this->service()->save($id, $payload);
},
function (): array {
if (!$this->mutationGuardValid(self::CAPABILITY, self::NONCE_ACTION)) {
return ['ok' => false, 'error' => 'Mutation guard rejected request.'];
}
$id = $this->requestInt('id', 0);
$this->service()->delete($id);
return ['ok' => true];
}
);
} catch (\Throwable $e) {
$this->wp->sendJson(['ok' => false, 'error' => $e->getMessage()], 500);
}
}
private function service(): PdfAssetService
{
return ($this->serviceFactory)();
}
/** @return array{name:string,file_name:string,mime_type:string,file_bytes_base64:string,width_mm:string,height_mm:string,justification:string} */
private function pdfAssetPayload(string $base64): array
{
return [
'name' => $this->requestString('name', ''),
'file_name' => $this->requestString('file_name', ''),
'mime_type' => $this->requestString('mime_type', ''),
'file_bytes_base64' => $base64,
'width_mm' => $this->requestString('width_mm', '0'),
'height_mm' => $this->requestString('height_mm', '0'),
'justification' => $this->requestString('justification', 'in-place'),
];
}
}