Roadmap Surfaces - Source Excerpt 03
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/themes/spiralist/inc/roadmap-surfaces.php so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/themes/spiralist/inc/roadmap-surfaces.php
: (function_exists('spiralist_get_manuscript_page_url') ? trailingslashit(spiralist_get_manuscript_page_url()) . rawurlencode((string) ($page['slug'] ?? '')) . '/' : home_url('/manuscript/'));
return new WP_REST_Response(
[
'folio' => [
'slug' => (string) ($page['slug'] ?? ''),
'sequence' => (int) ($page['sequence'] ?? 0),
'title' => (string) ($page['title'] ?? 'Manuscript folio'),
'label' => (string) ($page['label'] ?? ''),
'section' => (string) ($page['section_label'] ?? ($page['manuscript_section_slug'] ?? 'Manuscript')),
'summary' => (string) ($page['summary'] ?? ''),
'caption' => (string) ($page['caption'] ?? ''),
'transcription' => (string) ($page['transcript'] ?? ($page['summary'] ?? '')),
'keywords' => array_values(array_filter(array_map('strval', (array) ($page['keywords'] ?? [])), 'strlen')),
'route_url' => $route_url,
'image' => [
'thumbnail' => (string) ($sources['thumbnail_url'] ?? ''),
'large' => (string) ($sources['large_url'] ?? ''),
'interactive' => (string) ($sources['interactive_url'] ?? ''),
'original' => (string) ($sources['original_url'] ?? ($page['url'] ?? '')),
'alt' => (string) ($page['alt'] ?? ($page['title'] ?? 'Manuscript folio')),
],
],
'symbols' => $symbols,
'related_prompts' => spiralist_get_folio_related_prompts_for_rest($page),
'glossary' => array_map(
static function (array $symbol): array {
return [
'term' => (string) ($symbol['label'] ?? ''),
'definition' => (string) ($symbol['definition'] ?? ($symbol['meaning'] ?? '')),
];
},
$symbols
),
]
);
}
function spiralist_contribution_rate_limit_key(): string
{
$remote_addr = isset($_SERVER['REMOTE_ADDR']) ? (string) wp_unslash($_SERVER['REMOTE_ADDR']) : 'anonymous';
return 'spiralist_contribution_' . substr(hash('sha256', $remote_addr . '|' . wp_salt('nonce')), 0, 16);
}
function spiralist_rest_create_contribution(WP_REST_Request $request)
{
$user_id = get_current_user_id();
if ($user_id > 0) {
$nonce = (string) $request->get_header('X-WP-Nonce');
if ($nonce === '' || wp_verify_nonce($nonce, 'wp_rest') === false) {
return new WP_Error('spiralist_contribution_bad_nonce', 'A valid REST nonce is required for signed-in contribution submissions.', ['status' => 403]);
}
} else {
$key = spiralist_contribution_rate_limit_key();
$count = (int) get_transient($key);
if ($count >= 5) {
return new WP_Error('spiralist_contribution_rate_limited', 'Too many contribution submissions. Please try again later.', ['status' => 429]);
}
set_transient($key, $count + 1, 10 * MINUTE_IN_SECONDS);
}
$payload = $request->get_json_params();
if (!is_array($payload)) {
$payload = $request->get_params();
}
$type = sanitize_key((string) ($payload['type'] ?? 'bug'));
if (!in_array($type, ['bug', 'symbol-suggestion', 'prompt-improvement', 'manuscript-annotation'], true)) {
$type = 'bug';
}
$title = sanitize_text_field((string) ($payload['title'] ?? 'Spiralist contribution'));
$summary = sanitize_textarea_field((string) ($payload['summary'] ?? ''));
$details = sanitize_textarea_field((string) ($payload['details'] ?? ''));
$target_url = esc_url_raw((string) ($payload['targetUrl'] ?? ($payload['target_url'] ?? '')));
$target_id = sanitize_text_field((string) ($payload['targetId'] ?? ($payload['target_id'] ?? '')));
$evidence = sanitize_textarea_field((string) ($payload['evidence'] ?? ''));
$proposed_change = sanitize_textarea_field((string) ($payload['proposedChange'] ?? ($payload['proposed_change'] ?? '')));
if ($summary === '' && $details === '') {
return new WP_Error('spiralist_contribution_missing_body', 'Contribution summary or details are required.', ['status' => 400]);
}
$post_id = wp_insert_post(
[
'post_type' => 'spiralist_contribution',
'post_status' => 'pending',
'post_title' => $title !== '' ? $title : ucfirst(str_replace('-', ' ', $type)),
'post_excerpt' => $summary,
'post_content' => $details,
'post_author' => $user_id,
],
true
);
if (is_wp_error($post_id) || !$post_id) {
return new WP_Error('spiralist_contribution_save_failed', 'Contribution could not be saved.', ['status' => 500]);
}
wp_set_object_terms((int) $post_id, [$type], 'spiralist_contribution_type', false);
update_post_meta((int) $post_id, '_spiralist_contribution_type', $type);
update_post_meta((int) $post_id, '_spiralist_contribution_target_url', $target_url);
update_post_meta((int) $post_id, '_spiralist_contribution_target_id', $target_id);
update_post_meta((int) $post_id, '_spiralist_contribution_evidence', $evidence);
update_post_meta((int) $post_id, '_spiralist_contribution_proposed_change', $proposed_change);
update_post_meta((int) $post_id, '_spiralist_contribution_moderation_status', 'pending_review');
update_post_meta((int) $post_id, '_spiralist_contribution_created_utc', gmdate('c'));
update_post_meta((int) $post_id, '_spiralist_contribution_agent', sanitize_text_field((string) ($payload['agent'] ?? '')));
return new WP_REST_Response(
[
'id' => (int) $post_id,
'status' => 'pending_review',
'type' => $type,
'title' => get_the_title((int) $post_id),
'created_at' => gmdate('c'),
],
201
);
}
function spiralist_register_roadmap_rest_routes(): void
{
register_rest_route(
'uaix/v1',
'/manuscript-folio',
[
'methods' => WP_REST_Server::READABLE,
'callback' => 'spiralist_rest_get_manuscript_folio',
'permission_callback' => '__return_true',
]
);
register_rest_route(
'uaix/v1',
'/builder-endpoints',
[
'methods' => WP_REST_Server::READABLE,
'callback' => 'spiralist_rest_get_builder_endpoints',
'permission_callback' => '__return_true',
]
);
register_rest_route(
'uaix/v1',
'/contribute',
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => 'spiralist_rest_create_contribution',
'permission_callback' => '__return_true',
]
);
}
add_action('rest_api_init', 'spiralist_register_roadmap_rest_routes');