'GET', 'callback' => array( $this, 'get_articles' ), 'permission_callback' => '__return_true', 'args' => array( 'search' => array( 'type' => 'string', 'default' => '' ), // category, source, exclude_source and exclude_query take either // one value or a list (key[]=a&key[]=b), so they're left untyped // here and normalized by the API client instead. 'category' => array( 'default' => '' ), 'source' => array( 'default' => '' ), 'exclude_source' => array( 'default' => '' ), 'exclude_query' => array( 'default' => '' ), 'language' => array( 'type' => 'string', 'default' => '' ), 'date_preset' => array( 'type' => 'string', 'default' => '' ), 'date' => array( 'type' => 'string', 'default' => '' ), 'date_from' => array( 'type' => 'string', 'default' => '' ), 'date_to' => array( 'type' => 'string', 'default' => '' ), 'query_mode' => array( 'type' => 'string', 'default' => '' ), 'search_in' => array( 'type' => 'string', 'default' => '' ), 'has_image' => array( 'type' => 'boolean', 'default' => false ), 'sort' => array( 'type' => 'string', 'default' => '' ), 'limit' => array( 'type' => 'integer', 'default' => 12 ), 'offset' => array( 'type' => 'integer', 'default' => 0 ), 'fetch_extras' => array( 'type' => 'boolean', 'default' => true ), 'template' => array( 'type' => 'string', 'default' => '' ), ), ) ); // Editor-only: the list comes from an endpoint that needs the site's API key. register_rest_route( self::API_NAMESPACE, '/channels', array( 'methods' => 'GET', 'callback' => array( $this, 'get_channels' ), 'permission_callback' => static fn() => current_user_can( 'edit_posts' ), ) ); register_rest_route( self::API_NAMESPACE, '/categories', array( 'methods' => 'GET', 'callback' => array( $this, 'get_categories' ), 'permission_callback' => '__return_true', ) ); } public function get_categories(): WP_REST_Response { return rest_ensure_response( DynamiRSS_Settings::get_categories() ); } public function get_channels(): WP_REST_Response { return rest_ensure_response( DynamiRSS_Settings::get_channels() ); } public function get_articles( WP_REST_Request $request ) { $search = (string) $request->get_param( 'search' ); $terms = array_values( array_filter( array_map( 'trim', explode( ',', $search ) ) ) ); $category = DynamiRSS_Api_Client::to_list( $request->get_param( 'category' ) ); $limit = max( 1, min( 200, (int) $request->get_param( 'limit' ) ) ); $offset = max( 0, (int) $request->get_param( 'offset' ) ); $fetch_extras = (bool) $request->get_param( 'fetch_extras' ); // Excluded words may arrive comma-separated in one string, same as "search". $exclude_query = $request->get_param( 'exclude_query' ); if ( is_string( $exclude_query ) ) { $exclude_query = explode( ',', $exclude_query ); } $filters = DynamiRSS_Api_Client::normalize_filters( array( 'language' => $request->get_param( 'language' ), 'source' => $request->get_param( 'source' ), 'exclude_source' => $request->get_param( 'exclude_source' ), 'date_preset' => $request->get_param( 'date_preset' ), 'date' => $request->get_param( 'date' ), 'date_from' => $request->get_param( 'date_from' ), 'date_to' => $request->get_param( 'date_to' ), 'query_mode' => $request->get_param( 'query_mode' ), 'search_in' => $request->get_param( 'search_in' ), 'exclude_query' => $exclude_query, 'has_image' => $request->get_param( 'has_image' ), 'sort' => $request->get_param( 'sort' ), ) ); $result = DynamiRSS_Api_Client::instance()->scan( $terms, $category, $limit, $offset, $fetch_extras, $filters ); if ( $result['error'] ) { return new WP_Error( 'dynamirss_scan_failed', $result['error'], array( 'status' => 502 ) ); } // The API does the source filtering now (it used to be done here, on // one already-fetched page), so every article returned is a match. $articles = $result['articles']; // Only meaningful for a request that isn't narrowed to specific sources; // the frontend ignores it otherwise so the dropdown never shrinks down // to one option once a visitor has already picked a source. $available_sources = array_values( array_unique( array_filter( array_map( static fn( $a ) => (string) ( $a['source'] ?? '' ), $articles ) ) ) ); sort( $available_sources ); $template_hash = sanitize_key( (string) $request->get_param( 'template' ) ); $template_blocks = $template_hash ? DynamiRSS_Blocks::get_cached_template( $template_hash ) : DynamiRSS_Blocks::default_template_blocks(); return rest_ensure_response( array( 'articles' => $articles, 'has_more' => $result['has_more'], // Older cached results have no page_size; their page length is the same thing. 'next_offset' => $offset + ( $result['page_size'] ?? count( $articles ) ), 'available_sources' => $available_sources, 'html' => DynamiRSS_Blocks::render_articles( $articles, $template_blocks ), ) ); } }