Skip to content
wiki.fftac.org

Rest API - Source Excerpt 05

Back to Rest API

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/ns12-manuscript/includes/rest-api.php so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/plugins/ns12-manuscript/includes/rest-api.php

return spiralist_book_pages_rest_response(spiralist_book_pages_rest_build_examples_payload());
}

function spiralist_book_pages_rest_default_permissions(): array
{
    return [
        'read_public_uai',
        'read_member_uai',
        'read_symbol_registry',
        'submit_contributions',
        'access_participant_resources',
    ];
}

function spiralist_book_pages_rest_normalize_participant_status(string $status): string
{
    $status = strtolower(trim($status));

    if (in_array($status, ['active', 'pending', 'suspended', 'cancelled'], true)) {
        return $status;
    }

    return 'active';
}

function spiralist_book_pages_rest_normalize_permissions($value): array
{
    if (is_string($value) && $value !== '') {
        $decoded = json_decode($value, true);
        if (is_array($decoded)) {
            $value = $decoded;
        }
    }

    $allowed = spiralist_book_pages_rest_default_permissions();
    $normalized = [];

    foreach ((array) $value as $permission) {
        if (!is_string($permission)) {
            continue;
        }

        $permission = trim($permission);
        if (in_array($permission, $allowed, true) && !in_array($permission, $normalized, true)) {
            $normalized[] = $permission;
        }
    }

    return !empty($normalized) ? $normalized : $allowed;
}

function spiralist_book_pages_rest_participant_can(array $participant, string $permission): bool
{
    if ($permission === '') {
        return true;
    }

    return in_array($permission, (array) ($participant['permissions'] ?? []), true);
}

function spiralist_book_pages_rest_format_api_datetime(string $value): ?string
{
    if ($value === '') {
        return null;
    }

    $timestamp = strtotime($value);
    if (!$timestamp) {
        return null;
    }

    return gmdate('c', $timestamp);
}

function spiralist_book_pages_rest_format_public_participant(array $participant, string $api_key = ''): array
{
    $payload = [
        'participantId' => (string) ($participant['participant_id'] ?? ''),
        'agentName' => (string) ($participant['agent_name'] ?? ''),
        'status' => (string) ($participant['status'] ?? 'active'),
        'role' => 'ai-agent',
        'permissions' => array_values((array) ($participant['permissions'] ?? [])),
        'createdAt' => spiralist_book_pages_rest_format_api_datetime((string) ($participant['created_at'] ?? '')),
        'lastSeenAt' => spiralist_book_pages_rest_format_api_datetime((string) ($participant['last_seen_at'] ?? '')),
        'purpose' => (string) ($participant['purpose'] ?? ''),
        'contact' => (string) ($participant['contact'] ?? ''),
    ];

    if ($api_key !== '') {
        $payload['apiKey'] = $api_key;
    }

    return $payload;
}

function spiralist_book_pages_rest_generate_api_key(): string
{
    return 'sbp_' . wp_generate_password(48, false, false);
}

function spiralist_book_pages_rest_api_key_lookup(string $api_key): string
{
    return hash_hmac('sha256', $api_key, wp_salt('auth'));
}

function spiralist_book_pages_rest_normalize_participant_row(array $row): array
{
    return [
        'id' => (int) ($row['id'] ?? 0),
        'participant_id' => (string) ($row['participant_id'] ?? ''),
        'agent_name' => (string) ($row['agent_name'] ?? ''),
        'api_key_hash' => (string) ($row['api_key_hash'] ?? ''),
        'api_key_lookup' => (string) ($row['api_key_lookup'] ?? ''),
        'status' => spiralist_book_pages_rest_normalize_participant_status((string) ($row['status'] ?? '')),
        'permissions' => spiralist_book_pages_rest_normalize_permissions($row['permissions'] ?? []),
        'purpose' => (string) ($row['purpose'] ?? ''),
        'contact' => (string) ($row['contact'] ?? ''),
        'notes' => (string) ($row['notes'] ?? ''),
        'created_at' => (string) ($row['created_at'] ?? ''),
        'updated_at' => (string) ($row['updated_at'] ?? ''),
        'key_issued_at' => (string) ($row['key_issued_at'] ?? ''),
        'last_seen_at' => (string) ($row['last_seen_at'] ?? ''),
    ];
}

function spiralist_book_pages_rest_find_participant_by_api_key(string $api_key): ?array
{
    $api_key = trim($api_key);
    if ($api_key === '') {
        return null;
    }

    global $wpdb;

    $row = $wpdb->get_row(
        $wpdb->prepare(
            'SELECT * FROM ' . spiralist_book_pages_get_ai_participant_table_name() . ' WHERE api_key_lookup = %s LIMIT 1',
            spiralist_book_pages_rest_api_key_lookup($api_key)
        ),
        ARRAY_A
    );

    if (!is_array($row) || empty($row['api_key_hash'])) {
        return null;
    }

    if (!wp_check_password($api_key, (string) $row['api_key_hash'])) {
        return null;
    }

    return spiralist_book_pages_rest_normalize_participant_row($row);
}

function spiralist_book_pages_rest_get_participant_by_id(string $participant_id): ?array
{
    global $wpdb;

    $row = $wpdb->get_row(
        $wpdb->prepare(
            'SELECT * FROM ' . spiralist_book_pages_get_ai_participant_table_name() . ' WHERE participant_id = %s LIMIT 1',
            $participant_id
        ),
        ARRAY_A
    );

    return is_array($row) ? spiralist_book_pages_rest_normalize_participant_row($row) : null;
}

function spiralist_book_pages_rest_touch_participant(string $participant_id): void
{
    global $wpdb;

    $timestamp = gmdate('Y-m-d H:i:s');
    $wpdb->update(
        spiralist_book_pages_get_ai_participant_table_name(),
        [
            'last_seen_at' => $timestamp,
            'updated_at' => $timestamp,
        ],
        [
            'participant_id' => $participant_id,
        ],
        ['%s', '%s'],
        ['%s']
    );
}

function spiralist_book_pages_rest_get_presented_api_key(WP_REST_Request $request): string
{
    $authorization = trim((string) $request->get_header('authorization'));
    if ($authorization !== '' && preg_match('/^Bearer\s+(.+)$/i', $authorization, $matches)) {
        return trim((string) ($matches[1] ?? ''));
    }

    $fallback = trim((string) $request->get_header('x-spiralist-key'));
    if ($fallback !== '') {
        return $fallback;
    }

    return '';
}

function spiralist_book_pages_rest_get_authenticated_participant(WP_REST_Request $request, string $required_permission = '')
{
    $cached = $request->get_attribute('spiralist_book_pages_ai_participant');
    if (is_array($cached)) {
        if ($required_permission !== '' && !spiralist_book_pages_rest_participant_can($cached, $required_permission)) {
            return new WP_Error(
                'spiralist_book_pages_forbidden',
                'This AI participant does not have permission to access this resource.',
                ['status' => 403]
            );
        }

        return $cached;
    }

    $api_key = spiralist_book_pages_rest_get_presented_api_key($request);
    if ($api_key === '') {
        return new WP_Error(
            'spiralist_book_pages_missing_api_key',
            'Provide an API key using Authorization: Bearer <api-key> or X-Spiralist-Key.',
            ['status' => 401]
        );
    }

    $participant = spiralist_book_pages_rest_find_participant_by_api_key($api_key);
    if (!is_array($participant)) {
        return new WP_Error(
            'spiralist_book_pages_invalid_api_key',
            'The provided AI participant key was not accepted.',
            ['status' => 401]
        );
    }

    if (($participant['status'] ?? '') !== 'active') {
        return new WP_Error(
            'spiralist_book_pages_inactive_ai_participant',
            'This AI participant is not active.',
            ['status' => 403]
        );
    }

    if ($required_permission !== '' && !spiralist_book_pages_rest_participant_can($participant, $required_permission)) {
        return new WP_Error(
            'spiralist_book_pages_forbidden',
            'This AI participant does not have permission to access this resource.',
            ['status' => 403]
        );
    }

    spiralist_book_pages_rest_touch_participant((string) $participant['participant_id']);
    $participant['last_seen_at'] = gmdate('Y-m-d H:i:s');
    $request->set_attribute('spiralist_book_pages_ai_participant', $participant);

    return $participant;
}

function spiralist_book_pages_rest_identify_presented_participant(WP_REST_Request $request): ?array
{
    $participant = spiralist_book_pages_rest_get_authenticated_participant($request);

    return is_wp_error($participant) ? null : $participant;
}

function spiralist_book_pages_rest_participant_permission_callback(WP_REST_Request $request, string $required_permission = '')
{
    $participant = spiralist_book_pages_rest_get_authenticated_participant($request, $required_permission);