\WP_REST_Server::READABLE, 'callback' => [ __CLASS__, 'rest_list_jobs' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ __CLASS__, 'rest_create_job' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); register_rest_route( self::NAMESPACE, '/scan/jobs/(?P[A-Za-z0-9_-]+)', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ __CLASS__, 'rest_get_job' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], [ 'methods' => \WP_REST_Server::DELETABLE, 'callback' => [ __CLASS__, 'rest_delete_job' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); register_rest_route( self::NAMESPACE, '/scan/jobs/(?P[A-Za-z0-9_-]+)/claim', [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ __CLASS__, 'rest_claim_job' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); register_rest_route( self::NAMESPACE, '/scan/jobs/(?P[A-Za-z0-9_-]+)/heartbeat', [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ __CLASS__, 'rest_heartbeat' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); register_rest_route( self::NAMESPACE, '/scan/jobs/(?P[A-Za-z0-9_-]+)/payload', [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ __CLASS__, 'rest_store_payload' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); register_rest_route( self::NAMESPACE, '/scan/jobs/(?P[A-Za-z0-9_-]+)/complete', [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ __CLASS__, 'rest_complete_job' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); register_rest_route( self::NAMESPACE, '/scan/jobs/(?P[A-Za-z0-9_-]+)/fail', [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ __CLASS__, 'rest_fail_job' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); register_rest_route( self::NAMESPACE, '/scan/jobs/(?P[A-Za-z0-9_-]+)/results', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ __CLASS__, 'rest_get_results' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); register_rest_route( self::NAMESPACE, '/scan/jobs/(?P[A-Za-z0-9_-]+)/services', [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ __CLASS__, 'rest_update_services' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); register_rest_route( self::NAMESPACE, '/scan/jobs/(?P[A-Za-z0-9_-]+)/data', [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ __CLASS__, 'rest_save_scan_data' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); // Global services endpoints (not job-specific). register_rest_route( self::NAMESPACE, '/scan/services', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ __CLASS__, 'rest_list_services' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ __CLASS__, 'rest_create_service' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); register_rest_route( self::NAMESPACE, '/scan/services/(?P\d+)', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ __CLASS__, 'rest_get_service' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ __CLASS__, 'rest_update_service' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], [ 'methods' => \WP_REST_Server::DELETABLE, 'callback' => [ __CLASS__, 'rest_delete_service' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); register_rest_route( self::NAMESPACE, '/scan/services/bulk-assign', [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ __CLASS__, 'rest_bulk_assign_services' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); // Get all scannable URLs (pages, posts, custom post types) register_rest_route( self::NAMESPACE, '/scan/urls', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ __CLASS__, 'rest_list_scannable_urls' ], 'permission_callback' => [ __CLASS__, 'permission_callback' ], ], ] ); } public static function permission_callback() { return current_user_can( 'manage_options' ); } protected static function ensure_storage() { if ( ! class_exists( 'AS_CM_Services_Scan_Storage' ) ) { return new \WP_Error( 'scan_storage_missing', __( 'Scan storage service is not available.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 500 ] ); } return true; } public static function rest_list_jobs() { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $jobs = AS_CM_Services_Scan_Storage::list_jobs(); return rest_ensure_response( [ 'jobs' => $jobs ] ); } public static function rest_get_job( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $job = AS_CM_Services_Scan_Storage::get_job( $request['id'] ); if ( ! $job ) { return new \WP_Error( 'scan_job_not_found', __( 'Scan job not found.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 404 ] ); } return rest_ensure_response( $job ); } public static function rest_create_job( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $params = $request->get_json_params(); $start_url = isset( $params['start_url'] ) ? esc_url_raw( $params['start_url'] ) : ''; if ( empty( $start_url ) ) { return new \WP_Error( 'scan_invalid_url', __( 'A valid start URL is required.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 400 ] ); } $job = AS_CM_Services_Scan_Storage::create_job( $start_url, home_url( '/' ) ); if ( is_wp_error( $job ) ) { $job->add_data( [ 'status' => 500 ] ); return $job; } return new \WP_REST_Response( $job, 201 ); } public static function rest_claim_job( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $job = AS_CM_Services_Scan_Storage::get_job( $request['id'] ); if ( ! $job ) { return new \WP_Error( 'scan_job_not_found', __( 'Scan job not found.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 404 ] ); } if ( in_array( $job['status'], [ 'completed', 'failed' ], true ) ) { return new \WP_Error( 'scan_job_locked', __( 'Scan job can no longer be claimed.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 409 ] ); } $params = $request->get_json_params(); $resume_cursor = isset( $params['resume_cursor'] ) ? sanitize_text_field( $params['resume_cursor'] ) : null; $updated = AS_CM_Services_Scan_Storage::claim_job( $request['id'], $resume_cursor ); return rest_ensure_response( $updated ); } public static function rest_heartbeat( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $job = AS_CM_Services_Scan_Storage::get_job( $request['id'] ); if ( ! $job ) { return new \WP_Error( 'scan_job_not_found', __( 'Scan job not found.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 404 ] ); } $params = $request->get_json_params(); $processed_targets = isset( $params['processed_targets'] ) ? intval( $params['processed_targets'] ) : null; $resume_cursor = isset( $params['resume_cursor'] ) ? sanitize_text_field( $params['resume_cursor'] ) : null; $updated = AS_CM_Services_Scan_Storage::heartbeat_job( $request['id'], $processed_targets, $resume_cursor ); return rest_ensure_response( $updated ); } public static function rest_store_payload( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $job = AS_CM_Services_Scan_Storage::get_job( $request['id'] ); if ( ! $job ) { return new \WP_Error( 'scan_job_not_found', __( 'Scan job not found.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 404 ] ); } $params = $request->get_json_params(); $result = AS_CM_Services_Scan_Storage::record_payload( $request['id'], $params ); if ( is_wp_error( $result ) ) { $result->add_data( [ 'status' => 500 ] ); return $result; } return rest_ensure_response( $result ); } public static function rest_complete_job( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $job = AS_CM_Services_Scan_Storage::get_job( $request['id'] ); if ( ! $job ) { return new \WP_Error( 'scan_job_not_found', __( 'Scan job not found.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 404 ] ); } $params = $request->get_json_params(); $resume_cursor = isset( $params['resume_cursor'] ) ? sanitize_text_field( $params['resume_cursor'] ) : null; $updated = AS_CM_Services_Scan_Storage::mark_job_completed( $request['id'], $resume_cursor ); return rest_ensure_response( $updated ); } public static function rest_fail_job( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $job = AS_CM_Services_Scan_Storage::get_job( $request['id'] ); if ( ! $job ) { return new \WP_Error( 'scan_job_not_found', __( 'Scan job not found.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 404 ] ); } $params = $request->get_json_params(); $error_code = isset( $params['error_code'] ) ? sanitize_text_field( $params['error_code'] ) : 'unknown_error'; $error_message= isset( $params['error_message'] ) ? sanitize_text_field( $params['error_message'] ) : __( 'Unknown error.', 'adsimple-cookie-manager-for-wp' ); $updated = AS_CM_Services_Scan_Storage::mark_job_failed( $request['id'], $error_code, $error_message ); return rest_ensure_response( $updated ); } public static function rest_get_results( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $data = AS_CM_Services_Scan_Storage::get_results( $request['id'], 10 ); if ( ! $data ) { return new \WP_Error( 'scan_job_not_found', __( 'Scan job not found.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 404 ] ); } return rest_ensure_response( $data ); } public static function rest_update_services( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $params = $request->get_json_params(); $services = isset( $params['services'] ) && is_array( $params['services'] ) ? $params['services'] : []; $result = AS_CM_Services_Scan_Storage::update_services_for_job( $request['id'], $services ); if ( is_wp_error( $result ) ) { $result->add_data( [ 'status' => 500 ] ); return $result; } return rest_ensure_response( $result ); } /** * Save scan data including services and cookie metadata. */ public static function rest_save_scan_data( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $job = AS_CM_Services_Scan_Storage::get_job( $request['id'] ); if ( ! $job ) { return new \WP_Error( 'scan_job_not_found', __( 'Scan job not found.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 404 ] ); } $params = $request->get_json_params(); $services = isset( $params['services'] ) && is_array( $params['services'] ) ? $params['services'] : []; $cookies = isset( $params['cookies'] ) && is_array( $params['cookies'] ) ? $params['cookies'] : []; $result = AS_CM_Services_Scan_Storage::save_scan_data( $request['id'], $services, $cookies ); if ( is_wp_error( $result ) ) { $result->add_data( [ 'status' => 500 ] ); return $result; } return rest_ensure_response( $result ); } public static function rest_delete_job( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $deleted = AS_CM_Services_Scan_Storage::delete_job( $request['id'] ); if ( ! $deleted ) { return new \WP_Error( 'scan_job_not_found', __( 'Scan job not found.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 404 ] ); } return new \WP_REST_Response( null, 204 ); } public static function get_assignments() { if ( ! class_exists( 'AS_CM_Services_Scan_Storage' ) ) { return []; } return AS_CM_Services_Scan_Storage::get_global_assignments(); } /** * Get blocking data with cookies and hosts. * * @return array */ public static function get_blocking_data() { if ( ! class_exists( 'AS_CM_Services_Scan_Storage' ) ) { return [ 'services' => [], 'cookies' => [], 'hosts' => [], ]; } return AS_CM_Services_Scan_Storage::get_blocking_data(); } /** * List all detected services (global, not job-specific). */ public static function rest_list_services( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $params = $request->get_query_params(); $category = isset( $params['category'] ) ? sanitize_text_field( $params['category'] ) : null; $search = isset( $params['search'] ) ? sanitize_text_field( $params['search'] ) : null; $services = AS_CM_Services_Scan_Storage::list_all_services( $category, $search ); return rest_ensure_response( [ 'services' => $services ] ); } /** * Get a single service by ID. */ public static function rest_get_service( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $service = AS_CM_Services_Scan_Storage::get_service_by_id( intval( $request['id'] ) ); if ( ! $service ) { return new \WP_Error( 'service_not_found', __( 'Service not found.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 404 ] ); } return rest_ensure_response( $service ); } /** * Update a single service. */ public static function rest_update_service( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $params = $request->get_json_params(); $data = []; if ( array_key_exists( 'category_assigned', $params ) ) { $cat = $params['category_assigned']; // Accept string, array, or null. $data['category_assigned'] = $cat; } if ( array_key_exists( 'auto_blocking', $params ) ) { $data['auto_blocking'] = ! empty( $params['auto_blocking'] ) ? 1 : 0; } if ( array_key_exists( 'notes', $params ) ) { $data['notes'] = sanitize_textarea_field( $params['notes'] ?? '' ); } $service = AS_CM_Services_Scan_Storage::update_service( intval( $request['id'] ), $data ); if ( ! $service ) { return new \WP_Error( 'service_not_found', __( 'Service not found.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 404 ] ); } return rest_ensure_response( $service ); } /** * Delete a service. */ public static function rest_delete_service( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $deleted = AS_CM_Services_Scan_Storage::delete_service( intval( $request['id'] ) ); if ( ! $deleted ) { return new \WP_Error( 'service_not_found', __( 'Service not found.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 404 ] ); } return new \WP_REST_Response( null, 204 ); } /** * Bulk assign categories to multiple services. */ public static function rest_bulk_assign_services( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $params = $request->get_json_params(); $service_ids = isset( $params['service_ids'] ) && is_array( $params['service_ids'] ) ? array_map( 'intval', $params['service_ids'] ) : []; $category = array_key_exists( 'category_assigned', $params ) ? ( $params['category_assigned'] !== null && $params['category_assigned'] !== '' ? sanitize_text_field( $params['category_assigned'] ) : null ) : null; $auto_blocking = isset( $params['auto_blocking'] ) ? ( ! empty( $params['auto_blocking'] ) ? 1 : 0 ) : null; if ( empty( $service_ids ) ) { return new \WP_Error( 'no_services', __( 'No services specified.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 400 ] ); } $updated = AS_CM_Services_Scan_Storage::bulk_update_services( $service_ids, $category, $auto_blocking ); return rest_ensure_response( [ 'updated' => $updated ] ); } /** * Manually create a new service/host entry. */ public static function rest_create_service( \WP_REST_Request $request ) { $storage_ready = self::ensure_storage(); if ( is_wp_error( $storage_ready ) ) { return $storage_ready; } $params = $request->get_json_params(); $host = isset( $params['host'] ) ? sanitize_text_field( $params['host'] ) : ''; $category = isset( $params['category_assigned'] ) ? $params['category_assigned'] : null; if ( empty( $host ) ) { return new \WP_Error( 'invalid_host', __( 'A valid hostname is required.', 'adsimple-cookie-manager-for-wp' ), [ 'status' => 400 ] ); } $service = AS_CM_Services_Scan_Storage::create_service( $host, $category ); if ( is_wp_error( $service ) ) { $service->add_data( [ 'status' => 409 ] ); return $service; } return new \WP_REST_Response( $service, 201 ); } /** * Get all scannable URLs for multi-page scanning. * Returns published pages, posts, and custom post types. */ public static function rest_list_scannable_urls() { $urls = []; // Get home URL first $home_url = home_url( '/' ); $urls[] = [ 'url' => $home_url, 'type' => 'home', 'title' => __( 'Homepage', 'adsimple-cookie-manager-for-wp' ), ]; // Get all public post types $post_types = get_post_types( [ 'public' => true, ], 'names' ); // Exclude attachments unset( $post_types['attachment'] ); // Query published posts from all public post types $posts = get_posts( [ 'post_type' => array_values( $post_types ), 'post_status' => 'publish', 'posts_per_page' => 100, // Limit to prevent timeout 'orderby' => 'date', 'order' => 'DESC', ] ); foreach ( $posts as $post ) { $permalink = get_permalink( $post ); if ( $permalink && $permalink !== $home_url ) { $urls[] = [ 'url' => $permalink, 'type' => $post->post_type, 'title' => $post->post_title, ]; } } // Get taxonomy archives (categories, tags) $taxonomies = get_taxonomies( [ 'public' => true, ], 'names' ); foreach ( $taxonomies as $taxonomy ) { $terms = get_terms( [ 'taxonomy' => $taxonomy, 'hide_empty' => true, 'number' => 20, // Limit per taxonomy ] ); if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) { foreach ( $terms as $term ) { $term_link = get_term_link( $term ); if ( ! is_wp_error( $term_link ) ) { $urls[] = [ 'url' => $term_link, 'type' => 'taxonomy:' . $taxonomy, 'title' => $term->name, ]; } } } } // Remove duplicates based on URL $seen = []; $unique_urls = []; foreach ( $urls as $item ) { $normalized = rtrim( $item['url'], '/' ); if ( ! isset( $seen[ $normalized ] ) ) { $seen[ $normalized ] = true; $unique_urls[] = $item; } } return rest_ensure_response( [ 'urls' => $unique_urls, 'total' => count( $unique_urls ), ] ); } }