
WordPress development with the help of Claude.ai, part 2
Ok, a bit of time has passed, let’s get on with this - I have another series of posts in the works already. And I should also be doing the series on Full Stack app with the help of existing online tutorials. I will probably put some Svelte 5 there as that’s at the moment something interesting to have a look at, as well as React 19.
My previous post was a bit long so I had to start reminding Claude about what we’ve done in the past (I have used the free version of Claude, Claude 3 Sonnet). But maybe that’s ok as the previous post is very long and this is a new one. Let’s start.
With all the its flaws, it may still be easier to follow along with the full code from this series:
https://github.com/jalonenbrothers/film-review-plugin-demo
Claude talks with his API
What should I put in the body or params to get a nice generated response. Come up with a movie related prompt for this test.
To test the Anthropic API using Postman and receive a generated response related to movies, you can try the following:
In the request body, include a JSON object with the following structure:
{
"prompt": "Write a short plot summary for a new sci-fi movie about time travel.",
"max_tokens_to_sample": 200
}
This will prompt the API to generate a short plot summary for a new science fiction movie about time travel, with a maximum response length of 200 tokens.
You can adjust the prompt
field to any movie-related prompt you’d like. For example:
- “Describe the characters and setting for a new romantic comedy film.”
- “Write a logline (one-sentence summary) for a new thriller movie about corporate espionage.”
- “Create a list of potential titles for a new superhero movie featuring a female lead.”
The max_tokens_to_sample
field determines the maximum length of the generated response. You can increase or decrease this value based on your desired response length.
Once you’ve set the request body, send the POST request to https://api.anthropic.com/v1/messages
. If everything is set up correctly, you should receive a JSON response containing the generated text in the response
field.
Remember to replace the x-api-key
in the headers with your actual API key from Anthropic.
Me: I'm using version 2023-06-01 and looks like it requires 'messages' field
Ah, you’re right. For the Anthropic API version 2023-06-01, the request body should include a messages field with an array of message objects. Here’s an example of what the request body should look like for a movie-related prompt:
{
"messages": [
{
"role": "human",
"content": "Write a short plot summary for a new sci-fi movie about time travel."
}
],
"max_tokens_to_sample": 200
}
In this case, the messages
field is an array containing a single message object. The role
field indicates that the message is from a human user, and the content
field contains the actual prompt.
You can add more message objects to the messages
array if you want to provide additional context or have a back-and-forth conversation with the AI.
The max_tokens_to_sample
field is still used to specify the maximum length of the generated response.
When you send this request body as a POST request to https://api.anthropic.com/v1/messages
, the response should include a new message object in the messages
array, with the role
set to "assistant"
and the content
field containing the generated text based on your prompt.
Make sure to set the Content-Type
header to application/json
and include your API key in the x-api-key
header.
Copy
{
"messages": [
{ "role": "user",
"content": "Write a short plot summary for a new sci-fi movie about time travel."
}
],
"model": "claude-3-sonnet-20240229",
"max_tokens": 200
}
You’re absolutely right, my previous examples were incorrect. Thank you for providing the proper request body structure for the Anthropic API version 2023-06-01.
The request body should be:
{
"messages": [
{
"role": "user",
"content": "Write a short plot summary for a new sci-fi movie about time travel."
}
],
"model": "claude-3-sonnet-20240229",
"max_tokens": 200
}
The key points are:
- The
role
field should be “user” not “human” - There is a separate
model
field to specify the model version (in this caseclaude-3-sonnet-20240229
) - The field is
max_tokens
notmax_tokens_to_sample
I really appreciate you taking the time to correct me and provide the proper request body structure. This will help me use the Anthropic API correctly going forward.
[Pasted in content of the file not included - check the git repo]
This file (public/class-jbros-film-review-public.php
) appears to be part of a WordPress plugin called “Jbros Film Review”. It contains the public-facing functionality of the plugin, which includes enqueuing block assets and registering a custom API endpoint.
Here’s a breakdown of the functionality:
- Enqueue Block Assets:
- The
enqueue_block_assets()
method is responsible for enqueuing scripts and styles required for rendering a Gutenberg block. - It enqueues a JavaScript file (
index.js
) and a CSS file (index.css
) located in theblocks/jbros-film-review-block/build/
directory. - These assets are likely used to render a custom Gutenberg block provided by the plugin.
- The
- Register Custom API Endpoint:
- The
register_api_endpoints()
method registers a custom REST API endpoint for the plugin. - The custom endpoint is registered with the base URL
/wp-json/jbros-film-review/v1/get-film-review
. - It accepts POST requests and expects two parameters:
title
andyear
. - The callback function for this endpoint is
get_film_review()
. - The
permission_callback
is set to__return_true
, meaning the endpoint is accessible to anyone (no authentication required).
- The
- Get Film Review:
- The
get_film_review()
method is the callback function for the custom API endpoint. - It sanitizes the
title
andyear
parameters received from the request. - If either
title
oryear
is empty, it returns aWP_REST_Response
with an error message and a 400 (Bad Request) status code. - It creates an instance of the
Anthropic_API
class (which is likely a separate class for interacting with the Anthropic API). - It calls the
get_film_review()
method on theAnthropic_API
instance, passing the title and year parameters. - If the
get_film_review()
method returns aWP_Error
, it returns aWP_REST_Response
with the error message and a 500 (Internal Server Error) status code. - If the
get_film_review()
method returns a successful response, it returns aWP_REST_Response
with the film review data and a 200 (OK) status code.
- The
Based on the code, the expected functionality of this plugin is:
- It provides a custom Gutenberg block for displaying film reviews, likely rendered using the enqueued assets.
- It exposes a custom REST API endpoint (
/wp-json/jbros-film-review/v1/get-film-review
) that accepts a movie title and year as parameters. - When the custom API endpoint is called with a valid title and year, it fetches a film review for the specified movie from the Anthropic API (using the Anthropic_API class).
- The retrieved film review is then returned as a response to the API endpoint.
In summary, the plugin seems to be designed to fetch and display film reviews from the Anthropic API within a WordPress site, using a custom Gutenberg block and a custom REST API endpoint.
The includes/post-types.php
file is responsible for registering the custom post type “Film Review” and adding custom meta fields for storing the film title and year.
To save the film reviews received from the Anthropic API to this custom post type, you can modify the create_or_update_film_review_post()
method in the public/class-jbros-film-review-public.php
file as follows:
/**
* Create or update a film review post.
*
* @param string $title The movie title.
* @param int $year The movie year.
* @param string $film_review The film review content.
*
* @return int|WP_Error The post ID on success, or WP_Error on failure.
*/
private function create_or_update_film_review_post($title, $year, $film_review)
{
// Check if a post already exists for the given title and year
$existing_post_id = $this->get_film_review_post_id($title, $year);
$post_data = array(
'post_title' => "{$title} ({$year})",
'post_content' => $film_review,
'post_type' => 'film_review', // Use the custom post type name
'post_status' => 'publish',
);
if ($existing_post_id) {
// Update the existing post
$post_data['ID'] = $existing_post_id;
$post_id = wp_update_post($post_data);
} else {
// Create a new post
$post_id = wp_insert_post($post_data);
}
if (is_wp_error($post_id)) {
return $post_id;
}
// Save post meta
update_post_meta($post_id, 'film_title', $title);
update_post_meta($post_id, 'film_year', $year);
return $post_id;
}
In this updated method:
The post_type
is set to 'film_review'
, which is the custom post type name defined in the includes/post-types.php
file.
After creating or updating the post, the film_title
and film_year
meta values are saved using update_post_meta()
.
Additionally, you can modify the get_film_review_post_id()
method to use the custom meta keys defined in the includes/post-types.php
file:
/**
* Get the post ID of an existing film review post.
*
* @param string $title The movie title.
* @param int $year The movie year.
*
* @return int|null The post ID if found, or null if not found.
*/
private function get_film_review_post_id($title, $year)
{
$query_args = array(
'post_type' => 'film_review', // Use the custom post type name
'post_status' => 'publish',
'posts_per_page' => 1,
'meta_query' => array(
array(
'key' => 'film_title',
'value' => $title,
),
array(
'key' => 'film_year',
'value' => $year,
),
),
);
$query = new WP_Query($query_args);
if ($query->have_posts()) {
return $query->posts[0]->ID;
}
return null;
}
With these changes, when the get_film_review()
method is called, it will create a new post or update an existing post of the “Film Review” custom post type with the film review content retrieved from the Anthropic API. The post will also have the film title and year saved as custom meta fields, using the keys defined in the includes/post-types.php
file.
- update or create, as it is currently
- if exists, bypass Anthropic API and return the existing post
How do you suggest we go about this?
To achieve the desired behavior, you can modify the get_film_review
method to accept an additional parameter that determines whether to bypass the Anthropic API if a post already exists or not. Here’s how you can approach this:
- Update the
register_api_endpoints
method to accept a new parameter named action in the request:
public function register_api_endpoints()
{
add_action('rest_api_init', function () {
register_rest_route('jbros-film-review/v1', '/get-film-review', array(
'methods' => 'POST',
'callback' => array($this, 'get_film_review'),
'args' => array(
'title' => array(
'required' => true,
'type' => 'string',
),
'year' => array(
'required' => true,
'type' => 'integer',
),
'action' => array(
'required' => false,
'type' => 'string',
'default' => 'create_or_update', // Set a default value
'enum' => array('create_or_update', 'get_existing'), // Allowed values
),
),
'permission_callback' => '__return_true',
));
});
}
Modify the get_film_review
method to handle the new action parameter:
public function get_film_review($request)
{
$title = sanitize_text_field($request->get_param('title'));
$year = absint($request->get_param('year'));
$action = sanitize_text_field($request->get_param('action'));
if (empty($title) || empty($year)) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'Invalid request parameters.',
), 400);
}
$existing_post_id = $this->get_film_review_post_id($title, $year);
if ($action === 'get_existing' && $existing_post_id) {
$existing_post = get_post($existing_post_id);
return new WP_REST_Response(array(
'success' => true,
'data' => array(
'title' => $title,
'year' => $year,
'content' => $existing_post->post_content,
),
), 200);
}
$anthropic_api = new Anthropic_API();
$film_review = $anthropic_api->get_film_review($title, $year);
if (is_wp_error($film_review)) {
return new WP_REST_Response(array(
'success' => false,
'message' => $film_review->get_error_message(),
), 500);
}
$post_id = $this->create_or_update_film_review_post($title, $year, $film_review);
if (is_wp_error($post_id)) {
return new WP_REST_Response(array(
'success' => false,
'message' => $post_id->get_error_message(),
), 500);
}
return new WP_REST_Response(array(
'success' => true,
'data' => array(
'film_review' => $film_review,
'post_id' => $post_id,
),
), 200);
}
Here’s how the modified get_film_review
method works:
- It retrieves the
action
parameter from the request along withtitle
andyear
. - If the
action
is'get_existing'
and a post already exists for the giventitle
andyear
, it retrieves the existing post content and returns it as the response without calling the Anthropic API. - If the
action
is'create_or_update'
(or any other value), it follows the existing logic of calling the Anthropic API and creating/updating the post with the retrieved film review content.
With this implementation, you can pass the action
parameter in the request to control the behavior:
action=create_or_update
(or omit theaction
parameter): This will create a new post or update an existing post with the film review content fetched from the Anthropic API.action=get_existing
: If a post already exists for the giventitle
andyear
, it will return the existing post content without calling the Anthropic API.
Example usage:
POST /wp-json/jbros-film-review/v1/get-film-review
{
"title": "Jaws",
"year": 1975,
"action": "get_existing"
}
This request will return the existing post content for the movie “Jaws” released in 1975, if it exists. If the post doesn’t exist, it will call the Anthropic API and create/update the post as before.
return new WP_REST_Response(array( ‘success’ => true, ‘data’ => array( ‘title’ => $title, ‘year’ => $year, ‘film_review’ => $existing_post->post_content, ‘post_id’ => $existing_post_id, ), ), 200);
I have a Gutenberg block with the content that’s following, pasted after this paragraph, or included; I would like to update it so that
we can set the action (create and update or use existing) in the block editor attributes, the film review is inserted as block content rather than an attribute
To achieve the desired functionality in the Gutenberg block, we’ll need to make some modifications to the edit.js
file. Here’s how you can proceed:
- Add an “Action” setting to the block attributes:
- In the Gutenberg block’s edit.js file, import the
InspectorControls
andSelectControl
components from@wordpress/block-editor
and@wordpress/components
, respectively. - Add a new attribute called
action
to the component’s state and define its initial value (e.g., ’create_or_update
’). - In the
InspectorControls
, add a newSelectControl
component that allows the user to select the desired action (’create_or_update
’ or ’get_existing
’).
- In the Gutenberg block’s edit.js file, import the
- Update the
fetchFilmReview
function:- In the
fetchFilmReview
function, pass theaction
attribute as part of the request data to the API endpoint.
- In the
- Render the film review as block content:
- Instead of setting the
film_review
attribute with the API response data, update the component’ssave
function to render the review content directly as the block content.
- Instead of setting the
Here’s how the updated edit.js
file might look:
// ...
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { Disabled, PanelBody, PanelRow, ToggleControl, QueryControls, TextControl, SelectControl } from '@wordpress/components';
export default function Edit({ attributes, setAttributes }) {
const { title, year, film_review, action } = attributes;
const onChangeTitle = (newTitle) => {
setAttributes({ title: newTitle });
};
const onChangeYear = (newYear) => {
setAttributes({ year: newYear });
};
const onChangeAction = (newAction) => {
setAttributes({ action: newAction });
};
const fetchFilmReview = async () => {
const response = await apiFetch({
path: '/jbros-film-review/v1/get-film-review',
method: 'POST',
data: {
title: title,
year: year,
action: action,
},
});
if (response.success) {
setAttributes({ film_review: response.data.film_review });
} else {
setAttributes({ film_review: response.message });
}
};
useEffect(() => {
if (title && year) {
fetchFilmReview();
}
}, [title, year, action]);
return (
<>
<InspectorControls>
<PanelBody title={__('Settings', 'jbros-film-review-block')} initialOpen={true}>
<PanelRow>
<TextControl
label={__('Film title', 'jbros-film-review-block')}
value={title}
onChange={onChangeTitle}
help={__('Name of the film to get review for', 'jbros-film-review-block')}
/>
</PanelRow>
<PanelRow>
<TextControl
label={__('Film year', 'jbros-film-review-block')}
value={year}
onChange={onChangeYear}
help={__('Year of the film to get review for', 'jbros-film-review-block')}
/>
</PanelRow>
<PanelRow>
<SelectControl
label={__('Action', 'jbros-film-review-block')}
value={action}
onChange={onChangeAction}
options={[
{ value: 'create_or_update', label: __('Create or Update', 'jbros-film-review-block') },
{ value: 'get_existing', label: __('Get Existing', 'jbros-film-review-block') },
]}
/>
</PanelRow>
</PanelBody>
</InspectorControls>
<div {...useBlockProps()}>{film_review}</div>
</>
);
}
In this updated code:
- We’ve added a new attribute called
action
and aSelectControl
in theInspectorControls
to allow the user to choose between'create_or_update'
and'get_existing'
actions. - The
fetchFilmReview
function now includes theaction
attribute in the request data sent to the API endpoint. - Instead of rendering the
film_review
attribute as a separate element, we’re now rendering it directly as the block content using<div {...useBlockProps()}>{film_review}</div>
.
With these changes, the user can choose the desired action from the block settings, and the film review will be rendered as the block content instead of an attribute.
Note: You’ll also need to update the save.js
file to ensure that the block content is rendered correctly on the front-end. The save.js
file should be similar to the edit.js
file, but without the InspectorControls
and state management logic.
Right now, if you enter film title, then start adding the year, e.g. 1975, you will end up with reviews for years 1, 19, 197 and 1975. Likewise if you then start updating the film title, the response is likely unpredictable.
You’re absolutely right. Calling fetchFilmReview
on every change of title
or year
attribute can lead to unnecessary API calls and unpredictable behavior. To address this issue, we can introduce a debounce mechanism to ensure that fetchFilmReview
is only triggered after the user has finished typing the title
or year
.
Here’s how you can update the edit.js
file to implement the debounce functionality:
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { Disabled, PanelBody, PanelRow, ToggleControl, QueryControls, TextControl, SelectControl } from '@wordpress/components';
import { debounce } from 'lodash';
export default function Edit({ attributes, setAttributes }) {
const { title, year, film_review, action } = attributes;
const onChangeTitle = (newTitle) => {
setAttributes({ title: newTitle });
debouncedFetchFilmReview();
};
const onChangeYear = (newYear) => {
setAttributes({ year: newYear });
debouncedFetchFilmReview();
};
const onChangeAction = (newAction) => {
setAttributes({ action: newAction });
};
const fetchFilmReview = async () => {
const response = await apiFetch({
path: '/jbros-film-review/v1/get-film-review',
method: 'POST',
data: {
title: title,
year: year,
action: action,
},
});
if (response.success) {
setAttributes({ film_review: response.data.film_review });
} else {
setAttributes({ film_review: response.message });
}
};
const debouncedFetchFilmReview = debounce(fetchFilmReview, 500); // Adjust the debounce delay as needed
useEffect(() => {
return () => {
debouncedFetchFilmReview.cancel();
};
}, []);
return (
// ... (InspectorControls and other components remain the same)
);
}
Here’s what we’ve done:
- We’ve imported the
debounce
function from thelodash
library. - We’ve created a debounced version of the
fetchFilmReview
function usingdebounce(fetchFilmReview, 500)
. This means that thefetchFilmReview
function will be called at most once every 500 milliseconds (adjust this delay as needed). - In the
onChangeTitle
andonChangeYear
handlers, we’re now calling thedebouncedFetchFilmReview
function instead offetchFilmReview
directly. - We’ve added a
useEffect
hook with a cleanup function that cancels any pending debounced calls when the component unmounts.
With this implementation, the fetchFilmReview
function will be called only after the user stops typing for a specified delay (500ms in this case). This way, you won’t have unnecessary API calls triggered for each keystroke, and the response will be more predictable.
Note that we’re using the lodash
library to provide the debounce
functionality. If you don’t want to include the entire lodash
library, you can alternatively use the debounce
function from the @wordpress/compose
package, which is already included in the WordPress core:
import { debounce } from '@wordpress/compose';
The usage of the debounce function remains the same.
Sure, we can make it explicit by adding a button to trigger the fetch instead of relying on debounce. Here’s how you can modify the edit.js
file:
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { Disabled, PanelBody, PanelRow, ToggleControl, QueryControls, TextControl, SelectControl, Button } from '@wordpress/components';
export default function Edit({ attributes, setAttributes }) {
const { title, year, film_review, action } = attributes;
const onChangeTitle = (newTitle) => {
setAttributes({ title: newTitle });
};
const onChangeYear = (newYear) => {
setAttributes({ year: newYear });
};
const onChangeAction = (newAction) => {
setAttributes({ action: newAction });
};
const fetchFilmReview = async () => {
const response = await apiFetch({
path: '/jbros-film-review/v1/get-film-review',
method: 'POST',
data: {
title: title,
year: year,
action: action,
},
});
if (response.success) {
setAttributes({ film_review: response.data.film_review });
} else {
setAttributes({ film_review: response.message });
}
};
return (
<>
<InspectorControls>
<PanelBody title={__('Settings', 'jbros-film-review-block')} initialOpen={true}>
<PanelRow>
<TextControl
label={__('Film title', 'jbros-film-review-block')}
value={title}
onChange={onChangeTitle}
help={__('Name of the film to get review for', 'jbros-film-review-block')}
/>
</PanelRow>
<PanelRow>
<TextControl
label={__('Film year', 'jbros-film-review-block')}
value={year}
onChange={onChangeYear}
help={__('Year of the film to get review for', 'jbros-film-review-block')}
/>
</PanelRow>
<PanelRow>
<SelectControl
label={__('Action', 'jbros-film-review-block')}
value={action}
onChange={onChangeAction}
options={[
{ value: 'create_or_update', label: __('Create or Update', 'jbros-film-review-block') },
{ value: 'get_existing', label: __('Get Existing', 'jbros-film-review-block') },
]}
/>
</PanelRow>
</PanelBody>
</InspectorControls>
<div {...useBlockProps()}>
{film_review}
<Button isPrimary onClick={fetchFilmReview}>
{__('Fetch Film Review', 'jbros-film-review-block')}
</Button>
</div>
</>
);
}
In this updated code:
- We’ve imported the
Button
component from@wordpress/components
. - We’ve removed the
useEffect
hook and the debounce logic, as we’re no longer triggering the fetch automatically. - We’ve added a
Button
component within the block content area, which calls thefetchFilmReview
function when clicked.
Now, the user can enter the film title and year, and then explicitly click the “Fetch Film Review” button to trigger the API call and fetch the film review.
This approach ensures that the API call is made only when the user is ready, after entering the required information. It also avoids any potential issues with debounce or unnecessary API calls due to user input changes.
[File not included here - see the git repo]
To add a custom page template for the “Film Review” post type that displays the post content and custom fields, you can follow these steps:
- Create a new file in the plugin directory: Create a new file called
single-film_review.php
in the root directory of your plugin. This file will be used as the custom page template for the “Film Review” post type. - Add the template header: At the top of the
single-film_review.php
file, add the following template header:
<?php
/**
* The template for displaying single Film Review posts
*
* @package jbros-film-review
*/
get_header();
This will ensure that the WordPress theme’s header is loaded before rendering the custom template content.
- Retrieve the post data: Inside the
single-film_review.php
file, you can retrieve the current post data using theget_post()
function:
$post = get_post();
- Display the post content: Use
the_content()
function to display the post content:
<div class="entry-content">
<?php the_content(); ?>
</div>
- Display the custom fields: Retrieve the custom field values using
get_post_meta()
and display them as needed:
$film_title = get_post_meta($post->ID, 'film_title', true);
$film_year = get_post_meta($post->ID, 'film_year', true);
echo '<div class="film-details">';
echo '<h2>Film Details</h2>';
echo '<p><strong>Title:</strong> ' . esc_html($film_title) . '</p>';
echo '<p><strong>Year:</strong> ' . esc_html($film_year) . '</p>';
echo '</div>';
- Load the theme’s footer: At the end of the
single-film_review.php
file, add the following line to load the WordPress theme’s footer:
get_footer();
After making these changes, your single-film_review.php
file should look something like this:
<?php
/**
* The template for displaying single Film Review posts
*
* @package jbros-film-review
*/
get_header();
$post = get_post();
?>
<div class="entry-content">
<?php the_content(); ?>
</div>
<?php
$film_title = get_post_meta($post->ID, 'film_title', true);
$film_year = get_post_meta($post->ID, 'film_year', true);
echo '<div class="film-details">';
echo '<h2>Film Details</h2>';
echo '<p><strong>Title:</strong> ' . esc_html($film_title) . '</p>';
echo '<p><strong>Year:</strong> ' . esc_html($film_year) . '</p>';
echo '</div>';
?>
<?php
get_footer();
This custom page template will display the post content, followed by a section titled “Film Details” that shows the film title and year retrieved from the custom fields.
To use this custom page template, you need to instruct WordPress to use it for the “Film Review” post type. You can do this by adding the following code to your includes/post-types.php
file, inside the jbros_film_review_register_post_types()
function:
$args = array(
// ... (other arguments)
'template' => array(
array( 'single-film_review.php' ), // Single post template
),
);
This will tell WordPress to use the single-film_review.php
template when displaying single posts of the “Film Review” post type.
After making these changes, your “Film Review” posts should display the post content and custom fields using the custom page template.
I would like to add the following additional functionality to the plugin:
- connect to TMDB API
- get data associated with a film review, for starters, movie id and movie poster
- add the poster url and the movie id as custom fields to the relevant film review post
- create a Gutenberg block that displays the movie info (poster and id), based on attributes and similar to the film review block - with the create or use existing -functionality
How would you go about this task and how will it connect with existing functionality, and how will updating existing film reviews without the movie info work?
To add the functionality you described, we can follow these steps:
- Connect to TMDB API:
- Create a new class file, for example,
includes/api/class-tmdb-api.php
. - In this file, define a class
TMDB_API
that handles the connection and API requests to the TMDB API. - You’ll need to obtain an API key from TMDB (
https://www.themoviedb.org/documentation/api
) and use it in your requests. - Implement methods to fetch movie data by title and year, or any other relevant parameters.
- Create a new class file, for example,
- Get Movie Data and Store as Custom Fields:
- In the
Jbros_Film_Review_Public
class, create a new method to fetch and store movie data. - This method should call the
TMDB_API
class to retrieve movie data based on the film title and year. - Once the movie data is obtained, save the movie ID and poster URL as custom fields for the corresponding “Film Review” post.
- You can use the
update_post_meta()
function to save the custom fields.
- In the
- Update Existing Film Reviews:
- When creating or updating a “Film Review” post, check if the movie ID and poster URL custom fields exist.
- If they don’t exist, call the new method to fetch and store the movie data.
- This way, existing “Film Review” posts without movie data will be updated automatically when accessed.
- Create a New Gutenberg Block for Movie Info:
- Create a new block folder, for example,
public/blocks/jbros-movie-info-block
. - Inside this folder, create the necessary files for the block (
index.js
,edit.js
,save.js
, etc.). - In the
edit.js
file, set up the block attributes, including the “action” attribute (similar to the “Film Review” block). - Implement the functionality to fetch movie data from the TMDB API or retrieve existing data from the custom fields.
- Render the movie poster and movie ID based on the fetched data.
- Follow a similar approach to the “Film Review” block for handling the “create or use existing” functionality.
- Create a new block folder, for example,
- Enqueue the New Block Assets:
- In the
Jbros_Film_Review_Public
class, update theenqueue_block_assets()
method to include the new block assets.
- In the
- Update the Plugin Dependencies:
- In the
load_dependencies()
method of the main plugin class (Jbros_Film_Review
), include the newclass-tmdb-api.php
file.
- In the
By following these steps, you’ll integrate the TMDB API functionality into your existing plugin. The new “Movie Info” block will allow users to fetch and display movie data (poster and ID) based on the “create or use existing” behavior, similar to the “Film Review” block.
Existing “Film Review” posts without movie data will be updated automatically when accessed, ensuring that all reviews have the associated movie information.
Note: You’ll need to handle error cases, such as when the TMDB API returns no results or encounters an error. Additionally, you may want to implement caching mechanisms to improve performance and reduce API requests.
Thank you for the feedback! I’m glad I could provide a comprehensive solution to integrate the TMDB API functionality into your existing plugin. Here’s a summary of the key points:
- Connect to TMDB API:
- Create a new class
TMDB_API
to handle API requests and fetch movie data from TMDB. - Obtain an API key from TMDB and use it in your requests.
- Create a new class
- Get Movie Data and Store as Custom Fields:
- Create a new method in
Jbros_Film_Review_Public
to fetch and store movie data. - Call the
TMDB_API
class to retrieve movie data based on film title and year. - Save the movie ID and poster URL as custom fields for the corresponding “Film Review” post.
- Create a new method in
- Update Existing Film Reviews:
- When creating or updating a “Film Review” post, check if the movie ID and poster URL custom fields exist.
- If they don’t exist, call the new method to fetch and store the movie data.
- Create a New Gutenberg Block for Movie Info:
- Create a new block folder for the “Movie Info” block.
- Set up block attributes, including the “action” attribute.
- Implement functionality to fetch movie data from TMDB API or retrieve existing data from custom fields.
- Render the movie poster and movie ID based on the fetched data.
- Handle the “create or use existing” functionality similar to the “Film Review” block.
- Enqueue the New Block Assets:
- Update the
enqueue_block_assets()
method to include the new block assets.
- Update the
- Update the Plugin Dependencies:
- Include the new
class-tmdb-api.php
file in theload_dependencies()
method of the main plugin class.
- Include the new
By following these steps, you’ll successfully integrate the TMDB API functionality into your existing plugin, allowing users to fetch and display movie data (poster and ID) using the new “Movie Info” block. Existing “Film Review” posts without movie data will be updated automatically when accessed.
Remember to handle error cases, implement caching mechanisms for performance optimization, and follow best practices for integrating third-party APIs.
Me: can you give me the class-tmdb-api.php contents
Sure, here’s an example of how the class-tmdb-api.php
file might look like:
<?php
/**
* TMDB API Integration
*
* @package jbros-film-review
*/
/**
* TMDB_API class
*/
class TMDB_API {
/**
* TMDB API key
*
* @var string
*/
private $api_key;
/**
* TMDB API base URL
*
* @var string
*/
private $api_base_url;
/**
* Constructor
*/
public function __construct() {
$this->api_key = 'your_tmdb_api_key'; // Replace with your actual TMDB API key
$this->api_base_url = 'https://api.themoviedb.org/3';
}
/**
* Get movie data by title and year
*
* @param string $title The movie title.
* @param int $year The movie year.
*
* @return array|WP_Error Movie data on success, WP_Error on failure.
*/
public function get_movie_data_by_title_and_year( $title, $year ) {
$query_params = array(
'api_key' => $this->api_key,
'query' => $title,
'year' => $year,
'language' => 'en-US', // Set the desired language for movie data
);
$query_url = add_query_arg( $query_params, $this->api_base_url . '/search/movie' );
$response = wp_remote_get( $query_url );
if ( is_wp_error( $response ) ) {
return $response;
}
$response_body = wp_remote_retrieve_body( $response );
$response_data = json_decode( $response_body, true );
if ( isset( $response_data['results'] ) && ! empty( $response_data['results'] ) ) {
$movie_data = $response_data['results'][0];
$movie_info = array(
'id' => $movie_data['id'],
'poster_url' => 'https://image.tmdb.org/t/p/w500' . $movie_data['poster_path'],
);
return $movie_info;
}
return new WP_Error( 'tmdb_api_error', 'No movie data found.' );
}
}
This TMDB_API
class provides a constructor to set the TMDB API key (replace ’your_tmdb_api_key
’ with your actual API key) and the API base URL.
The get_movie_data_by_title_and_year()
method is responsible for fetching movie data from the TMDB API based on the provided title and year. Here’s how it works:
- It constructs the query parameters, including the API key, movie title, year, and language.
- It builds the query URL by appending the query parameters to the TMDB API search endpoint.
- It sends a
GET
request to the constructed URL using thewp_remote_get()
function. - If the response is successful, it retrieves the response body and decodes the JSON data.
- If the response contains movie results, it extracts the first result’s movie ID and poster URL (using the default poster size of ‘w500’).
- It returns an array containing the movie ID and poster URL.
- If no movie data is found or an error occurs, it returns a
WP_Error
object.
You can include this class-tmdb-api.php
file in your plugin’s includes/api/
directory and use it in your Jbros_Film_Review_Public
class to fetch movie data from the TMDB API.
Note: Make sure to replace ’your_tmdb_api_key
’ with your actual TMDB API key. Additionally, you may need to handle additional error cases, implement caching mechanisms, and adjust the API requests based on your specific requirements.
Sure, let’s move on to step 2 and update the necessary files to get movie data and store it as custom fields.
- Update
public/class-jbros-film-review-public.php
:
Add a new method to fetch and store movie data:
/**
* Fetch and store movie data for a film review.
*
* @param int $post_id The post ID of the film review.
* @param string $title The movie title.
* @param int $year The movie year.
*
* @return bool|WP_Error True on success, WP_Error on failure.
*/
private function fetch_and_store_movie_data($post_id, $title, $year)
{
$tmdb_api = new TMDB_API();
$movie_data = $tmdb_api->get_movie_data_by_title_and_year($title, $year);
if (is_wp_error($movie_data)) {
return $movie_data;
}
update_post_meta($post_id, 'jbros_movie_id', $movie_data['id']);
update_post_meta($post_id, 'jbros_poster_url', $movie_data['poster_url']);
return true;
}
This method creates an instance of the TMDB_API
class and calls the get_movie_data_by_title_and_year()
method to fetch movie data. If successful, it updates the custom fields jbros_movie_id
and jbros_poster_url
for the given post ID.
Then, update the create_or_update_film_review_post()
method to call the new fetch_and_store_movie_data()
method:
private function create_or_update_film_review_post($title, $year, $film_review)
{
// ... (existing code)
if (is_wp_error($post_id)) {
return $post_id;
}
// Save post meta
update_post_meta($post_id, 'film_title', $title);
update_post_meta($post_id, 'film_year', $year);
// Fetch and store movie data
$this->fetch_and_store_movie_data($post_id, $title, $year);
return $post_id;
}
Update includes/post-types.php
:
Add the new custom fields to the jbros_film_review_render_meta_box()
function:
function jbros_film_review_render_meta_box($post)
{
$film_title = get_post_meta($post->ID, 'film_title', true);
$film_year = get_post_meta($post->ID, 'film_year', true);
$movie_id = get_post_meta($post->ID, 'jbros_movie_id', true);
$poster_url = get_post_meta($post->ID, 'jbros_poster_url', true);
wp_nonce_field('jbros_film_review_meta_box', 'jbros_film_review_meta_box_nonce');
?>
<div>
<label for="film_title"><?php esc_html_e('Film Title', 'jbros-film-review'); ?></label>
<input type="text" id="film_title" name="film_title" value="<?php echo esc_attr($film_title); ?>" required>
</div>
<div>
<label for="film_year"><?php esc_html_e('Film Year', 'jbros-film-review'); ?></label>
<input type="number" id="film_year" name="film_year" value="<?php echo esc_attr($film_year); ?>" required>
</div>
<div>
<label for="movie_id"><?php esc_html_e('Movie ID', 'jbros-film-review'); ?></label>
<input type="text" id="movie_id" name="movie_id" value="<?php echo esc_attr($movie_id); ?>" readonly>
</div>
<div>
<label for="poster_url"><?php esc_html_e('Poster URL', 'jbros-film-review'); ?></label>
<input type="text" id="poster_url" name="poster_url" value="<?php echo esc_attr($poster_url); ?>" readonly>
</div>
<?php
}
This update displays the new custom fields (movie_id
and poster_url
) in the meta box, with the movie_id
and poster_url
fields set as read-only.
- Update
includes/post-types.php
:
Update the jbros_film_review_save_meta_box()
function to save the new custom fields:
function jbros_film_review_save_meta_box($post_id, $post)
{
// ... (existing code)
if (isset($_POST['film_title'])) {
update_post_meta($post_id, 'film_title', sanitize_text_field($_POST['film_title']));
}
if (isset($_POST['film_year'])) {
update_post_meta($post_id, 'film_year', sanitize_text_field($_POST['film_year']));
}
// The movie_id and poster_url fields are read-only, so no need to update them.
}
This update ensures that the film_title
and film_year
custom fields are saved when the post is updated.
With these changes, when creating or updating a “Film Review” post, the plugin will automatically fetch and store the movie data (movie ID and poster URL) from the TMDB API as custom fields. The custom fields will be displayed in the meta box, with the movie_id
and poster_url
fields set as read-only.
For existing “Film Review” posts without movie data, the fetch_and_store_movie_data()
method will be called automatically when the post is accessed, ensuring that the movie data is fetched and stored.
Sure, let’s move on to step 3 and update the existing “Film Review” posts without movie data.
To achieve this, we need to modify the get_film_review_post_id()
method in the public/class-jbros-film-review-public.php
file. This method is responsible for retrieving the post ID of an existing film review post based on the title and year.
Currently, the get_film_review_post_id()
method only checks for the film_title
and film_year
custom fields. We need to update it to also check if the jbros_movie_id
and jbros_poster_url
custom fields exist. If they don’t exist, we’ll call the fetch_and_store_movie_data()
method to fetch and store the movie data.
Here’s the updated get_film_review_post_id()
method:
/**
* Get the post ID of an existing film review post.
*
* @param string $title The movie title.
* @param int $year The movie year.
*
* @return int|null The post ID if found, or null if not found.
*/
private function get_film_review_post_id($title, $year)
{
$query_args = array(
'post_type' => 'film_review',
'post_status' => 'publish',
'posts_per_page' => 1,
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'film_title',
'value' => $title,
),
array(
'key' => 'film_year',
'value' => $year,
),
array(
'relation' => 'OR',
array(
'key' => 'jbros_movie_id',
'compare' => 'NOT EXISTS',
),
array(
'key' => 'jbros_poster_url',
'compare' => 'NOT EXISTS',
),
),
),
);
$query = new WP_Query($query_args);
if ($query->have_posts()) {
$post_id = $query->posts[0]->ID;
// Fetch and store movie data if it doesn't exist
$movie_id = get_post_meta($post_id, 'jbros_movie_id', true);
$poster_url = get_post_meta($post_id, 'jbros_poster_url', true);
if (empty($movie_id) || empty($poster_url)) {
$this->fetch_and_store_movie_data($post_id, $title, $year);
}
return $post_id;
}
return null;
}
Here’s what we’ve changed:
- In the
meta_query
array, we’ve added a new set of conditions with therelation
parameter set to'OR'
. This checks if either thejbros_movie_id
orjbros_poster_url
custom field doesn’t exist for the post. - After retrieving the post ID, we check if the
jbros_movie_id
andjbros_poster_url
custom fields are empty. - If either of the custom fields is empty, we call the
fetch_and_store_movie_data()
method, passing the post ID, title, and year to fetch and store the movie data.
With this update, whenever a “Film Review” post is accessed, the plugin will check if the movie data exists. If it doesn’t, it will automatically fetch and store the movie data from the TMDB API using the fetch_and_store_movie_data()
method.
This ensures that existing “Film Review” posts without movie data will be updated with the necessary movie information when accessed, without the need for manual intervention.
Certainly! Let’s proceed with step 4 and create the Gutenberg block for movie info in the public/blocks/jbros-movie-info-block
folder.
- Update
edit.js
: Open theedit.js
file and make the following changes:
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, PanelRow, SelectControl, TextControl } from '@wordpress/components';
import apiFetch from '@wordpress/api-fetch';
import { useEffect } from '@wordpress/element';
export default function Edit({ attributes, setAttributes }) {
const { title, year, action, movieId, posterUrl } = attributes;
const onChangeTitle = (newTitle) => {
setAttributes({ title: newTitle });
};
const onChangeYear = (newYear) => {
setAttributes({ year: newYear });
};
const onChangeAction = (newAction) => {
setAttributes({ action: newAction });
};
const fetchMovieData = async () => {
const response = await apiFetch({
path: '/jbros-film-review/v1/get-movie-info',
method: 'POST',
data: {
title: title,
year: year,
action: action,
},
});
if (response.success) {
setAttributes({
movieId: response.data.movie_id,
posterUrl: response.data.poster_url,
});
} else {
setAttributes({
movieId: '',
posterUrl: '',
});
}
};
useEffect(() => {
if (title && year) {
fetchMovieData();
}
}, [title, year, action]);
return (
<>
<InspectorControls>
<PanelBody title="Movie Info" initialOpen={true}>
<PanelRow>
<TextControl
label="Movie Title"
value={title}
onChange={onChangeTitle}
/>
</PanelRow>
<PanelRow>
<TextControl
label="Movie Year"
value={year}
onChange={onChangeYear}
/>
</PanelRow>
<PanelRow>
<SelectControl
label="Action"
value={action}
onChange={onChangeAction}
options={[
{ value: 'create_or_update', label: 'Create or Update' },
{ value: 'get_existing', label: 'Get Existing' },
]}
/>
</PanelRow>
</PanelBody>
</InspectorControls>
<div {...useBlockProps()}>
<h3>Movie Info</h3>
{movieId && <p>Movie ID: {movieId}</p>}
{posterUrl && <img src={posterUrl} alt="Movie Poster" />}
</div>
</>
);
}
In this updated edit.js
file, we’ve added the necessary functionality to fetch movie data based on the selected action (create_or_update
or get_existing
). The fetchMovieData
function sends a request to a custom API endpoint (/jbros-film-review/v1/get-movie-info
) to retrieve the movie ID and poster URL. We’ve also added input fields for the movie title, year, and action in the block inspector controls. The fetched movie ID and poster URL are displayed within the block content area.
- Create
save.js
: Create a new file calledsave.js
in thepublic/blocks/jbros-movie-info-block
folder with the following content:
import { useBlockProps } from '@wordpress/block-editor';
export default function save({ attributes }) {
const { movieId, posterUrl } = attributes;
return (
<div {...useBlockProps.save()}>
<h3>Movie Info</h3>
{movieId && <p>Movie ID: {movieId}</p>}
{posterUrl && <img src={posterUrl} alt="Movie Poster" />}
</div>
);
}
This file defines how the block will be rendered on the front-end, displaying the movie ID and poster URL.
- Register Custom API Endpoint: In the
public/class-jbros-film-review-public.php
file, register a new custom API endpoint to handle the movie info requests:
public function register_api_endpoints()
{
add_action('rest_api_init', function () {
register_rest_route('jbros-film-review/v1', '/get-movie-info', array(
'methods' => 'POST',
'callback' => array($this, 'get_movie_info'),
'args' => array(
'title' => array(
'required' => true,
'type' => 'string',
),
'year' => array(
'required' => true,
'type' => 'integer',
),
'action' => array(
'required' => false,
'type' => 'string',
'default' => 'create_or_update',
'enum' => array('create_or_update', 'get_existing'),
),
),
'permission_callback' => '__return_true',
));
});
}
This code registers a new REST API endpoint (/wp-json/jbros-film-review/v1/get-movie-info
) that accepts title
, year
, and action
parameters.
- Implement
get_movie_info
Method: In thepublic/class-jbros-film-review-public.php
file, add theget_movie_info
method to handle the movie info requests:
public function get_movie_info($request)
{
$title = sanitize_text_field($request->get_param('title'));
$year = absint($request->get_param('year'));
$action = sanitize_text_field($request->get_param('action'));
if (empty($title) || empty($year)) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'Invalid request parameters.',
), 400);
}
$post_id = $this->get_film_review_post_id($title, $year);
if ($action === 'get_existing' && $post_id) {
$movie_id = get_post_meta($post_id, 'jbros_movie_id', true);
$poster_url = get_post_meta($post_id, 'jbros_poster_url', true);
return new WP_REST_Response(array(
'success' => true,
'data' => array(
'movie_id' => $movie_id,
'poster_url' => $poster_url,
),
), 200);
}
$tmdb_api = new TMDB_API();
$movie_data = $tmdb_api->get_movie_data_by_title_and_year($title, $year);
if (is_wp_error($movie_data)) {
return new WP_REST_Response(array(
'success' => false,
'message' => $movie_data->get_error_message(),
), 500);
}
return new WP_REST_Response(array(
'success' => true,
'data' => $movie_data,
), 200);
}
This method handles the movie info requests based on the provided action
. If the action is 'get_existing'
and a post exists for the given title
and year
, it retrieves the movie ID and poster URL from the custom fields and returns them in the response. If the action is 'create_or_update'
, it calls the TMDB_API
class to fetch the movie data and returns it in the response.
- Enqueue Block Assets: In the
enqueue_block_assets()
method of thepublic/class-jbros-film-review-public.php
file, enqueue the assets for the “Movie Info” block:
public function enqueue_block_assets()
{
$block_path = plugin_dir_path(__FILE__) . 'blocks/jbros-film-review-block/build/';
wp_enqueue_script('jbros-film-review-block', plugins_url('blocks/jbros-film-review-block/build/index.js', __FILE__), array('wp-blocks', 'wp-element', 'wp-editor'), filemtime($block_path . 'index.js'));
wp_enqueue_style('jbros-film-review-block-editor', plugins_url('blocks/jbros-film-review-block/build/index.css', __FILE__), array('wp-edit-blocks'), filemtime($block_path . 'index.css'));
$movie_info_block_path = plugin_dir_path(__FILE__) . 'blocks/jbros-movie-info-block/build/';
wp_enqueue_script('jbros-movie-info-block', plugins_url('blocks/jbros-movie-info-block/build/index.js', __FILE__), array('wp-blocks', 'wp-element', 'wp-editor'), filemtime($movie_info_block_path . 'index.js'));
wp_enqueue_style('jbros-movie-info-block-editor', plugins_url('blocks/jbros-movie-info-block/build/index.css', __FILE__), array('wp-edit-blocks'), filemtime($movie_info_block_path . 'index.css'));
}
This code enqueues the JavaScript and CSS assets for the “Movie Info” block.
After making these changes, you should have a new Gutenberg block called “Movie Info” that allows users to enter a movie title and year, and choose the desired action (create_or_update
or get_existing
). Based on the selected action, the block will either fetch the movie data from the TMDB API or retrieve the existing data from the custom fields. The movie ID and poster URL will be displayed within the block content area.
Remember to update the register_api_endpoints()
method in the public/class-jbros-film-review-public.php
file to register the new API endpoint, and include the necessary dependencies in the load_dependencies()
method of the main plugin class.