[ 'type' => 'integer', 'required' => true, ], ]; register_rest_route(self::NAMESPACE, '/recordings', [ 'methods' => 'POST', 'callback' => [self::class, 'startRecording'], 'permission_callback' => [self::class, 'canManage'], ]); register_rest_route(self::NAMESPACE, '/recordings/(?P\d+)', [ 'methods' => 'DELETE', 'callback' => [self::class, 'discardRecording'], 'permission_callback' => [self::class, 'canEditDraft'], 'args' => $id, ]); register_rest_route(self::NAMESPACE, '/recordings/(?P\d+)/finish', [ 'methods' => 'POST', 'callback' => [self::class, 'finishRecording'], 'permission_callback' => [self::class, 'canEditDraft'], 'args' => $id + [ 'title' => ['type' => 'string', 'default' => ''], 'steps' => ['type' => 'array', 'default' => []], 'ai' => ['type' => 'boolean', 'default' => false], 'area' => [ 'type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_key', ], ], ]); register_rest_route(self::NAMESPACE, '/ai/steps', [ 'methods' => 'POST', 'callback' => [self::class, 'describeSteps'], 'permission_callback' => [self::class, 'canManage'], 'args' => [ 'steps' => ['type' => 'array', 'required' => true], ], ]); register_rest_route(self::NAMESPACE, '/walkthroughs/(?P\d+)/progress', [ 'methods' => 'POST', 'callback' => [self::class, 'saveProgress'], 'permission_callback' => [self::class, 'canTake'], 'args' => $id + [ 'status' => [ 'type' => 'string', 'enum' => Baton_Walkthroughs::STATUSES, 'required' => true, ], ], ]); } public static function canManage(): bool { return current_user_can(Baton_Walkthroughs::CAPABILITY); } // recordings in progress are drafts; a published walkthrough is never overwritten by a recorder public static function canEditDraft(WP_REST_Request $request): bool { $post = self::walkthrough($request); return $post !== null && $post->post_status === 'draft' && current_user_can('edit_post', $post->ID); } public static function canTake(WP_REST_Request $request): bool { $post = self::walkthrough($request); return $post !== null && $post->post_status === 'publish' && is_user_logged_in() && Baton_Walkthroughs::isFor($post->ID, wp_get_current_user()); } public static function startRecording(): WP_REST_Response { $id = wp_insert_post([ 'post_type' => Baton_Walkthroughs::POST_TYPE, 'post_status' => 'draft', 'post_title' => __('Untitled walkthrough', 'baton'), ], true); if (is_wp_error($id)) { return new WP_REST_Response(['message' => $id->get_error_message()], 500); } return new WP_REST_Response(['id' => $id], 201); } public static function discardRecording(WP_REST_Request $request): WP_REST_Response { wp_delete_post((int) $request['id'], true); return new WP_REST_Response(['deleted' => true]); } public static function finishRecording(WP_REST_Request $request) { if (!Baton_Areas::isEnabled($request['area'])) { return new WP_Error('baton_area_off', __('Walkthroughs can not be recorded there on this site.', 'baton'), ['status' => 400]); } $id = (int) $request['id']; $steps = Baton_Walkthroughs::sanitizeSteps($request['steps']); $ai = 'off'; if ($request['ai'] && $steps) { $rewritten = Baton_AI::rewrite($steps); $ai = is_wp_error($rewritten) ? $rewritten->get_error_message() : 'ok'; $steps = is_wp_error($rewritten) ? $steps : $rewritten; // the recorder heads straight to the edit screen; say there why the steps are the plain ones if (is_wp_error($rewritten)) { set_transient(Baton_Admin::AI_NOTICE . get_current_user_id(), $ai, 5 * MINUTE_IN_SECONDS); } } Baton_Walkthroughs::saveSteps($id, Baton_Walkthroughs::fillMissingText($steps)); update_post_meta($id, Baton_Walkthroughs::AREA_META, $request['area']); $title = sanitize_text_field($request['title']); if ($title !== '') { wp_update_post(['ID' => $id, 'post_title' => $title]); } return new WP_REST_Response([ 'id' => $id, 'ai' => $ai, 'edit_url' => get_edit_post_link($id, 'raw'), ]); } public static function describeSteps(WP_REST_Request $request) { $steps = Baton_Walkthroughs::sanitizeSteps($request['steps']); if (!$steps) { return new WP_Error('baton_no_steps', __('There are no steps to describe.', 'baton'), ['status' => 400]); } $texts = Baton_AI::describe($steps); if (is_wp_error($texts)) { $texts->add_data(['status' => 502]); return $texts; } return new WP_REST_Response(['texts' => $texts]); } public static function saveProgress(WP_REST_Request $request): WP_REST_Response { $userId = get_current_user_id(); Baton_Walkthroughs::recordProgress($userId, (int) $request['id'], $request['status']); // the login launch has happened Baton_Walkthroughs::clearPending($userId); return new WP_REST_Response(['saved' => true]); } private static function walkthrough(WP_REST_Request $request): ?WP_Post { $post = get_post((int) $request['id']); return $post instanceof WP_Post && $post->post_type === Baton_Walkthroughs::POST_TYPE ? $post : null; } } }