Skip to main content Senior Full Stack Web Developer - Ossi Jalonen

WordPress plugin dev with the help of Claude.ai, part 5

Published: 2024-04-26
Updated: 2024-04-26

It’s time to finish this series and leave you with a mix of a few small updates to the code, and a few additional ideas on how to explore the plugin and Claude’s involvement in future.

Towards the end of this series, not everything in the conversation has ended up in the plugin codebase, so this may get a bit harder to follow along with the codebase. But these last couple of posts are a lot shorter, and it is perhaps more about concepts, than actually building this plugin, and just examples of how Claude approaches these questions.
Anyhow, it may still be good to have this link here to the full code from this series:
https://github.com/jalonenbrothers/film-review-plugin-demo

A bit of HTML and CSS

Me: I need a responsive 'Card' Element to hold film details in a single-film_review.php template of a WordPress plugin. The page main content is the film review itself, but it also contains all film related details as custom fields, so decide what details are most important and include those in the card template. Create html structure for a card, and css to make it look like a card and also responsive. The film data includes two images, portrait aspect ratio poster, and a landscape aspect ratio 'backdrop'. You can use one or both as you see fit.

I can provide a general structure for a responsive card element that can hold film details, along with the necessary CSS to style it.

HTML Structure:

<div class="card">
  <div class="card-image">
    <img src="poster.jpg" alt="Film Poster">
    <img src="backdrop.jpg" alt="Film Backdrop">
  </div>
  <div class="card-content">
    <h2 class="card-title">Film Title</h2>
    <p class="card-meta">
      <span class="release-year">2022</span> |
      <span class="runtime">120 min</span> |
      <span class="genre">Action, Adventure</span>
    </p>
    <p class="card-description">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam euismod, nisl eget ultricies aliquam, nisl nisl aliquet nisl, eget aliquam nisl nisl eget nisl.
    </p>
    <div class="card-details">
      <p><strong>Director:</strong> John Doe</p>
      <p><strong>Writer:</strong> Jane Smith</p>
      <p><strong>Stars:</strong> Actor 1, Actor 2, Actor 3</p>
    </div>
  </div>
</div>

CSS:

.card {
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  overflow: hidden;
  max-width: 600px;
  margin: 0 auto;
}

.card-image {
  position: relative;
  padding-bottom: 56.25%; /* 16:9 aspect ratio */
  height: 0;
  overflow: hidden;
}

.card-image img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.card-content {
  padding: 20px;
}

.card-title {
  font-size: 24px;
  margin-bottom: 10px;
}

.card-meta {
  color: #666;
  font-size: 14px;
  margin-bottom: 10px;
}

.card-description {
  line-height: 1.5;
  margin-bottom: 20px;
}

.card-details p {
  margin: 5px 0;
}

@media (max-width: 600px) {
  .card {
    max-width: 100%;
    border-radius: 0;
  }
}

This structure includes a div with the class card that contains two main sections: card-image and card-content. The card-image section can hold one or both of the provided images (poster and backdrop) using an aspect ratio container. The card-content section includes placeholders for the film title, metadata (release year, runtime, genre), description, and additional details like director, writer, and stars.

The CSS styles provided make the card responsive, with a maximum width of 600 pixels on larger screens and a full-width layout on smaller screens (up to 600 pixels). The card has a white background, rounded corners, and a subtle box shadow. The image section maintains the aspect ratio of the images (in this case, 16:9), ensuring they don’t get distorted.

You can customize the HTML structure and CSS styles further based on your specific requirements and the available film data in your WordPress plugin.

Me: I'm creating a website with film reviews of popular films. Give me 10 different film review personas, give name, age, profession or equivalent to their life situation, short description with likes and interests, in relation to tv and cinema, to be used as a short biog of film reviewer / review author on the website. describe the their social background as well, this is not shown on the website but used as information when creating film reviews.

just to give you an idea on what aspects of the plugin are still very much not developed. I guess this is the type of work Claude has to do a lot more as not everyone is a coder. But we could carry on building a website with a number of different film critics with different backgrounds, offering different views on various films. You can probably think of quite a few other ideas that could make this plugin actually useful.

Here are 10 different film review personas with their names, ages, professions/life situations, descriptions with likes and interests related to TV and cinema, and social backgrounds.

  1. Emma Thompson, 28, Marketing Executive
    Description: Emma is a passionate cinephile who loves exploring the depths of character development and symbolism in films. She has a keen eye for detail and enjoys analyzing the artistic choices made by directors and cinematographers. Emma’s interests lie in thought-provoking dramas, independent films, and foreign cinema.
    Social Background: Upper-middle-class, grew up in a suburban area with parents who encouraged her love for the arts.

  2. David Lee, 42, High School Teacher
    Description: As an English teacher, David has a deep appreciation for storytelling and the power of narrative. He enjoys films that tackle complex themes and challenge societal norms. David is particularly drawn to historical dramas, biopics, and coming-of-age stories that offer insights into the human experience.
    Social Background: Working-class, raised in an inner-city neighborhood by a single mother who instilled in him a love for literature and education.

  3. Sophia Rodriguez, 19, College Student Description: Sophia is a film enthusiast who loves exploring diverse genres and cultures through cinema. She enjoys discovering hidden gems and lesser-known films, and she has a particular interest in international movies that provide a window into different parts of the world. Sophia is also passionate about supporting female filmmakers and stories that celebrate diversity.
    Social Background: Middle-class, grew up in a multicultural community with parents who encouraged her curiosity and open-mindedness.

  4. Michael Clark, 62, Retired Engineer Description: After a successful career in engineering, Michael has found a newfound passion for classic films. He appreciates the craftsmanship and attention to detail in older movies, particularly those from the golden age of Hollywood. Michael enjoys analyzing the technical aspects of filmmaking, such as cinematography, set design, and special effects.
    Social Background: Upper-middle-class, raised in a suburban area with parents who valued education and hard work.

  5. Samantha Kim, 35, Graphic Designer Description: Samantha is a visual storyteller who is drawn to the artistic and aesthetic elements of filmmaking. She has a keen eye for color, composition, and visual metaphors. Samantha enjoys films that push the boundaries of traditional storytelling and explore unique visual styles, such as experimental cinema and avant-garde films.
    Social Background: Middle-class, grew up in an urban area with parents who encouraged her creative pursuits.

  6. Alex Johnson, 27, Software Developer Description: Alex is a tech-savvy film lover who enjoys exploring the intersection of cinema and technology. He is particularly interested in science fiction and fantasy films that challenge our understanding of the world and push the boundaries of what’s possible. Alex also enjoys analyzing the use of special effects and CGI in movies.
    Social Background: Upper-middle-class, raised in a family of professionals who valued education and technological advancements.

  7. Olivia Martin, 45, Stay-at-Home Mom Description: As a stay-at-home mom, Olivia finds solace in the world of cinema. She enjoys films that explore complex family dynamics, relationships, and the challenges of parenthood. Olivia appreciates movies that offer a relatable and emotional experience, allowing her to connect with the characters and their struggles.
    Social Background: Middle-class, raised in a traditional family with a strong emphasis on family values and personal connections.

  8. James Wilson, 31, Personal Trainer Description: James is a fitness enthusiast who loves action-packed movies that showcase impressive stunts, fight choreography, and physical feats. He enjoys analyzing the training and preparation that goes into creating those adrenaline-fueled sequences. James also has a soft spot for sports dramas that celebrate the human spirit and perseverance.
    Social Background: Working-class, grew up in a blue-collar neighborhood where physical strength and determination were valued.

  9. Sarah Thompson, 24, Social Media Influencer Description: Sarah is a trendsetter who loves exploring the latest movies and TV shows that capture the zeitgeist. She has a keen eye for pop culture trends and enjoys analyzing the social and cultural impact of films. Sarah is particularly interested in movies that resonate with younger audiences and challenge societal norms.
    Social Background: Upper-middle-class, raised in an affluent area with access to the latest trends and technology.

  10. Mark Davis, 51, Business Owner Description: Mark is a successful business owner who enjoys escaping into the world of cinema as a form of relaxation and entertainment. He appreciates well-crafted blockbuster movies that offer a thrilling and immersive experience. Mark is drawn to big-budget action films, superhero movies, and epic adventures that transport him to other worlds.
    Social Background: Upper-class, grew up in a wealthy family with access to the finer things in life, including luxury movie-going experiences.

For the last time in this series, let's get back to the code with one more update and some ideas on WordPress plugin development. I find Claude is pretty good at explaining existing code (or was it Gemini?), but I'm not sure these generative AIs know much about what 'best practice' means. How it comes up with code, is obviously very complex, but evaluating, or making decisions about multiple approaches to a particular coding problem can't be easy, and I'm not sure Claude spends much time on this. In many cases the first version it offers, is close to what you'd find on the first page of Google results. And only once you guide Claude a little bit, it shows that it does have the knowledge to do things differently as well. It just wants to take the shortest path and hopes it's not questioned further. But it's early days still of course. The future is bright.

Me: The plugin settings page is not very well structured and adding new settings is time consuming. what is the best practise structure for a WordPress plugin admin settings page to handle display of multiple types of settings across different tabs or pages?

When it comes to structuring a WordPress plugin’s admin settings page, it’s best to follow a modular and organized approach. This will make it easier to manage and add new settings in the future. Here are some best practices to consider:

  1. Use WordPress Settings API: WordPress provides a built-in Settings API that simplifies the process of creating and managing settings pages. It handles the rendering of form fields, validating user input, and saving options in the database.
  2. Separate Settings into Logical Sections: If you have a large number of settings, it’s a good idea to separate them into logical sections or tabs. This makes it easier for users to navigate and find specific settings. You can use the add_settings_section function to create sections within your settings page.
  3. Use Settings Fields: Within each section, you can add individual settings fields using the add_settings_field function. This function allows you to specify the type of field (text input, checkbox, textarea, etc.), a description, and a callback function to render the field.
  4. Use Callbacks for Rendering Fields: Instead of hardcoding the HTML for each field, it’s better to use callback functions. This makes your code more modular and easier to maintain. You can define separate functions for rendering different field types (text input, checkbox, etc.).
  5. Use Object-Oriented Approach: Consider using an object-oriented approach to structure your plugin’s settings page. You can create a class for managing settings, with methods for registering settings sections, fields, and handling form submissions.
  6. Use Tabs or Metaboxes: If you need to display settings across different tabs or pages, you can use WordPress metaboxes or custom tabs. Metaboxes can be added to your settings page using the add_meta_box function, and custom tabs can be created using JavaScript or a plugin like the Settings API.
  7. Sanitize and Validate User Input: Always sanitize and validate user input before saving it to the database. WordPress provides built-in sanitization functions like sanitize_text_field, sanitize_textarea_field, and sanitize_option that you can use in your callback functions.
  8. Use WordPress Coding Standards: Follow the WordPress Coding Standards to ensure your code is consistent, readable, and maintainable.

By following these best practices, you can create a well-structured and extensible settings page for your WordPress plugin. It will be easier to add new settings, maintain existing ones, and provide a better user experience for your users.

Me: Following best practice outlined above, rewrite my settings page for me. I'd also like to separate the two different APIs settings and I'd like another one for other plugin settings. For now, add to the 'other' plugin settings a setting for 'display size options'; this field can either take multiple values or one comma separated list, which ever is considered best practise.

Here’s a rewritten version of your WordPress plugin settings page, following the best practices outlined earlier. I’ve separated the settings into three sections: Anthropic API Settings, TMDB API Settings, and Other Plugin Settings. Additionally, I’ve added a setting for ‘Display Size Options’ in the Other Plugin Settings section, which accepts a comma-separated list of values.

<?php
/**
 * Jbros_Film_Review_Settings class.
 */
class Jbros_Film_Review_Settings {
    /**
     * The plugin option group.
     *
     * @var string
     */
    private $option_group = 'jbros_film_review_settings';

    /**
     * Constructor.
     */
    public function __construct() {
        add_action( 'admin_init', array( $this, 'register_settings' ) );
        add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
    }

    /**
     * Register plugin settings.
     */
    public function register_settings() {
        // Register Anthropic API settings
        register_setting(
            $this->option_group,
            'jbros_film_review_anthropic_api_key',
            array(
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_text_field',
            )
        );

        register_setting(
            $this->option_group,
            'jbros_film_review_anthropic_api_url',
            array(
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_text_field',
            )
        );

        // Register TMDB API settings
        register_setting(
            $this->option_group,
            'jbros_film_review_tmdb_api_key',
            array(
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_text_field',
            )
        );

        register_setting(
            $this->option_group,
            'jbros_film_review_tmdb_api_base_url',
            array(
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_text_field',
            )
        );

        // Register other plugin settings
        register_setting(
            $this->option_group,
            'jbros_film_review_display_size_options',
            array(
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_text_field',
            )
        );

        // Register settings sections and fields
        add_settings_section(
            'jbros_film_review_anthropic_api_settings',
            'Anthropic API Settings',
            array( $this, 'render_anthropic_api_settings_section' ),
            'jbros-film-review'
        );

        add_settings_field(
            'jbros_film_review_anthropic_api_key',
            'Anthropic API Key',
            array( $this, 'render_anthropic_api_key_field' ),
            'jbros-film-review',
            'jbros_film_review_anthropic_api_settings'
        );

        add_settings_field(
            'jbros_film_review_anthropic_api_url',
            'Anthropic API URL',
            array( $this, 'render_anthropic_api_url_field' ),
            'jbros-film-review',
            'jbros_film_review_anthropic_api_settings'
        );

        add_settings_section(
            'jbros_film_review_tmdb_api_settings',
            'TMDB API Settings',
            array( $this, 'render_tmdb_api_settings_section' ),
            'jbros-film-review'
        );

        add_settings_field(
            'jbros_film_review_tmdb_api_key',
            'TMDB API Key',
            array( $this, 'render_tmdb_api_key_field' ),
            'jbros-film-review',
            'jbros_film_review_tmdb_api_settings'
        );

        add_settings_field(
            'jbros_film_review_tmdb_api_base_url',
            'TMDB API Base URL',
            array( $this, 'render_tmdb_api_base_url_field' ),
            'jbros-film-review',
            'jbros_film_review_tmdb_api_settings'
        );

        add_settings_section(
            'jbros_film_review_other_settings',
            'Other Plugin Settings',
            array( $this, 'render_other_settings_section' ),
            'jbros-film-review'
        );

        add_settings_field(
            'jbros_film_review_display_size_options',
            'Display Size Options',
            array( $this, 'render_display_size_options_field' ),
            'jbros-film-review',
            'jbros_film_review_other_settings'
        );
    }

    /**
     * Add the settings page to the admin menu.
     */
    public function add_settings_page() {
        add_options_page(
            'JBros Film Review Settings',
            'JBros Film Review',
            'manage_options',
            'jbros-film-review',
            array( $this, 'render_settings_page' )
        );
    }

    /**
     * Render the Anthropic API settings section.
     */
    public function render_anthropic_api_settings_section() {
        echo '<p>Configure the Anthropic API settings for the JBros Film Review plugin.</p>';
    }

    /**
     * Render the TMDB API settings section.
     */
    public function render_tmdb_api_settings_section() {
        echo '<p>Configure the TMDB API settings for the JBros Film Review plugin.</p>';
    }

    /**
     * Render the other settings section.
     */
    public function render_other_settings_section() {
        echo '<p>Configure other settings for the JBros Film Review plugin.</p>';
    }

    /**
     * Render the Anthropic API key field.
     */
    public function render_anthropic_api_key_field() {
        $value = get_option( 'jbros_film_review_anthropic_api_key' );
        echo '<input type="text" name="jbros_film_review_anthropic_api_key" value="' . esc_attr( $value ) . '" />';
    }

    /**
     * Render the Anthropic API URL field.
     */
    public function render_anthropic_api_url_field() {
        $value = get_option( 'jbros_film_review_anthropic_api_url' );
        echo '<input type="text" name="jbros_film_review_anthropic_api_url" value="' . esc_attr( $value ) . '" />';
    }

    /**
     * Render the TMDB API key field.
     */
    public function render_tmdb_api_key_field() {
        $value = get_option( 'jbros_film_review_tmdb_api_key' );
        echo '<input type="text" name="jbros_film_review_tmdb_api_key" value="' . esc_attr( $value ) . '" />';
    }

    /**
     * Render the TMDB API URL field.
     */
    public function render_tmdb_api_base_url_field() {
        $value = get_option( 'jbros_film_review_tmdb_api_base_url' );
        echo '<input type="text" name="jbros_film_review_tmdb_api_base_url" value="' . esc_attr( $value ) . '" />';
    }

    /**
     * Render the Display Size Options field.
     */
    public function render_display_size_options_field() {
        $value = get_option( 'jbros_film_review_display_size_options' );
        echo '<input type="text" name="jbros_film_review_display_size_options" value="' . esc_attr( $value ) . '" placeholder="Enter comma-separated values" />';
        echo '<p class="description">Enter a comma-separated list of display size options.</p>';
    }

    /**
     * Render the settings page.
     */
    public function render_settings_page() {
        ?>
        <div class="wrap">
            <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
            <form action="options.php" method="post">
                <?php
                settings_fields( $this->option_group );
                do_settings_sections( 'jbros-film-review' );
                submit_button();
                ?>
            </form>
        </div>
        <?php
    }
}

// Initialize the settings class
$jbros_film_review_settings = new Jbros_Film_Review_Settings();

In this updated version:

  1. I’ve added separate sections for Anthropic API Settings, TMDB API Settings, and Other Plugin Settings using add_settings_section.
  2. For each section, I’ve added respective fields using add_settings_field and callback functions to render the fields.
  3. The ‘Display Size Options’ field in the Other Plugin Settings section accepts a comma-separated list of values, and I’ve added a placeholder text and description to guide the user.

This structure follows the best practices by separating concerns, using callbacks for rendering fields, and providing a clear organization for the settings. It should make it easier to add or modify settings in the future.

Me: There is so much repetition. What's the best practise way of separating the actual settings fields info from the code so that we can pass it to some method in the settings class and it can loop over and create the settings?

You’re right, there’s a lot of repetition in the code. A better approach would be to separate the settings fields information from the code and pass it to a method in the settings class that can loop over and create the settings dynamically.

Here’s how you can refactor the code to achieve this:

  1. Define an array with all the settings fields information, including their sections, names, labels, and any other necessary data.
  2. Create a method in the settings class that loops over this array and registers the settings, sections, and fields dynamically.

Here’s an example implementation:

<?php
/**
 * Jbros_Film_Review_Settings class.
 */
class Jbros_Film_Review_Settings {
    /**
     * The plugin option group.
     *
     * @var string
     */
    private $option_group = 'jbros_film_review_settings';

    /**
     * Array of settings fields.
     *
     * @var array
     */
    private $settings_fields = array();

    /**
     * Constructor.
     */
    public function __construct() {
        $this->set_settings_fields();
        add_action( 'admin_init', array( $this, 'register_settings' ) );
        add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
    }

    /**
     * Set the settings fields array.
     */
    private function set_settings_fields() {
        $this->settings_fields = array(
            'anthropic_api' => array(
                'section_title' => 'Anthropic API Settings',
                'section_description' => 'Configure the Anthropic API settings for the JBros Film Review plugin.',
                'fields' => array(
                    'jbros_film_review_anthropic_api_key' => array(
                        'label' => 'Anthropic API Key',
                        'type'  => 'string',
                    ),
                    'jbros_film_review_anthropic_api_url' => array(
                        'label' => 'Anthropic API URL',
                        'type'  => 'string',
                    ),
                ),
            ),
            'tmdb_api' => array(
                'section_title' => 'TMDB API Settings',
                'section_description' => 'Configure the TMDB API settings for the JBros Film Review plugin.',
                'fields' => array(
                    'jbros_film_review_tmdb_api_key' => array(
                        'label' => 'TMDB API Key',
                        'type'  => 'string',
                    ),
                    'jbros_film_review_tmdb_api_base_url' => array(
                        'label' => 'TMDB API Base URL',
                        'type'  => 'string',
                    ),
                ),
            ),
            'other_settings' => array(
                'section_title' => 'Other Plugin Settings',
                'section_description' => 'Configure other settings for the JBros Film Review plugin.',
                'fields' => array(
                    'jbros_film_review_display_size_options' => array(
                        'label' => 'Display Size Options',
                        'type'  => 'string',
                        'description' => 'Enter a comma-separated list of display size options.',
                    ),
                ),
            ),
        );
    }

    /**
     * Register plugin settings.
     */
    public function register_settings() {
        foreach ( $this->settings_fields as $section_id => $section_data ) {
            // Register settings section
            add_settings_section(
                $section_id,
                $section_data['section_title'],
                array( $this, 'render_section_description' ),
                'jbros-film-review'
            );

            // Register settings fields
            foreach ( $section_data['fields'] as $field_id => $field_data ) {
                register_setting(
                    $this->option_group,
                    $field_id,
                    array(
                        'type'              => $field_data['type'],
                        'sanitize_callback' => 'sanitize_text_field',
                    )
                );

                add_settings_field(
                    $field_id,
                    $field_data['label'],
                    array( $this, 'render_field' ),
                    'jbros-film-review',
                    $section_id,
                    array(
                        'field_id'    => $field_id,
                        'description' => isset( $field_data['description'] ) ? $field_data['description'] : '',
                    )
                );
            }
        }
    }

    /**
     * Render the section description.
     *
     * @param array $args
     */
    public function render_section_description( $args ) {
        $section_id = $args['id'];
        $section_data = $this->settings_fields[ $section_id ];
        echo '<p>' . $section_data['section_description'] . '</p>';
    }

    /**
     * Render the field.
     *
     * @param array $args
     */
    public function render_field( $args ) {
        $field_id    = $args['field_id'];
        $description = $args['description'];
        $value       = get_option( $field_id );

        echo '<input type="text" name="' . esc_attr( $field_id ) . '" value="' . esc_attr( $value ) . '" />';

        if ( ! empty( $description ) ) {
            echo '<p class="description">' . $description . '</p>';
        }
    }

    // ... (Other methods remain the same)
}

// Initialize the settings class
$jbros_film_review_settings = new Jbros_Film_Review_Settings();

In this refactored version:

  1. The set_settings_fields method defines an array $settings_fields containing all the settings fields information, including their sections, names, labels, types, and descriptions.
  2. The register_settings method loops over the $settings_fields array and dynamically registers the settings sections, fields, and their respective callbacks.
  3. The render_section_description method is used to render the section description based on the section data from $settings_fields.
  4. The render_field method is used to render the field input and description based on the field data from $settings_fields.

This approach separates the settings fields information from the code, making it easier to add, modify, or remove settings fields without having to change the core logic. It also reduces the amount of repetitive code and makes the codebase more maintainable.

This was a fun project for me, but I think we have gone far enough with this, I can’t see it (the post series) being very easy to follow or read. Maybe I’ll aim for shorter posts in future, or long series where each post is somewhat independent. That won’t work with an example application code so well, but I can certainly give it a go.

Time to move on

Thanks for following along. If you're reading this, I hope you read some of the actual posts too. I know it's a bit of a copy&paste job and I'm not really the main author here either, but that is what this is supposed to be. Showing what these new 'tools' are capable of, already, in their very early stages, while they're still rapidly evolving in front of our eyes. It really isn't about what the generative AI can do today ( 26 April 2024 ), is it?