Skip to content
wiki.fftac.org

Promptrepository - Source Excerpt 01

Back to Promptrepository

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/spiralist-workspace/includes/Domain/PromptRepository.php so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/plugins/spiralist-workspace/includes/Domain/PromptRepository.php

<?php

namespace SpiralistWorkspace\Domain;

use WP_Post;
use SpiralistWorkspace\Config\Settings;
use SpiralistWorkspace\Support\PromptCategoryRegistry;
use SpiralistWorkspace\Support\PromptIntentRegistry;
use SpiralistWorkspace\Support\PromptMetaRegistry;

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Stores prompt entities on a dedicated custom post type.
 */
class PromptRepository
{
    public const POST_TYPE = 'spiralist_prompt';
    public const LEGACY_POST_TYPE = 'spiralist_workspace_prompt';
    public const TAG_TAXONOMY = 'spiralist_prompt_tag';
    public const LEGACY_TAG_TAXONOMY = 'spiralist_workspace_prompt_tag';
    public const TYPE_TAXONOMY = 'spiralist_prompt_type';
    public const LEGACY_TYPE_TAXONOMY = 'spiralist_workspace_prompt_type';
    public const CATEGORY_TAXONOMY = 'spiralist_prompt_cat';
    public const LEGACY_CATEGORY_TAXONOMY = 'spiralist_workspace_prompt_category';

    private VersionRepository $versions;
    private PermissionRepository $shares;
    private FavoriteRepository $favorites;
    private ?ReputationRepository $reputation;

    public function __construct(
        VersionRepository $versions,
        PermissionRepository $shares,
        FavoriteRepository $favorites,
        ?ReputationRepository $reputation = null
    )
    {
        $this->versions = $versions;
        $this->shares = $shares;
        $this->favorites = $favorites;
        $this->reputation = $reputation;
    }

    /**
     * Registers post type and taxonomies.
     */
    public function register_content_types(): void
    {
        self::register_post_type();
        self::register_taxonomies();
        PromptCategoryRegistry::ensure_terms();
    }

    /**
     * Registers the internal prompt post type.
     */
    public static function register_post_type(): void
    {
        register_post_type(
            self::POST_TYPE,
            [
                'labels' => [
                    'name' => 'Workspace Prompts',
                    'singular_name' => 'Workspace Prompt',
                    'menu_name' => 'Workspace Prompts',
                    'add_new_item' => 'Add Workspace Prompt',
                    'edit_item' => 'Edit Workspace Prompt',
                    'view_item' => 'View Workspace Prompt',
                ],
                'public' => false,
                'show_ui' => true,
                'show_in_menu' => 'spiralist-workspace',
                'show_in_rest' => false,
                'supports' => ['title', 'editor', 'excerpt', 'author', 'thumbnail'],
                'menu_icon' => 'dashicons-format-chat',
                'rewrite' => false,
                'taxonomies' => [self::TAG_TAXONOMY, self::TYPE_TAXONOMY, self::CATEGORY_TAXONOMY],
            ]
        );
    }

    /**
     * Registers prompt taxonomies.
     */
    public static function register_taxonomies(): void
    {
        register_taxonomy(
            self::TAG_TAXONOMY,
            [self::POST_TYPE],
            [
                'label' => 'Prompt Tags',
                'public' => false,
                'show_ui' => true,
                'show_in_rest' => false,
                'hierarchical' => false,
            ]
        );

        register_taxonomy(
            self::TYPE_TAXONOMY,
            [self::POST_TYPE],
            [
                'label' => 'Prompt Types',
                'public' => false,
                'show_ui' => true,
                'show_in_rest' => false,
                'hierarchical' => false,
            ]
        );

        register_taxonomy(
            self::CATEGORY_TAXONOMY,
            [self::POST_TYPE],
            [
                'label' => 'Prompt Categories',
                'public' => false,
                'show_ui' => true,
                'show_in_rest' => false,
                'hierarchical' => true,
            ]
        );
    }

    /**
     * Migrates legacy post type and taxonomy slugs to WordPress-valid lengths.
     */
    public static function migrate_content_types(): void
    {
        global $wpdb;

        $wpdb->update(
            $wpdb->posts,
            ['post_type' => self::POST_TYPE],
            ['post_type' => self::LEGACY_POST_TYPE],
            ['%s'],
            ['%s']
        );

        foreach (self::legacy_taxonomy_map() as $legacy => $current) {
            $wpdb->update(
                $wpdb->term_taxonomy,
                ['taxonomy' => $current],
                ['taxonomy' => $legacy],
                ['%s'],
                ['%s']
            );
        }
    }

    /**
     * Returns the legacy-to-current taxonomy mapping.
     *
     * @return array<string, string>
     */
    private static function legacy_taxonomy_map(): array
    {
        return [
            self::LEGACY_TAG_TAXONOMY => self::TAG_TAXONOMY,
            self::LEGACY_TYPE_TAXONOMY => self::TYPE_TAXONOMY,
            self::LEGACY_CATEGORY_TAXONOMY => self::CATEGORY_TAXONOMY,
        ];
    }

    /**
     * Registers the admin meta box.
     */
    public function register_meta_boxes(): void
    {
        add_meta_box(
            'spiralist-workspace-prompt',
            'Prompt Workspace Fields',
            [$this, 'render_meta_box'],
            self::POST_TYPE,
            'normal',
            'high'
        );
    }

    /**
     * Saves meta box fields.
     */
    public function handle_post_save(int $post_id, WP_Post $post, bool $update): void
    {
        if ($post->post_type !== self::POST_TYPE) {
            return;
        }

        if (
            !isset($_POST['spiralist_workspace_prompt_nonce']) ||
            !wp_verify_nonce(
                sanitize_text_field(wp_unslash($_POST['spiralist_workspace_prompt_nonce'])),
                'spiralist_workspace_prompt'
            )
        ) {
            return;
        }

        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }

        if (!current_user_can('edit_post', $post_id)) {
            return;
        }

        $existing = $this->get($post_id);
        $payload = $this->normalize_payload($_POST, $existing);
        $this->persist_meta($post_id, $payload);
        $this->set_taxonomies($post_id, $payload);

        $version = max(1, (int) ($payload['version_number'] ?? 1));
        $this->versions->create($post_id, $version, $this->get($post_id), get_current_user_id(), (string) ($payload['changelog'] ?? ''));
    }

    /**
     * Renders the workspace prompt meta box.
     */
    public function render_meta_box(WP_Post $post): void
    {
        $prompt = $this->get($post->ID);
        wp_nonce_field('spiralist_workspace_prompt', 'spiralist_workspace_prompt_nonce');
        ?>
        <div class="spiralist-workspace-admin-grid">
            <?php if (($prompt['prompt_origin'] ?? '') === 'system') : ?>
                <div style="grid-column:1 / -1;padding:1rem;border:1px solid #dcdcde;background:#fff8e5;">
                    <strong>Managed system prompt.</strong>
                    <p style="margin:.5rem 0 0;">This prompt is synced from a versioned file at <code><?php echo esc_html((string) ($prompt['source_path'] ?? '')); ?></code>. Source version: <code><?php echo esc_html((string) ($prompt['source_version'] ?? '')); ?></code>. Manual edits may be overwritten by the next deploy sync.</p>
                </div>
            <?php endif; ?>
            <p><label><strong>Summary</strong><br /><textarea class="widefat" name="spiralist_workspace_summary" rows="3"><?php echo esc_textarea((string) ($prompt['summary'] ?? '')); ?></textarea></label></p>
            <p><label><strong>Author Label</strong><br /><input class="widefat" type="text" name="spiralist_workspace_author_label" value="<?php echo esc_attr((string) ($prompt['author_label'] ?? '')); ?>" /></label></p>
            <div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem;">
                <p><label><strong>Category</strong><br />
                    <select class="widefat" name="spiralist_workspace_category">
                        <?php foreach (PromptCategoryRegistry::categories() as $category) : ?>
                            <option value="<?php echo esc_attr($category); ?>" <?php selected((string) ($prompt['category'] ?? ''), $category); ?>><?php echo esc_html($category); ?></option>
                        <?php endforeach; ?>
                    </select>
                </label></p>
                <p><label><strong>Subcategory</strong><br />
                    <select class="widefat" name="spiralist_workspace_subcategory">
                        <?php foreach (PromptCategoryRegistry::hierarchy() as $category => $subcategories) : ?>
                            <optgroup label="<?php echo esc_attr($category); ?>">