[ 'delField' => __('Delete this field? Existing content is not deleted, but the front end will no longer be able to read it.', 'aumframe'), 'delType' => __('Delete this type? Existing content is NOT deleted, but it will disappear from the admin (nothing registers the type any more). Are you sure?', 'aumframe'), ], ], JSON_UNESCAPED_UNICODE) . ';', 'before'); } // ------------------------------------------------------------------ Saving public static function handle_post(): void { if (empty($_POST['aumframe_ct_action']) || !current_user_can('manage_options')) { return; } check_admin_referer('aumframe_ct_model', self::NONCE); $action = sanitize_key(wp_unslash($_POST['aumframe_ct_action'])); $notice = ''; if ($action === 'save') { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Store::save() hands the whole structure to Store::sanitize_model(), which sanitises each post type and taxonomy. $raw = isset($_POST['model']) && is_array($_POST['model']) ? wp_unslash($_POST['model']) : []; $model = [ 'postTypes' => array_values((array) ($raw['postTypes'] ?? [])), 'taxonomies' => array_values((array) ($raw['taxonomies'] ?? [])), ]; Store::save($model); $notice = 'saved'; } if ($action === 'import') { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON, unslashed then decoded by Store::import(), which runs the same sanitize_model(). $json = isset($_POST['import_json']) ? wp_unslash($_POST['import_json']) : ''; $merge = empty($_POST['import_replace']); $result = Store::import((string) $json, $merge); $notice = $result['ok'] ? 'imported-' . $result['added'] . '-' . $result['skipped'] : 'import-failed'; } wp_safe_redirect(add_query_arg( ['page' => self::PAGE, 'notice' => $notice], admin_url('admin.php') )); exit; } // ------------------------------------------------------------------ Rendering public static function render(): void { if (!current_user_can('manage_options')) { return; } // Export is an ordinary GET — no AJAX download button, since copy and paste is used more often anyway if (isset($_GET['export'])) { self::render_export(); return; } $model = Store::all(); ?>


$pt) : ?>

$tx) : ?>


←

%s

', esc_html__('Saved. Permalinks have been flushed.', 'aumframe')); return; } if ($notice === 'import-failed') { printf('

%s

', esc_html__('Import failed: that is not a valid model pack.', 'aumframe')); return; } if (strpos($notice, 'imported-') === 0) { $parts = explode('-', $notice); printf( '

%s

', esc_html(sprintf( /* translators: 1: Number of content types added. 2: Number skipped because a definition already existed. */ __('Import done: %1$d added, %2$d skipped (existing definitions kept).', 'aumframe'), (int) ($parts[1] ?? 0), (int) ($parts[2] ?? 0) )) ); } } // ------------------------------------------------------------------ Cards /** * The card for an extension entry — a content-model entry that only attaches fields to a type * somebody else registered (the built-in `page` / `post`, or another plugin's type). * * It needs its own card for two reasons, and the second one loses data: * * - Nothing registration-time applies. Labels, URL prefix, menu icon, visibility, archive and * supported areas all belong to whoever registered the type. The ordinary card offers all of them, * and `sanitize_post_type()` drops every one of them again on save — "set it and nothing happened". * - `extends` has to come back on submit. The form is the whole entry: whatever it does not * render is not in `$_POST`, and `sanitize_post_type()` reads `$pt['extends']` to decide which * shape it is looking at. An extension entry rendered through the ordinary card came back as an * ordinary one, with no labels — and an ordinary entry with no labels is discarded. So opening * this page and pressing Save deleted every extension entry, reporting success. */ private static function extension_card(array $pt, string $i): void { $n = 'model[postTypes][' . $i . ']'; $name = (string) ($pt['name'] ?? ''); ?>

'', 'labels' => ['singular' => '', 'plural' => ''], 'public' => true, 'hasArchive' => true, 'slug' => '', 'menuIcon' => '', 'supports' => ['title', 'editor', 'excerpt', 'thumbnail'], 'taxonomies' => [], 'fields' => [], ]; } private static function blank_taxonomy(): array { return [ 'name' => '', 'labels' => ['singular' => '', 'plural' => ''], 'hierarchical' => true, 'public' => true, 'slug' => '', 'postTypes' => [], 'fields' => [], ]; } private static function post_type_card(array $pt, string $i): void { if (!empty($pt['extends'])) { self::extension_card($pt, $i); return; } $n = 'model[postTypes][' . $i . ']'; $count = is_numeric($i) && $pt['name'] !== '' ? wp_count_posts($pt['name']) : null; $total = $count ? array_sum((array) $count) : 0; ?>

__('Title', 'aumframe'), 'editor' => __('Content', 'aumframe'), 'excerpt' => __('Excerpt', 'aumframe'), 'thumbnail' => __('Featured image', 'aumframe'), 'page-attributes' => __('Order', 'aumframe'), 'comments' => __('Comments', 'aumframe'), ] as $key => $label) : ?>
__('— none —', 'aumframe')]; foreach ($fields as $f) { if (!empty($f['key'])) { $opts[$f['key']] = ($f['label'] ?? $f['key']) . ' · ' . $f['key']; } } $sel = static function (string $name, string $cur, array $opts): void { ?>

' . esc_html($dedicated[$pt_name]) . '' ); ?>

true], 'objects') as $pt) { if ($pt->name !== 'attachment' && !isset($choices[$pt->name])) { $choices[$pt->name] = $pt->labels->name ?? $pt->name; } } foreach ($choices as $key => $label) : ?>
$f) : ?>

'', 'label' => '', 'type' => 'text', 'help' => '', 'options' => '', 'postType' => '', 'taxonomy' => '', 'fields' => []]; } private static function field_row(array $f, string $base, string $i, bool $sub): void { $n = $base . '[' . $i . ']'; $f = array_merge(self::blank_field(), $f); $types = $sub ? Fields::sub_types() : Fields::types(); ?>
true], 'objects') as $pt) { if ($pt->name !== 'attachment' && !isset($out[$pt->name])) { $out[$pt->name] = ($pt->labels->name ?? $pt->name) . ' · ' . $pt->name; } } return $out; } private static function taxonomy_choices(): array { $out = []; foreach (Store::taxonomies() as $tx) { $out[$tx['name']] = ($tx['labels']['plural'] ?? $tx['name']) . ' · ' . $tx['name']; } foreach (get_taxonomies(['public' => true], 'objects') as $tx) { if ($tx->name !== 'post_format' && !isset($out[$tx->name])) { $out[$tx->name] = ($tx->labels->name ?? $tx->name) . ' · ' . $tx->name; } } return $out; } }