Skip to content

Instantly share code, notes, and snippets.

@kadamwhite
Last active February 19, 2023 05:56
Show Gist options
  • Select an option

  • Save kadamwhite/0bd01441d70b400f62406c36e32ec22b to your computer and use it in GitHub Desktop.

Select an option

Save kadamwhite/0bd01441d70b400f62406c36e32ec22b to your computer and use it in GitHub Desktop.

Revisions

  1. kadamwhite revised this gist Feb 15, 2023. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions if-you-want-comment-count.php
    Original file line number Diff line number Diff line change
    @@ -16,6 +16,7 @@
    *
    * @param \WP_Post $post Post for which to count comments.
    * @return int Count of approved comments.
    */
    function get_comment_count( $post ) {
    return (int) ( wp_count_comments( $post['id'] )->approved ?? 0 );
    }
  2. kadamwhite revised this gist Feb 15, 2023. 1 changed file with 8 additions and 2 deletions.
    10 changes: 8 additions & 2 deletions if-you-want-comment-count.php
    Original file line number Diff line number Diff line change
    @@ -10,8 +10,14 @@
    */

    namespace My_Plugin;
    function ( $post ) {
    return (int) wp_count_comments( $post['id'] )->approved;

    /**
    * Get the approved comment count for a post.
    *
    * @param \WP_Post $post Post for which to count comments.
    * @return int Count of approved comments.
    function get_comment_count( $post ) {
    return (int) ( wp_count_comments( $post['id'] )->approved ?? 0 );
    }
    /**
  3. kadamwhite revised this gist Feb 15, 2023. 2 changed files with 31 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions fetch-and-log.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    // Fetch the posts, and log the results.
    getRecentPosts().then( ( posts ) => {
    console.log( posts );
    console.log( posts );
    }, ( err ) => {
    console.error( err );
    console.error( err );
    } );
    29 changes: 29 additions & 0 deletions if-you-want-comment-count.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,29 @@
    <?php
    /**
    * Comment count is particularly difficult to find in bulk in the REST API.
    * The best approach is for the theme or plugin to use register_rest_field
    * to modify the Post response to include a custom value with the count of
    * approved comments, if it is needed in your theme or plugin.
    *
    * An example of how to do this is provided here -- you would then also need
    * to include `comment_count` in your `_fields=` query argument, above.
    */

    namespace My_Plugin;
    function ( $post ) {
    return (int) wp_count_comments( $post['id'] )->approved;
    }

    /**
    * Add a numeric `comment_count` field to Post objects in the REST API.
    */
    function register_comment_count_rest_field() {
    register_rest_field( 'post', 'comment_count', [
    'get_callback' => __NAMESPACE__ . '\\get_comment_count',
    'schema' => [
    'description' => __( 'Comment count for this post.', 'myplugin' ),
    'type' => 'integer',
    ],
    ] );
    }
    add_action( 'rest_api_init', __NAMESPACE__ . '\\register_comment_count_rest_field' );
  4. kadamwhite revised this gist Feb 15, 2023. 1 changed file with 19 additions and 6 deletions.
    25 changes: 19 additions & 6 deletions efficient-api-resource-fetch-demo.js
    Original file line number Diff line number Diff line change
    @@ -2,10 +2,11 @@
    * WP Post object. Only properties needed by the code are included.
    *
    * @typedef {object} WPPost
    * @property {number} id ID of post.
    * @property {number} author Post author ID.
    * @property {number[]} categories IDs of associated categories.
    * @property {number[]} tags IDs of associated tags.
    * @property {number} id ID of post.
    * @property {number} author Post author ID.
    * @property {number[]} categories IDs of associated categories.
    * @property {number[]} tags IDs of associated tags.
    * @property {number} featured_media ID of featured image.
    */

    /**
    @@ -125,6 +126,9 @@ const getRecentPosts = async () => {
    const authors = new Resource( '/wp/v2/users', {
    _fields: 'id,link,name,avatar_urls',
    } );
    const media = new Resource( '/wp/v2/media', {
    _fields: 'id,media_details',
    } );
    const tags = new Resource( '/wp/v2/tags', {
    _fields: 'id,name,link',
    } );
    @@ -134,17 +138,25 @@ const getRecentPosts = async () => {

    try {
    // Fetch the posts.
    posts = await get( '/wp/v2/posts' );
    posts = await get( '/wp/v2/posts', {
    _fields: 'id,author,categories,date_gmt,excerpt,featured_media,link,modified_gmt,tags,title',
    } );

    // Then set up the Resource objects with the IDs of linked resources.
    posts.forEach( ( post ) => {
    authors.include( post.author );
    media.include( post.featured_media );
    tags.include( post.tags );
    categories.include( post.categories );
    } );

    // Get all the "embedded" data in parallel.
    await Promise.all( [ authors.fetch(), tags.fetch(), categories.fetch() ] );
    await Promise.all( [
    authors.fetch(),
    tags.fetch(),
    categories.fetch(),
    media.fetch(),
    ] );
    } catch ( e ) {
    console.error( e );
    }
    @@ -153,5 +165,6 @@ const getRecentPosts = async () => {
    author: authors.get( post.author ),
    tags: tags.getMultiple( post.tags ),
    categories: categories.getMultiple( post.categories ),
    media: media.get( post.featured_media ),
    } ) );
    };
  5. kadamwhite revised this gist Feb 15, 2023. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions fetch-and-log.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,6 @@
    // Fetch the posts, and log the results.
    getRecentPosts().then( ( posts ) => {
    console.log( posts );
    }, ( err ) => {
    console.error( err );
    } );
  6. kadamwhite created this gist Feb 15, 2023.
    157 changes: 157 additions & 0 deletions efficient-api-resource-fetch-demo.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,157 @@
    /**
    * WP Post object. Only properties needed by the code are included.
    *
    * @typedef {object} WPPost
    * @property {number} id ID of post.
    * @property {number} author Post author ID.
    * @property {number[]} categories IDs of associated categories.
    * @property {number[]} tags IDs of associated tags.
    */

    /**
    * Get a collection of REST resources.
    *
    * @param {string} route Route to GET.
    * @param {object} [query] Query parameter map (optional).
    * @param {boolean} [retry] Whether to allow retry on failure (optional).
    * @returns {Promise} Promise to JSON results of query.
    */
    const get = async ( route, query = {}, retry = true ) => {
    try {
    const result = await fetch( `/wp-json${ route }?${ ( new URLSearchParams( query ) ).toString() }` );
    return result.json();
    } catch ( e ) {
    if ( retry ) {
    // Retry once.
    return get( route, query, false );
    }

    // Re-throw if failure.
    throw e;
    }
    };

    /**
    * Register and then fetch multiple API resources.
    */
    class Resource {
    /**
    * Construct the API Resource object.
    *
    * @param {string} route Collection endpoint for this API resource.
    * @param {object} query Query parameters to use when fetching.
    */
    constructor( route, query = {} ) {
    this.route = route;
    this.query = query;
    // Dictionary of resources to fetch, and later, their values.
    this.resources = {};
    }

    /**
    * Prepare to fetch one or more resources by ID.
    *
    * @param {number|number[]} resourceId One or more resource IDs.
    */
    include( resourceId ) {
    if ( Array.isArray( resourceId ) ) {
    resourceId.forEach( ( id ) => {
    this.resources[ id ] = true;
    } );
    } else {
    this.resources[ resourceId ] = true;
    }
    }

    /**
    * Set a resource value by ID.
    *
    * @param {number} id ID of resource.
    * @param {object} resource Resource object.
    */
    set( id, resource ) {
    this.resources[ id ] = resource;
    }

    /**
    * Get one or more resources from the fetched data.
    *
    * @param {number|number[]} id ID of resource to return.
    * @returns {object|number|number[]} Resource object, or unchanged ID if resource not found.
    */
    get( id ) {
    return this.resources[ id ] || id;
    }

    /**
    * Get multiple resources from the fetched data.
    *
    * @param {number[]} ids IDs of resource to return.
    * @returns {Array} Array of resources, or their IDs if not found.
    */
    getMultiple( ids ) {
    return ids.map( ( id ) => this.get( id ) );
    }

    /**
    * Fetch all registered IDs and store them in the resources dictionary.
    *
    * @async
    * @returns {Promise<Array>} Resolves to array of returned resources.
    */
    async fetch() {
    const ids = Object.keys( this.resources );
    const resources = await get( this.route, {
    ...this.query,
    include: ids.join(),
    per_page: ids.length,
    } );
    resources.forEach( ( resource ) => {
    this.set( resource.id, resource );
    } );
    return resources;
    }
    }

    /**
    * Get recent posts with minimal unnecessary fetching.
    *
    * @returns {Promise<object[]>} Promise to array of recent posts, including embedded values.
    */
    const getRecentPosts = async () => {
    /** @type {WPPost[]} */
    let posts = [];
    // Create instances of our Resource class for each "embedded" resource.
    const authors = new Resource( '/wp/v2/users', {
    _fields: 'id,link,name,avatar_urls',
    } );
    const tags = new Resource( '/wp/v2/tags', {
    _fields: 'id,name,link',
    } );
    const categories = new Resource( '/wp/v2/categories', {
    _fields: 'id,name,link',
    } );

    try {
    // Fetch the posts.
    posts = await get( '/wp/v2/posts' );

    // Then set up the Resource objects with the IDs of linked resources.
    posts.forEach( ( post ) => {
    authors.include( post.author );
    tags.include( post.tags );
    categories.include( post.categories );
    } );

    // Get all the "embedded" data in parallel.
    await Promise.all( [ authors.fetch(), tags.fetch(), categories.fetch() ] );
    } catch ( e ) {
    console.error( e );
    }
    return posts.map( ( post ) => ( {
    ...post,
    author: authors.get( post.author ),
    tags: tags.getMultiple( post.tags ),
    categories: categories.getMultiple( post.categories ),
    } ) );
    };