fonts = $fonts; } public function register(): void { add_action('admin_menu', [$this, 'menu'], 20); add_action('admin_enqueue_scripts', [$this, 'assets']); add_action('admin_post_' . self::ACTION_SAVE, [$this, 'save']); add_action('admin_post_' . self::ACTION_DELETE, [$this, 'delete']); add_action('admin_post_' . self::ACTION_ADOBE, [$this, 'save_adobe']); } public static function url(array $args = []): string { return add_query_arg(['page' => self::SLUG] + $args, admin_url('admin.php')); } public function menu(): void { $this->hook = (string) add_submenu_page('zadex', __('Custom Fonts', 'zadex'), __('Custom Fonts', 'zadex'), Fonts::CAPABILITY, self::SLUG, [$this, 'render']); } public function assets(string $hook): void { if ($hook !== $this->hook) { return; } wp_enqueue_script('zadex-site-assets-admin', ZADEX_URL . 'includes/Admin/assets/site-assets.js', [], ZADEX_VERSION, true); $css = $this->fonts->css(); if ($css !== '') { wp_register_style('zadex-custom-fonts-admin', false, [], ZADEX_VERSION); wp_enqueue_style('zadex-custom-fonts-admin'); wp_add_inline_style('zadex-custom-fonts-admin', wp_strip_all_tags($css)); } $this->fonts->enqueue_adobe(); } /** * Builds the variations from the form: kept files (hidden id/url, unless "remove" is ticked) and new uploads * (`$files` = `$_FILES['font_files']`, keyed [name|tmp_name|error|size][row][ext]). Rows marked for deletion * or left without any file are dropped. `$upload` stores one file and returns {id, url} or WP_Error. * * @return array{variations: array, errors: string[]} */ public static function variations_from_form(array $rows, array $files, callable $upload): array { $out = []; $errors = []; foreach ($rows as $i => $row) { if (!is_array($row) || !empty($row['delete'])) { continue; } $kept = []; foreach (array_keys(Fonts::FORMATS) as $ext) { $f = $row['files'][$ext] ?? null; if (is_array($f) && !empty($f['url']) && empty($row['remove'][$ext])) { $kept[$ext] = ['id' => (int) ($f['id'] ?? 0), 'url' => (string) $f['url']]; } $name = $files['name'][$i][$ext] ?? ''; if (is_string($name) && $name !== '' && (int) ($files['error'][$i][$ext] ?? 4) !== 4) { $stored = $upload([ 'name' => $name, 'type' => (string) ($files['type'][$i][$ext] ?? ''), 'tmp_name' => (string) ($files['tmp_name'][$i][$ext] ?? ''), 'error' => (int) ($files['error'][$i][$ext] ?? 1), 'size' => (int) ($files['size'][$i][$ext] ?? 0), ]); if (is_wp_error($stored)) { $errors[] = $name . ': ' . $stored->get_error_message(); } elseif (strtolower(pathinfo($name, PATHINFO_EXTENSION)) === $ext) { $kept[$ext] = $stored; } else { $errors[] = $name . ': ' . __('Font files must be .woff2, .woff or .ttf.', 'zadex'); } } } if ($kept) { $out[] = ['weight' => (string) ($row['weight'] ?? '400'), 'style' => (string) ($row['style'] ?? 'normal'), 'files' => $kept]; } } return ['variations' => Fonts::sanitize_variations($out), 'errors' => $errors]; } /** * Saves a font from the already-sanitized form fields (`family`, `display`, `id`, `variations` rows) and the * sanitized `font_files` upload entry. * * @return array{id: int|\WP_Error, errors: string[]} */ public function handle_save(array $post, array $files) { $built = self::variations_from_form(is_array($post['variations'] ?? null) ? $post['variations'] : [], $files, [Fonts::class, 'upload']); $id = $this->fonts->save([ 'family' => (string) ($post['family'] ?? ''), 'display' => (string) ($post['display'] ?? 'swap'), 'variations' => $built['variations'], ], (int) ($post['id'] ?? 0)); return ['id' => $id, 'errors' => $built['errors']]; } /** admin-post.php: add / update a font. */ public function save(): void { if (!current_user_can(Fonts::CAPABILITY)) { wp_die(esc_html__('You are not allowed to manage fonts.', 'zadex'), 403); } check_admin_referer(self::ACTION_SAVE); $form = [ 'id' => isset($_POST['id']) ? absint(wp_unslash($_POST['id'])) : 0, 'family' => isset($_POST['family']) ? sanitize_text_field(wp_unslash($_POST['family'])) : '', 'display' => isset($_POST['display']) ? sanitize_key(wp_unslash($_POST['display'])) : 'swap', 'variations' => isset($_POST['variations']) && is_array($_POST['variations']) ? map_deep(wp_unslash($_POST['variations']), 'sanitize_text_field') : [], ]; $files = UploadedFile::from_request('font_files', self::ACTION_SAVE) ?? []; $result = $this->handle_save($form, $files); $args = ['action' => 'edit']; if (is_wp_error($result['id'])) { $args['id'] = $form['id']; $result['errors'][] = $result['id']->get_error_message(); } else { $args['id'] = (int) $result['id']; Notices::add(self::SLUG, __('Font saved.', 'zadex'), 'success', 'font-saved'); } if ($result['errors']) { Notices::add(self::SLUG, implode(' | ', $result['errors']), 'error'); } wp_safe_redirect(self::url($args)); exit; } /** admin-post.php: delete a font (its files stay in the media library). */ public function delete(): void { if (!current_user_can(Fonts::CAPABILITY)) { wp_die(esc_html__('You are not allowed to manage fonts.', 'zadex'), 403); } check_admin_referer(self::ACTION_DELETE); $this->fonts->delete(isset($_GET['id']) ? absint(wp_unslash($_GET['id'])) : 0); Notices::add(self::SLUG, __('Font deleted.', 'zadex')); wp_safe_redirect(self::url()); exit; } /** admin-post.php: Adobe Fonts project id and families. */ public function save_adobe(): void { if (!current_user_can(Fonts::CAPABILITY)) { wp_die(esc_html__('You are not allowed to manage fonts.', 'zadex'), 403); } check_admin_referer(self::ACTION_ADOBE); $project = isset($_POST['adobe_project']) ? sanitize_text_field(wp_unslash($_POST['adobe_project'])) : ''; $families = isset($_POST['adobe_families']) ? sanitize_textarea_field(wp_unslash($_POST['adobe_families'])) : ''; $refresh = !empty($_POST['adobe_refresh']); $result = $this->fonts->save_adobe($project, $families, $refresh); if (is_wp_error($result)) { Notices::add(self::SLUG, $result->get_error_message(), 'error'); } else { Notices::add(self::SLUG, __('Adobe Fonts settings saved.', 'zadex')); } wp_safe_redirect(self::url()); exit; } public function render(): void { if (!current_user_can(Fonts::CAPABILITY)) { return; } // Screen routing only (list vs. add/edit form): nothing is changed here, the capability is checked above // and saving goes through the nonce-checked admin-post handlers. // phpcs:disable WordPress.Security.NonceVerification.Recommended $screen_action = isset($_GET['action']) ? sanitize_key(wp_unslash($_GET['action'])) : ''; $edit_id = isset($_GET['id']) ? absint(wp_unslash($_GET['id'])) : 0; // phpcs:enable WordPress.Security.NonceVerification.Recommended if ($screen_action === 'edit') { $this->render_edit($edit_id); return; } $fonts = $this->fonts->all(); $adobe = $this->fonts->adobe(); ?>
0 ? $this->fonts->get($id) : null; if (!$font) { $id = 0; $font = ['family' => '', 'display' => 'swap', 'variations' => []]; } $rows = $font['variations'] ?: [['weight' => '400', 'style' => 'normal', 'files' => []]]; ?>