WordPress developer course part 13 Querying post

Starting the Popular Recipes Block

Introduction

In this section, we’ll set up a new block called “Popular Recipes.” This block will allow users to display a list of popular recipes based on user-defined criteria, such as cuisine type and the number of posts to show. We’ll also set up server-side rendering to ensure the block reflects any updates to the content.

Step 1: Create the Block Directory and Files

  1. Create a Directory: Name it Popular Recipes.
  2. Add Files: Inside this directory, create the following files:
    • block.json
    • index.js
    • popular-recipes.php

Step 2: Set Up the block.json File

In the block.json file, add the following configuration:

jsonCopy code{
  "name": "plugin/popular-recipes",
  "title": "Popular Recipes",
  "attributes": {
    "title": {
      "type": "string",
      "default": "Popular Recipes"
    },
    "postCount": {
      "type": "number",
      "default": 5
    },
    "cuisines": {
      "type": "array",
      "default": []
    }
  },
  "render": "plugin/popular_recipes_cb"
}

Step 3: Set Up the index.js File

In the index.js file, import the necessary components and create the block:

javascriptCopy codeimport { registerBlockType } from '@wordpress/blocks';
import { InspectorControls, PanelBody, TextControl, RangeControl } from '@wordpress/editor';

registerBlockType('plugin/popular-recipes', {
  edit({ attributes, setAttributes }) {
    return (
      <>
        <InspectorControls>
          <PanelBody title="Settings">
            <TextControl
              label="Title"
              value={attributes.title}
              onChange={(title) => setAttributes({ title })}
            />
            <RangeControl
              label="Number of Posts"
              value={attributes.postCount}
              onChange={(postCount) => setAttributes({ postCount })}
              min={1}
              max={10}
            />
            {/* Additional controls for cuisines can be added here */}
          </PanelBody>
        </InspectorControls>
        <div className="single-post">{attributes.title}</div>
      </>
    );
  },
  save() {
    return null; // Server-side rendering
});

Step 4: Register the Block in PHP

In your register-blocks.php file, add the following code to register the new block:

phpCopy codefunction register_popular_recipes_block() {
  register_block_type( __DIR__ . '/popular-recipes/block.json' );
}
add_action('init', 'register_popular_recipes_block');

Step 5: Create the Server-Side Rendering Callback

In the popular-recipes.php file, define the server-side rendering function:

phpCopy codefunction popular_recipes_cb($attributes) {
  // Output buffering to capture the HTML
  ob_start();

  // Example: Fetching posts and rendering HTML
  $query_args = [
    'post_type' => 'recipe',
    'posts_per_page' => $attributes['postCount'],
    // Add query args for cuisines if needed
  ];

  $query = new WP_Query($query_args);
  if ($query->have_posts()) {
    echo '<div class="popular-recipes">';
    while ($query->have_posts()) {
      $query->the_post();
      echo '<div class="single-post">';
      echo '<h2>' . get_the_title() . '</h2>';
      echo '<div>' . get_the_content() . '</div>';
      echo '<p>By ' . get_the_author() . '</p>';
      echo '</div>';
    }
    echo '</div>';
  }

  // Reset post data
  wp_reset_postdata();

  return ob_get_clean();
}

Enhancing the Popular Recipes Block with Query Controls

Introduction

We’ll enhance our Popular Recipes block by utilizing the Query Controls component from the WordPress components library. This component simplifies the process of modifying query parameters for fetching posts, such as limiting the number of posts and filtering by categories.

Step 1: Import the Query Controls Component

  1. Open the index.js file of your Popular Recipes block.
  2. Import the Query Controls component:
javascriptCopy codeimport { QueryControls } from '@wordpress/components';

Step 2: Add Query Controls to the Sidebar

Next, locate the PanelBody component within your index.js file. Inside this component, add the QueryControls component. Set up the properties as follows:

javascriptCopy code<PanelBody title="Query Settings">
  <QueryControls
    numberOfItems={attributes.postCount}
    minItems={1}
    maxItems={10}
    onNumberOfItemsChange={(count) => setAttributes({ postCount: count })}
    // Add additional properties here for categories
  />
</PanelBody>

Explanation of Properties

  • numberOfItems: Sets the initial post count using the postCount attribute.
  • minItems and maxItems: Restrict the number of posts that can be displayed to between 1 and 10.
  • onNumberOfItemsChange: Updates the postCount attribute whenever the user changes the slider.

Step 3: Adding Category Filtering

To allow filtering by categories, we will need to enhance the QueryControls component. First, you’ll want to modify the attributes in your block.json file to include a categories attribute:

jsonCopy code"attributes": {
  "title": {
    "type": "string",
    "default": "Popular Recipes"
  },
  "postCount": {
    "type": "number",
    "default": 5
  },
  "cuisines": {
    "type": "array",
    "default": []
  },
  "categories": {
    "type": "array",
    "default": []
  }
}

Updating the Query Controls for Categories

Now, within the QueryControls, add properties to handle categories:

javascriptCopy code<QueryControls
  numberOfItems={attributes.postCount}
  minItems={1}
  maxItems={10}
  onNumberOfItemsChange={(count) => setAttributes({ postCount: count })}
  // New properties for categories
  categories={attributes.categories}
  onCategoriesChange={(newCategories) => setAttributes({ categories: newCategories })}
/>

Explanation of New Properties

  • categories: Binds the categories attribute to the QueryControls so that it can reflect the selected categories.
  • onCategoriesChange: Updates the categories attribute when the user modifies the selected categories.

Step 4: Testing the Block

  1. Refresh the Gutenberg editor.
  2. Add the Popular Recipes block to the page.
  3. Check the sidebar to ensure the Query Controls component displays the slider for the number of posts and a section for category selection.

Adding Category Suggestions to the Popular Recipes Block

Introduction

We will enhance our Popular Recipes block by adding category suggestions for filtering recipes based on specific cuisines. This will allow users to display popular recipes tailored to their preferred cuisine.

Step 1: Import Required Function

First, we need to import the useSelect function from the WordPress Data package to query the database for cuisines:

javascriptCopy codeimport { useSelect } from '@wordpress/data';

Step 2: Query for Cuisine Terms

Inside the edit function of your index.js, we will use the useSelect function to retrieve all terms from the “cuisine” taxonomy. Create a variable called terms:

javascriptCopy codeconst terms = useSelect((select) => {
    return select('core').getEntityRecords('taxonomy', 'cuisine', { per_page: -1 }) || [];
}, []);

Explanation

  • getEntityRecords: This function retrieves the terms from the specified taxonomy.
  • per_page: -1: This parameter ensures we get all available terms without any limit.

Step 3: Format the Retrieved Terms

Next, we need to convert the retrieved terms into the format required by the Query Controls component. Below the terms variable, define another variable called suggestions:

javascriptCopy codelet suggestions = {};
terms.forEach(term => {
    suggestions[term.name] = term;
});

Optional Chaining

To prevent errors if terms is null, we can use optional chaining:

javascriptCopy codeterms?.forEach(term => {
    suggestions[term.name] = term;
});

Step 4: Pass Suggestions to Query Controls

Now we can pass the suggestions variable to the QueryControls component. Add the following properties:

javascriptCopy code<QueryControls
    // Other properties
    categorySuggestions={suggestions}
    onCategoryChange={(newTerms) => console.log(newTerms)}
/>

Explanation of New Properties

  • categorySuggestions: This property is set to the formatted suggestions object. It enables the input field for cuisine suggestions.
  • onCategoryChange: This event handler logs the selected terms when users make changes.

Step 5: Testing the Component

  1. Refresh the Gutenberg editor.
  2. Add the Popular Recipes block to the page.
  3. Initially, the input for categories may not appear until the terms are fetched.
  4. Once the request is completed, you should see the input field.
  5. Start typing in the input; existing cuisines should be suggested based on user input.

Updating the Cuisines Attribute in the Popular Recipes Block

Introduction

We’ll enhance our Popular Recipes block by updating the cuisines attribute. This attribute will store an array of objects, where each object represents a term with its corresponding ID. This is essential for filtering recipes based on cuisine when querying posts.

Step 1: Modify the onCategoryChange Event

In your index.js file, scroll to the onCategoryChange event in the Query Controls component. Replace the console statement with the following code:

javascriptCopy codeconst newCuisines = [];

Explanation

  • newCuisines: This variable will hold the new array of cuisine objects that will replace the current cuisines attribute.

Step 2: Loop Through the New Terms

Next, loop through the newTerms argument with the forEach function:

javascriptCopy codenewTerms.forEach(cuisine => {
    let cuisineTerm;

    if (typeof cuisine === 'object') {
        // If it's an existing term object, push it directly.
        newCuisines.push(cuisine);
        return; // Early exit for this iteration
    }

    // Otherwise, it's a string, so we need to find the term.
    cuisineTerm = terms.find(term => term.name === cuisine);
    
    if (cuisineTerm) {
        newCuisines.push(cuisineTerm); // Push the found term object
    }
});

Explanation

  • Type Check: The first condition checks if cuisine is an object (representing an existing term). If it is, we push it directly into the newCuisines array and exit early from the loop.
  • Finding Terms: If cuisine is a string, we search for it in the terms array and, if found, push the corresponding term object into the newCuisines array.

Step 3: Update the Cuisines Attribute

After looping through all terms, we can update the cuisines attribute with the new array:

javascriptCopy codesetAttributes({ cuisines: newCuisines });

Step 4: Add Initial Values to the Query Controls

Before testing, make sure to set the selectedCategories property in the Query Controls component. This allows the component to reflect the current selections:

javascriptCopy code<QueryControls
    // Other properties...
    categorySuggestions={suggestions}
    onCategoryChange={onCategoryChange}
    selectedCategories={attributes.cuisines.map(term => term.name)} // Add this line
/>

Explanation

  • selectedCategories: This property maps the cuisines attribute to an array of term names, ensuring the Query Controls reflect the current selections.

Step 5: Testing the Code

  1. Refresh the page in the Gutenberg editor.
  2. Add the Popular Recipes block to the page.
  3. Try adding cuisines through the input field. You should be able to add and remove cuisines seamlessly.
  4. Check the console to ensure the attribute is being updated correctly.

Testing the Recipe Endpoint with Postman

Introduction

We’ll test the custom endpoint for our recipes using Postman. We will ensure that we can retrieve our created recipes in the correct order based on their ratings.

Step 1: Create Test Recipes

Before testing, ensure you have created three recipes:

  • Recipe One: 5 stars
  • Recipe Two: 2 stars
  • Recipe Three: 4 stars

Step 2: Set Up Postman

  1. Open Postman and create a new request.
  2. Set the request type to GET.
  3. In the URL field, enter:bashCopy codehttp://udemy.local/wp-json/wp/v2/recipe

Step 3: Add Query Parameters

We want to filter the results:

  1. Under the Params tab, add the following parameters:
    • per_page: 10
    • cuisine: [insert_cuisine_term_id_here] (replace this with the actual term ID for the cuisine)
    • _embed: (add this parameter without a value)

How to Find the Cuisine Term ID

  1. Go to the WordPress Admin dashboard.
  2. Navigate to Cuisines under the Taxonomies section.
  3. Click on the cuisine associated with your recipes.
  4. In the URL, find the tag_ID parameter to get the term ID.

Step 4: Send the Request

  1. Click Send to execute the request.
  2. Check the response body. You should see an array of recipes.

Expected Output

  • The response should include three recipes.
  • Each recipe should have properties like title, author, and _embedded which contains details about the author and WP featured image.

Step 5: Validate Response Order

Check the order of recipes in the response. They should ideally be:

  1. Recipe One (5 stars)
  2. Recipe Three (4 stars)
  3. Recipe Two (2 stars)

Problem Identification

If the order is incorrect (e.g., Recipe Three comes first), we need to address how the posts are being sorted. By default, WordPress does not support sorting by custom metadata (like ratings).

Step 6: Modifying the Query for Sorting

To sort by rating, we will need to implement a filter to adjust the query parameters before they are sent to the database.

Modifying the Recipe Query for Sorting by Rating

Introduction

We will modify the REST API query to ensure that recipes are sorted by their rating. We’ll achieve this by using a filter hook that allows us to adjust the query parameters before they are sent to the database.

Step 1: Set Up the Filter Hook

  1. Open the Main Plugin File: Navigate to your main plugin file.
  2. Add the Filter Hook: At the bottom of the hook section, add the following line:phpCopy codeadd_filter('rest_recipe_query', 'up_rest_recipe_query', 10, 2);
    • The hook name uses a dynamic placeholder for the post type.
    • The function name will be up_rest_recipe_query.

Step 2: Define the Query Modification Function

  1. Create a New File: Inside the includes/rest-api folder, create a file named recipe-query-mod.php.
  2. Define the Function:phpCopy codefunction up_rest_recipe_query($args, $request) { // Check for 'order_by_rating' parameter if (isset($request['order_by_rating'])) { $args['orderby'] = 'meta_value_num'; $args['meta_key'] = 'recipe_rating'; // Key for sorting by rating } return $args; }
    • Here, $args contains the query settings.
    • We check if the order_by_rating parameter is present in the request. If it is, we modify the query to order by the recipe rating.

Step 3: Update the Postman Request

  1. Open Postman: Go back to your Postman setup from the previous lecture.
  2. Modify the Request:
    • Add the following parameters to your request:
      • order_by_rating: (this parameter does not need a value, just include it)
      • order: descending
    • Your complete query parameters should look like this:
      • per_page: 10
      • cuisine: [insert_cuisine_term_id_here]
      • _embed: (leave blank)
      • order_by_rating: (include this)
      • order: descending

Step 4: Send the Modified Request

  1. Click Send: Execute the request in Postman.
  2. Check the Response: Look at the response body. The order of the recipes should now be:
    • Recipe One (5 stars)
    • Recipe Three (4 stars)
    • Recipe Two (2 stars)

Updating the Block to Perform a Request for Posts

Introduction

We will modify our block to request posts from the REST API based on the selected cuisine terms. This request will be similar to what we previously did in Postman, with a few necessary adjustments.

Step 1: Extract Cuisine IDs

  1. Open the index.js File: Start by opening your main JavaScript file for the block.
  2. Create a Variable for Cuisine IDs:javascriptCopy codeconst cuisineIds = cuisines.map(term => term.id);
    • This line creates an array of IDs from the cuisines attribute, which contains the term objects. The map function extracts the id property from each term.

Step 2: Initiate the Request for Posts

  1. Define a Variable for Posts:javascriptCopy codeconst posts = useSelect((select) => { return select('core').getEntityRecords('postType', 'recipe', { per_page: count, _embed: true, cuisine: cuisineIds, order: 'desc', orderby: 'rating', }); }, [count, cuisineIds]);
    • Here, we use useSelect to fetch the posts. The getEntityRecords function retrieves posts of the recipe post type.
    • The parameters passed to getEntityRecords include:
      • per_page: Set to the variable count.
      • _embed: Set to true to include embedded data (like featured images and authors).
      • cuisine: Set to the cuisineIds array.
      • order: Set to desc for descending order.
      • orderby: Set to rating to sort by recipe ratings.

Step 3: Watch for Changes

  • Set Up Dependencies:javascriptCopy code}, [count, cuisineIds]);
    • This ensures that if the user changes the count or cuisineIds, the request will be sent again, providing fresh results.

Step 4: Log the Posts Data

  1. Log the Posts:javascriptCopy codeconsole.log(posts);
    • This will help us verify that the data is being fetched correctly.

Step 5: Testing the Request

  1. Refresh the Gutenberg Editor: Open the Gutenberg editor to see your changes.
  2. Add the Popular Recipes Block: Insert the block into your post or page.
  3. Open Developer Tools: Navigate to the console panel.
    • Initially, the log will show null, indicating that the data is not yet available.
    • After a few moments, you should see an array logged in the console with the post data.

Rendering the Template for Our Block

Introduction

In this lecture, we will render the template for our block to display posts. The data we need is already available, so we’ll loop through it to create the necessary HTML structure.

Step 1: Open the index.js File

  1. Access the File: Start by opening the index.js file in your code editor.

Step 2: Create a Loop for Rendering Posts

  1. Scroll to the Single Post Class: Locate the class that contains the template for displaying a single post.
  2. Add a Loop:javascriptCopy code{posts ? posts.map((post) => { return ( <div className="single-post"> {/* Template for rendering each post */} </div> ); }) : null}
    • This loop will iterate over the posts array, returning a template for each post.

Step 3: Check for Featured Image

  1. Define a Variable for the Featured Image:javascriptCopy codeconst featuredImage = post._embedded && post._embedded['wp:featuredmedia'] && post._embedded['wp:featuredmedia'].length > 0 ? post._embedded['wp:featuredmedia'][0] : null;
    • This line checks if the post has an embedded featured image and assigns it to the featuredImage variable.

Step 4: Render the Featured Image

  1. Conditionally Render the Image:javascriptCopy code{featuredImage && ( <a href={post.link} className="single-post-image"> <img src={featuredImage.media_details.sizes.thumbnail.source_url} alt={featuredImage.alt_text} /> </a> )}
    • This checks if the featuredImage exists before rendering the image and anchor tag.

Step 5: Render Post Title and Author

  1. Add Title and Author Below the Image:javascriptCopy code<a href={post.link}> <RawHTML>{post.title.rendered}</RawHTML> </a> <p>{post._embedded.author[0].name}</p>
    • This displays the title and author below the featured image. The title is wrapped in a RawHTML component to prevent HTML escaping.

Step 6: Testing the Block

  1. Refresh the Page: Go to the Gutenberg editor and refresh.
  2. Add the Popular Recipes Block: Insert the block into your post or page.
  3. Observe the Behavior: Initially, the list of posts will be empty as the request is being processed. After a few moments, the posts should appear.

Step 7: Verify Changes

  • You can modify the block settings (like filtering by a taxonomy term) to see if the list updates correctly. This ensures that our block works as intended.

Step 8: Prepare for Server-Side Rendering

  • Before moving on, ensure you save your post with the block changes. We’ll focus on rendering the block on the front end with PHP in the next lecture.

Finishing the Popular Recipes Block

Introduction

Let’s begin working on finishing the popular recipes block by rendering it with the server.

Initial Steps

We’ve taken the first steps of accomplishing this task by defining a function for rendering the block in your editor.

File Access

Open the popular recipes dot PHP file.

Grabbing Attributes

First things first.

Accepting Attributes

Let’s grab the attributes by accepting the attributes argument.

Extracting the Title

Next, we can begin extracting the attributes, starting with the title.

  1. Create a variable called title.
  2. The value for this variable will be the escape html function with the attributes title variable.
  3. As usual, we are escaping the title before rendering it on the screen.
  4. This step is to reduce the likelihood of malicious HTML being inserted into the document.

Grabbing Cuisine IDs

Next, let’s grab the cuisine IDs.

  1. Create a variable called cuisine IDs.
  2. The value for this variable should be an array of cuisine IDs in our attributes.
  3. We’re storing an array of objects where each object represents a term from the cuisine taxonomy.

Creating an Array of IDs

Let’s create a new array where each item is the ID.

  1. Let’s set this variable to the return value of the array map function.
  2. This function is defined by the PHP language.
  3. The array map function will loop through an array.
  4. On each iteration, we can return a new value to replace the current value of the item.
  5. For our purpose, we’re trying to grab the ID.

Setting Up the Array Map Function

There are two arguments which is a function for handling each iteration of the loop and the array.

  1. Let’s pass in an anonymous PHP function.
  2. Next, pass in the attributes cuisine’s variable.

Function Definition

The function passed into the array map function will be given each item in the array.

  1. Let’s reference each item as an argument called term.
  2. Within the function.
  3. Let’s return the term ID item.

Object to Array Conversion

Technically, the items in the array are objects.

  1. However, WordPress will convert the JavaScript objects into PHP arrays.
  2. The property name will be converted into an array key.
  3. We can reference a specific item from the object by its name as an array key.
  4. The value returned by this function will be a new array of term IDs.

Initiating the Query for Posts

We can finally begin initiating the query for posts.

Creating the Query

Previously, WordPress would create the query for us.

  1. We did not have to create the query ourselves with the rest API.
  2. This time we must manually create the query with the WP query class.

Defining the Query Variable

After the variables define a variable called query, the value for this variable will be a new instance of the WP query class.

  1. New instances of a class can be configured by passing data inside the parentheses.
  2. We haven’t had the opportunity to discuss this feature.
  3. However, the PHP classes can support arguments similar to functions by default.

Querying the Database

WordPress will query the database for a list of posts for our block.

  1. We’re not interested in posts, so let’s modify the query by passing in an array above the query variable.
  2. Define a variable called arguments with an initial value of an array.
  3. The query can be modified with an array.
  4. In this array we can add parameters documented on the page for the WP query class.
  5. Most of these parameters are going to be familiar to us.

Setting Query Parameters

Filtering by Post Type

First, let’s filter the posts by the recipe post type by setting the post type key to recipe.

Limiting Number of Posts

Next, we can limit the number of posts to retrieve by setting the posts per page key.

  1. The value for this key will be the attributes count variable.
  2. After limiting the results, we can start sorting the results by the rating of the recipe.
  3. Set the order by parameter to meta value num.
  4. Next, set the meta key parameter to recipe rating.
  5. Lastly, set the order key to descending.

Finalizing the Query

The query is almost ready.

  1. There’s one piece of information to add to the query.
  2. The results should be filtered by a specific taxonomy term.

Conditional Statement for Cuisine IDs

If the block has cuisine IDs, the query must be altered to filter the results below.

  1. The arguments variable create a conditional statement with the following condition not empty cuisine IDs.
  2. Before altering the query.
  3. We’re checking the cuisine IDs variable.
  4. It’s possible that the user may not want to filter their results by a specific cuisine.
  5. If that’s the case, the query should not be altered.

Modifying the Query

If the array contains at least one value, we can begin modifying the query in the conditional block.

  1. Let’s add a new key called tax query.
  2. A new item can be pushed into an array with this syntax.
  3. We do not need a function to push items into an array.
  4. PHP is very flexible with its arrays.

Taxonomy Filtering

The value for this new item will be an array within this array.

  1. We can add more arrays with configuration settings in each child array.
  2. We can filter the results by a specific taxonomy.
  3. As you know, a post type can have multiple taxonomies.
  4. The query can be refined for multiple taxonomies.
  5. However, in our case, we’re interested in filtering the results by the cuisine taxonomy.

Adding Taxonomy Parameters

Inside this array, let’s add a parameter called taxonomy.

  1. Set this parameter to cuisine.
  2. Next, we need to specify the field to filter the recipes by taxonomy.
  3. Terms can be sorted by their name, slug or ID.
  4. We only have their ids, so let’s set the field parameter to term ID.

Adding Terms Parameter

As a reminder, the information for these parameters can be found on the WP Query Class Documentation page.

  1. Next, let’s add a parameter called terms.
  2. The value for this parameter will be the cuisine IDs variable.

Summary of the Query

Let me break down the query we’ve created.

  1. Starting from the Loop, we’re performing a query for recipe posts.
  2. The number of results should not exceed ten posts.
  3. The order of the recipes will be sorted by their ratings.
  4. The highest rated recipe will appear at the top of the array.
  5. If the posts should be filtered by a specific taxonomy term, we are adding to the query by adding the tax query parameter.
  6. In this parameter, we are filtering the posts further by a set of terms within the cuisine taxonomy.

Finalizing the Arguments

Overall, we have a query for grabbing the most highly rated recipes on our site.

  1. Let’s pass on the arguments variable to the new instance of the WP query class.
  2. After the instance has been created, WordPress will initiate a query to the database.
  3. This query will return a list of posts which are stored in the query variable.

Looping Through Query Results

Introduction

We are going to begin looping through the results in the query variable before writing a single line of code.

Understanding the Loop

I want to quickly mention a concept called the Loop. WordPress has a term to refer to what we’re doing in our block. The idea of querying the database for posts and looping through the results is known as the Loop. This concept is mainly for theme developers. However, it can apply to plugin developers too.

Documentation Reference

This documentation page will describe the proper method for querying the database and looping through the results. Feel free to read through this page for more info.

Getting Started

We are going to be adhering to the process recommended on this page.

Opening the File

Let’s get started in your editor. Open the popular recipes dot PHP file.

Writing the Template

Inside the function, we will begin writing the template after the OB start function.

Wrapping the Block

The block will be wrapped with a div tag. This tag will have a class called WP BLOCK Utility plus popular recipes.

Rendering the Title

Next, we can render the title by adding a pair of H six tags. Inside these tags, let’s echo the title variable.

Displaying the Posts

Afterward, we can begin displaying the posts for this task. We need to go back into PHP mode inside a pair of PHP tags.

Adding Conditional Statement

Add a conditional statement. On the query object, there’s a method called have posts. Let’s call this function from within the conditional statement.

Checking for Results

Before looping through the results, we should check the query for results. It’s possible that WordPress may not have been able to find recipes. In this case, we should not bother looping through the results. This function will return a boolean value. If there are results, the value will be true.

Beginning the Loop

Inside this conditional block, we can begin looping through the data with a while loop. Inside the while loop, call the have posts function again.

Loop Continuation

The have posts function performs another action. It’ll check if the posts have been looped through. If every post has been looped through, this function will return false, thus causing the loop to end.

Moving to the Next Post

At the moment, the loop will run infinitely. We are not moving on to the next post in the while block. Run a function called the post. The post function will check if the loop is running for the first time; if it is, the first post is grabbed from the results, otherwise it will move on to the next post in the list of results.

Importance of the Post Function

It’s always important to call this function at the beginning of the loop. Otherwise, you may experience inconsistent results.

Displaying Post Data

Now that we have our data, we can begin displaying the template for each post.

Adding Single Post Wrapper

First, let’s exit out of PHP mode. Next, add a div tag with a class called Single Post.

Adding Anchor Element

Afterward, add an anchor element with a class called Single Post Image. On this element, let’s set the href attribute to a function called the permalink.

Using Template Tags

The permalink function is a template tag. We talked about template tags before. There are functions for rendering specific pieces of content. There are a series of template tags that can run inside a loop. These functions will be able to detect their inside a loop.

Benefits of Using the Loop

If a template tag detects a loop, it’ll attempt to grab the post in the current iteration. This is the main benefit of using the loop. WordPress has designed its functions for handling most of the work. You do not need to provide the ID of the post or any other piece of information. It’s capable of doing the heavy lifting as long as we’re calling the post function on each iteration. Everything else is handled for us. This function will return the URL to a specific post.

Rendering the Thumbnail

Let’s render the image from within this element. Inside the anchor element, run a function called the thumbnail. The thumbnail function will generate an image tag with the source pointing to the image of the post. This function has an optional parameter which is the size of the image. Let’s set the size to thumbnail.

Rendering Title and Author

We’ve successfully rendered the thumbnail. Let’s proceed to render the title of the post and the author below.

Adding Single Post Detail

The anchor element added div tag with a class called Single Post Detail. Next, add an anchor element with the href attribute. The value for this attribute will be the permalink function. Inside this tag, we are going to render the title. The title can be rendered by calling the title function.

Author Information

For your convenience, I provided a link to a page with a complete list of template tags in WordPress. Feel free to check it out. Most of these functions will handle grabbing the information for us.

Rendering Author Name

Next, let’s render the name of the author below. The anchor element will add a span tag. Inside this tag, add the word “by,” followed by an anchor element. One more time, set the href attribute to the permalink function. Inside the element, we are going to render the name of the author. The author can be displayed by calling the author function.

Final Steps

The template is ready. There’s one more crucial step below the loop.

Resetting Post Data

Let’s run a function called WP Reset Post Data. We’ve talked about this before, but I want to mention it again.

Importance of Resetting

Regardless of what page we’re viewing, WordPress will always perform a query to the database for a list of posts. This query is considered the main query; queries written by a plugin or theme are considered secondary queries.

Handling Query Focus

Whenever we create a custom query, WordPress will shift its focus from the main query to our secondary query. This shift in focus allows us to use template tags without worrying about informing WordPress of the current post.

Consequences of Custom Queries

This behavior does have consequences. If another developer performs the loop, the data from the loop will contain the data from our query. That’s not what we want. It’s likely that other developers will want data from the main query.

Finalizing the Query

By calling this function, we’re telling WordPress that we’re finished with the secondary query. It can go back to focusing on the main query.

Creating the Recipe of the Day Block

Introduction

We are going to create a block for displaying the recipe of the day. Every day we’re going to randomly select a recipe from the current site. This recipe will be displayed to our visitors like the other blocks.

Resources

I prepared this block for us in the resource section of this lecture. I provide a link to the starter files in our plugin. Create a directory called Daily Recipe inside this directory and these files. Pause the video and good luck.

Setting Up the Starter Files

Welcome back. I’ve created the starter files in my plugin. Let’s go through the files together.

Reviewing the Block JSON File

Starting with the block JSON file, we have a basic configuration file. This block is going to have a single attribute, which is an attribute for storing the title of the block.

Moving to the JavaScript File

Next, let’s move over to the JavaScript file. Inside this file, we have a basic block registration. Inside the template, we’re rendering a rich text component for modifying the block’s title. Below this component, we are rendering a post image and title.

API Request

As you can imagine, we are going to be sending a request for a random post from the REST API. There’s not much else to say about the files.

Registering the Block

Let’s go ahead with registering this block. Open the register block start PHP file.

Modifying the Blocks Array

Inside the blocks array, add another array for the daily recipe block. Let’s add the name option. Next, add the options key.

Server Side Rendering

Querying the database for a random recipe is going to require server-side rendering. Let’s set the render callback option to up daily recipe CB.

Creating the PHP File

Next, create a file called daily recipe dot PHP inside the include slash blocks directory. Define the up daily recipe CB function. Lastly, insert the output buffer snippet.

Writing the Query for a Random Recipe

Introduction

We are going to begin writing the query for grabbing a random recipe before we get into writing code. There’s an important concept we should cover.

Understanding Transients

WordPress has a feature called Transients. In the resource section of this lecture, I provide a link to the documentation page for this feature. Up until now, data stored in a database has been permanent. It’s possible to delete data, but it’s not common. Storing data in a database is a great way to persist data.

Temporary Data Storage

However, what if we want to store data temporarily? We could use variables, but a variable is cleared from a system’s memory after PHP is finished running. Transients are the solution for storing data for longer than a few seconds. A transient is a value stored in the database. The record will be deleted after a specific expiration time.

Benefits of Transients

Best of all, WordPress manages this process for us. We never need to worry about deleting the record from the database. WordPress automates the deleting of a record past an expiration time.

Why Use Transients?

The question becomes: Why do we need transients? Our block will display the recipe of the day. Every day we’re going to refresh the recipe. The ID of the current recipe will be stored in a database for 24 hours. After 24 hours, the ID should be deleted. A new ID will take its place. Transients are the perfect solution for handling this scenario.

Storing Recipe IDs

We are going to store the ID of the recipe with the Transients API. If WordPress deletes the value from the database, we can safely query the database again for another recipe. It’s a great API for caching data.

Creating the Generate Daily Recipe Function

Let’s get started by heading over to our editor. In the includes directory, create a file called Generate Daily Recipe PHP.

Defining the Function

Inside this file, define a function called up generate daily recipe. This function will be responsible for querying the database for a recipe.

Custom SQL Query

WordPress does not have a function for generating a randomized value. Since that’s the case, we will be writing a custom SQL query inside the function. Let’s grab the WP DB global variable.

Querying the Database

Next, define a variable called ID. The value for this variable will be the wp db get var function. The goal of this query is to grab a single value. We’re not interested in the entire record of a post. The VAR function is our best option for retrieving a single value from a query.

Hard-Coding the Query

Unlike before, we’re not going to prepare the query. Preparing the query is useful for sanitizing a query that has user input. For this query, the query will not be created with user input. It’s going to be a hard-coded query inside the get var function.

Writing the SQL Query

Type the following query:

sqlCopy codeSELECT ID FROM WP db posts

The query we’re building will select the ID column from the post table. We’re not hard-coding the name of the table. WordPress has a property for the WP post table. I prefer dynamically inserting the name over hard coding the name. The name of the table may change; by relying on WordPress to set the name, we can safely avoid typos.

Adding Conditions to the Query

Next, let’s add the following to the query:

sqlCopy codeWHERE post_status = 'publish' AND post_type = 'recipe'

The results will be filtered by two conditions. Not all posts are published immediately. Some authors may want to create drafts of a recipe. We’re not interested in drafts. The posts table can store drafted and published posts. In this query, we’re limiting the results to published posts. The second condition will limit the posts further to the recipe post type.

Finalizing the Query

All post types are stored in the post table. This includes custom post types. Once again, we’re not interested in another post type besides recipes. After these conditions, let’s finalize the query with the following code:

sqlCopy codeORDER BY RANDOM LIMIT 1

By default, the database will order the results in descending order. Our goal is to grab a random recipe. SQL has a function called RANDOM for randomizing the records in a database. This will not affect the database directly; it will only affect the results.

Storing the ID

We can sort the ordering by using the ORDER BY keywords. The last keyword is called LIMIT. The LIMIT keyword will limit the results returned by the query. After this keyword, we can specify the limit. Overall, this query will retrieve the ID from a random record in the post table.

Using Transients API

Let’s store the ID in our database. After the ID variable, run a function called set transient. The set transient function will store the value in the database with an expiration date. The first argument of this function is the custom name of the transient. Let’s set the name to up daily recipe ID.

Setting Expiration

The second argument is the value passed in the ID variable. Lastly, we must provide the expiration in seconds. Let’s head back to the documentation page for the Transients API. On the sidebar, there is a link called Using Time Constraints. Click on it.

Choosing Expiration Constants

This link will take you to a section with a list of constraints defined by WordPress. We can manually provide a time. On the other hand, WordPress has constants for the most common expiration times. I prefer to use these constants. Let’s grab the day in seconds constant.

Finalizing the Function

Next, let’s update the set transient function by passing in this constant as the value for the third argument. Lastly, let’s return the ID from the function. This function is not tied to a specific hook or request. It’s a plain PHP function.

Purpose of the Function

The reason is simple. It’s because we’ll be performing the action of grabbing a recipe from different locations in our app to prevent our codebase from becoming repetitive. We’ve outsourced the logic into a separate function.

Creating a Custom REST API Endpoint for a Random Recipe

Introduction

We are going to create a custom REST API endpoint for grabbing a random recipe. We’ll be using our function to help us generate this information.

Setting Up the Endpoint

First, let’s register a new route in your editor. Open the init.php file.

Registering the Route

At the bottom of the function, add a new route with the register_rest_route function.

Setting the Namespace and Endpoint

Set the namespace to up/v1 and the endpoint to daily-recipe.

Configuring the Route

Next, pass in an array to configure the settings of our new route:

  • Methods: Set the methods option to the WP_REST_Server::READABLE constant. This will set the method of the route to GET, which makes sense since we aren’t allowing users to create new daily recipes. The endpoint will always return a single recipe.
  • Callback: Set the callback option to up_rest_api_daily_recipe_handler.
  • Permission Callback: Set the permission callback option to a simple function that returns true. This means the endpoint will be accessible to all users without special permissions.

Completing the Route Registration

Now that our route has been registered, it’s time to handle the request by defining the function.

Defining the Handler Function

Inside the includes/rest-api directory, create a file called daily-recipe.php.

Writing the Function

Inside this file, define the up_rest_api_daily_recipe_handler function. Typically, we would send a response with a key called status. However, this endpoint will return post data and not contain a status.

Creating the Response Array

Create an array called response. Inside this array, we’ll store the URL, image, and title of a post. This information is sufficient for displaying the recipe of the day.

Retrieving the Transient

Define a variable called ID. The value for this variable will be the get_transient function, which will return a transient value from the database. The name of the transient must be passed as the first argument, which in our case is up_daily_recipe_id.

Checking for a Valid ID

If WordPress is unable to grab a value, it will return false. To handle this, let’s verify the contents of the variable with a conditional statement. Check if the ID variable is false. If it is, we should generate a new transient by calling the up_generate_daily_recipe function.

Updating the Response

At this point, we are guaranteed to have a post ID. The last step is to update the response with the appropriate values:

  • Response URL: Set the response URL variable to the get_permalink function with the ID variable.
  • Response Image: Set the response image variable to the get_the_post_thumbnail_url function with the ID variable. Pass full as the second argument to get the original image size.
  • Response Title: Set the response title variable to the get_the_title function with the ID variable.

Returning the Response

After updating the response, return it from the function.

Testing the Endpoint

Let’s test the endpoint using Postman. Postman should have saved your previous session. Modify the current URL to our new endpoint:

bashCopy codeup/v1/daily-recipe

Sending the Request

After making those changes, send the request. In the request body, you should receive a random recipe from the posts.

Verifying the Behavior

Try sending the request again. The same post should appear in the body. A new recipe will not be selected until 24 hours have passed, which is the exact behavior we were looking for.

Displaying the Recipe in the Gutenberg Block

Introduction

We are going to begin displaying the recipe in the block for the Gutenberg editor. We have already prepared the endpoint for grabbing this information.

Setting Up the Block

Let’s start by sending a request to our custom endpoint from our block. Open the index.js file in your editor.

Importing Required Components

At the top of the file, we need to import some components and functions:

  1. API Fetch: Import the fetch function from @wordpress/api-fetch. This function will help us send a request to our REST API.
  2. React Hooks: Import useState and useEffect from @wordpress/element.
    • The useEffect function will help us send a request when the component initializes.
    • The useState function will be used to store the post data in the component state.
  3. Spinner Component: Import the Spinner component from @wordpress/components. This will indicate that the post is loading while the request is being processed.

Initializing Component State

Scroll to the edit function and define the component state using useState.

javascriptCopy codeconst [post, setPost] = useState({
    isLoading: true,
    url: null,
    image: null,
    title: null,
});
  • The isLoading property is set to true initially, showing the spinner while the request is pending.
  • The other properties (url, image, and title) will be updated with the data from the response.

Sending the Request

Next, let’s use useEffect to send the request.

  1. Call useEffect and pass an arrow function as the first argument. Use the async keyword before the function definition to handle the asynchronous request.
  2. Pass an empty array as the second argument to ensure the function runs only once during the component’s initialization.

Inside the effect, send the request:

javascriptCopy codeconst fetchData = async () => {
    const response = await apiFetch({ path: '/up/v1/daily-recipe' });
    setPost({
        isLoading: false,
        ...response,
    });
};
fetchData();
  • Here, we use apiFetch to send a request to our custom endpoint.
  • Once we receive the response, we update the state with setPost, setting isLoading to false and spreading the response data into the state.

Updating the Template

Now, let’s update the block’s template to display the recipe data.

Rendering the Spinner and Recipe

Scroll to the template and update the return statement:

javascriptCopy codereturn (
    <>
        {post.isLoading ? (
            <Spinner />
        ) : (
            <a href={post.url}>
                <img src={post.image} alt={post.title} />
                <h3>{post.title}</h3>
            </a>
        )}
    </>
);
  • Use a ternary operator to check post.isLoading. If it’s true, render the Spinner component. If false, render the anchor element with the recipe data.

Setting Attributes Dynamically

Within the anchor element:

  • Set the href attribute to post.url.
  • Set the src attribute of the img element to post.image.
  • Set the inner text of the h3 element to post.title.

Testing the Block

Let’s test the block before moving on.

  1. Refresh the Gutenberg editor.
  2. Add the daily recipe block to the page.

Initially, the spinner should appear for a brief moment. Once the request is complete, the post data will be displayed in the block.

Finalizing the Daily Recipe Block

Introduction

We are going to finish our block by rendering it with our PHP function. Many of these steps will be familiar to you, so we’ll move through them quickly.

Updating the PHP Function

Open the daily-recipe.php file. This file was created to define the function for rendering the block on the server.

Adding the Attributes Argument

  1. Add Attributes Argument: Update the function to accept an $attributes argument.
  2. Extract Attributes: Since there is only one attribute (the title), define a variable called $title and set its value using the esc_html() function:phpCopy code$title = esc_html($attributes['title']);

Getting the Recipe ID

Next, grab the recipe ID:

  1. Define ID Variable: Create a variable called $ID and set its value using the get_transient() function with the transient name up_daily_recipe_ID.
  2. Check for Empty Value: Create a conditional statement to check if $ID is empty. If it is, call the up_generate_daily_recipe() function to get a new recipe ID.phpCopy codeif (empty($ID)) { $ID = up_generate_daily_recipe(); }

Rendering the Output

Now, we can render the information in the output buffer:

  1. Create the Main Container: Add a <div> tag with a class of wp-block-udemy-daily-recipe:phpCopy codeecho '<div class="wp-block-udemy-daily-recipe">';
  2. Render the Title: Inside this div, add an <h6> tag and echo the $title variable:phpCopy codeecho '<h6>' . $title . '</h6>';
  3. Create the Anchor Element: Below the <h6> tag, add an anchor element with the href attribute set to the permalink of the post:phpCopy codeecho '<a href="' . get_permalink($ID) . '">';
  4. Render the Image: Use the get_the_post_thumbnail_url() function to retrieve the post’s thumbnail URL. Set this as the src attribute of an <img> element:phpCopy codeecho '<img src="' . get_the_post_thumbnail_url($ID, 'full') . '" alt="' . esc_attr(get_the_title($ID)) . '" />';
  5. Render the Post Title: Finally, add another pair of <h3> tags to echo the post title:phpCopy codeecho '<h3>' . get_the_title($ID) . '</h3>'; echo '</a>'; // Close the anchor tag echo '</div>'; // Close the main container

Testing the Block

Once you’ve completed the updates, it’s time to test the code. Refresh the page where the daily recipe block is used in the Gutenberg editor.

Understanding Performance in WordPress

Importance of Performance

Performance is crucial for any website. A slow-loading site can lead to user frustration and increased bounce rates, which we definitely want to avoid—especially with our daily recipe block.

The SQL Query

We implemented a custom SQL query for retrieving a random recipe, which is efficient but worth discussing further.

WordPress vs. Custom Queries

I want to clarify something: while WordPress has its built-in solutions for querying the database, these can sometimes be slower than a custom query.

Measuring Performance

To measure the performance of our code, we can utilize the microtime() function in PHP. This function captures the current time in microseconds, allowing us to determine how long a specific line of code takes to execute.

Example of Performance Measurement

  1. Capture Start Time: At the start of our up_generate_daily_recipe() function, we use microtime(true) to capture the current time.phpCopy code$start_time = microtime(true);
  2. Execute the SQL Query: After storing the start time, we run our SQL query to fetch a random recipe.
  3. Capture End Time: Immediately after the query completes, we again call microtime(true) to get the end time.phpCopy code$end_time = microtime(true);
  4. Calculate Execution Time: By subtracting the start time from the end time, we can find out how long the SQL query took to execute.phpCopy code$sql_execution_time = $end_time - $start_time;
  5. Using WP_Query: In contrast, we also recreate the same query using the WP_Query class and follow the same steps to capture its execution time.

Results

After adding these measurements to our block, refreshing the page shows the execution times for both queries. Typically, you will find that the custom SQL query runs faster than the WP_Query method.

Key Takeaways

  1. Custom SQL Queries: While they can be faster in specific scenarios, always consider the trade-offs in safety and maintainability.
  2. Performance Measurement: Using microtime() allows you to easily measure and compare the performance of different approaches.
  3. Choosing the Right Approach: While raw SQL queries can be more efficient, it’s often safer and easier to use WordPress’s built-in methods unless performance becomes a critical issue.

Share this post :

Facebook
Twitter
LinkedIn
Pinterest

Leave a Reply

Your email address will not be published. Required fields are marked *

Health is the key of sucess

Keep your health is well
Latest News
Categories

    Subscribe our newsletter

    Purus ut praesent facilisi dictumst sollicitudin cubilia ridiculus.