Skip to content
wiki.fftac.org

Managedpromptadmin - Source Excerpt 09

Back to Managedpromptadmin

Summary

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

**Source path:** Spiralist/wp-content/plugins/spiralist-workspace/includes/Admin/ManagedPromptAdmin.php

],
                admin_url('admin-post.php')
            ),
            'spiralist_workspace_download_prompt_template'
        );
    }

    /**
     * Returns the JSON starter template contents.
     */
    private function get_json_template_contents(): string
    {
        $payload = $this->prompt_template_payload();
        $payload['version'] = self::DEFAULT_VERSION;
        $encoded = wp_json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        return is_string($encoded) ? $encoded . "\n" : "{}\n";
    }

    /**
     * Returns the filesystem prompt.php starter template contents.
     */
    private function get_php_template_contents(): string
    {
        return "<?php\n\nreturn " . $this->export_value($this->prompt_template_payload(), 0) . ";\n";
    }

    /**
     * Returns one prompt file as an array if the file is valid.
     *
     * @return array<string,mixed>
     */
    private function load_prompt_file(string $path): array
    {
        if (!is_file($path) || !is_readable($path)) {
            return [];
        }

        $definition = include $path;

        return is_array($definition) ? $definition : [];
    }

    /**
     * Reorders prompt fields into a stable readable shape.
     *
     * @param array<string,mixed> $definition
     * @return array<string,mixed>
     */
    private function sort_definition_fields(array $definition): array
    {
        $ordered = [];

        foreach (self::FIELD_ORDER as $field) {
            if (!array_key_exists($field, $definition)) {
                continue;
            }

            $ordered[$field] = $definition[$field];
            unset($definition[$field]);
        }

        foreach ($definition as $key => $value) {
            if (!is_string($key) || str_starts_with($key, '__')) {
                continue;
            }

            $ordered[$key] = $value;
        }

        return $ordered;
    }

    /**
     * Serializes a PHP value into a readable prompt.php payload.
     *
     * @param mixed $value
     */
    private function export_value($value, int $indent, string $key_hint = 'VALUE'): string
    {
        if (is_array($value)) {
            $is_list = array_keys($value) === range(0, count($value) - 1);
            if (empty($value)) {
                return '[]';
            }

            $lines = ['['];

            foreach ($value as $key => $item) {
                $line = str_repeat(' ', $indent + 4);

                if (!$is_list) {
                    $line .= var_export((string) $key, true) . ' => ';
                }

                $line .= $this->export_value($item, $indent + 4, is_string($key) ? $key : $key_hint);
                $line .= ',';
                $lines[] = $line;
            }

            $lines[] = str_repeat(' ', $indent) . ']';

            return implode("\n", $lines);
        }

        if (is_string($value)) {
            return $this->export_string($value, $indent, $key_hint);
        }

        if (is_bool($value)) {
            return $value ? 'true' : 'false';
        }

        if (is_int($value) || is_float($value)) {
            return (string) $value;
        }

        if ($value === null) {
            return 'null';
        }

        return var_export((string) $value, true);
    }

    /**
     * Serializes a string, using heredoc for multiline prompt text.
     */
    private function export_string(string $value, int $indent, string $key_hint): string
    {
        if (!str_contains($value, "\n")) {
            return var_export($value, true);
        }

        $label = strtoupper(preg_replace('/[^A-Za-z0-9]+/', '_', $key_hint) ?: 'PROMPT');
        if ($label === '') {
            $label = 'PROMPT';
        }

        while (str_contains($value, "\n" . $label) || str_contains($value, $label . "\n")) {
            $label .= '_BLOCK';
        }

        return "<<<'" . $label . "'\n" . rtrim($value, "\n") . "\n" . str_repeat(' ', $indent) . $label;
    }

    /**
     * Converts a multi-file upload array into a simple list of upload records.
     *
     * @return array<int,array{name:string,type:string,tmp_name:string,error:int,size:int}>
     */
    private function normalize_file_input(string $input_name): array
    {
        $input = $_FILES[$input_name] ?? null;
        if (!is_array($input) || empty($input['name'])) {
            return [];
        }

        if (!is_array($input['name'])) {
            return [[
                'name' => (string) ($input['name'] ?? ''),
                'type' => (string) ($input['type'] ?? ''),
                'tmp_name' => (string) ($input['tmp_name'] ?? ''),
                'error' => (int) ($input['error'] ?? UPLOAD_ERR_NO_FILE),
                'size' => (int) ($input['size'] ?? 0),
            ]];
        }

        $files = [];
        $count = count((array) $input['name']);

        for ($index = 0; $index < $count; $index++) {
            $name = (string) ($input['name'][$index] ?? '');
            if ($name === '') {
                continue;
            }

            $files[] = [
                'name' => $name,
                'type' => (string) ($input['type'][$index] ?? ''),
                'tmp_name' => (string) ($input['tmp_name'][$index] ?? ''),
                'error' => (int) ($input['error'][$index] ?? UPLOAD_ERR_NO_FILE),
                'size' => (int) ($input['size'][$index] ?? 0),
            ];
        }

        return $files;
    }

    /**
     * Normalizes a comma-separated or newline-separated list field.
     *
     * @return array<int,string>
     */
    private function parse_text_list(string $value): array
    {
        return array_values(
            array_unique(
                array_filter(
                    array_map(
                        static fn(string $item): string => sanitize_text_field(trim($item)),
                        preg_split('/[\r\n,]+/', $value) ?: []
                    )
                )
            )
        );
    }

    /**
     * Returns the absolute prompt root path.
     */
    private function prompt_root(): string
    {
        return rtrim(SPIRALIST_WORKSPACE_DIR, '/\\') . DIRECTORY_SEPARATOR . 'prompts';
    }

    /**
     * Builds a prompt.php path inside the managed prompt registry.
     */
    private function build_prompt_file_path(string $slug, string $version): string
    {
        return $this->prompt_root()
            . DIRECTORY_SEPARATOR
            . sanitize_title($slug)
            . DIRECTORY_SEPARATOR
            . $this->sanitize_version($version)
            . DIRECTORY_SEPARATOR
            . 'prompt.php';
    }

    /**
     * Sanitizes a prompt version token for filesystem usage.
     */
    private function sanitize_version(string $value): string
    {
        $value = trim($value);
        $value = preg_replace('/[^A-Za-z0-9._-]/', '', $value);

        return is_string($value) && $value !== '' ? $value : self::DEFAULT_VERSION;
    }

    /**
     * Verifies capability + nonce for admin-post actions.
     */
    private function require_admin_permission(string $nonce_action): void
    {
        if (!current_user_can('manage_spiralist_workspace')) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'spiralist-workspace'));
        }

        check_admin_referer($nonce_action);
    }

    /**
     * Stores a short admin notice for the current user.
     *
     * @param string[] $details
     */
    private function set_notice(bool $is_error, string $message, array $details = []): void
    {
        set_transient(
            self::NOTICE_TRANSIENT_PREFIX . get_current_user_id(),
            [
                'is_error' => $is_error,
                'message' => $message,
                'details' => array_values(array_filter(array_map('strval', $details))),
            ],
            MINUTE_IN_SECONDS * 10
        );
    }

    /**
     * Reads and clears the current user's admin notice.
     *
     * @return array{is_error:bool,message:string,details:array<int,string>}
     */
    private function consume_notice(): array
    {
        $key = self::NOTICE_TRANSIENT_PREFIX . get_current_user_id();
        $notice = get_transient($key);
        delete_transient($key);

        if (!is_array($notice)) {
            return [
                'is_error' => false,
                'message' => '',
                'details' => [],
            ];
        }

        return [
            'is_error' => !empty($notice['is_error']),
            'message' => (string) ($notice['message'] ?? ''),