Skip to content
wiki.fftac.org

Settings - Source Excerpt 01

Back to Settings

Summary

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

**Source path:** Spiralist/wp-content/plugins/spiralist-workspace/includes/Config/Settings.php

<?php

namespace SpiralistWorkspace\Config;

use SpiralistWorkspace\Admin\ManagedPromptAdmin;
use SpiralistWorkspace\Support\PromptMetaRegistry;
use SpiralistWorkspace\Support\SystemPromptRegistry;

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

/**
 * Stores plugin configuration.
 */
class Settings
{
    public const OPTION = 'spiralist_workspace_settings';
    private const DEFAULT_MODEL_ALLOWLIST = "prompt-art-local";
    private const LEGACY_MODEL_ALLOWLIST = "gpt-5.4\ngpt-5.4-mini\ngpt-5.2\ngpt-5-mini";
    private const DEFAULT_DAILY_LIMITS = [
        'anonymous_public_runs' => 3,
        'spiralist_member' => 20,
        'spiralist_curator' => 50,
        'spiralist_admin' => 200,
        'participant' => 20,
        'contributor' => 50,
        'administrator' => 200,
    ];
    private const LEGACY_DAILY_LIMITS = [
        'anonymous_public_runs' => 0,
        'spiralist_member' => 20,
        'spiralist_curator' => 50,
        'spiralist_admin' => 200,
        'participant' => 20,
        'contributor' => 50,
        'administrator' => 200,
    ];
    private ?ManagedPromptAdmin $managed_prompt_admin = null;

    /**
     * Returns all settings merged with defaults.
     */
    public function all(): array
    {
        $stored = get_option(self::OPTION, []);
        $stored = is_array($stored) ? $stored : [];

        return $this->upgrade_legacy_defaults(wp_parse_args($stored, self::defaults()), $stored);
    }

    /**
     * Returns a single setting.
     */
    public function get(string $key, $default = null)
    {
        $all = $this->all();

        return array_key_exists($key, $all) ? $all[$key] : $default;
    }

    /**
     * Former API key accessor retained for compatibility.
     *
     * Spiralist is prompt/art only, so server-side AI credentials are ignored.
     */
    public function get_openai_api_key(): string
    {
        return '';
    }

    /**
     * Former endpoint accessor retained for compatibility.
     */
    public function get_openai_base_url(): string
    {
        return '';
    }

    /**
     * Returns the allowed model list.
     */
    public function get_allowed_models(): array
    {
        $raw = (string) $this->get('model_allowlist', '');
        $items = preg_split('/[\r\n,]+/', $raw) ?: [];

        $models = array_values(
            array_unique(
                array_filter(
                    array_map(
                        static function ($item): string {
                            return preg_replace('/[^A-Za-z0-9._:-]/', '', trim((string) $item)) ?: '';
                        },
                        $items
                    )
                )
            )
        );

        return !empty($models) ? $models : ['prompt-art-local'];
    }

    /**
     * Returns per-role daily limits.
     */
    public function get_daily_limits(): array
    {
        $raw = (string) $this->get('daily_limits_json', '');
        $decoded = json_decode($raw, true);

        if (!is_array($decoded)) {
            $decoded = [];
        }

        return wp_parse_args(
            $decoded,
            self::DEFAULT_DAILY_LIMITS
        );
    }

    /**
     * Returns whether public execution is enabled.
     */
    public function allows_public_execution(): bool
    {
        return $this->get('public_execution_enabled', '1') === '1';
    }

    /**
     * Registers the admin options page.
     */
    public function register_admin_page(): void
    {
        add_menu_page(
            'Spiralist Workspace',
            'Workspace',
            'manage_spiralist_workspace',
            'spiralist-workspace',
            [$this, 'render_admin_page'],
            'dashicons-superhero-alt',
            58
        );
    }

    /**
     * Registers the setting definition.
     */
    public function register_settings(): void
    {
        register_setting(
            'spiralist_workspace',
            self::OPTION,
            [$this, 'sanitize']
        );
    }

    /**
     * Injects the admin helper used for managed filesystem prompt tools.
     */
    public function set_managed_prompt_admin(ManagedPromptAdmin $managed_prompt_admin): void
    {
        $this->managed_prompt_admin = $managed_prompt_admin;
    }

    /**
     * Enqueues admin assets for the workspace screen.
     */
    public function enqueue_admin_assets(string $hook): void
    {
        if ($hook !== 'toplevel_page_spiralist-workspace') {
            return;
        }

        wp_enqueue_style(
            'spiralist-workspace-admin',
            SPIRALIST_WORKSPACE_URL . 'assets/css/workspace.css',
            [],
            SPIRALIST_WORKSPACE_VERSION
        );

        wp_enqueue_script(
            'spiralist-workspace-admin',
            SPIRALIST_WORKSPACE_URL . 'assets/js/workspace.js',
            [],
            SPIRALIST_WORKSPACE_VERSION,
            true
        );

        wp_localize_script(
            'spiralist-workspace-admin',
            'SpiralistWorkspace',
            [
                'apiRoot' => esc_url_raw(rest_url('spiralist-workspace/v1/')),
                'nonce' => wp_create_nonce('wp_rest'),
            ]
        );
    }

    /**
     * Sanitizes option values before storage.
     */
    public function sanitize(array $input): array
    {
        $defaults = self::defaults();
        $existing = get_option(self::OPTION, []);
        $existing = is_array($existing) ? $existing : [];
        $sanitized = [];

        foreach ($defaults as $key => $value) {
            $raw = $input[$key] ?? $value;

            if (is_string($value)) {
                $sanitized[$key] = sanitize_textarea_field((string) $raw);
                continue;
            }

            $sanitized[$key] = $raw;
        }

        $sanitized['openai_base_url'] = '';
        $sanitized['openai_api_key'] = '';
        $sanitized['model_allowlist'] = self::DEFAULT_MODEL_ALLOWLIST;
        $sanitized['public_execution_enabled'] = !empty($input['public_execution_enabled']) ? '1' : '0';
        $sanitized['conversation_sharing_enabled'] = !empty($input['conversation_sharing_enabled']) ? '1' : '0';
        $sanitized['member_approval_required'] = !empty($input['member_approval_required']) ? '1' : '0';
        $sanitized['conversation_memory_token_threshold'] = (string) max(1000, (int) $sanitized['conversation_memory_token_threshold']);
        $sanitized['conversation_memory_max_output_tokens'] = (string) max(300, min(4000, (int) $sanitized['conversation_memory_max_output_tokens']));

        return $sanitized;
    }

    /**
     * Renders the admin page.
     */
    public function render_admin_page(): void
    {
        if (!current_user_can('manage_spiralist_workspace')) {
            wp_die(esc_html__('You do not have permission to access this page.', 'spiralist-workspace'));
        }

        $settings = $this->all();
        $created = isset($_GET['system_prompts_created'])
            ? max(0, (int) sanitize_text_field(wp_unslash((string) $_GET['system_prompts_created'])))
            : 0;
        $updated = isset($_GET['system_prompts_updated'])
            ? max(0, (int) sanitize_text_field(wp_unslash((string) $_GET['system_prompts_updated'])))
            : 0;
        $unchanged = isset($_GET['system_prompts_unchanged'])
            ? max(0, (int) sanitize_text_field(wp_unslash((string) $_GET['system_prompts_unchanged'])))
            : 0;
        $errors = isset($_GET['system_prompts_errors'])
            ? max(0, (int) sanitize_text_field(wp_unslash((string) $_GET['system_prompts_errors'])))
            : 0;
        $sync_error_messages = get_option(SystemPromptRegistry::LAST_ERROR_OPTION, []);
        $sync_error_messages = is_array($sync_error_messages) ? array_values(array_filter(array_map('strval', $sync_error_messages))) : [];
        $runtime_active = function_exists('spiralist_ai_runtime');
        $runtime_key_detected = false;
        $runtime_provider = 'prompt_art_only';
        $runtime_provider_label = 'Prompt/art only';
        if ($runtime_active) {
            $runtime_settings = spiralist_ai_runtime()->settings();
            $runtime_provider = $runtime_settings->get_default_provider();
            $runtime_provider_config = $runtime_settings->get_provider_config($runtime_provider);
            $runtime_labels = \SpiralistAIRuntime\Config\Settings::provider_labels();
            $runtime_provider_label = (string) ($runtime_labels[$runtime_provider] ?? $runtime_provider);
            $runtime_key_detected = false;
        }
        $api_key_status = 'Disabled';
        $api_key_status_class = 'is-missing';
        ?>
        <div class="wrap spiralist-workspace-admin">
            <h1>Spiralist Workspace</h1>