<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Shahibur Rahman</title>
    <description>The latest articles on Forem by Shahibur Rahman (@shahibur_rahman_6670cd024).</description>
    <link>https://forem.com/shahibur_rahman_6670cd024</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3317095%2F48dfe61d-0284-4834-923b-395e5c86567e.png</url>
      <title>Forem: Shahibur Rahman</title>
      <link>https://forem.com/shahibur_rahman_6670cd024</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/shahibur_rahman_6670cd024"/>
    <language>en</language>
    <item>
      <title>Demystifying WordPress Plugin License Activation: A Step-by-Step Client-Server Guide</title>
      <dc:creator>Shahibur Rahman</dc:creator>
      <pubDate>Sun, 05 Apr 2026 17:12:27 +0000</pubDate>
      <link>https://forem.com/shahibur_rahman_6670cd024/demystifying-wordpress-plugin-license-activation-a-step-by-step-client-server-guide-3b3a</link>
      <guid>https://forem.com/shahibur_rahman_6670cd024/demystifying-wordpress-plugin-license-activation-a-step-by-step-client-server-guide-3b3a</guid>
      <description>&lt;p&gt;Understanding &lt;strong&gt;WordPress Plugin License Activation&lt;/strong&gt; can often feel opaque for developers and users. How do commercial plugins truly verify legitimate usage? How are domain limits gracefully enforced without being overly restrictive or easily bypassed? This article aims to pull back the curtain, breaking down a robust, complementary client-server process for managing plugin licenses effectively.&lt;/p&gt;

&lt;p&gt;We'll explore how modern payment and license key generation platforms (like Lemon Squeezy or Easy Digital Downloads) integrate with custom license hubs. This intricate dance involves webhooks, secure database management, and API validation, ensuring your &lt;strong&gt;WordPress Plugin License Activation&lt;/strong&gt; system is both secure and scalable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core of WordPress Plugin License Activation: Client-Server Harmony
&lt;/h2&gt;

&lt;p&gt;At its core, a robust &lt;strong&gt;WordPress Plugin License Activation&lt;/strong&gt; system involves two main components working in harmony:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Server-Side (License Management Hub)&lt;/strong&gt;: This is typically a dedicated WordPress installation or a separate web application. It acts as the central authority, listening for purchase events (via webhooks), securely storing license data, and providing an API for client plugins to validate their licenses.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Client-Side (Your Premium WordPress Plugin)&lt;/strong&gt;: This is your plugin, installed on a user's site. It communicates with the License Management Hub to activate, validate, and periodically check its license status.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's dive into the technical details of how these two sides interact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: The Server-Side Foundation - Webhooks &amp;amp; Database Management
&lt;/h2&gt;

&lt;p&gt;Our journey begins the moment a customer purchases your plugin. Upon a successful payment, the payment platform dispatches a &lt;code&gt;license_key_created&lt;/code&gt; webhook to a specified endpoint on your License Management Hub. This webhook carries crucial information about the newly generated license key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Securely Receiving Webhooks with &lt;code&gt;LicenseHubWebhookReceiver&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;LicenseHubWebhookReceiver&lt;/code&gt; class is designed to set up a REST API endpoint within your WordPress License Management Hub. This endpoint securely catches incoming webhooks and processes them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LicenseHubWebhookReceiver&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$plugin_base_slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$plugin_base_slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_base_slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$plugin_base_slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register_webhook_route&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;register_rest_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my-licensing-api/v1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/payment-webhook'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'methods'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'callback'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'handle_incoming_webhook'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'permission_callback'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'__return_true'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Signature verification inside callback&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle_incoming_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;WP_REST_Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$body&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_body&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x-signature'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Example header for webhook signature&lt;/span&gt;
        &lt;span class="nv"&gt;$secret&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_base_slug&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'_webhook_secret'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Stored securely&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. SECURITY: Verify the Webhook Signature&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;is_valid_webhook_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Invalid Webhook Signature detected."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WP_REST_Response&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Invalid signature'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WP_REST_Response&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'empty_payload'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'meta'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'event_name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// e.g., 'license_key_created'&lt;/span&gt;
        &lt;span class="nv"&gt;$db&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LicenseHubDataManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_base_slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'license_key_created'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'attributes'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// Contains license key, email, limits, etc.&lt;/span&gt;
            &lt;span class="nv"&gt;$inserted&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;insert_new_license&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$inserted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"New license key inserted: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WP_REST_Response&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Verifies the webhook signature against a shared secret.
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;is_valid_webhook_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nv"&gt;$computed_signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;hash_hmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sha256'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;hash_equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$computed_signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$signature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key takeaway here:&lt;/strong&gt; The &lt;code&gt;is_valid_webhook_signature&lt;/code&gt; method is absolutely critical for security. It ensures that the incoming webhook genuinely originated from your payment platform and hasn't been tampered with, preventing malicious actors from forging license keys or triggering false events.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Storing License Data with &lt;code&gt;LicenseHubDataManager&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Once the webhook is verified, the &lt;code&gt;handle_incoming_webhook&lt;/code&gt; method delegates to &lt;code&gt;insert_new_license&lt;/code&gt; from &lt;code&gt;LicenseHubDataManager&lt;/code&gt;. This class is responsible for all database interactions, including creating the necessary tables and storing the license details.&lt;/p&gt;

&lt;p&gt;Here's a simplified look at how a &lt;code&gt;plugin_licenses&lt;/code&gt; table might be structured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// ... (part of LicenseHubDataManager::create_license_table method)&lt;/span&gt;

        &lt;span class="nv"&gt;$sql_licenses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CREATE TABLE &lt;/span&gt;&lt;span class="nv"&gt;$table_licenses&lt;/span&gt;&lt;span class="s2"&gt; (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            license_key varchar(100) NOT NULL,
            product_item_name varchar(255) NOT NULL,
            license_tier varchar(50) DEFAULT 'standard' NOT NULL,
            allowed_activations int(11) DEFAULT 1 NOT NULL,
            customer_email varchar(105) NOT NULL,
            activated_urls text DEFAULT NULL,
            status varchar(20) DEFAULT 'active' NOT NULL,
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY license_key (license_key),
            KEY product_item_name (product_item_name)
        ) &lt;/span&gt;&lt;span class="nv"&gt;$charset_collate&lt;/span&gt;&lt;span class="s2"&gt;;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="no"&gt;ABSPATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'wp-admin/includes/upgrade.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;dbDelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sql_licenses&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the &lt;code&gt;insert_new_license&lt;/code&gt; method that populates this table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// ... (part of LicenseHubDataManager class)&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Stores new license data from webhook payload.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;insert_new_license&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$license_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. Prevent Duplicates: Check if license key already exists&lt;/span&gt;
        &lt;span class="nv"&gt;$exists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;"SELECT COUNT(*) FROM &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;table_licenses&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; WHERE license_key = %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$license_key&lt;/span&gt;
        &lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// License key already exists, skip insert.&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Insert with dynamic fields from the webhook payload&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;table_licenses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'license_key'&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$license_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'customer_email'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user_email'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="s1"&gt;'product_item_name'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'variant_name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'My Awesome Plugin'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'license_tier'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status_formatted'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'Standard'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'allowed_activations'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;absint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'activation_limit'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'status'&lt;/span&gt;              &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'created_at'&lt;/span&gt;          &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;current_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'mysql'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This process ensures that every license key generated by the payment platform is securely recorded in your database, along with crucial details like the customer's email, the product name, the number of allowed activations, and the current status. This forms the authoritative source for all license information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: The Client-Side Implementation - Activating Your WordPress Plugin
&lt;/h2&gt;

&lt;p&gt;Now, let's shift our focus to your actual WordPress plugin. This is the part installed on your user's website, where they enter their license key, and the plugin initiates the validation process with your License Management Hub.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking License Status with &lt;code&gt;PluginLicenseClient&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;PluginLicenseClient&lt;/code&gt; class within your plugin is responsible for managing the local license state, communicating with the remote server, and caching validation results to optimize performance and reduce server load.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PluginLicenseClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... private properties like $api_secret_token, $api_endpoint, $license_option_key, $status_option_key, $cache_transient_key, $plugin_slug&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$api_secret_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'YOUR_SHARED_SECRET_FOR_CLIENT_SERVER'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Must match server-side&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$api_endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://your-license-hub.com/wp-json/my-licensing-api/v1/validate'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$license_option_key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$status_option_key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$cache_transient_key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$plugin_slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$product_identifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'my-awesome-plugin-slug'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Unique ID for your plugin&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$plugin_slug&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$plugin_slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;license_option_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'_license_key'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status_option_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'_license_status'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cache_transient_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'_license_cache'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Check if the plugin license is currently active.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;is_license_active&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$cached_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_transient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cache_transient_key&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// If no cached status, perform remote validation&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$cached_status&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$license_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;license_option_key&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$license_key&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// No license key entered locally&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nv"&gt;$cached_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send_validation_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$license_key&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Cache the result for a day if active, otherwise clear cache&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$cached_status&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;set_transient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cache_transient_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$cached_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DAY_IN_SECONDS&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;delete_transient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cache_transient_key&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$cached_status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Sends a request to the remote License Management Hub for validation.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;send_validation_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$license_key&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_remote_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;api_endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'timeout'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'user-agent'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/1.0.0 ('&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;home_url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;')'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'headers'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s1"&gt;'X-Auth-Token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;api_secret_token&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Custom auth header&lt;/span&gt;
                    &lt;span class="s1"&gt;'Accept'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'body'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s1"&gt;'license_key'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$license_key&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="s1"&gt;'product_identifier'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;product_identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'site_url'&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;home_url&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;is_wp_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"License validation API error: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_error_message&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;wp_remote_retrieve_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="s1"&gt;'valid'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;update_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status_option_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;update_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status_option_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'inactive'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'inactive'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... other methods like display_admin_notice, deactivate_local_license&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;is_license_active()&lt;/code&gt; is called, it first checks a WordPress transient (e.g., &lt;code&gt;myplugin_license_cache&lt;/code&gt;). If no cached status is found, it proceeds to &lt;code&gt;send_validation_request()&lt;/code&gt;. This method performs an HTTP POST request to your License Management Hub's &lt;code&gt;/my-licensing-api/v1/validate&lt;/code&gt; endpoint. It sends the &lt;code&gt;license_key&lt;/code&gt;, a &lt;code&gt;product_identifier&lt;/code&gt; (a unique identifier for your plugin), and the &lt;code&gt;site_url&lt;/code&gt; of the client site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; The &lt;code&gt;X-Auth-Token&lt;/code&gt; header contains a shared secret, &lt;code&gt;$api_secret_token&lt;/code&gt;, which acts as an authorization key for the API request. This token is crucial for securing your validation endpoint and preventing unauthorized calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: The Server-Side Validation API - The Heart of WordPress Plugin License Activation
&lt;/h2&gt;

&lt;p&gt;Back on the License Management Hub, the &lt;code&gt;LicenseHubAPIHandler&lt;/code&gt; class handles the incoming validation requests from client plugins. This is the core of the &lt;strong&gt;WordPress Plugin License Activation&lt;/strong&gt; process, where the legitimacy of a license and its domain usage are confirmed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Validation Requests with &lt;code&gt;LicenseHubAPIHandler&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;LicenseHubAPIHandler&lt;/code&gt; class registers the &lt;code&gt;/my-licensing-api/v1/validate&lt;/code&gt; endpoint and defines how to process requests to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LicenseHubAPIHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$plugin_base_slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$data_manager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// This secret must match the client-side token for API authentication&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$api_shared_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'YOUR_SHARED_SECRET_FOR_CLIENT_SERVER'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$plugin_base_slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_base_slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$plugin_base_slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data_manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LicenseHubDataManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_base_slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register_api_routes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;register_rest_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my-licensing-api/v1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/validate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'methods'&lt;/span&gt;             &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'callback'&lt;/span&gt;            &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'handle_validation_request'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'permission_callback'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'authenticate_api_request'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Custom permission callback&lt;/span&gt;
            &lt;span class="s1"&gt;'args'&lt;/span&gt;                &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'license_key'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'sanitize_callback'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'sanitize_text_field'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'product_identifier'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'sanitize_callback'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'sanitize_text_field'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'site_url'&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'sanitize_callback'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'esc_url_raw'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Authenticates API requests using a shared token.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;authenticate_api_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$auth_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'X-Auth-Token'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;hash_equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$auth_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;api_shared_secret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Handles the incoming license validation request.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle_validation_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$license_key&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'license_key'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nv"&gt;$product_identifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product_identifier'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nv"&gt;$site_url&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;esc_url_raw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'site_url'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product_identifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$site_url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WP_Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'missing_params'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Required fields are missing.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-license-hub'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$license_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data_manager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_license_details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$product_identifier&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Check if license exists and is in an 'active' or 'inactive' (but not expired/revoked) state&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;in_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;strtolower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'inactive'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WP_REST_Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'status'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'invalid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'License is invalid or does not exist.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-license-hub'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Perform domain validation and activation logic&lt;/span&gt;
        &lt;span class="nv"&gt;$is_domain_allowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data_manager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate_and_update_domain_activation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$site_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$is_domain_allowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WP_REST_Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'status'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'invalid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Domain activation limit reached for this license.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-license-hub'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;rest_ensure_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'status'&lt;/span&gt;               &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'valid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'license_tier'&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;license_tier&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'allowed_activations'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;allowed_activations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'active_domain_count'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data_manager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_activated_domain_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'created_at'&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;mysql_to_rfc3339&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'validation_checksum'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;wp_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_key&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$site_url&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;api_shared_secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Simple checksum for client verification&lt;/span&gt;
        &lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  License and Domain Validation Logic
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;handle_validation_request&lt;/code&gt; method performs several critical checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;API Authentication&lt;/strong&gt;: &lt;code&gt;authenticate_api_request&lt;/code&gt; verifies the &lt;code&gt;X-Auth-Token&lt;/code&gt; header to ensure the request is authorized.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Parameter Validation&lt;/strong&gt;: Ensures all required data (&lt;code&gt;license_key&lt;/code&gt;, &lt;code&gt;product_identifier&lt;/code&gt;, &lt;code&gt;site_url&lt;/code&gt;) is present.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;License Existence&lt;/strong&gt;: &lt;code&gt;get_license_details&lt;/code&gt; fetches the license from the database.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Domain Activation&lt;/strong&gt;: The &lt;code&gt;validate_and_update_domain_activation&lt;/code&gt; method is the core logic for managing domain limits.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// ... (part of LicenseHubDataManager class)&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Validates a license key against a domain and manages activation limits.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;validate_and_update_domain_activation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$current_domain_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Ensure license is found and is in an 'active' or 'inactive' state (not revoked/expired)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;in_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;strtolower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'inactive'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$activated_urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;activated_urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;activated_urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$activated_urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'trim'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$activated_urls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. If the current domain is already activated, it's valid.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;in_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$current_domain_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$activated_urls&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Check if there's room to add this new domain activation&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$activated_urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;allowed_activations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$activated_urls&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$current_domain_url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nv"&gt;$new_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;implode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;array_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$activated_urls&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Filter empty entries&lt;/span&gt;

            &lt;span class="c1"&gt;// Update the license record with the new activated domain&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;table_licenses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s1"&gt;'activated_urls'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$new_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'status'&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt; &lt;span class="c1"&gt;// Ensure status is 'active' once a domain is linked&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// If domain not found and limit reached&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Helper to count currently activated domains for a license.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_activated_domain_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;activated_urls&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nv"&gt;$domains&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$license_data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;activated_urls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'trim'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$domains&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;validate_and_update_domain_activation&lt;/code&gt; method is pivotal. It first checks if the domain requesting validation is &lt;em&gt;already&lt;/em&gt; activated under this license. If not, it then checks if there's available capacity to activate a new domain based on &lt;code&gt;allowed_activations&lt;/code&gt;. If successful, it updates the &lt;code&gt;activated_urls&lt;/code&gt; list in the database and ensures the license status is &lt;code&gt;active&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Complete WordPress Plugin License Activation Flow Summarized
&lt;/h2&gt;

&lt;p&gt;Let's put all the pieces together and summarize the entire &lt;strong&gt;WordPress Plugin License Activation&lt;/strong&gt; process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Purchase&lt;/strong&gt;: A customer buys your premium plugin through your chosen payment platform (e.g., Lemon Squeezy).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Webhook Trigger&lt;/strong&gt;: The payment platform sends a &lt;code&gt;license_key_created&lt;/code&gt; webhook to your License Management Hub.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Hub Receives Webhook&lt;/strong&gt;: &lt;code&gt;LicenseHubWebhookReceiver::handle_incoming_webhook&lt;/code&gt; receives the webhook, securely verifies its signature, and parses the license data.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;License Stored&lt;/strong&gt;: &lt;code&gt;LicenseHubDataManager::insert_new_license&lt;/code&gt; stores the new license key, allowed activations, customer email, and other details in your hub's database.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Client Plugin Activation&lt;/strong&gt;: The user installs your plugin, enters their license key in their WordPress dashboard, and clicks "Activate."&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Client Initiates Validation&lt;/strong&gt;: Your plugin's &lt;code&gt;PluginLicenseClient::send_validation_request&lt;/code&gt; sends an HTTP POST request to your License Management Hub's API. This request includes the license key, your plugin's unique ID, and the client site's URL.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Hub Validates Request&lt;/strong&gt;: &lt;code&gt;LicenseHubAPIHandler::handle_validation_request&lt;/code&gt; receives the request, authenticates it using the &lt;code&gt;X-Auth-Token&lt;/code&gt; header, retrieves the license data from its database, and calls &lt;code&gt;LicenseHubDataManager::validate_and_update_domain_activation&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Domain Check &amp;amp; Activation&lt;/strong&gt;: &lt;code&gt;validate_and_update_domain_activation&lt;/code&gt; checks if the domain is already activated or if it can be added within the &lt;code&gt;allowed_activations&lt;/code&gt; limit. If a new domain is activated, the database record is updated.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Hub Responds&lt;/strong&gt;: The API sends a &lt;code&gt;valid&lt;/code&gt; or &lt;code&gt;invalid&lt;/code&gt; status back to the client plugin, along with other license details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client Updates Status&lt;/strong&gt;: Your plugin receives the response, updates its local license status (e.g., in &lt;code&gt;wp_options&lt;/code&gt;), and caches the result using transients (e.g., for 24 hours) to minimize future API calls.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This robust, complementary flow ensures that your licenses are securely managed, domain limits are enforced, and your plugin's premium functionality is exclusively available to legitimate users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways for Robust WordPress Plugin License Activation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Security is Paramount&lt;/strong&gt;: Always verify webhook signatures and API tokens to prevent fraudulent license activations and unauthorized access.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Centralized License Management&lt;/strong&gt;: A dedicated License Management Hub simplifies tracking, updating, and revoking licenses across all your users.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Database as the Source of Truth&lt;/strong&gt;: A well-structured license table (including &lt;code&gt;license_key&lt;/code&gt;, &lt;code&gt;allowed_activations&lt;/code&gt;, &lt;code&gt;activated_urls&lt;/code&gt;, and &lt;code&gt;status&lt;/code&gt;) is fundamental for &lt;strong&gt;WordPress Plugin License Activation&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Client-Server Synergy&lt;/strong&gt;: The client plugin and server API must work hand-in-hand, with client-side caching to reduce the load on your License Management Hub.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Clear Separation of Concerns&lt;/strong&gt;: Distinct logic for license creation (via webhooks) and license validation (via API requests) makes the system maintainable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Implementing a system like this provides a solid and secure foundation for selling and managing your WordPress plugins, giving you control and ensuring your users have a smooth, reliable activation experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are your thoughts?
&lt;/h2&gt;

&lt;p&gt;Have you built similar license management systems for your WordPress plugins? What specific challenges did you encounter, and what innovative solutions did you implement? Share your experiences and insights in the comments below! If you found this in-depth analysis of &lt;strong&gt;WordPress Plugin License Activation&lt;/strong&gt; helpful, consider following me for more technical content on plugin development and best practices.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>plugindevelopment</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Beyond Basic Chatbots: Revolutionizing WordPress with an Advanced AI Plugin for 24/7 Sales &amp; Support</title>
      <dc:creator>Shahibur Rahman</dc:creator>
      <pubDate>Tue, 31 Mar 2026 16:43:02 +0000</pubDate>
      <link>https://forem.com/shahibur_rahman_6670cd024/beyond-basic-chatbots-revolutionizing-wordpress-with-an-advanced-ai-plugin-for-247-sales-support-4h5b</link>
      <guid>https://forem.com/shahibur_rahman_6670cd024/beyond-basic-chatbots-revolutionizing-wordpress-with-an-advanced-ai-plugin-for-247-sales-support-4h5b</guid>
      <description>&lt;p&gt;In today's fast-paced digital landscape, businesses running on WordPress and WooCommerce face a constant challenge: how to provide exceptional 24/7 customer support and drive sales without overwhelming their teams or budget. Traditional chatbots often fall short, offering canned responses that frustrate users. But what if your website could host an intelligent, always-on digital employee capable of understanding complex queries, recommending products, and even assisting with purchases? This is where a sophisticated &lt;strong&gt;WordPress AI plugin&lt;/strong&gt; steps in, transforming your site from a static presence into a dynamic, revenue-generating powerhouse.&lt;/p&gt;

&lt;p&gt;The era of passive websites is over. Customers expect instant gratification and personalized experiences. Missing a sales opportunity because support isn't available after hours, or losing a lead due to slow response times, is no longer an option. Let's delve into how an advanced AI solution can solve these critical pain points for your WordPress ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Era of 24/7 Customer Engagement with an AI WordPress Plugin
&lt;/h2&gt;

&lt;p&gt;Imagine your website visitors receiving personalized attention at any hour, in any language. A cutting-edge &lt;strong&gt;WordPress AI plugin&lt;/strong&gt; doesn't just answer questions; it actively engages, understands intent, and guides users through their journey. This constant availability means fewer abandoned carts, higher customer satisfaction, and a significant reduction in the workload for your human support staff.&lt;/p&gt;

&lt;p&gt;This isn't about replacing human interaction, but augmenting it. By handling routine inquiries and providing instant information, AI frees your team to focus on more complex issues and build deeper customer relationships. It's like having an expert sales assistant and support agent embedded directly into your site, working tirelessly behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building an Intelligent Knowledge Base: Smart Content Sync in Action
&lt;/h2&gt;

&lt;p&gt;The true power of an intelligent AI assistant lies in its knowledge. Unlike generic AI tools, a purpose-built WordPress solution can learn directly from your website's unique content. Features like &lt;strong&gt;Smart Content Sync&lt;/strong&gt; automatically scan your pages, products, FAQs, and even uploaded documents (PDFs, TXT, images) to build a comprehensive, private knowledge base.&lt;/p&gt;

&lt;p&gt;This means your AI agent is always up-to-date with your latest offerings, policies, and information, ensuring accurate and relevant responses. Regular updates via WP-Cron keep this knowledge base fresh, so your AI always speaks with authority and accuracy, reflecting your brand's current status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Empowering eCommerce: Advanced WooCommerce AI Commerce Capabilities
&lt;/h2&gt;

&lt;p&gt;For WooCommerce store owners, the benefits extend directly to the bottom line. An AI agent integrated deeply with your store can revolutionize the shopping experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Personalized Product Recommendations:&lt;/strong&gt; Based on conversation context and user behavior, the AI can suggest relevant products, increasing average order value.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Direct Add-to-Cart Actions:&lt;/strong&gt; Customers can add items to their cart directly from the chat interface, streamlining the purchase process.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Seamless Order Tracking:&lt;/strong&gt; Provide instant updates on order status, reducing common support queries and improving post-purchase satisfaction.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;24/7 Sales Assistance:&lt;/strong&gt; Guide visitors through product discovery, answer pre-sale questions, and overcome objections, leading to more conversions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This level of automated commerce assistance transforms casual browsers into confident buyers, making your store more efficient and profitable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Global Reach and Future-Proof Flexibility with Your WordPress AI Plugin
&lt;/h2&gt;

&lt;p&gt;In a global marketplace, language barriers can be a significant hurdle. An advanced &lt;strong&gt;WordPress AI plugin&lt;/strong&gt; overcomes this by offering support for &lt;strong&gt;40+ languages&lt;/strong&gt; natively. This means you can serve international customers without the need for additional translation plugins or multilingual staff, expanding your market reach effortlessly.&lt;/p&gt;

&lt;p&gt;Furthermore, the best AI solutions offer multi-model AI support, allowing you to choose your preferred AI provider – be it OpenAI, Google Gemini, or xAI (Grok). This flexibility ensures you can leverage the latest advancements in AI, optimize for cost or performance, and future-proof your investment against evolving technology trends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous Improvement and Actionable Insights
&lt;/h2&gt;

&lt;p&gt;An intelligent AI system isn't static; it learns and improves. Features like AI Feedback &amp;amp; Training Systems allow you to review conversations, correct responses, and continuously refine your AI agent's accuracy and performance. This iterative process ensures your digital employee becomes smarter and more effective over time.&lt;/p&gt;

&lt;p&gt;Beyond performance, gaining insights into visitor intent and common queries is invaluable. AI summaries and exportable conversation analytics provide data-driven intelligence, helping you understand your audience better and make informed business decisions to further optimize your site and offerings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started: Launching Your AI Sales &amp;amp; Support Agent
&lt;/h2&gt;

&lt;p&gt;Integrating an intelligent AI agent into your WordPress site is designed to be straightforward. Here’s a typical flow for deploying such a powerful &lt;strong&gt;WordPress AI plugin&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Install Plugin:&lt;/strong&gt; Upload and activate the plugin within your WordPress dashboard.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Sync Content &amp;amp; Store:&lt;/strong&gt; Leverage the Smart Content Sync feature to automatically import your pages, FAQs, and WooCommerce products. This builds the core knowledge base.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Configure AI Engine &amp;amp; Persona:&lt;/strong&gt; Connect your preferred AI model (OpenAI, Gemini, Grok) with your API keys and define your agent's persona, which can often be auto-generated from your site's content.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Go Live:&lt;/strong&gt; Your AI assistant immediately begins engaging visitors, answering questions, and assisting with sales.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This streamlined process enables you to quickly transform your website into a 24/7 sales and support powerhouse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways for Your Business
&lt;/h2&gt;

&lt;p&gt;The integration of a powerful AI sales and support engine represents a paradigm shift for WordPress and WooCommerce users. It's about moving beyond reactive customer service to proactive engagement, turning every website visit into an opportunity for connection and conversion.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;24/7 Engagement:&lt;/strong&gt; Provide instant, personalized support and sales assistance around the clock.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Intelligent Knowledge:&lt;/strong&gt; AI learns directly from your site's content, ensuring accurate responses.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Boost WooCommerce Sales:&lt;/strong&gt; Personalized recommendations, direct add-to-cart, and order tracking.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Global Reach:&lt;/strong&gt; Support for 40+ languages expands your market effortlessly.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Future-Proof:&lt;/strong&gt; Multi-model AI support allows flexibility and adaptability.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Continuous Learning:&lt;/strong&gt; AI improves over time with feedback and training.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Transform Your WordPress Site Today
&lt;/h2&gt;

&lt;p&gt;If you're ready to automate your customer engagement, drive revenue 24/7, and elevate your online presence, the time to explore this technology is now. We believe deeply in the transformative power of this approach and encourage you to see the benefits firsthand.&lt;/p&gt;

&lt;p&gt;⚡️ &lt;strong&gt;Action:&lt;/strong&gt; Discover how this innovative approach can benefit your WordPress site. Check out our 30% OFF PH-exclusive deal here: &lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://www.producthunt.com/products/intelliagent-ai?launch=intelliagent-ai" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;producthunt.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;For more details, visit our website: &lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://aica-intelliagent.com/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faica-intelliagent.com%2Fwp-content%2Fuploads%2F2025%2F04%2Fcropped-Logo-automataIQ-3-HD.png" height="167" class="m-0" width="499"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://aica-intelliagent.com/" rel="noopener noreferrer" class="c-link"&gt;
            IntelliAgent AI - Turn Your WordPress Site Into a 24/7 Sales &amp;amp; Support PowerhouseIntelliAgent AI - AI Sales &amp;amp; Support Agent for WordPress &amp;amp; WooCommerce
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Turn Your WordPress Site Into a 24/7 Sales &amp;amp; Support Powerhouse Transform your website into an intelligent assistant that answers questions, recommends products, manages FAQs, supports 40+ languages, and helps customers buy — automatically. Formerly AICA IntelliAgent Get IntelliAgent AI Now — One-Time Payment AI Support • AI Sales Assistant • Automated Knowledge Base •
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faica-intelliagent.com%2Fwp-content%2Fuploads%2F2026%2F02%2Frobot-avatar.svg" width="100" height="100"&gt;
          aica-intelliagent.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If this analysis resonated with you, please consider clapping and following for more insights into leveraging AI for your business!&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>ai</category>
      <category>ecommerce</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Deep Dive: Ensuring WordPress Plugin Quality with Plugin Check (PCP)</title>
      <dc:creator>Shahibur Rahman</dc:creator>
      <pubDate>Tue, 10 Mar 2026 20:28:28 +0000</pubDate>
      <link>https://forem.com/shahibur_rahman_6670cd024/deep-dive-ensuring-wordpress-plugin-quality-with-plugin-check-pcp-59e9</link>
      <guid>https://forem.com/shahibur_rahman_6670cd024/deep-dive-ensuring-wordpress-plugin-quality-with-plugin-check-pcp-59e9</guid>
      <description>&lt;p&gt;Developing robust WordPress plugins demands adherence to high standards. A powerful tool like Plugin Check (PCP) is instrumental in validating that your creations meet stringent &lt;strong&gt;WordPress plugin quality&lt;/strong&gt;, security, and WordPress.org guidelines. This article provides an in-depth analysis of PCP, an open-source solution designed for comprehensive plugin analysis, streamlining compliance and enhancing overall readiness for high-quality &lt;strong&gt;WordPress plugin development&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Comprehensive WordPress Plugin Quality Checks Matter
&lt;/h2&gt;

&lt;p&gt;Beyond mere functionality, high-quality WordPress plugins integrate best practices in areas such as internationalization, accessibility, performance, and security. Neglecting these aspects can lead to issues ranging from rejection by the WordPress.org directory to compromised user experience and significant security vulnerabilities. PCP serves as an analytical co-pilot, identifying potential problems early in the development cycle and guiding developers towards more compliant and robust solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Utilizing Plugin Check (PCP) for WordPress Plugin Quality Assurance
&lt;/h2&gt;

&lt;p&gt;PCP offers flexible integration methods, catering to different development workflows, from graphical interfaces to command-line automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  WP Admin User Interface
&lt;/h3&gt;

&lt;p&gt;For developers who prefer a graphical interface, PCP integrates directly into the WordPress admin area. After installation, navigate to &lt;code&gt;Tools &amp;gt; Plugin Check&lt;/code&gt;. This interface allows for intuitive analysis, presenting flagged issues in a categorized manner, which helps in systematically addressing concerns. Access to this screen requires appropriate user capabilities to manage plugins.&lt;/p&gt;

&lt;h3&gt;
  
  
  WP-CLI for Automated WordPress Plugin Checks
&lt;/h3&gt;

&lt;p&gt;For developers favoring command-line workflows and automated testing, WP-CLI integration provides a powerful mechanism. This method supports scriptable analysis, making it ideal for inclusion in continuous integration/continuous deployment (CI/CD) pipelines.&lt;/p&gt;

&lt;p&gt;To perform static checks on a plugin, use the &lt;code&gt;wp plugin check&lt;/code&gt; command followed by the main plugin file path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp plugin check your-plugin/your-plugin.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For runtime checks, which involve executing parts of your plugin's code within a WordPress environment, a specific &lt;code&gt;--require&lt;/code&gt; argument is necessary. This workaround ensures that PCP's CLI helper file is loaded before WordPress fully initializes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp plugin check your-plugin/your-plugin.php &lt;span class="nt"&gt;--require&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./wp-content/plugins/plugin-check/cli.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PCP also supports checking plugins from arbitrary paths or remote URLs, offering flexibility for various testing scenarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check a plugin from a local path&lt;/span&gt;
wp plugin check /path/to/your-plugin/plugin.php

&lt;span class="c"&gt;# Check a plugin from a remote ZIP URL&lt;/span&gt;
wp plugin check https://example.com/plugin.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding PCP's Issue Categories and Resolution for WordPress Plugin Quality
&lt;/h2&gt;

&lt;p&gt;PCP categorizes identified issues, providing structured feedback across critical development facets. This section explores common issue types and approaches to their resolution, crucial for achieving high &lt;strong&gt;WordPress plugin quality&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internationalization Issues
&lt;/h3&gt;

&lt;p&gt;PCP flags instances where text strings are not properly prepared for translation, ensuring your plugin can be localized for a global audience.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Example Issue&lt;/strong&gt;: A hardcoded string like &lt;code&gt;echo "Hello World!";&lt;/code&gt; without a translation function.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;PCP Flag&lt;/strong&gt;: "String not translatable."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Resolution&lt;/strong&gt;: Wrap all user-facing strings in &lt;code&gt;__()&lt;/code&gt; or &lt;code&gt;_e()&lt;/code&gt; functions, e.g.,
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;_e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Hello World!'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'your-text-domain'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Security Concerns
&lt;/h3&gt;

&lt;p&gt;The tool identifies potential security vulnerabilities, such as improper data sanitization, missing nonces, or inadequate capabilities checks, which are vital for a secure &lt;strong&gt;WordPress Plugin Check&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Example Issue&lt;/strong&gt;: Directly using &lt;code&gt;$_POST['data']&lt;/code&gt; without sanitization or validation.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;PCP Flag&lt;/strong&gt;: "Unsanitized input from &lt;code&gt;$_POST&lt;/code&gt; detected."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Resolution&lt;/strong&gt;: Always sanitize and validate user input. For example,
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and implement nonces for form submissions, e.g., &lt;code&gt;wp_verify_nonce()&lt;/code&gt; with &lt;code&gt;check_admin_referer()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Optimizations
&lt;/h3&gt;

&lt;p&gt;PCP can highlight code patterns that might impact plugin performance, such as inefficient database queries or excessive resource loading.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Example Issue&lt;/strong&gt;: Making a database query inside a loop without caching, e.g.,
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$items&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT * FROM ..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;PCP Flag&lt;/strong&gt;: "Potential performance bottleneck: repeated database query."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Resolution&lt;/strong&gt;: Optimize queries, use WordPress API functions like &lt;code&gt;get_posts()&lt;/code&gt; with appropriate arguments, implement object caching, or perform bulk operations where possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Accessibility Best Practices
&lt;/h3&gt;

&lt;p&gt;The tool assists in ensuring your plugin's interface and output are accessible to users with disabilities by checking for proper HTML semantics and attributes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Example Issue&lt;/strong&gt;: An &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag without an &lt;code&gt;alt&lt;/code&gt; attribute, e.g., &lt;code&gt;&amp;lt;img src="image.png"&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;PCP Flag&lt;/strong&gt;: "Image missing &lt;code&gt;alt&lt;/code&gt; attribute."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Resolution&lt;/strong&gt;: Provide descriptive &lt;code&gt;alt&lt;/code&gt; text for all images, e.g.,
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"image.png"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Description of the image for screen readers"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Plugin Namer Tool
&lt;/h2&gt;

&lt;p&gt;Beyond code quality, PCP includes a Plugin Namer tool, accessible via &lt;code&gt;Tools &amp;gt; Plugin Check Namer&lt;/code&gt;. This AI-powered feature helps developers evaluate potential plugin names against existing plugins, trademarks, and WordPress naming guidelines. It provides instant feedback and suggestions for choosing a unique and compliant name, though it's important to remember that final approval always rests with the WordPress.org Plugins team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways for Enhancing WordPress Plugin Quality
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  Plugin Check (PCP) is an open-source tool for validating WordPress plugin compliance and best practices.&lt;/li&gt;
&lt;li&gt;  It supports both WP Admin UI and WP-CLI for flexible integration into various development workflows.&lt;/li&gt;
&lt;li&gt;  PCP identifies issues across critical categories like internationalization, security, performance, and accessibility.&lt;/li&gt;
&lt;li&gt;  WP-CLI allows for automated static and runtime checks, which are crucial for CI/CD pipelines.&lt;/li&gt;
&lt;li&gt;  The Plugin Namer tool aids in selecting unique and compliant plugin names, complementing technical code checks.&lt;/li&gt;
&lt;li&gt;  Regular use of PCP can significantly improve overall &lt;strong&gt;WordPress plugin quality&lt;/strong&gt; and readiness for the WordPress.org repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Integrating automated quality checks like PCP into your WordPress plugin development process can save significant time and effort, leading to more robust and compliant solutions. What are your experiences with automated plugin quality tools? Share your insights and best practices in the comments below! Follow me for more in-depth analyses on WordPress development.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>plugins</category>
      <category>codequality</category>
      <category>development</category>
    </item>
    <item>
      <title>Mastering WordPress Internationalization (i18n): A Beginner's Guide to Global Plugins</title>
      <dc:creator>Shahibur Rahman</dc:creator>
      <pubDate>Fri, 27 Feb 2026 17:28:51 +0000</pubDate>
      <link>https://forem.com/shahibur_rahman_6670cd024/mastering-wordpress-internationalization-i18n-a-beginners-guide-to-global-plugins-59dg</link>
      <guid>https://forem.com/shahibur_rahman_6670cd024/mastering-wordpress-internationalization-i18n-a-beginners-guide-to-global-plugins-59dg</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4ppkqjsowry7e8gptwq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4ppkqjsowry7e8gptwq.jpg" alt=" " width="800" height="436"&gt;&lt;/a&gt;Developing a successful WordPress plugin means reaching a global audience. The key to unlocking this reach is &lt;strong&gt;WordPress Internationalization (i18n)&lt;/strong&gt;. By making your plugin translatable, you allow it to adapt to different languages and locales, directly connecting with users worldwide and even tapping into WordPress.org's automatic translation system supporting numerous languages.&lt;/p&gt;

&lt;p&gt;This guide will walk you through the essential steps and best practices for properly internationalizing your WordPress plugin, ensuring it's ready for a global audience and compliant with WordPress.org standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why WordPress Internationalization is Crucial for Your Plugin
&lt;/h2&gt;

&lt;p&gt;Imagine a user in a non-English speaking country trying to use your plugin, but all the labels, buttons, and messages are in English. They'll likely abandon it. Internationalization isn't just a nice-to-have; it's a fundamental aspect of user experience and market reach. Here's why &lt;strong&gt;WordPress i18n&lt;/strong&gt; is so important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Broader Audience:&lt;/strong&gt; Your plugin becomes accessible to non-English speakers, significantly expanding your potential user base.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;WordPress.org Compatibility:&lt;/strong&gt; Proper i18n is a strict requirement for plugins hosted on WordPress.org, enabling automatic language pack downloads.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Community Contributions:&lt;/strong&gt; It empowers volunteers to translate your plugin into their native languages through platforms like GlotPress.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Professionalism:&lt;/strong&gt; A translatable plugin reflects a higher standard of development and attention to user needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's dive into the core steps to achieve proper &lt;strong&gt;WordPress i18n&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Plugin Header: Your i18n Blueprint
&lt;/h2&gt;

&lt;p&gt;Every WordPress plugin starts with a header. For &lt;strong&gt;WordPress Internationalization&lt;/strong&gt;, two fields are critical: &lt;code&gt;Text Domain&lt;/code&gt; and &lt;code&gt;Domain Path&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Text Domain&lt;/code&gt;&lt;/strong&gt;: This is a unique identifier for all translatable strings in your plugin. It &lt;strong&gt;must match your plugin's slug&lt;/strong&gt; (the folder name of your plugin).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Domain Path&lt;/code&gt;&lt;/strong&gt;: This tells WordPress where to find your translation files. It's almost always &lt;code&gt;/languages&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Plugin Name: My Awesome Plugin
 * Plugin URI:  https://myawesomeplugin.com/
 * Description: A simple plugin to demonstrate WordPress i18n.
 * Version:     1.0.0
 * Author:      Your Name
 * Author URI:  https://yourwebsite.com/
 * License:     GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain: my-awesome-plugin
 * Domain Path: /languages
 */&lt;/span&gt;

&lt;span class="c1"&gt;// ... rest of your plugin code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaway:&lt;/strong&gt; Your &lt;code&gt;Text Domain&lt;/code&gt; (e.g., &lt;code&gt;my-awesome-plugin&lt;/code&gt;) must be consistent across your entire plugin, both in the header and in every translation function call.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Standard Language Folder Structure
&lt;/h2&gt;

&lt;p&gt;WordPress expects your translation files to reside in a specific location within your plugin's directory. Create a folder named &lt;code&gt;languages&lt;/code&gt; at the root of your plugin.&lt;/p&gt;

&lt;p&gt;Your plugin structure should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-awesome-plugin/
│
├── my-awesome-plugin.php
├── languages/
│   ├── my-awesome-plugin.pot
│   ├── my-awesome-plugin-en_US.mo (Optional: Default language)
│   └── ... (Other bundled .mo files, if any)
└── ... (other plugin files and folders)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Naming Rule:&lt;/strong&gt; Translation files follow the pattern &lt;code&gt;{text-domain}-{locale}.mo&lt;/code&gt; (e.g., &lt;code&gt;my-awesome-plugin-fr_FR.mo&lt;/code&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; You only need to ship the &lt;code&gt;.pot&lt;/code&gt; file and optionally your default language's &lt;code&gt;.mo&lt;/code&gt; file. WordPress.org handles downloading other language packs automatically into &lt;code&gt;wp-content/languages/plugins/&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Loading Translations the WordPress Standard Way
&lt;/h2&gt;

&lt;p&gt;The timing of loading your plugin's text domain is crucial. It should happen early enough in the WordPress lifecycle for all strings to be available, but not too early. The &lt;code&gt;init&lt;/code&gt; hook is the recommended place.&lt;/p&gt;

&lt;p&gt;First, in your main plugin file (e.g., &lt;code&gt;my-awesome-plugin.php&lt;/code&gt;), define a constant for the main plugin file path. This is crucial for &lt;code&gt;plugin_basename()&lt;/code&gt; to work reliably:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// my-awesome-plugin.php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MY_AWESOME_PLUGIN_FILE'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MY_AWESOME_PLUGIN_FILE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ... rest of your plugin code, including your Bootstrap and Main classes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, in your main plugin class (e.g., &lt;code&gt;MyAwesomePlugin_Main&lt;/code&gt;), add a &lt;code&gt;load_textdomain&lt;/code&gt; method and register it to the &lt;code&gt;init&lt;/code&gt; hook via your loader class. This ensures proper &lt;strong&gt;WordPress i18n&lt;/strong&gt; initialization.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// includes/class-main.php (or wherever your main plugin class is)&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAwesomePlugin_Main&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$loader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$plugin_slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ... other properties ...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$plugin_slug&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$plugin_slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MyAwesomePlugin_Loader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Assuming you have a loader class&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;define_hooks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;define_hooks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// WP Standard: Load textdomain on init hook via loader&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'load_textdomain'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// ... rest of your hooks (admin pages, public scripts, etc.) ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * WP Standard: Load the plugin text domain for internationalization.
     * This method first checks for global language packs and then falls back
     * to the plugin's bundled language files. This is key for global language support.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;load_textdomain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;determine_locale&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. Load translations from wp-content/languages/plugins/ (for WP.org language packs)&lt;/span&gt;
        &lt;span class="nf"&gt;load_textdomain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="no"&gt;WP_LANG_DIR&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/plugins/'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$locale&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'.mo'&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Fallback to plugin's bundled /languages/ directory&lt;/span&gt;
        &lt;span class="nf"&gt;load_plugin_textdomain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;plugin_basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="no"&gt;MY_AWESOME_PLUGIN_FILE&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/languages/'&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why this is the best practice:&lt;/strong&gt; This two-step loading process ensures WordPress prioritizes official language packs downloaded from WordPress.org, then falls back to any &lt;code&gt;.mo&lt;/code&gt; files you bundle with your plugin. Hooking it to &lt;code&gt;init&lt;/code&gt; means translations are loaded at the correct time in the WordPress lifecycle, after all plugins are loaded but before strings are used in the UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. Translating Strings in Your Code
&lt;/h2&gt;

&lt;p&gt;To make any text in your plugin translatable, you must wrap it in a WordPress i18n function and provide your plugin's text domain (e.g., &lt;code&gt;'my-awesome-plugin'&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Here are the most common functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;__()&lt;/code&gt;&lt;/strong&gt;: Returns a translated string.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;_e()&lt;/code&gt;&lt;/strong&gt;: Echos a translated string directly.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;esc_html__()&lt;/code&gt;&lt;/strong&gt;: Returns a translated string, escaped for HTML output.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;esc_html_e()&lt;/code&gt;&lt;/strong&gt;: Echos a translated string, escaped for HTML output.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;esc_attr__()&lt;/code&gt;&lt;/strong&gt;: Returns a translated string, escaped for HTML attribute output.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;printf()&lt;/code&gt;&lt;/strong&gt;: For strings with dynamic content (placeholders like &lt;code&gt;%s&lt;/code&gt;, &lt;code&gt;%d&lt;/code&gt;).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// Example in an Admin menu&lt;/span&gt;
&lt;span class="nf"&gt;add_submenu_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Dashboard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-awesome-plugin'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Translatable string for the title&lt;/span&gt;
    &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Dashboard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-awesome-plugin'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Translatable string for the menu item&lt;/span&gt;
    &lt;span class="s1"&gt;'manage_options'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'display_admin_dashboard'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Example for plain text output&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;p&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;esc_html__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Welcome to My Awesome Plugin.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-awesome-plugin'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/p&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Example with placeholders and translator comments (CRITICAL for WP-CLI warnings)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$last_sync&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* translators: %s = human readable time (e.g. 5 minutes) */&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;esc_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Last backup completed %s ago'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-awesome-plugin'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;human_time_diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$last_sync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;wp_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'U'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;esc_html_e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'No background sync recorded yet'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-awesome-plugin'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Translator Comments (&lt;code&gt;/* translators: ... */&lt;/code&gt;):&lt;/strong&gt; Always add these comments directly above strings that contain placeholders (&lt;code&gt;%s&lt;/code&gt;, &lt;code&gt;%d&lt;/code&gt;, &lt;code&gt;%1$s&lt;/code&gt;, etc.). They provide context to translators, helping them understand what the placeholders represent and ensuring accurate translation, even if the word order changes in another language. This is vital for quality &lt;strong&gt;WordPress plugin translation&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  5. Generating the &lt;code&gt;.pot&lt;/code&gt; File
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.pot&lt;/code&gt; (Portable Object Template) file is a plain text file that contains all the translatable strings from your plugin. Translators use this file as a template to create language-specific &lt;code&gt;.po&lt;/code&gt; (Portable Object) files, which are then compiled into machine-readable &lt;code&gt;.mo&lt;/code&gt; (Machine Object) files that WordPress uses.&lt;/p&gt;

&lt;p&gt;You don't write this file manually; you generate it using tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A: Using WP-CLI (Recommended for Developers)
&lt;/h3&gt;

&lt;p&gt;If you have WP-CLI installed, navigate to your plugin's root directory in your terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp i18n make-pot &lt;span class="nb"&gt;.&lt;/span&gt; languages/my-awesome-plugin.pot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command scans all PHP files in your current directory (&lt;code&gt;.&lt;/code&gt;) and extracts all strings wrapped in i18n functions, saving them to &lt;code&gt;languages/my-awesome-plugin.pot&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B: Using Poedit (GUI Tool)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Download and install &lt;a href="https://poedit.net" rel="noopener noreferrer"&gt;Poedit&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; Open Poedit, go to &lt;code&gt;File&lt;/code&gt; &amp;gt; &lt;code&gt;New from POT/PO Template&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Select your plugin's root directory.&lt;/li&gt;
&lt;li&gt; Set the &lt;code&gt;Text domain&lt;/code&gt; to &lt;code&gt;my-awesome-plugin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Poedit will scan your files and list all translatable strings. Save the generated file as &lt;code&gt;languages/my-awesome-plugin.pot&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After successfully generating your &lt;code&gt;.pot&lt;/code&gt; file, you should see a &lt;code&gt;Success: POT file successfully generated.&lt;/code&gt; message in your terminal (if using WP-CLI) and the file will be in your &lt;code&gt;languages&lt;/code&gt; folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways for WordPress Internationalization
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Consistent Text Domain:&lt;/strong&gt; Ensure your &lt;code&gt;Text Domain&lt;/code&gt; in the plugin header matches &lt;strong&gt;exactly&lt;/strong&gt; the text domain used in all &lt;code&gt;__()&lt;/code&gt;, &lt;code&gt;_e()&lt;/code&gt;, &lt;code&gt;esc_html__()&lt;/code&gt;, etc., functions throughout your code.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;Domain Path: /languages&lt;/code&gt;:&lt;/strong&gt; Always set this in your plugin header.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Load on &lt;code&gt;init&lt;/code&gt; Hook:&lt;/strong&gt; Use &lt;code&gt;load_plugin_textdomain()&lt;/code&gt; or &lt;code&gt;load_textdomain()&lt;/code&gt; within a method hooked to &lt;code&gt;init&lt;/code&gt; for proper loading timing.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Prioritize Global Language Packs:&lt;/strong&gt; Implement the two-step &lt;code&gt;load_textdomain&lt;/code&gt; approach (loading from &lt;code&gt;WP_LANG_DIR&lt;/code&gt; first) to support WordPress.org's automatic language downloads and &lt;strong&gt;WordPress language support&lt;/strong&gt; for a global audience.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Wrap All User-Facing Strings:&lt;/strong&gt; Every piece of text visible to the user must be wrapped in an i18n function.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Escape Output:&lt;/strong&gt; Use &lt;code&gt;esc_html__()&lt;/code&gt;, &lt;code&gt;esc_html_e()&lt;/code&gt;, &lt;code&gt;esc_attr__()&lt;/code&gt; where appropriate to prevent security vulnerabilities.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Translator Comments for Placeholders:&lt;/strong&gt; Add &lt;code&gt;/* translators: ... */&lt;/code&gt; comments above strings with &lt;code&gt;%s&lt;/code&gt;, &lt;code&gt;%d&lt;/code&gt;, etc., to provide context for translators.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Ship Only &lt;code&gt;.pot&lt;/code&gt;:&lt;/strong&gt; For WordPress.org plugins, only include &lt;code&gt;languages/your-plugin.pot&lt;/code&gt; in your submission. WordPress handles the rest!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By following these steps, your WordPress plugin will be fully equipped to serve users in numerous languages, significantly boosting its reach and user satisfaction. Happy translating!&lt;/p&gt;

&lt;h2&gt;
  
  
  What are your thoughts or questions on WordPress i18n? Share your experiences in the comments below!
&lt;/h2&gt;

</description>
      <category>wordpress</category>
      <category>i18n</category>
      <category>php</category>
      <category>plugin</category>
    </item>
    <item>
      <title>Mastering WordPress Plugin Best Practices: Security, i18n, and Performance for Beginners</title>
      <dc:creator>Shahibur Rahman</dc:creator>
      <pubDate>Tue, 20 Jan 2026 20:02:07 +0000</pubDate>
      <link>https://forem.com/shahibur_rahman_6670cd024/mastering-wordpress-plugin-best-practices-security-i18n-and-performance-for-beginners-3bc8</link>
      <guid>https://forem.com/shahibur_rahman_6670cd024/mastering-wordpress-plugin-best-practices-security-i18n-and-performance-for-beginners-3bc8</guid>
      <description>&lt;p&gt;Developing a WordPress plugin can be a rewarding experience, and adhering to stringent &lt;strong&gt;WordPress Plugin Best Practices&lt;/strong&gt; from the outset is crucial for creating robust, secure, and widely usable software. This isn't just about functionality; it's about security, internationalization, performance, and overall code quality.&lt;/p&gt;

&lt;p&gt;For beginners looking to build high-quality plugins, understanding these core principles is essential. In this in-depth analysis, we'll break down the critical refinements needed to elevate your plugin from functional to truly professional, drawing directly from common development standards and crucial feedback points.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Robust WordPress Plugin Best Practices Matter
&lt;/h2&gt;

&lt;p&gt;Before diving into the 'how,' let's understand the 'why.' A plugin that meets high standards isn't just easier to maintain; it's more reliable, secure, and user-friendly. Adhering to these &lt;strong&gt;WordPress Plugin Best Practices&lt;/strong&gt; is crucial for protecting your users and ensuring your plugin is a valuable, sustainable asset for the entire WordPress ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internationalization (i18n): Speaking Every Language
&lt;/h2&gt;

&lt;p&gt;One of WordPress's greatest strengths is its global reach. Your plugin should be accessible to users worldwide, which means all text strings must be translatable. This is a fundamental &lt;strong&gt;WordPress Plugin Best Practice&lt;/strong&gt; for broad adoption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing Basic Translation
&lt;/h3&gt;

&lt;p&gt;Wrap all user-facing strings in &lt;code&gt;__()&lt;/code&gt; for echoing or &lt;code&gt;_e()&lt;/code&gt; for direct output. Remember to specify your unique text domain, often matching your plugin's slug (e.g., &lt;code&gt;'aica-plugin'&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// For strings that need to be returned&lt;/span&gt;
&lt;span class="nv"&gt;$text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Hello World'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica-plugin'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// For strings that need to be echoed directly&lt;/span&gt;
&lt;span class="nf"&gt;_e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Welcome to my plugin!'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica-plugin'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advanced Translation with Placeholders and HTML
&lt;/h3&gt;

&lt;p&gt;When you need to include dynamic data or HTML tags within a translatable string, &lt;code&gt;printf()&lt;/code&gt; is your best friend. It allows translators to reorder phrases if necessary, without breaking your HTML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cm"&gt;/* translators: 1: format list, 2: strong tag start, 3: strong tag end */&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;esc_html__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'JPG, PNG, or SVG, up to 1MB. Ideal size: %2$s535 x 535 pixels%3$s.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica-plugin'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'JPG, PNG, SVG'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'&amp;lt;strong&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'&amp;lt;/strong&amp;gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pluralization for Dynamic Messages
&lt;/h3&gt;

&lt;p&gt;For messages that change based on quantity (e.g., "1 minute ago" vs. "5 minutes ago"), use &lt;code&gt;_n()&lt;/code&gt; to handle pluralization correctly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nv"&gt;$count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;_n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'%s minute ago'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'%s minutes ago'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica-plugin'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;number_format_i18n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$count&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Localizing JavaScript Strings
&lt;/h3&gt;

&lt;p&gt;JavaScript strings also need to be translatable. &lt;code&gt;wp_localize_script()&lt;/code&gt; is the standard way to pass translated strings (and other dynamic data) from PHP to your JavaScript files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In your PHP file (e.g., during script enqueue)&lt;/span&gt;
&lt;span class="nf"&gt;wp_localize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'-admin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'aica_admin_data'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'ajax_url'&lt;/span&gt;            &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;admin_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin-ajax.php'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'nonce'&lt;/span&gt;               &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;wp_create_nonce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'aica_admin_nonce'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'i18n_visitor'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Visitor'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica-plugin'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'i18n_ai'&lt;/span&gt;             &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'AI Agent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica-plugin'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'i18n_deleting'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Deleting...'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica-plugin'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'i18n_saving'&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Saving...'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica-plugin'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'i18n_summary_title'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'AI Intelligence Summary'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica-plugin'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'i18n_confirm_delete'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Delete this conversation forever?'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica-plugin'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In your JavaScript file (e.g., admin.js)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aica_admin_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;i18n_deleting&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Security &amp;amp; Escaping: Protecting Against Malice
&lt;/h2&gt;

&lt;p&gt;Security is paramount in WordPress development. Every piece of data displayed to the user or processed from user input must be properly escaped and sanitized. This is a non-negotiable &lt;strong&gt;WordPress Plugin Best Practice&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Escaping Output: Don't Trust Anything!
&lt;/h3&gt;

&lt;p&gt;Always escape data before outputting it to the browser. This prevents Cross-Site Scripting (XSS) vulnerabilities.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;esc_html()&lt;/code&gt;: For general HTML content.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;esc_attr()&lt;/code&gt;: For HTML attribute values.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;esc_url()&lt;/code&gt;: For URLs.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;checked()&lt;/code&gt;: Helps securely mark radio buttons/checkboxes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When generating links or other HTML elements with dynamic PHP content, use functions like &lt;code&gt;add_query_arg&lt;/code&gt; for URL building and &lt;code&gt;esc_url&lt;/code&gt; for final output. Similarly, for HTML attributes like input values or checked states, &lt;code&gt;esc_attr&lt;/code&gt; and &lt;code&gt;checked&lt;/code&gt; are essential.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// Example: Building a secure URL with add_query_arg() and escaping it&lt;/span&gt;
&lt;span class="nv"&gt;$page_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;admin_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'admin.php?page=my-plugin'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$active_tab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Example value&lt;/span&gt;

&lt;span class="nv"&gt;$link_href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;esc_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;add_query_arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'tab'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$page_url&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$tab_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$active_tab&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'nav-tab-active'&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Translatable link text:&lt;/span&gt;
&lt;span class="nf"&gt;_e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Content'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica-plugin'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Example: Safely outputting an HTML attribute and checked state&lt;/span&gt;
&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'dark'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Example value&lt;/span&gt;
&lt;span class="nv"&gt;$current_theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'dark'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Example value&lt;/span&gt;

&lt;span class="c1"&gt;// The value attribute:&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;esc_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

&lt;span class="c1"&gt;// The 'checked' attribute conditionally:&lt;/span&gt;
&lt;span class="nf"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$current_theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sanitizing Input: Clean Data is Safe Data
&lt;/h3&gt;

&lt;p&gt;Before saving or using user-provided data, sanitize it to ensure it conforms to expected formats and doesn't contain malicious code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;sanitize_key()&lt;/code&gt;: For sanitizing keys/slugs.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;intval()&lt;/code&gt;: For ensuring integers.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;wp_strip_all_tags()&lt;/code&gt;: For stripping HTML from strings (e.g., email subjects).&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;wp_kses_post()&lt;/code&gt;: For sanitizing HTML content, allowing only safe tags (useful for rich text in emails).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// Example: Sanitizing a session ID&lt;/span&gt;
&lt;span class="nv"&gt;$session_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'session_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Example: Sanitizing an integer ID&lt;/span&gt;
&lt;span class="nv"&gt;$item_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;intval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'item_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Example: Sanitizing feedback before passing to an LLM&lt;/span&gt;
&lt;span class="nv"&gt;$user_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;esc_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user_query'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Use esc_html for display, or more robust sanitization for LLM input&lt;/span&gt;

&lt;span class="c1"&gt;// Example: Sanitizing email subject and HTML content&lt;/span&gt;
&lt;span class="nv"&gt;$subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_strip_all_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email_subject'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$summary_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_kses_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email_body'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Preventing XSS in JavaScript
&lt;/h3&gt;

&lt;p&gt;When dynamically inserting content into the DOM using JavaScript, be extremely careful. Direct use of &lt;code&gt;.html()&lt;/code&gt; or template literals without proper escaping can introduce XSS vulnerabilities. Always prefer &lt;code&gt;.text()&lt;/code&gt; for plain text or ensure content is sanitized on the server-side if it's expected to contain safe HTML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ DANGEROUS: Potential XSS vulnerability&lt;/span&gt;
&lt;span class="c1"&gt;// $('.chat-content').html(msg.content);&lt;/span&gt;
&lt;span class="c1"&gt;// const username = response.data;&lt;/span&gt;
&lt;span class="c1"&gt;// $('#user-display').text(`Welcome, ${username}!`);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ SAFER: Use .text() for plain text or sanitize before using .html()&lt;/span&gt;
&lt;span class="c1"&gt;// For displaying plain text&lt;/span&gt;
&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.user-message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// If msg.content is *expected* to contain safe HTML, it must be sanitized on the server-side&lt;/span&gt;
&lt;span class="c1"&gt;// before being passed to the client. If it's pure text, use .text()&lt;/span&gt;

&lt;span class="c1"&gt;// For dynamic user names, always use .text() or equivalent for plain text insertion&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aica_admin_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;i18n_visitor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Use localized string&lt;/span&gt;
&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#user-display&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Nonce Verification for AJAX Calls
&lt;/h3&gt;

&lt;p&gt;Always verify nonces for AJAX requests to prevent CSRF (Cross-Site Request Forgery) attacks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// In your AJAX handler&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'nonce'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nf"&gt;wp_verify_nonce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'nonce'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'aica_admin_nonce'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;wp_send_json_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Security check failed.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica-plugin'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Proceed with AJAX logic&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Database Interaction &amp;amp; SQL Security
&lt;/h2&gt;

&lt;p&gt;Interacting with the database is common, but it's also a prime target for SQL Injection. Adhering to &lt;code&gt;wpdb&lt;/code&gt; best practices is a must.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prepared Queries with &lt;code&gt;$wpdb-&amp;gt;prepare()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the golden rule for database security. Never concatenate user input directly into SQL queries. Always use &lt;code&gt;$wpdb-&amp;gt;prepare()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'my_custom_table'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$data_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;intval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'data_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Secure way to fetch data&lt;/span&gt;
&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;"SELECT * FROM %i WHERE id = %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$data_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Secure way to insert data&lt;/span&gt;
&lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'column1'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'input1'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'column2'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;intval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'input2'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'%d'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Format specifiers&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Indexing for Performance
&lt;/h3&gt;

&lt;p&gt;Proper indexing improves database query speeds. When creating tables, ensure that &lt;code&gt;VARCHAR(255)&lt;/code&gt; columns used for indexing are limited to &lt;code&gt;191&lt;/code&gt; characters to ensure compatibility with older MySQL/MariaDB versions using &lt;code&gt;UTF8mb4&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`wp_my_custom_table`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;`id`&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;`session_key`&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;191&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;`created_at`&lt;/span&gt; &lt;span class="nb"&gt;DATETIME&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`id`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="nv"&gt;`session_key_idx`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`session_key`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;CHARSET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4_unicode_520_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Existence Checks for Robustness
&lt;/h3&gt;

&lt;p&gt;Add &lt;code&gt;IF NOT EXISTS&lt;/code&gt; logic to your &lt;code&gt;CREATE TABLE&lt;/code&gt; statements to prevent errors if the table already exists, making your plugin more robust during updates or re-installations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'my_custom_table'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$charset_collate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_charset_collate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CREATE TABLE IF NOT EXISTS `&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$table_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;` (
    id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    session_key VARCHAR(191) NOT NULL,
    created_at DATETIME NOT NULL,
    PRIMARY KEY  (id),
    KEY session_key_idx (session_key)
) &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$charset_collate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;require_once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="no"&gt;ABSPATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'wp-admin/includes/upgrade.php'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;dbDelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$sql&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Accessibility &amp;amp; User Experience
&lt;/h2&gt;

&lt;p&gt;Good plugins consider all users. Accessibility features and a smooth user experience are critical &lt;strong&gt;WordPress Plugin Best Practices&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Alt Tags
&lt;/h3&gt;

&lt;p&gt;Always provide meaningful &lt;code&gt;alt&lt;/code&gt; attributes for images, especially in the admin area, to assist users with screen readers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// Example: Safely outputting an image's URL and translatable alt text&lt;/span&gt;
&lt;span class="nv"&gt;$avatar_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'path/to/avatar.png'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// For the 'src' attribute:&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;esc_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$avatar_url&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; 

&lt;span class="c1"&gt;// For the 'alt' attribute, which is also translatable:&lt;/span&gt;
&lt;span class="nf"&gt;esc_attr_e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'AI Agent Avatar'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica-plugin'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Localized Dates
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;date_i18n()&lt;/code&gt; instead of &lt;code&gt;date()&lt;/code&gt; to display dates and times according to the user's WordPress locale settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// Display current date in localized format&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;date_i18n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'date_format'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Display a specific timestamp in localized format&lt;/span&gt;
&lt;span class="nv"&gt;$timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;strtotime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'-5 minutes'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;date_i18n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'F j, Y, g:i a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$timestamp&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Robustness &amp;amp; Error Prevention
&lt;/h2&gt;

&lt;p&gt;Preventing fatal errors and ensuring your plugin degrades gracefully is a sign of a well-engineered product.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;class_exists&lt;/code&gt; for Sub-Models
&lt;/h3&gt;

&lt;p&gt;If your plugin relies on dynamically loaded or optional classes, use &lt;code&gt;class_exists()&lt;/code&gt; before instantiating them. This prevents fatal errors if a file is missing or a dependency isn't met.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;class_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'My_Grok_Model'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$grok_instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;My_Grok_Model&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Fallback or log error&lt;/span&gt;
    &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'My_Grok_Model class not found.'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Header Security
&lt;/h2&gt;

&lt;p&gt;When sending emails, ensure your headers are secure to prevent injection attacks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;aica_filter_from_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$from_email&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$site_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'blogname'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Strip angle brackets and double quotes from site name to prevent header injection&lt;/span&gt;
    &lt;span class="nv"&gt;$safe_site_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$site_name&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$safe_site_name&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' &amp;lt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$from_email&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'wp_mail_from_name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aica_filter_from_header'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Takeaways for Mastering WordPress Plugin Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Internationalization is mandatory&lt;/strong&gt;: Translate &lt;em&gt;all&lt;/em&gt; strings using &lt;code&gt;__&lt;/code&gt;, &lt;code&gt;_e&lt;/code&gt;, &lt;code&gt;_n&lt;/code&gt;, &lt;code&gt;printf&lt;/code&gt;, and &lt;code&gt;wp_localize_script&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Security first&lt;/strong&gt;: Escape &lt;em&gt;all&lt;/em&gt; output (&lt;code&gt;esc_html&lt;/code&gt;, &lt;code&gt;esc_attr&lt;/code&gt;, &lt;code&gt;esc_url&lt;/code&gt;) and sanitize &lt;em&gt;all&lt;/em&gt; input (&lt;code&gt;sanitize_key&lt;/code&gt;, &lt;code&gt;intval&lt;/code&gt;, &lt;code&gt;wp_strip_all_tags&lt;/code&gt;, &lt;code&gt;wp_kses_post&lt;/code&gt;). Use &lt;code&gt;add_query_arg&lt;/code&gt; for URL building and &lt;code&gt;wp_verify_nonce&lt;/code&gt; for AJAX.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;SQL Safety&lt;/strong&gt;: Always use &lt;code&gt;$wpdb-&amp;gt;prepare()&lt;/code&gt; for database queries to prevent SQL injection.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Performance&lt;/strong&gt;: Implement proper indexing (&lt;code&gt;VARCHAR(191)&lt;/code&gt;) and use &lt;code&gt;IF NOT EXISTS&lt;/code&gt; for robust table creation.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Accessibility&lt;/strong&gt;: Provide &lt;code&gt;alt&lt;/code&gt; tags for images and use &lt;code&gt;date_i18n()&lt;/code&gt; for localized dates.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Error Prevention&lt;/strong&gt;: Use &lt;code&gt;class_exists&lt;/code&gt; checks and secure email headers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By meticulously applying these &lt;strong&gt;WordPress Plugin Best Practices&lt;/strong&gt;, you'll create a superior plugin that is robust, secure, and user-friendly. These principles are the bedrock of professional WordPress development.&lt;/p&gt;

&lt;p&gt;What are your go-to best practices for WordPress plugin development? Share your insights and experiences in the comments below!&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>plugin</category>
      <category>php</category>
      <category>security</category>
    </item>
    <item>
      <title>Mastering LLM Integrations in WordPress: A Plugin Boilerplate Deep Dive</title>
      <dc:creator>Shahibur Rahman</dc:creator>
      <pubDate>Sun, 18 Jan 2026 09:18:00 +0000</pubDate>
      <link>https://forem.com/shahibur_rahman_6670cd024/mastering-llm-integrations-in-wordpress-a-plugin-boilerplate-deep-dive-4l39</link>
      <guid>https://forem.com/shahibur_rahman_6670cd024/mastering-llm-integrations-in-wordpress-a-plugin-boilerplate-deep-dive-4l39</guid>
      <description>&lt;p&gt;&lt;strong&gt;LLM integrations&lt;/strong&gt; are rapidly transforming how developers build intelligent applications. Integrating Large Language Models (LLMs) like OpenAI, Gemini, and Grok into a WordPress plugin can unlock powerful AI capabilities directly within your website. This article will guide you through a robust WordPress plugin boilerplate, detailing the architecture and core classes responsible for these advanced &lt;strong&gt;LLM integrations in WordPress&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We'll explore a well-structured plugin designed for extensibility and maintainability, leveraging a popular WordPress plugin development structure to provide a solid foundation. You'll understand how to connect to different LLM APIs, handle multimodal inputs (text, images, documents), and manage conversation context effectively.&lt;/p&gt;

&lt;p&gt;Here's the GitHub repository for the complete code:&lt;br&gt;


&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/d5b94396feba3" rel="noopener noreferrer"&gt;
        d5b94396feba3
      &lt;/a&gt; / &lt;a href="https://github.com/d5b94396feba3/llms-apis-wp-boilerplate-plugin" rel="noopener noreferrer"&gt;
        llms-apis-wp-boilerplate-plugin
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A collection of LLM (Large Language Model) API integration classes for WordPress plugins. Provides unified integration with multiple LLM providers including OpenAI, Google Gemini, and xAI Grok.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LLM APIs WordPress Plugin Component&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A collection of LLM (Large Language Model) API integration classes for WordPress plugins. Provides unified integration with multiple LLM providers including OpenAI, Google Gemini, and xAI Grok. Designed to be integrated into WordPress plugins that follow the &lt;a href="https://github.com/d5b94396feba3/WP-Plugin-Boilerplate" rel="noopener noreferrer"&gt;WP Plugin Boilerplate&lt;/a&gt; structure.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📦 What This Is&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This repository contains &lt;strong&gt;only the LLM API integration classes&lt;/strong&gt; - it is &lt;strong&gt;not a complete WordPress plugin&lt;/strong&gt;. These classes are designed to be integrated into a parent WordPress plugin that uses the WP Plugin Boilerplate structure.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-LLM Support&lt;/strong&gt;: Integrate with OpenAI, Google Gemini, and xAI Grok APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File Attachment Support&lt;/strong&gt;: Upload and process PDFs, images, and text files as knowledge bases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversation History&lt;/strong&gt;: Maintain context across multiple interactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multimodal Support&lt;/strong&gt;: Handle both text and image inputs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WordPress Standards&lt;/strong&gt;: Built following WordPress coding standards and best practices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object-Oriented Architecture&lt;/strong&gt;: Clean, maintainable code…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/d5b94396feba3/llms-apis-wp-boilerplate-plugin" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;h2&gt;
  
  
  The Plugin Boilerplate Foundation for LLM Integrations
&lt;/h2&gt;

&lt;p&gt;Our example plugin, &lt;code&gt;my-wp-plugin&lt;/code&gt;, is built upon a standardized WordPress development approach, promoting a scalable and maintainable structure. This separation of concerns makes the plugin easier to develop, debug, and maintain, especially when dealing with complex &lt;strong&gt;LLM integrations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's a quick look at the typical directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-wp-plugin/
├── 📄 my-wp-plugin.php                 # Main plugin file
├── 📁 includes/                        # Core PHP classes
│   ├── 📄 class-main.php               # Plugin orchestrator
│   ├── 📄 class-loader.php             # Hook manager
│   ├── 📄 class-activator.php          # Activation handler
│   └── 📄 class-deactivator.php        # Deactivation handler
├── 📁 admin/                           # Backend functionality ONLY
│   ├── 📄 class-admin.php              # Admin controller
│   └── 📁 partials/                    # Admin HTML templates
├── 📁 public/                          # Frontend functionality ONLY
│   └── 📄 class-public.php             # Frontend controller
├── 📁 assets/                          # ALL static files (CSS, JS, Images)
├── 📁 languages/                       # Translation files
└── 📄 uninstall.php                    # Cleanup when deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For our &lt;strong&gt;LLM integrations&lt;/strong&gt; specifically, the key classes we'll focus on reside within the &lt;code&gt;includes/&lt;/code&gt; directory.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Central Brain: &lt;code&gt;AICA_Plugin_LLM_Processor&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;AICA_Plugin_LLM_Processor&lt;/code&gt; class acts as the central hub for all LLM interactions within the plugin. It's responsible for orchestrating the entire process: selecting the active LLM, constructing the full system instruction, and dispatching requests to the appropriate LLM-specific integration class.&lt;/p&gt;
&lt;h3&gt;
  
  
  Constructor (&lt;code&gt;__construct&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This method is crucial for dynamically loading the correct LLM integration. Based on the user's settings (e.g., in the WordPress admin), it instantiates either the Grok, OpenAI, or Gemini class.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AICA_Plugin_LLM_Processor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$plugin_slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$system_instruction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$llm_model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$plugin_slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$plugin_slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;system_instruction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ai_agent_system_prompt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'You are an AI Assistant...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$active_llm_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'llm_model'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Grok'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$active_llm_model&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Grok"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;llm_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AICA_Plugin_LLM_Grok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;elseif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$active_llm_model&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"OpenAI"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;llm_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AICA_Plugin_LLM_OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;elseif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$active_llm_model&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Gemini"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;llm_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AICA_Plugin_LLM_Gemini&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ... rest of the class&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Getting an LLM Response (&lt;code&gt;get_llm_response&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This is the primary method for handling user queries and generating LLM responses. It meticulously constructs the &lt;code&gt;system_instruction&lt;/code&gt; by combining a core prompt, user-defined prompt, positive/negative feedback examples from the database, and content from the plugin's knowledge base. It then delegates the actual API call to the &lt;code&gt;llm_api_v2&lt;/code&gt; method of the active LLM model, which supports multimodal inputs.&lt;/p&gt;
&lt;h3&gt;
  
  
  Chat Summarization (&lt;code&gt;get_chat_summary&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This utility method uses the &lt;code&gt;llm_api_v3&lt;/code&gt; (text-only) of the chosen LLM to summarize a conversation history. It prepares a structured prompt with the chat transcript and requests a concise summary.&lt;/p&gt;
&lt;h3&gt;
  
  
  Automated System Prompt Generation (&lt;code&gt;generate_automated_system_prompt&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Leveraging the LLM's own capabilities, this method can automatically generate a sophisticated system prompt based on the website's knowledge base content. It uses a template, replaces a placeholder with extracted content, and then calls &lt;code&gt;llm_api_v3&lt;/code&gt; to generate the prompt.&lt;/p&gt;
&lt;h2&gt;
  
  
  Deep Dive into Individual LLM Integrations
&lt;/h2&gt;

&lt;p&gt;Each LLM (Gemini, Grok, OpenAI) has its own dedicated class, abstracting the specifics of its API. This modular approach makes it easier to add new LLMs or update existing ones. They share common patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;llm_api_v2&lt;/code&gt;: Handles multimodal inputs (text, images, documents) and conversation history.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;llm_api_v3&lt;/code&gt;: Designed for simpler, text-only interactions, often used for tasks like summarization or prompt generation.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;prepare_attachments&lt;/code&gt;: A private helper to process local files (extract text from PDFs/TXTs, encode images to Base64, or upload large files to the LLM's service).&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;request_llm_service_name&lt;/code&gt;: The core method that makes the actual API call to the respective LLM, including robust error handling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's break down each integration to see how they manage diverse inputs and API requirements for &lt;strong&gt;LLM integrations in WordPress&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Gemini Integration (&lt;code&gt;AICA_Plugin_LLM_Gemini&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This class handles communication with Google's Gemini API, supporting both text and multimodal inputs. It smartly manages file uploads for large documents.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;llm_api_v2&lt;/code&gt; and &lt;code&gt;prepare_attachments&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Gemini's &lt;code&gt;llm_api_v2&lt;/code&gt; is designed to handle attachments efficiently. For images, it encodes them as Base64 &lt;code&gt;inline_data&lt;/code&gt;. For PDFs and plain text files, it checks their size. Small files are directly embedded as &lt;code&gt;file_context_text&lt;/code&gt;. Larger files (over 1MB) are uploaded to Gemini's file service via &lt;code&gt;upload_to_gemini&lt;/code&gt; to get a &lt;code&gt;file_uri&lt;/code&gt;, which is then referenced in the API request. This optimizes API calls and handles large data payloads.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Excerpt from AICA_Plugin_LLM_Gemini::prepare_attachments&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;prepare_attachments&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$file_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ai_agent_knowledge_file_id'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'multimodal_parts'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="s1"&gt;'file_context_text'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_attached_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$mime_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_post_mime_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$file_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;filesize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$extraction_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1MB&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mime_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'multimodal_parts'&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'inline_data'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'mime_type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$mime_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'data'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;base64_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mime_type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'application/pdf'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$mime_type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'text/plain'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$extraction_threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// ... extract text directly ...&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$transient_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'aica_gemini_uri_'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nv"&gt;$file_uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_transient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$transient_key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$file_uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$file_uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;upload_to_gemini&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$mime_type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;set_transient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$transient_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$file_uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="no"&gt;HOUR_IN_SECONDS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'multimodal_parts'&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'file_data'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                        &lt;span class="s1"&gt;'mime_type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$mime_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s1"&gt;'file_uri'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$file_uri&lt;/span&gt;
                    &lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;upload_to_gemini&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This method handles the resumable upload protocol required by Gemini for large files. It first initiates an upload to get a unique upload URL, then sends the file content in chunks or a single request depending on the protocol's specifics.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;request_gemini&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This private method constructs the final API payload, including the system instruction, conversation contents, and generation configurations like temperature. It then dispatches the &lt;code&gt;wp_remote_post&lt;/code&gt; request to the Gemini API endpoint, managing potential errors and parsing the LLM's response.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Grok Integration (&lt;code&gt;AICA_Plugin_LLM_Grok&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The Grok integration allows your WordPress plugin to tap into xAI's Grok models. This implementation supports both direct text and file-based multimodal interactions, crucial for comprehensive &lt;strong&gt;LLM integrations&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;llm_api_v2&lt;/code&gt; and &lt;code&gt;prepare_attachments&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Similar to Gemini, Grok's &lt;code&gt;llm_api_v2&lt;/code&gt; utilizes &lt;code&gt;prepare_attachments&lt;/code&gt; for handling files. Images are Base64 encoded and included directly in the &lt;code&gt;content&lt;/code&gt; array for the user message. For PDF or plain text files, if they are small, their content is appended to the system instruction (&lt;code&gt;file_context_text&lt;/code&gt;). For larger files, the &lt;code&gt;ensure_grok_file&lt;/code&gt; method is called to upload the file to Grok's service, and the returned &lt;code&gt;file_id&lt;/code&gt; is used in the &lt;code&gt;multimodal_parts&lt;/code&gt; array.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Excerpt from AICA_Plugin_LLM_Grok::prepare_attachments&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;prepare_attachments&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$file_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ai_agent_knowledge_file_id'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'multimodal_parts'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="s1"&gt;'file_context_text'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_attached_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$mime_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_post_mime_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$file_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;filesize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1MB&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mime_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'multimodal_parts'&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'image_url'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'image_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"data:&lt;/span&gt;&lt;span class="nv"&gt;$mime_type&lt;/span&gt;&lt;span class="s2"&gt;;base64,"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;base64_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s1"&gt;'detail'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'auto'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mime_type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'application/pdf'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$mime_type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'text/plain'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// ... extract text directly ...&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$grok_file_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;ensure_grok_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"grok_file_id : "&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$grok_file_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$grok_file_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'multimodal_parts'&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'file'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'file_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$grok_file_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;ensure_grok_file&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This method handles uploading files to Grok's &lt;code&gt;/v1/files&lt;/code&gt; endpoint using a &lt;code&gt;multipart/form-data&lt;/code&gt; request. It's designed to be efficient by caching the file ID, preventing redundant uploads of the same file within a short period.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;request_grok&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This method constructs the full message payload for Grok, including the system prompt (referred to as &lt;code&gt;developer_text&lt;/code&gt; in the code) and the conversation history. It then sends this payload to the &lt;code&gt;/v1/chat/completions&lt;/code&gt; endpoint, managing API keys and error responses.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. OpenAI Integration (&lt;code&gt;AICA_Plugin_LLM_OpenAI&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The OpenAI integration connects your plugin to various OpenAI models, including those supporting multimodal capabilities (like GPT-4o). This specific implementation uses the &lt;code&gt;/v1/responses&lt;/code&gt; endpoint, which is optimized for conversational AI with structured input/output, enhancing &lt;strong&gt;WordPress LLM integrations&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;llm_api_v2&lt;/code&gt; and &lt;code&gt;prepare_attachments&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;For &lt;code&gt;llm_api_v2&lt;/code&gt;, &lt;code&gt;prepare_attachments&lt;/code&gt; processes incoming files. Images are Base64 encoded as &lt;code&gt;input_image&lt;/code&gt; types. Text-based documents (PDF, TXT) are either directly embedded if small or uploaded to OpenAI's file service via &lt;code&gt;ensure_openai_file&lt;/code&gt; to obtain a &lt;code&gt;file_id&lt;/code&gt;, which is then referenced as an &lt;code&gt;input_file&lt;/code&gt; type. This flexible handling supports diverse data types.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Excerpt from AICA_Plugin_LLM_OpenAI::prepare_attachments&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;prepare_attachments&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$file_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ai_agent_knowledge_file_id'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'multimodal_parts'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="s1"&gt;'file_context_text'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_attached_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"AICA OpenAI: File path not found for ID &lt;/span&gt;&lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$mime_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_post_mime_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$file_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;filesize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 10MB&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mime_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'multimodal_parts'&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'input_image'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'image_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"data:&lt;/span&gt;&lt;span class="nv"&gt;$mime_type&lt;/span&gt;&lt;span class="s2"&gt;;base64,"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;base64_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mime_type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'application/pdf'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$mime_type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'text/plain'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// ... extract text directly ...&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$openai_file_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;ensure_openai_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$openai_file_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'multimodal_parts'&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'input_file'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'file'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'file_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$openai_file_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;ensure_openai_file&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This method uploads files to OpenAI's &lt;code&gt;/v1/files&lt;/code&gt; endpoint with a &lt;code&gt;purpose&lt;/code&gt; of &lt;code&gt;user_data&lt;/code&gt;. It also utilizes a transient cache to store the OpenAI file ID, preventing redundant uploads and speeding up subsequent requests.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;format_conversation_items&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This method is crucial for structuring the conversation for the &lt;code&gt;/v1/responses&lt;/code&gt; endpoint. It meticulously formats messages into an &lt;code&gt;items&lt;/code&gt; array, differentiating between &lt;code&gt;input_text&lt;/code&gt; (user queries) and &lt;code&gt;output_text&lt;/code&gt; (assistant responses), and integrating any multimodal parts seamlessly.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;request_openai&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This method constructs the final payload for the &lt;code&gt;/v1/responses&lt;/code&gt; endpoint, including &lt;code&gt;instructions&lt;/code&gt; (system prompt), &lt;code&gt;input&lt;/code&gt; (formatted conversation items), model, and temperature. It then sends the request and processes the structured response from OpenAI, handling potential API errors.&lt;/p&gt;
&lt;h2&gt;
  
  
  Key Takeaways for WordPress LLM Integrations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Modular Design&lt;/strong&gt;: The plugin uses a boilerplate to separate concerns, making LLM integrations clean, manageable, and easy to extend.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Dynamic LLM Selection&lt;/strong&gt;: Users can switch between Grok, OpenAI, and Gemini models directly from WordPress settings without altering core plugin logic.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Multimodal Support&lt;/strong&gt;: The &lt;code&gt;llm_api_v2&lt;/code&gt; methods and &lt;code&gt;prepare_attachments&lt;/code&gt; enable rich interactions with various data types, including images and documents.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Context Management&lt;/strong&gt;: System instructions are dynamically built, incorporating core prompts, user-defined settings, feedback mechanisms, and knowledge base content for more relevant responses.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Robust Error Handling&lt;/strong&gt;: Each integration includes checks for missing API keys, connection errors, and specific LLM-reported errors, providing clearer feedback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architecture provides a powerful and flexible way to embed cutting-edge AI capabilities directly into your WordPress applications. By understanding these core classes and their interactions, you're well-equipped to build intelligent features that enhance user experience and automate complex tasks in your &lt;strong&gt;WordPress plugin development&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Get Involved!
&lt;/h2&gt;

&lt;p&gt;What are your thoughts on building LLM-powered WordPress plugins? Have you implemented similar &lt;strong&gt;LLM integrations in WordPress&lt;/strong&gt; or faced unique challenges? Share your experiences and questions in the comments below!&lt;/p&gt;

&lt;p&gt;If you found this deep dive helpful, please consider sharing it with your network and following me for more content on AI, WordPress, and plugin development.&lt;/p&gt;

&lt;p&gt;Complete code at this repo:&lt;br&gt;


&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/d5b94396feba3" rel="noopener noreferrer"&gt;
        d5b94396feba3
      &lt;/a&gt; / &lt;a href="https://github.com/d5b94396feba3/llms-apis-wp-boilerplate-plugin" rel="noopener noreferrer"&gt;
        llms-apis-wp-boilerplate-plugin
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A collection of LLM (Large Language Model) API integration classes for WordPress plugins. Provides unified integration with multiple LLM providers including OpenAI, Google Gemini, and xAI Grok.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LLM APIs WordPress Plugin Component&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A collection of LLM (Large Language Model) API integration classes for WordPress plugins. Provides unified integration with multiple LLM providers including OpenAI, Google Gemini, and xAI Grok. Designed to be integrated into WordPress plugins that follow the &lt;a href="https://github.com/d5b94396feba3/WP-Plugin-Boilerplate" rel="noopener noreferrer"&gt;WP Plugin Boilerplate&lt;/a&gt; structure.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📦 What This Is&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;This repository contains &lt;strong&gt;only the LLM API integration classes&lt;/strong&gt; - it is &lt;strong&gt;not a complete WordPress plugin&lt;/strong&gt;. These classes are designed to be integrated into a parent WordPress plugin that uses the WP Plugin Boilerplate structure.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Features&lt;/h2&gt;
&lt;/div&gt;


&lt;ul&gt;

&lt;li&gt;

&lt;strong&gt;Multi-LLM Support&lt;/strong&gt;: Integrate with OpenAI, Google Gemini, and xAI Grok APIs&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;File Attachment Support&lt;/strong&gt;: Upload and process PDFs, images, and text files as knowledge bases&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Conversation History&lt;/strong&gt;: Maintain context across multiple interactions&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Multimodal Support&lt;/strong&gt;: Handle both text and image inputs&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;WordPress Standards&lt;/strong&gt;: Built following WordPress coding standards and best practices&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Object-Oriented Architecture&lt;/strong&gt;: Clean, maintainable code…&lt;/li&gt;

&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/d5b94396feba3/llms-apis-wp-boilerplate-plugin" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;





</description>
      <category>php</category>
      <category>wordpress</category>
      <category>llm</category>
      <category>ai</category>
    </item>
    <item>
      <title>Demystifying Real-time Admin Previews: JavaScript &amp; PHP for Dynamic Chat Widget Configuration</title>
      <dc:creator>Shahibur Rahman</dc:creator>
      <pubDate>Mon, 12 Jan 2026 10:25:08 +0000</pubDate>
      <link>https://forem.com/shahibur_rahman_6670cd024/demystifying-real-time-admin-previews-javascript-php-for-dynamic-chat-widget-configuration-373g</link>
      <guid>https://forem.com/shahibur_rahman_6670cd024/demystifying-real-time-admin-previews-javascript-php-for-dynamic-chat-widget-configuration-373g</guid>
      <description>&lt;p&gt;Ever configured a plugin or widget in an admin panel and wished you could see your changes instantly, without saving or refreshing? That's the magic of &lt;strong&gt;real-time admin previews&lt;/strong&gt;! This article will break down how a dynamic chat widget's admin preview works, focusing on the interplay between JavaScript (jQuery) and PHP to provide an immediate visual update as you tweak settings. We'll explore the core JavaScript function, its interaction with HTML elements, and how event listeners bring it all to life.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Foundation: Our HTML and PHP Setup for Real-time Admin Previews
&lt;/h2&gt;

&lt;p&gt;Before diving into the JavaScript, it's crucial to understand the basic HTML structure that PHP generates. The WordPress admin page, powered by PHP, renders both the input fields where you enter settings (like display name, welcome message, colors) and the actual chat widget preview area. Each input field is designed with specific &lt;code&gt;name&lt;/code&gt; attributes that JavaScript will target, and many also have a special class like &lt;code&gt;admin-preview-trigger&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's a simplified look at how PHP might generate an input field and the corresponding preview element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- PHP snippet (conceptual) for an input field --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ai_agent_display_name"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;esc_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ai_agent_display_name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'AI Assistant'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"large-text professional-input admin-preview-trigger"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- PHP snippet (conceptual) for the preview wrapper --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"aica-admin-preview-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ai-chat-wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chat-header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"header-name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;AI Assistant&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"chat-messages"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="c"&gt;&amp;lt;!-- Welcome messages will be dynamically added here --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chat-input-area"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"chat-input"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Message..."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ai-chat-bubble"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"launcher-btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;name&lt;/code&gt; attributes (e.g., &lt;code&gt;ai_agent_display_name&lt;/code&gt;) on the input fields and unique &lt;code&gt;id&lt;/code&gt;s or classes (e.g., &lt;code&gt;header-name&lt;/code&gt;, &lt;code&gt;ai-chat-wrapper&lt;/code&gt;, &lt;code&gt;aica-admin-preview-container&lt;/code&gt;) on the preview elements. These are the hooks our JavaScript will use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The JavaScript Engine: &lt;code&gt;syncAdminPreview()&lt;/code&gt; Function for Dynamic Updates
&lt;/h2&gt;

&lt;p&gt;The heart of our &lt;strong&gt;real-time admin previews&lt;/strong&gt; is the &lt;code&gt;syncAdminPreview()&lt;/code&gt; function, wrapped in a &lt;code&gt;jQuery(document).ready()&lt;/code&gt; block to ensure the DOM is fully loaded before execution. This means the function won't try to manipulate elements that haven't been loaded by the browser yet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;jQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncAdminPreview&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ... (function body explained below)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... (event bindings explained later)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Gathering Your Settings with &lt;code&gt;syncAdminPreview()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The first job of &lt;code&gt;syncAdminPreview()&lt;/code&gt; is to collect all the current settings from the admin input fields. It smartly uses &lt;code&gt;localStorage&lt;/code&gt; as a fallback. This means if you switch between admin tabs (e.g., from 'Content' to 'Style'), the preview can still remember and display the values from the previous tab's inputs.&lt;/p&gt;

&lt;p&gt;Here’s how it retrieves key values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Display Name&lt;/strong&gt;: &lt;code&gt;$('input[name="ai_agent_display_name"]').val()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Welcome Message&lt;/strong&gt;: &lt;code&gt;$('textarea[name="ai_agent_welcome_msg"]').val()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Message Placeholder&lt;/strong&gt;: &lt;code&gt;$('input[name="ai_agent_placeholder"]').val()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Brand Color&lt;/strong&gt;: &lt;code&gt;$('#ai_agent_primary_color_input').val()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Avatar URL&lt;/strong&gt;: &lt;code&gt;$('#ai_agent_logo_preview').attr('src')&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Launcher Icon Type&lt;/strong&gt;: &lt;code&gt;$('select[name="ai_agent_bubble_icon"]').val()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Theme Mode&lt;/strong&gt;: &lt;code&gt;$('input[name="ai_agent_theme_mode"]:checked').val()&lt;/code&gt; (for radio buttons)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at a snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncAdminPreview&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// --- 1. GET VALUES WITH LOCAL STORAGE FALLBACK ---&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nameInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[name="ai_agent_display_name"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nameInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aica_preview_name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nameInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;val&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aica_preview_name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AI Assistant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;welcomeInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textarea[name="ai_agent_welcome_msg"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;welcomeInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aica_preview_welcome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;welcomeInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;val&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;welcomeText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aica_preview_welcome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;brandColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ai_agent_primary_color_input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;val&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#000000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;avatarUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ai_agent_logo_preview&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;avatarUrl&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;avatarUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default-avater.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;avatarUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/wp-content/plugins/ai-chat-agent/assets/images/default-avater.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// ... other value retrievals&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Updating the Preview in Real-time
&lt;/h3&gt;

&lt;p&gt;Once all settings are gathered, &lt;code&gt;syncAdminPreview()&lt;/code&gt; proceeds to update the visual preview. It targets specific HTML elements within the &lt;code&gt;#aica-admin-preview-container&lt;/code&gt; and modifies their text, attributes, or even rebuilds entire sections.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Targeting Elements&lt;/strong&gt;: It first identifies the main preview container and the chat wrapper within it.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$preview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#aica-admin-preview-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$wrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$preview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ai-chat-wrapper&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Applying Theme Class&lt;/strong&gt;: It toggles CSS classes to switch between light and dark themes.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;$wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme-light theme-dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;themeMode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Updating Name &amp;amp; Placeholder&lt;/strong&gt;: The display name and input placeholder are updated directly.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;$preview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.header-name, .msg-bot-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;$preview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#chat-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;placeholder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Re-building Welcome Bubbles&lt;/strong&gt;: This is a dynamic process. The &lt;code&gt;welcomeText&lt;/code&gt; (from the textarea) is split by newlines to create individual chat bubbles. The HTML for these bubbles is generated on the fly and then prepended to the &lt;code&gt;#chat-messages&lt;/code&gt; container. This effectively clears old welcome messages and adds new ones.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;welcomeText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;welcomeHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;welcomeHtml&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`
            &amp;lt;div class="msg ai welcome-msg"&amp;gt;
                &amp;lt;div class="msg-avatar-container"&amp;gt;
                    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;img src="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;avatarUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" class="msg-avatar"&amp;gt;`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="msg-content-wrapper"&amp;gt;
                    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;div class="msg-bot-name"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
                    &amp;lt;div class="bubble"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;$preview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.welcome-msg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Clear existing welcome messages&lt;/span&gt;
    &lt;span class="nx"&gt;$preview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#chat-messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;welcomeHtml&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Add new ones&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Updating Colors and Avatars&lt;/strong&gt;: The primary brand color is applied to the chat bubble launcher and user messages, and the avatar image URL is updated.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;$preview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ai-chat-bubble, .user .bubble&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;brandColor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;$preview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.header-logo, .msg-avatar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;avatarUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Launcher Icon Update&lt;/strong&gt;: This logic determines whether to show the default message icon or the uploaded profile image on the floating chat button.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$iconContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$preview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.launcher-img-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iconSelect&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;$iconContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;is-default-icon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;img src="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;avatarUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" class="launcher-img"&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;$iconContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;is-default-icon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"&amp;gt;&amp;lt;path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"&amp;gt;&amp;lt;/path&amp;gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Making it Interactive: Event Listeners for Real-time Admin Previews
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;syncAdminPreview()&lt;/code&gt; function is powerful, but it needs to be called whenever a relevant setting changes. This is where &lt;strong&gt;event listeners&lt;/strong&gt; come in. They "listen" for user interactions on specific HTML elements and trigger the preview update.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Theme Changes&lt;/strong&gt;: A dedicated listener for radio buttons that control the theme. The &lt;code&gt;change&lt;/code&gt; event is suitable here as it fires when a radio button's selection is committed.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[name="ai_agent_theme_mode"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.aica-theme-card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.aica-theme-card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;removeClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;is-active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.theme-card-inner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;border-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#e2e8f0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;$card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;is-active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.theme-card-inner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;border-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#3b82f6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;syncAdminPreview&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Call the update function&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;General Input Changes&lt;/strong&gt;: A more generic listener for text inputs, color pickers, and select dropdowns. The key here is the &lt;code&gt;.admin-preview-trigger&lt;/code&gt; class, which is added to any input that should cause a real-time preview update.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.admin-preview-trigger, #ai_agent_primary_color_input, select[name="ai_agent_bubble_icon"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;syncAdminPreview&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Call the update function&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;  The &lt;code&gt;input&lt;/code&gt; event fires immediately as the user types, providing instant feedback.&lt;/li&gt;
&lt;li&gt;  The &lt;code&gt;change&lt;/code&gt; event fires when an element's value is committed (e.g., after typing and blurring, or selecting from a dropdown). Using both ensures broad coverage.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Live Walkthrough: Changing the Chat Widget Name
&lt;/h2&gt;

&lt;p&gt;Let's trace a simple interaction to solidify our understanding of &lt;strong&gt;real-time admin previews&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;User Action&lt;/strong&gt;: You navigate to the 'Content' tab in the admin panel and locate the "Display Name" input field. You start typing "My Awesome AI" into this input.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ai_agent_display_name"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"AI Assistant"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"large-text professional-input admin-preview-trigger"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Event Trigger&lt;/strong&gt;: As you type, the &lt;code&gt;input&lt;/code&gt; event fires on this specific &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; element. Since this input has the class &lt;code&gt;admin-preview-trigger&lt;/code&gt;, it matches the selector in our general event listener: &lt;code&gt;$(document).on('input change', '.admin-preview-trigger', ...)&lt;/code&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Function Call&lt;/strong&gt;: The event listener executes the &lt;code&gt;syncAdminPreview()&lt;/code&gt; function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Value Retrieval&lt;/strong&gt;: Inside &lt;code&gt;syncAdminPreview()&lt;/code&gt;, the line &lt;code&gt;const nameInput = $('input[name="ai_agent_display_name"]');&lt;/code&gt; finds your input field. &lt;code&gt;nameInput.val()&lt;/code&gt; retrieves "My Awesome AI". This value is then stored in &lt;code&gt;localStorage&lt;/code&gt; and assigned to the &lt;code&gt;name&lt;/code&gt; variable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Preview Update&lt;/strong&gt;: The line &lt;code&gt;$preview.find('.header-name, .msg-bot-name').text(name);&lt;/code&gt; targets the &lt;code&gt;&amp;lt;span class="header-name"&amp;gt;&lt;/code&gt; element (and other elements with &lt;code&gt;.msg-bot-name&lt;/code&gt;) inside your chat widget preview and updates its text content to "My Awesome AI".&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Result? As you type, the name in the chat widget preview updates character by character – a seamless, real-time experience!&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Real-time Admin Previews&lt;/strong&gt; significantly enhance user experience by providing instant feedback on configuration changes.&lt;/li&gt;
&lt;li&gt;  The core of the system is a robust &lt;strong&gt;JavaScript function&lt;/strong&gt; (like &lt;code&gt;syncAdminPreview()&lt;/code&gt;) that gathers settings and dynamically updates the preview's HTML and CSS.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;jQuery selectors&lt;/strong&gt; (&lt;code&gt;$&lt;/code&gt; functions) are crucial for efficiently targeting specific input fields and preview elements by their &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;, or &lt;code&gt;class&lt;/code&gt; attributes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Event listeners&lt;/strong&gt; (&lt;code&gt;.on('input change', ...)&lt;/code&gt;) act as the glue, triggering the update function whenever a user interacts with a setting.&lt;/li&gt;
&lt;li&gt;  Using &lt;code&gt;localStorage&lt;/code&gt; provides &lt;strong&gt;data persistence&lt;/strong&gt; across different admin tabs, making the preview more reliable and consistent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Your Turn!
&lt;/h2&gt;

&lt;p&gt;Understanding this pattern is incredibly useful for building dynamic interfaces. Have you implemented similar real-time previews in your projects? Share your experiences, challenges, or alternative approaches in the comments below! If you found this breakdown helpful, consider following for more web development insights.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>jquery</category>
      <category>webdev</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Automating WordPress Content Scraping: Build a Knowledge Base Plugin (Beginner's Guide)</title>
      <dc:creator>Shahibur Rahman</dc:creator>
      <pubDate>Thu, 08 Jan 2026 10:40:48 +0000</pubDate>
      <link>https://forem.com/shahibur_rahman_6670cd024/automating-wordpress-content-scraping-build-a-knowledge-base-plugin-beginners-guide-30a1</link>
      <guid>https://forem.com/shahibur_rahman_6670cd024/automating-wordpress-content-scraping-build-a-knowledge-base-plugin-beginners-guide-30a1</guid>
      <description>&lt;p&gt;Developing robust WordPress plugins often benefits from a structured approach. This tutorial dives into creating a powerful &lt;strong&gt;WordPress Content Scraping&lt;/strong&gt; and knowledge base synchronization plugin, leveraging a solid boilerplate structure. We'll explore how to automatically scan your website's pages, scrape their content, and store it efficiently in your database to build a custom knowledge base.&lt;/p&gt;

&lt;p&gt;This guide is designed for beginners looking to understand WordPress plugin architecture and implement advanced functionalities like web scraping within the WordPress ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Automate WordPress Content Scraping?
&lt;/h2&gt;

&lt;p&gt;In today's dynamic web, keeping an external AI or search index updated with your site's latest content can be a challenge. Manually updating knowledge bases is tedious and prone to errors. By implementing an automated &lt;strong&gt;WordPress Content Scraping&lt;/strong&gt; mechanism, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Maintain a Fresh Knowledge Base&lt;/strong&gt;: Ensure your AI chatbots or internal search features always have the most current information.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Improve Data Consistency&lt;/strong&gt;: Reduce discrepancies between your live site and its stored knowledge.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Save Time&lt;/strong&gt;: Eliminate manual content extraction and updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll achieve this by building upon a well-organized WordPress Plugin Boilerplate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Foundation: WordPress Plugin Boilerplate
&lt;/h2&gt;

&lt;p&gt;A boilerplate provides a standardized, organized, and extensible base for your plugin development. It separates concerns, making your code cleaner and easier to maintain. For this project, we'll imagine using a structure similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-wp-plugin/
├── 📄 my-wp-plugin.php
├── 📁 includes/
│   ├── 📄 class-main.php               # Plugin orchestrator
│   ├── 📄 class-loader.php             # Hook manager
│   ├── 📄 class-activator.php          # Activation handler
│   └── 📄 class-deactivator.php        # Deactivation handler
├── 📁 admin/                           # Backend functionality ONLY
│   ├── 📄 class-admin.php              # Admin controller
│   └── 📁 partials/                    # Admin HTML templates
│       └── 📄 admin-settings.php
├── 📁 public/                          # Frontend functionality ONLY
│   └── 📄 class-public.php             # Frontend controller
├── 📁 assets/                          # ALL static files
│   ├── 📁 css/
│   └── 📁 js/
│       ├── 📄 admin.js
│       └── 📄 knowledge-base.js
└── 📁 languages/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This structure helps us organize our PHP logic (&lt;code&gt;includes&lt;/code&gt;, &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;public&lt;/code&gt;) and static assets (&lt;code&gt;assets&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/d5b94396feba3" rel="noopener noreferrer"&gt;
        d5b94396feba3
      &lt;/a&gt; / &lt;a href="https://github.com/d5b94396feba3/WP-Plugin-Boilerplate" rel="noopener noreferrer"&gt;
        WP-Plugin-Boilerplate
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A modern, object-oriented WordPress plugin boilerplate following WordPress coding standards. Features a generic structure where you only need to change ONE FILE to create a completely new plugin.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;WP Plugin Boilerplate&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A modern, object-oriented WordPress plugin boilerplate following WordPress coding standards. Features a generic structure where you only need to change ONE FILE to create a completely new plugin.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Features&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;One-File Setup (True Dynamic Naming): Change only the main plugin file's header to rename the plugin, &lt;code&gt;slug&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt;, menu items, and file handles across the entire codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Object-Oriented Architecture&lt;/strong&gt;: Clean, maintainable code structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WordPress Standards&lt;/strong&gt;: Follows WordPress coding standards and best practices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security Ready&lt;/strong&gt;: Built-in security measures and data sanitization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internationalization&lt;/strong&gt;: Ready for translations with proper text domains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Admin &amp;amp; Public Separation&lt;/strong&gt;: Organized separation of backend and frontend functionality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hook Management&lt;/strong&gt;: Centralized WordPress hook system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asset Management&lt;/strong&gt;: Proper CSS/JS enqueuing for admin and public.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📁 Plugin Structure&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;my-wp-plugin/
├── 📄 my-wp-plugin.php                 # Main plugin file (ONLY FILE TO EDIT HEADER)
├── 📄 index.php
├── 📁 includes/                        # Core&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/d5b94396feba3/WP-Plugin-Boilerplate" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  Core Components for Your WordPress Content Scraper
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Admin Controller (&lt;code&gt;class-admin.php&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;AICA_Plugin_Admin&lt;/code&gt; class is responsible for all backend functionalities, including managing settings and handling AJAX requests for our content scraper.&lt;/p&gt;

&lt;p&gt;Here's a snippet showing how it registers settings and handles the content synchronization AJAX:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In my-wp-plugin/admin/class-admin.php&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AICA_Plugin_Admin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... other properties and constructor ...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register_settings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ... other settings ...&lt;/span&gt;
        &lt;span class="nf"&gt;register_setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'_prompt_settings'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ai_agent_knowledge_file_id'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle_sync_website_ajax&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;check_ajax_referer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin_proposal_nonce'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'nonce'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;current_user_can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'manage_options'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nf"&gt;wp_send_json_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Unauthorized'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sync_step'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sync_step'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'discovery'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_db_manager&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Initialize your scraper class (e.g., AICA_Plugin_SimpleHTMLDOM)&lt;/span&gt;
        &lt;span class="nv"&gt;$scraper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AICA_Plugin_SimpleHTMLDOM&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// This would be your custom scraper implementation&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$step&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'discovery'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_posts&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'post_type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'page'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'post'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; 
                &lt;span class="s1"&gt;'posts_per_page'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="s1"&gt;'post_status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'publish'&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;

            &lt;span class="nv"&gt;$pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
            &lt;span class="nv"&gt;$excluded_slugs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cart'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'checkout'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-account'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'logout'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'login'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'register'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'sample-page'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;in_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$excluded_slugs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_permalink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nv"&gt;$sync_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_sync_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="nv"&gt;$pages&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'url'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'title'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post_title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$sync_data&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'Synced'&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Pending'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'last'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$sync_data&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;date_i18n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'date_format'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;strtotime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sync_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'last_synced'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;
                &lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nf"&gt;wp_send_json_success&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'pages'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$pages&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$step&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'sync_page'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;esc_url_raw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'url'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
            &lt;span class="nv"&gt;$content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$scraper&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;scrape_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// The actual scraping happens here&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;wp_send_json_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Page empty or ignored'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nv"&gt;$db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update_knowledge_base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Store the scraped content&lt;/span&gt;

            &lt;span class="nf"&gt;wp_send_json_success&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Synced'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="s1"&gt;'last'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;current_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'mysql'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ... other methods ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;handle_sync_website_ajax&lt;/code&gt; method is crucial. It has two main steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Discovery&lt;/strong&gt;: It fetches all published WordPress pages and posts, filters out common irrelevant pages (like cart/checkout), and prepares a list of URLs to be synced.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Sync Page&lt;/strong&gt;: For each URL, it uses an instance of &lt;code&gt;AICA_Plugin_SimpleHTMLDOM&lt;/code&gt; (which you would implement or integrate from a library) to &lt;code&gt;scrape_page()&lt;/code&gt; and extract its content. This extracted content is then passed to the database manager.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. The Database Manager (&lt;code&gt;class-db-manager.php&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;AICA_Plugin_DB_Manager&lt;/code&gt; handles all interactions with the database, including creating custom tables and storing/retrieving scraped content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In my-wp-plugin/includes/class-db-manager.php&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AICA_Plugin_DB_Manager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... properties and constructor ...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$plugin_slug&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$clean_slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;sanitize_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$plugin_slug&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nv"&gt;$kb_table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$clean_slug&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'_knowledge_base'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Our knowledge base table&lt;/span&gt;

        &lt;span class="nv"&gt;$charset_collate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_charset_collate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$sql_kb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CREATE TABLE &lt;/span&gt;&lt;span class="nv"&gt;$kb_table&lt;/span&gt;&lt;span class="s2"&gt; (
            id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
            source_url VARCHAR(255) NOT NULL,
            content LONGTEXT NOT NULL,
            last_synced DATETIME NOT NULL,
            PRIMARY KEY (id),
            UNIQUE KEY source_url (source_url(191))
        ) &lt;/span&gt;&lt;span class="nv"&gt;$charset_collate&lt;/span&gt;&lt;span class="s2"&gt;;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="no"&gt;ABSPATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'wp-admin/includes/upgrade.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;dbDelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$sql_kb&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Creates or updates the table&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;update_knowledge_base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_kb_table_name&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Ensure content is clean&lt;/span&gt;

        &lt;span class="c1"&gt;// Uses wpdb-&amp;gt;replace to either insert new or update existing records based on source_url&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'source_url'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;esc_url_raw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'content'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'last_synced'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;current_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'mysql'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'%s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_sync_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_kb_table_name&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT last_synced FROM &lt;/span&gt;&lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="s2"&gt; WHERE source_url = %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="no"&gt;ARRAY_A&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;create_table&lt;/code&gt; method is called during plugin activation to set up our &lt;code&gt;_knowledge_base&lt;/code&gt; table. This table stores the &lt;code&gt;source_url&lt;/code&gt;, the &lt;code&gt;content&lt;/code&gt; scraped from that URL, and the &lt;code&gt;last_synced&lt;/code&gt; timestamp. The &lt;code&gt;update_knowledge_base&lt;/code&gt; method uses &lt;code&gt;wpdb-&amp;gt;replace&lt;/code&gt; which is convenient for either inserting new pages or updating existing ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Admin User Interface (&lt;code&gt;admin-settings.php&lt;/code&gt; &amp;amp; &lt;code&gt;knowledge-base.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The admin-settings page (&lt;code&gt;admin/partials/admin-settings.php&lt;/code&gt;) provides the user interface for initiating and monitoring the &lt;strong&gt;WordPress Content Scraping&lt;/strong&gt; process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;// Snippet from admin/partials/admin-settings.php for the Knowledge Base tab
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;elseif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$active_tab&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'prompt'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"options.php"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="nf"&gt;settings_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'_prompt_settings'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"postbox professional-box knowledge-sync-wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"postbox-header"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"toggle-sync-table"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hndle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Website Knowledge Sync&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
                &lt;span class="c"&gt;&amp;lt;!-- ... UI elements for status and toggle ... --&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"inside"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sync-collapsible-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"discovery-loader"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sync-progress-bar"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sync-success-msg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sync-data-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"wp-list-table widefat fixed striped"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;&lt;/span&gt;Page Source&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&lt;/span&gt;Status&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&lt;/span&gt;Last Sync&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;tbody&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sync-table-body"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"margin-top:20px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"run-bulk-sync"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"button button-hero button-primary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"start-discovery-refresh"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"button button-secondary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Refresh Map&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- ... other AI Persona settings ... --&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="nf"&gt;submit_button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Save AI Persona &amp;amp; Knowledge'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JavaScript file (&lt;code&gt;assets/js/admin/knowledge-base.js&lt;/code&gt;) handles the client-side logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  It triggers the &lt;code&gt;discovery&lt;/code&gt; AJAX call on page load (if on the correct tab).&lt;/li&gt;
&lt;li&gt;  It populates the table with discovered pages and their sync status.&lt;/li&gt;
&lt;li&gt;  It handles the "Run Bulk Sync" button click, iterating through pending pages and making individual &lt;code&gt;sync_page&lt;/code&gt; AJAX calls.&lt;/li&gt;
&lt;li&gt;  It updates the progress bar and displays success messages.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In my-wp-plugin/assets/js/admin/knowledge-base.js&lt;/span&gt;

&lt;span class="nf"&gt;jQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="c1"&gt;// Auto-load Discovery on Page Load&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urlParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prompt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;runDiscovery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Function to initiate page discovery via AJAX&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runDiscovery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#discovery-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Show loading spinner&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#sync-data-container, #sync-success-msg, #sync-progress-bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Hide other elements&lt;/span&gt;

        &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adminProposalData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ajax_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sync_website_knowledge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;sync_step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discovery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;adminProposalData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#discovery-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nf"&gt;renderRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Populate the table&lt;/span&gt;
                &lt;span class="nf"&gt;updateSyncButtonState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Update sync button text&lt;/span&gt;
                &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#sync-data-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fadeIn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#sync-summary-badge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; Pages Found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Bulk Sync Logic&lt;/span&gt;
    &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#run-bulk-sync&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$btn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Syncing...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#sync-progress-bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;slideDown&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#sync-success-msg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;syncedCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#sync-msg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Processing: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#sync-fill&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;width&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#sync-percent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Send individual AJAX request for each page sync&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adminProposalData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ajax_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sync_website_knowledge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;sync_step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sync_page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;adminProposalData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;syncedCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`tr[data-url="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"]`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.status-badge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;class&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status-badge status-approved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Synced&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.last-sync-col&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Just now&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Synced&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Update local state&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Completion UI sequence&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#sync-progress-bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;slideUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#sync-success-msg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fadeIn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nf"&gt;updateSyncButtonState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#sync-success-msg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fadeOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// ... other UI functions like renderRows, updateSyncButtonState ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This JavaScript orchestrates the entire synchronization process, providing real-time feedback to the user on the progress of the &lt;strong&gt;WordPress Content Scraping&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep Dive: The &lt;code&gt;AICA_Plugin_SimpleHTMLDOM&lt;/code&gt; for Content Scraping
&lt;/h2&gt;

&lt;p&gt;At the heart of our &lt;strong&gt;WordPress Content Scraping&lt;/strong&gt; mechanism is the &lt;code&gt;AICA_Plugin_SimpleHTMLDOM&lt;/code&gt; class. This class acts as a wrapper for the popular &lt;code&gt;simple_html_dom&lt;/code&gt; PHP library, making it easy to fetch and parse HTML content from URLs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrating the &lt;code&gt;simple_html_dom&lt;/code&gt; Library
&lt;/h3&gt;

&lt;p&gt;First, we need to ensure the &lt;code&gt;simple_html_dom.php&lt;/code&gt; file is loaded. In a typical WordPress plugin setup using a boilerplate, you might place external libraries in a &lt;code&gt;vendor/&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In my-wp-plugin/includes/class-simplehtmldom.php&lt;/span&gt;

&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AICA_Plugin_SimpleHTMLDOM&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;load_lib&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;load_lib&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Path to the simple_html_dom.php library&lt;/span&gt;
        &lt;span class="nv"&gt;$lib_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;AICA_PLUGIN_PATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'vendor/simplehtmldom/simple_html_dom.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$lib_path&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="nv"&gt;$lib_path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Handle error if library not found (e.g., log it)&lt;/span&gt;
            &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Simple HTML DOM library not found at: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$lib_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... rest of the class methods ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;load_lib()&lt;/code&gt; method checks if the &lt;code&gt;simple_html_dom.php&lt;/code&gt; file exists at the expected path and then includes it using &lt;code&gt;require_once&lt;/code&gt;. This ensures the library's functions (like &lt;code&gt;str_get_html&lt;/code&gt;) are available when needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  How &lt;code&gt;scrape_page&lt;/code&gt; Works
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;scrape_page&lt;/code&gt; method is where the actual content extraction happens. Let's break down its functionality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Inside AICA_Plugin_SimpleHTMLDOM class&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;scrape_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 1. Fetch the HTML content from the URL&lt;/span&gt;
        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_remote_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'timeout'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;is_wp_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Handle errors&lt;/span&gt;

        &lt;span class="nv"&gt;$html_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_remote_retrieve_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Use simple_html_dom to parse the HTML string&lt;/span&gt;
        &lt;span class="nv"&gt;$html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str_get_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$html_content&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nv"&gt;$html&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Return empty if parsing fails&lt;/span&gt;

        &lt;span class="c1"&gt;// 3. Early exit for 'Page Not Found' titles&lt;/span&gt;
        &lt;span class="nv"&gt;$title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;stripos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Page not found'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// 4. Remove unwanted elements (scripts, styles, navigation, footers, etc.)&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'script, style, nav, footer, header, noscript, .gdpr, .cookie, #cookie-law-info-bar'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;outertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// 5. Extract the pure plain text from the cleaned HTML&lt;/span&gt;
        &lt;span class="nv"&gt;$text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// 6. Perform a "nuclear cleanup" on the text&lt;/span&gt;
        &lt;span class="c1"&gt;// Convert HTML entities (like &amp;amp;nbsp;) to actual characters&lt;/span&gt;
        &lt;span class="nv"&gt;$text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;html_entity_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ENT_QUOTES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'UTF-8'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Replace all types of whitespace (multiple spaces, newlines, tabs) with a single space&lt;/span&gt;
        &lt;span class="nv"&gt;$text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/\s+/u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Clean up memory used by simple_html_dom&lt;/span&gt;
        &lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;unset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Return the final, cleaned text&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ... other methods like sync_website_content ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method demonstrates a robust way to fetch, clean, and extract meaningful text content from a web page, which is essential for accurate &lt;strong&gt;WordPress Content Scraping&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Structured Development&lt;/strong&gt;: Using a boilerplate significantly improves plugin organization and maintainability.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;AJAX for Background Tasks&lt;/strong&gt;: WordPress AJAX is ideal for long-running tasks like content scraping, preventing UI freezes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Custom Database Tables&lt;/strong&gt;: For storing custom data like scraped content, creating your own tables is often more efficient than using &lt;code&gt;wp_options&lt;/code&gt; or &lt;code&gt;wp_posts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Client-Side Feedback&lt;/strong&gt;: Providing progress bars and status updates through JavaScript enhances the user experience for intensive operations.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Targeted Scraping&lt;/strong&gt;: By fetching &lt;code&gt;post_type&lt;/code&gt; and filtering slugs, you can control exactly which content your &lt;strong&gt;WordPress Content Scraping&lt;/strong&gt; targets.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Simple HTML DOM&lt;/strong&gt;: A powerful PHP library that simplifies parsing and manipulating HTML content for scraping purposes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;This framework provides a solid foundation for your &lt;strong&gt;WordPress Content Scraping&lt;/strong&gt; plugin. You could extend it by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Implementing a more sophisticated &lt;code&gt;AICA_Plugin_SimpleHTMLDOM&lt;/code&gt; class to handle various content structures or even external websites.&lt;/li&gt;
&lt;li&gt;  Adding scheduling options to run the sync automatically using WordPress Cron.&lt;/li&gt;
&lt;li&gt;  Integrating with external AI services to process the scraped content for advanced knowledge base features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What are your thoughts on building an automated content scraper for WordPress? Share your ideas and experiences in the comments below!&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>webscraping</category>
      <category>plugin</category>
    </item>
    <item>
      <title>Mastering CSS Grid for Responsive Layouts: A Comprehensive Guide</title>
      <dc:creator>Shahibur Rahman</dc:creator>
      <pubDate>Tue, 16 Dec 2025 14:40:56 +0000</pubDate>
      <link>https://forem.com/shahibur_rahman_6670cd024/mastering-css-grid-for-responsive-layouts-a-comprehensive-guide-5gdj</link>
      <guid>https://forem.com/shahibur_rahman_6670cd024/mastering-css-grid-for-responsive-layouts-a-comprehensive-guide-5gdj</guid>
      <description>&lt;p&gt;Are you tired of wrestling with floats, complex media queries, and fragile layout systems to achieve responsive designs? It's time to master &lt;strong&gt;CSS Grid Responsive Layouts&lt;/strong&gt;! CSS Grid is a powerful two-dimensional layout system that allows you to design complex, responsive web layouts with unparalleled ease and flexibility. If you're an intermediate developer looking to elevate your front-end skills and build robust, adaptive interfaces, you're in the right place.&lt;/p&gt;

&lt;p&gt;In this guide, we'll dive deep into CSS Grid, covering its core concepts, practical implementation, and advanced techniques to create truly responsive designs that adapt seamlessly across all devices. Get ready to transform your approach to web layouts!&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Core Concepts of CSS Grid
&lt;/h2&gt;

&lt;p&gt;Before we start building, let's grasp the fundamental terminology that makes CSS Grid so intuitive.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Grid Container:&lt;/strong&gt; The element on which &lt;code&gt;display: grid;&lt;/code&gt; is applied. It becomes the parent for all grid items.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Grid Items:&lt;/strong&gt; The direct children of the grid container.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Grid Lines:&lt;/strong&gt; The dividing lines that make up the structure of the grid, both vertical (column lines) and horizontal (row lines).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Grid Tracks:&lt;/strong&gt; The space between two adjacent grid lines, forming columns or rows.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Grid Cells:&lt;/strong&gt; The intersection of a grid row and a grid column, similar to a table cell.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Grid Areas:&lt;/strong&gt; Named regions of the grid, defined by &lt;code&gt;grid-template-areas&lt;/code&gt;, making placement of items incredibly semantic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike Flexbox, which is primarily for one-dimensional layouts (rows or columns), CSS Grid excels at two-dimensional layouts, giving you control over both rows and columns simultaneously. This makes it the ideal tool for overall page structure and complex component arrangements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining Your Grid Structure with Ease
&lt;/h2&gt;

&lt;p&gt;The magic of CSS Grid begins by defining your grid container and its tracks. Let's look at the essential properties.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up Your Grid Container
&lt;/h3&gt;

&lt;p&gt;First, turn an element into a grid container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, define your columns and rows using &lt;code&gt;grid-template-columns&lt;/code&gt; and &lt;code&gt;grid-template-rows&lt;/code&gt;. You can use fixed units (px, em, rem), percentages, or the flexible &lt;code&gt;fr&lt;/code&gt; unit, which distributes available space proportionally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Three columns: middle is twice as wide */&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="m"&gt;200px&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Three rows: auto height, fixed 200px, remaining space */&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Space between grid cells */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another powerful feature is &lt;code&gt;repeat()&lt;/code&gt; for repetitive patterns and &lt;code&gt;minmax()&lt;/code&gt; for flexible sizing within bounds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.gallery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto-fit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;250px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="py"&gt;grid-gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;repeat(auto-fit, minmax(250px, 1fr))&lt;/code&gt; pattern is a cornerstone for &lt;strong&gt;CSS Grid Responsive Layouts&lt;/strong&gt;, creating as many 250px wide columns as possible, stretching them to fill available space, and wrapping when necessary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Using Grid Template Areas for Semantic Layouts
&lt;/h3&gt;

&lt;p&gt;For highly readable and maintainable layouts, &lt;code&gt;grid-template-areas&lt;/code&gt; is a game-changer. You define named areas visually.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.page-layout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-areas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="s1"&gt;"header header header"&lt;/span&gt;
    &lt;span class="s1"&gt;"nav    main   aside"&lt;/span&gt;
    &lt;span class="s1"&gt;"footer footer footer"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Placing and Aligning Items within Your &lt;strong&gt;CSS Grid Responsive Layouts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once your grid structure is defined, placing items is straightforward. You can use line-based placement or area-based placement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Line-Based Placement
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.item-a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Starts at column line 1, ends at column line 3 (spans 2 columns) */&lt;/span&gt;
  &lt;span class="nl"&gt;grid-row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.item-b&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Starts at column line 3, spans 1 column */&lt;/span&gt;
  &lt;span class="nl"&gt;grid-row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Starts at row line 1, spans 2 rows */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Area-Based Placement (with &lt;code&gt;grid-template-areas&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This is much cleaner when you've defined &lt;code&gt;grid-template-areas&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.nav&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.main&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.aside&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aside&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.footer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Alignment Properties
&lt;/h3&gt;

&lt;p&gt;CSS Grid also provides powerful alignment properties for items within their cells or areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;justify-items&lt;/code&gt; (horizontal alignment for all items in the grid container)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;align-items&lt;/code&gt; (vertical alignment for all items in the grid container)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;place-items&lt;/code&gt; (shorthand for &lt;code&gt;align-items&lt;/code&gt; and &lt;code&gt;justify-items&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;justify-self&lt;/code&gt;, &lt;code&gt;align-self&lt;/code&gt;, &lt;code&gt;place-self&lt;/code&gt; (for individual grid items)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building Truly &lt;strong&gt;CSS Grid Responsive Layouts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The real power of CSS Grid for responsive design comes from combining its flexible units and area definitions with media queries.&lt;/p&gt;

&lt;p&gt;Let's refine our &lt;code&gt;page-layout&lt;/code&gt; example to be responsive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"page-layout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;Header&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;Navigation&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;Main Content&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;aside&amp;gt;&lt;/span&gt;Sidebar&lt;span class="nt"&gt;&amp;lt;/aside&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;footer&amp;gt;&lt;/span&gt;Footer&lt;span class="nt"&gt;&amp;lt;/footer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.page-layout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Single column on small screens */&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-areas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="s1"&gt;"header"&lt;/span&gt;
    &lt;span class="s1"&gt;"nav"&lt;/span&gt;
    &lt;span class="s1"&gt;"main"&lt;/span&gt;
    &lt;span class="s1"&gt;"aside"&lt;/span&gt;
    &lt;span class="s1"&gt;"footer"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Ensure it takes full viewport height */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.page-layout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;grid-template-areas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="s1"&gt;"header header"&lt;/span&gt;
      &lt;span class="s1"&gt;"nav    main"&lt;/span&gt;
      &lt;span class="s1"&gt;"nav    aside"&lt;/span&gt;
      &lt;span class="s1"&gt;"footer footer"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1024px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.page-layout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;grid-template-areas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="s1"&gt;"header header header"&lt;/span&gt;
      &lt;span class="s1"&gt;"nav    main   aside"&lt;/span&gt;
      &lt;span class="s1"&gt;"footer footer footer"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Basic styling for visibility */&lt;/span&gt;
&lt;span class="nc"&gt;.page-layout&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e0e0e0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#a7d9b7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;nav&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#a7c0d9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;main&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#d9a7a7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;aside&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#d9cfa7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;footer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#b7a7d9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example demonstrates how effortlessly you can redefine your entire layout at different breakpoints using &lt;code&gt;grid-template-areas&lt;/code&gt; and media queries, making &lt;strong&gt;CSS Grid Responsive Layouts&lt;/strong&gt; a joy to implement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways for Mastering CSS Grid
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;CSS Grid&lt;/strong&gt; is a 2D layout system, perfect for overall page structure.&lt;/li&gt;
&lt;li&gt;  Use &lt;code&gt;display: grid;&lt;/code&gt; on the container.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;grid-template-columns&lt;/code&gt; and &lt;code&gt;grid-template-rows&lt;/code&gt; define your track structure, with &lt;code&gt;fr&lt;/code&gt;, &lt;code&gt;repeat()&lt;/code&gt;, and &lt;code&gt;minmax()&lt;/code&gt; being essential for flexibility.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;grid-template-areas&lt;/code&gt; provides a highly semantic way to define and manage complex layouts.&lt;/li&gt;
&lt;li&gt;  Combine Grid with &lt;strong&gt;media queries&lt;/strong&gt; to effortlessly create &lt;strong&gt;CSS Grid Responsive Layouts&lt;/strong&gt; that adapt to any screen size.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;auto-fit&lt;/code&gt;/&lt;code&gt;auto-fill&lt;/code&gt; with &lt;code&gt;minmax()&lt;/code&gt; is your go-to for dynamically sized, wrapping grids.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What are Your Favorite CSS Grid Tricks?
&lt;/h2&gt;

&lt;p&gt;CSS Grid is a game-changer for front-end development. By mastering its concepts, you can build incredibly robust and flexible layouts with less code and more clarity. Experiment with these properties and see how they transform your workflow.&lt;/p&gt;

&lt;p&gt;What are your go-to strategies for building &lt;strong&gt;CSS Grid Responsive Layouts&lt;/strong&gt;? Share your favorite tips, tricks, or challenges in the comments below! If you found this guide helpful, consider sharing it with your network and following me for more in-depth web development tutorials.&lt;/p&gt;

&lt;p&gt;Happy Gridding!&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>responsivedesign</category>
    </item>
    <item>
      <title>Automating Contract Signing and Payments in WordPress Using DocuSign + Stripe</title>
      <dc:creator>Shahibur Rahman</dc:creator>
      <pubDate>Thu, 06 Nov 2025 10:00:57 +0000</pubDate>
      <link>https://forem.com/shahibur_rahman_6670cd024/automating-contract-signing-and-payments-in-wordpress-using-docusign-stripe-440m</link>
      <guid>https://forem.com/shahibur_rahman_6670cd024/automating-contract-signing-and-payments-in-wordpress-using-docusign-stripe-440m</guid>
      <description>&lt;p&gt;If you want clients to sign a contract and pay right away inside your WordPress workflow, this setup shows exactly how to achieve a seamless &lt;strong&gt;WordPress DocuSign Stripe integration&lt;/strong&gt;. This guide outlines how a custom plugin connects DocuSign for e-signatures and Stripe for payments. When the client signs, they're instantly redirected to a secure checkout page – no manual steps, no chasing invoices.&lt;/p&gt;

&lt;p&gt;The integration is built around two core classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;Plugin_DocuSign_Contract&lt;/code&gt; – e-signature engine&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Plugin_Stripe_Processor&lt;/code&gt; – payment handler&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Plugin Architecture Overview
&lt;/h2&gt;

&lt;p&gt;The structure follows the &lt;a href="https://github.com/d5b94396feba3/WP-Plugin-Boilerplate" rel="noopener noreferrer"&gt;WordPress Plugin Boilerplate&lt;/a&gt; for clear separation between admin, public, and core logic, essential for robust &lt;strong&gt;WordPress DocuSign Stripe integration&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-wp-plugin/
├── my-wp-plugin.php
├── includes/
│ ├── class-main.php
│ ├── class-loader.php
│ ├── class-activator.php
│ └── class-deactivator.php
├── admin/
│ ├── class-admin.php
│ └── partials/
├── public/
│ └── class-public.php
├── assets/
│ ├── css/
│ ├── js/
│ └── images/
├── languages/
└── uninstall.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The DocuSign and Stripe integration lives inside &lt;code&gt;includes/&lt;/code&gt;, handled by the two main classes.&lt;/p&gt;


&lt;h2&gt;
  
  
  How DocuSign → Stripe Works: The Automated Flow
&lt;/h2&gt;

&lt;p&gt;This integration orchestrates a seamless hand-off between signing and payment. Here's a high-level overview of the process for &lt;strong&gt;automating contract signing and payments&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Plugin generates a DocuSign envelope&lt;/strong&gt;: It prepares JWT authentication, builds the envelope with both client and admin signers, and temporarily stores relevant data (like envelope ID) in WordPress transients.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Client signs&lt;/strong&gt;: The client uses an embedded signing experience via DocuSign's Recipient View, completing the signature process within DocuSign's UI.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;DocuSign redirects back&lt;/strong&gt;: Upon completion, DocuSign redirects the client back to a specified WordPress URL, passing crucial parameters.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Plugin retrieves stored data&lt;/strong&gt;: Your plugin retrieves the previously stored envelope data from transients.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Plugin creates a Stripe Checkout Session&lt;/strong&gt;: It dynamically generates a real-time product and price for the service, returning a unique Checkout URL.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Client completes payment&lt;/strong&gt;: The client is redirected to the secure Stripe Checkout page to finalize the payment.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Plugin verifies payment&lt;/strong&gt;: After successful payment, the plugin verifies the transaction status and triggers any necessary post-payment actions (e.g., updating contract status, sending confirmations).&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Stripe Integration (Payment Engine)
&lt;/h2&gt;

&lt;p&gt;This section details the &lt;strong&gt;Stripe integration&lt;/strong&gt; for handling payments within your WordPress plugin.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Initialization
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$plugin_slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;plugin_slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$plugin_slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;load_stripe_library&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;initialize_stripe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This method loads the Stripe PHP SDK and sets keys based on test/live mode.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Loading the Stripe Library
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;load_stripe_library&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$stripe_lib_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MY_PLUGIN_PATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'vendor/stripe/stripe-php/init.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$stripe_lib_path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="nv"&gt;$stripe_lib_path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Initializing Stripe Mode
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;initialize_stripe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stripe_mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stripe_mode'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'test'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stripe_secret_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stripe_mode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'live'&lt;/span&gt;
        &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stripe_live_secret_key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;get_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stripe_test_secret_key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;\Stripe\Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;setApiKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stripe_secret_key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  4. Creating a Checkout Session
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\Stripe\Checkout\Session&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'payment_method_types'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'card'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;'line_items'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;
        &lt;span class="s1"&gt;'price_data'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'currency'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'product_data'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Service Agreement - &lt;/span&gt;&lt;span class="nv"&gt;$company_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'unit_amount'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$amount_cents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'quantity'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="s1"&gt;'mode'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'payment'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'success_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;home_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/payment-success/'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'cancel_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;home_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/payment-cancelled/'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The client is then redirected to &lt;code&gt;$session-&amp;gt;url&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  DocuSign Integration (Contract Engine)
&lt;/h2&gt;

&lt;p&gt;This part handles authentication, envelope creation, embedded signing, and the crucial redirect to Stripe for the full &lt;strong&gt;WordPress DocuSign Stripe integration&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Required DocuSign Credentials
&lt;/h3&gt;

&lt;p&gt;Here's a list of the constants and their descriptions you'll need for &lt;strong&gt;DocuSign integration&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;DOCUSIGN_INTEGRATION_KEY&lt;/code&gt;: Your app's Client ID.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;DOCUSIGN_USER_ID&lt;/code&gt;: The API user's GUID.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;DOCUSIGN_ACCOUNT_ID&lt;/code&gt;: Your DocuSign account ID.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;DOCUSIGN_AUTH_SERVER&lt;/code&gt;: The OAuth URL (e.g., &lt;code&gt;account-d.docusign.com&lt;/code&gt; for demo, &lt;code&gt;account.docusign.com&lt;/code&gt; for live).&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;DOCUSIGN_API_BASE_PATH&lt;/code&gt;: The API base URL (e.g., &lt;code&gt;https://demo.docusign.net/restapi&lt;/code&gt; for demo).&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;DOCUSIGN_PRIVATE_KEY&lt;/code&gt;: The RSA private key generated for your app.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;DOCUSIGN_IMPERSONATED_USER_GUID&lt;/code&gt;: Typically the same as &lt;code&gt;DOCUSIGN_USER_ID&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. Getting Your Credentials
&lt;/h3&gt;

&lt;p&gt;To obtain these, you'll need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Create a DocuSign developer account (if you don't have one).&lt;/li&gt;
&lt;li&gt;  Add an app in your developer account settings and generate an Integration Key (Client ID).&lt;/li&gt;
&lt;li&gt;  Create an RSA keypair for JWT authentication.&lt;/li&gt;
&lt;li&gt;  Copy your User ID and Account ID from your account settings.&lt;/li&gt;
&lt;li&gt;  Add your plugin's redirect URL to your DocuSign app settings.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3. Authenticating Using JWT
&lt;/h3&gt;

&lt;p&gt;The plugin creates a signed JSON Web Token (JWT) and exchanges it for an access token with DocuSign:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
assertion=&amp;lt;signed-jwt&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;DocuSign then returns a bearer token, which is used for all subsequent API calls.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Creating and Sending the Envelope
&lt;/h3&gt;

&lt;p&gt;The plugin dynamically builds a PDF envelope with both client and admin signers, then sends it via the DocuSign API:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /accounts/{ACCOUNT_ID}/envelopes
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This API call returns an &lt;code&gt;envelopeId&lt;/code&gt;, which is essential for tracking and managing the signing process.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Generating the Signing URL
&lt;/h3&gt;

&lt;p&gt;For embedded signing, where the client signs within your WordPress workflow (or an iframe), you'll generate a recipient view URL:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /envelopes/{envelopeId}/views/recipient
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;DocuSign responds with a unique signing URL that you can open directly in a new window or embed.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Handling Completion → Starting Payment
&lt;/h3&gt;

&lt;p&gt;Once the client completes signing, DocuSign redirects them back to your specified WordPress URL, typically including the &lt;code&gt;envelopeId&lt;/code&gt; in the query parameters.&lt;/p&gt;

&lt;p&gt;At this point, the plugin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Retrieves any stored data associated with the &lt;code&gt;envelopeId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  Calls the &lt;code&gt;create_checkout_session()&lt;/code&gt; method from the Stripe integration.&lt;/li&gt;
&lt;li&gt;  Redirects the client directly to the Stripe Checkout page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures the client sees the payment page instantly after signing, making for a seamless transition.&lt;/p&gt;
&lt;h3&gt;
  
  
  7. Verifying Payment
&lt;/h3&gt;

&lt;p&gt;After the client completes the payment on Stripe, you can verify its status using the Stripe API:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\Stripe\Checkout\Session&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$session_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$session&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;payment_status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Update contract status, send confirmation emails, grant access, etc.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  End-to-End Flow Recap
&lt;/h2&gt;

&lt;p&gt;Let's quickly summarize the entire automated process for &lt;strong&gt;WordPress contract automation&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Admin sends contract&lt;/strong&gt;: An action within WordPress triggers the DocuSign envelope generation.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Plugin authenticates via JWT&lt;/strong&gt;: Securely connects to DocuSign.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;DocuSign sends envelope&lt;/strong&gt;: The client receives the contract.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Client signs&lt;/strong&gt;: Electronically signs the document within an embedded view.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Redirects to WordPress&lt;/strong&gt;: DocuSign hands control back to your plugin.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Stripe Checkout starts&lt;/strong&gt;: The client is immediately taken to the payment page.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Payment verified&lt;/strong&gt;: Upon successful payment, your system updates records and triggers post-payment actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This entire sequence is fully automated from start to finish.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Implementing this &lt;strong&gt;WordPress DocuSign Stripe integration&lt;/strong&gt; offers significant advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Smooth client experience&lt;/strong&gt;: Clients can sign and pay in one continuous, guided flow, reducing friction.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Secure handling of signatures + payments&lt;/strong&gt;: Leverage the robust security of DocuSign and Stripe.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Clear tracking&lt;/strong&gt;: All contract and payment data is linked, providing a transparent audit trail.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Ideal for various use cases&lt;/strong&gt;: Perfect for agencies onboarding new clients, freelancers securing projects, or SaaS businesses automating service agreement sign-ups.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Pro Tips
&lt;/h2&gt;

&lt;p&gt;When building out your integration, keep these best practices in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Use DocuSign Sandbox&lt;/strong&gt; (&lt;code&gt;demo.docusign.net&lt;/code&gt;): Always test in the demo environment before going live.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Use Stripe Test Mode&lt;/strong&gt; (&lt;code&gt;pk_test&lt;/code&gt;, &lt;code&gt;sk_test&lt;/code&gt;): Similarly, use Stripe's test keys to avoid real charges during development.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Sanitize all input&lt;/strong&gt;: Protect your application from malicious data.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Store envelope data in transients&lt;/strong&gt;: WordPress transients are excellent for temporary storage of &lt;code&gt;envelopeId&lt;/code&gt; and other session-specific data.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Swap demo URLs with production URLs&lt;/strong&gt; when going live: Ensure all API endpoints and redirect URLs point to the correct production environments.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/d5b94396feba3" rel="noopener noreferrer"&gt;
        d5b94396feba3
      &lt;/a&gt; / &lt;a href="https://github.com/d5b94396feba3/wp-docusign-stripe" rel="noopener noreferrer"&gt;
        wp-docusign-stripe
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      WordPress DocuSign &amp;amp; Stripe Integration Plugin
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;WordPress DocuSign &amp;amp; Stripe Integration Plugin&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A WordPress plugin that seamlessly integrates DocuSign electronic signatures with Stripe payment processing. This plugin automates the contract signing and payment workflow, allowing clients to sign agreements via DocuSign and immediately proceed to secure payment processing through Stripe.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Built With&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;This plugin is built using the &lt;strong&gt;&lt;a href="https://github.com/d5b94396feba3/WP-Plugin-Boilerplate" rel="noopener noreferrer"&gt;WP Plugin Boilerplate&lt;/a&gt;&lt;/strong&gt; - a modern, object-oriented WordPress plugin boilerplate following WordPress coding standards. The boilerplate provides a clean, maintainable structure with separation of concerns, proper security measures, and WordPress best practices.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Stripe SDK&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;This plugin uses the official &lt;strong&gt;Stripe PHP SDK&lt;/strong&gt; for payment processing. The Stripe SDK is loaded via Composer and provides secure, reliable access to Stripe's payment processing APIs. The SDK handles all Stripe API communications, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product and Price creation&lt;/li&gt;
&lt;li&gt;Checkout Session management&lt;/li&gt;
&lt;li&gt;Payment verification&lt;/li&gt;
&lt;li&gt;Error handling and API response parsing&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Overview&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;This plugin provides a complete solution for businesses that need to:&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;Send…&lt;/li&gt;

&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/d5b94396feba3/wp-docusign-stripe" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;





&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/d5b94396feba3" rel="noopener noreferrer"&gt;
        d5b94396feba3
      &lt;/a&gt; / &lt;a href="https://github.com/d5b94396feba3/WP-Plugin-Boilerplate" rel="noopener noreferrer"&gt;
        WP-Plugin-Boilerplate
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A modern, object-oriented WordPress plugin boilerplate following WordPress coding standards. Features a generic structure where you only need to change ONE FILE to create a completely new plugin.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;WP Plugin Boilerplate&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A modern, object-oriented WordPress plugin boilerplate following WordPress coding standards. Features a generic structure where you only need to change ONE FILE to create a completely new plugin.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Features&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;One-File Setup (True Dynamic Naming): Change only the main plugin file's header to rename the plugin, &lt;code&gt;slug&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt;, menu items, and file handles across the entire codebase.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Object-Oriented Architecture&lt;/strong&gt;: Clean, maintainable code structure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WordPress Standards&lt;/strong&gt;: Follows WordPress coding standards and best practices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security Ready&lt;/strong&gt;: Built-in security measures and data sanitization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Internationalization&lt;/strong&gt;: Ready for translations with proper text domains.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Admin &amp;amp; Public Separation&lt;/strong&gt;: Organized separation of backend and frontend functionality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hook Management&lt;/strong&gt;: Centralized WordPress hook system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Asset Management&lt;/strong&gt;: Proper CSS/JS enqueuing for admin and public.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📁 Plugin Structure&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;my-wp-plugin/
├── 📄 my-wp-plugin.php                 # Main plugin file (ONLY FILE TO EDIT HEADER)
├── 📄 index.php
├── 📁 includes/                        # Core&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/d5b94396feba3/WP-Plugin-Boilerplate" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;







&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Combining DocuSign with Stripe inside your own custom WordPress plugin creates a fast, automated pipeline for contracts and payments. Clients can sign and pay in minutes, and everything stays under your control – no external SaaS required. This powerful &lt;strong&gt;WordPress DocuSign Stripe integration&lt;/strong&gt; empowers you to streamline your business operations and provide a top-tier experience for your clients.&lt;/p&gt;

&lt;p&gt;What are your thoughts on automating contract and payment workflows? Share your experiences or questions in the comments below! If you found this guide helpful, consider following me for more technical content.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>docusign</category>
      <category>stripe</category>
      <category>php</category>
    </item>
    <item>
      <title>🎉 Building an Interactive 3D Flipping Coin with Three.js: Complete Guide</title>
      <dc:creator>Shahibur Rahman</dc:creator>
      <pubDate>Thu, 02 Oct 2025 15:54:22 +0000</pubDate>
      <link>https://forem.com/shahibur_rahman_6670cd024/building-an-interactive-3d-flipping-coin-with-threejs-complete-guide-1pic</link>
      <guid>https://forem.com/shahibur_rahman_6670cd024/building-an-interactive-3d-flipping-coin-with-threejs-complete-guide-1pic</guid>
      <description>&lt;p&gt;In this comprehensive tutorial, we'll build an interactive 3D Flipping commemorative coin using Three.js that features smooth animations, particle effects, and engaging user interactions - all without external animation libraries.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Live Demo&lt;br&gt;&lt;br&gt;


&lt;iframe height="600" src="https://codepen.io/Shahibur-Rahman/embed/myVryeK?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;


&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🎯 What We're Building
&lt;/h2&gt;

&lt;p&gt;By the end of this tutorial, you’ll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3D Coin with Dual Sides&lt;/strong&gt;: Front and back faces with custom designs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive Flip Animation&lt;/strong&gt;: Smooth 180-degree flip on click
-** Parallax Effects**: Mouse movement creates 3D depth illusion&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Particle System&lt;/strong&gt;: Animated particles floating around the coin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Counting Animation&lt;/strong&gt;: Number counter that increments automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive Design&lt;/strong&gt;: Works on desktop and mobile devices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Shaders&lt;/strong&gt;: GPU-accelerated particle rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll walk you through everything step by step — HTML, CSS, JavaScript, and Three.js specifics — so you can not only replicate this but also customize it for your own ideas.  &lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Step 1: Setting up the HTML
&lt;/h2&gt;

&lt;p&gt;We start with a barebones container for the coin and a loading screen. Nothing special — just the entry point where Three.js will inject its canvas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8"&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;gt;
    &amp;lt;title&amp;gt;3D Interactive Commemorative Coin&amp;lt;/title&amp;gt;

    &amp;lt;!-- Three.js Core --&amp;gt;
    &amp;lt;script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"&amp;gt;&amp;lt;/script&amp;gt;

    &amp;lt;!-- Three.js Addons --&amp;gt;
    &amp;lt;script src="https://threejs.org/examples/js/loaders/FontLoader.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src="https://threejs.org/examples/js/geometries/TextGeometry.js"&amp;gt;&amp;lt;/script&amp;gt;

    &amp;lt;!-- Fonts --&amp;gt;
    &amp;lt;link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;900&amp;amp;display=swap" rel="stylesheet"&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div id="coin-container"&amp;gt;
        &amp;lt;div id="canvas-container"&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;div id="loading"&amp;gt;Loading 3D Coin...&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;script src="scripts/interactive-coin.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🎨 Step 2: Adding Styles with CSS
&lt;/h2&gt;

&lt;p&gt;We’ll use CSS variables so it’s trivial to reskin the coin (gold, silver, neon, etc.) later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:root {
    /* Color System */
    --bg-gradient-start: #173A7A;
    --coin-gradient-start: #173A7A;
    --coin-gradient-end: #66B7E1;
    --coin-edge-color: #6c7aa1;
    --text-color-light: #ffffff;
    --text-color-dark: #173A7A;

    /* Animation */
    --flip-duration: 2.8s;
    --ripple-color: rgba(56, 189, 248, 0.6);
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    background: linear-gradient(45deg, var(--bg-gradient-start), var(--bg-gradient-end));
    font-family: 'Inter', sans-serif;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
}

#coin-container {
    position: relative;
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
}

#canvas-container {
    width: 100vw;
    max-width: 380px;
    aspect-ratio: 1 / 1;
    cursor: pointer;
    position: relative;
}

/* Click Ripple Effect */
.click-ripple {
    position: absolute;
    width: 100px;
    height: 100px;
    border-radius: 50%;
    background: radial-gradient(circle, var(--ripple-color) 0%, transparent 70%);
    transform: translate(-50%, -50%) scale(0);
    opacity: 0;
    pointer-events: none;
    animation: ripple 0.8s forwards;
}

@keyframes ripple {
    0% {
        transform: translate(-50%, -50%) scale(0);
        opacity: 0.8;
    }
    100% {
        transform: translate(-50%, -50%) scale(4);
        opacity: 0;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Pro tip: keeping the palette in variables means you can re-theme the coin without ever touching the JS.&lt;/p&gt;

&lt;p&gt;We also add a click ripple effect entirely in CSS — no extra GPU load, but it gives clicks/taps a nice tactile response.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 Step 3: Core JavaScript Class
&lt;/h2&gt;

&lt;p&gt;We wrap everything in a class InteractiveCoin. This avoids global chaos and makes the code extensible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class InteractiveCoin {
    constructor() {
        // Three.js Core Components
        this.scene = null;
        this.camera = null;
        this.renderer = null;

        // Coin Components
        this.coin = null;
        this.edgeMesh = null;

        // Animation State
        this.isFlipping = false;
        this.isAnimating = false;
        this.targetRotationX = 0;
        this.targetRotationY = 0;

        // Particle System
        this.particles = null;
        this.particlesData = [];

        // Interactive Elements
        this.stars = [];
        this.countTextMesh = null;
        this.currentCount = 1;
        this.targetCount = 20;

        // DOM Elements
        this.container = document.getElementById('canvas-container');

        this.init();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Think of this as a mini “engine” that controls state, scene graph, and user input.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 Step 4: Setting up the Scene
&lt;/h2&gt;

&lt;p&gt;Every Three.js project starts with scene, camera, and renderer. The difference here is in lighting — metals need multiple light passes to look convincing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class InteractiveCoin {
    // ... constructor ...

    init() {
        this.loadAssets().then(() =&amp;gt; {
            this.createScene();
            this.createCamera();
            this.createRenderer();
            this.createLighting();
            this.createCoin();
            this.createParticles();
            this.setupEventListeners();

            // Hide loading screen
            setTimeout(() =&amp;gt; {
                document.getElementById('loading').style.display = 'none';
            }, 100);

            this.animate();
        });
    }

    createScene() {
        this.scene = new THREE.Scene();
        this.scene.background = null; // Transparent background
    }

    createCamera() {
        const fov = 45;
        const aspect = this.container.clientWidth / this.container.clientHeight;
        this.camera = new THREE.PerspectiveCamera(fov, aspect, 0.1, 1000);
        this.updateCameraPosition();
    }

    createRenderer() {
        this.renderer = new THREE.WebGLRenderer({ 
            antialias: true,
            alpha: true // Enable transparency
        });
        this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
        this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        this.container.appendChild(this.renderer.domElement);
    }

    createLighting() {
        // Ambient light for overall illumination
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
        this.scene.add(ambientLight);

        // Directional light for shadows and highlights
        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
        directionalLight.position.set(3, 3, 3);
        this.scene.add(directionalLight);

        // Fill light to reduce harsh shadows
        const fillLight = new THREE.DirectionalLight(0xffffff, 0.3);
        fillLight.position.set(-3, -3, 2);
        this.scene.add(fillLight);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🪙 Step 5: Building the Coin
&lt;/h2&gt;

&lt;p&gt;Now we get to the fun part — the coin itself. Think of it like assembling LEGO, but in 3D and with metal that actually shines.&lt;/p&gt;

&lt;p&gt;We break the coin into three main parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Faces&lt;/strong&gt; – Front and back, each with dynamic textures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge&lt;/strong&gt; – The metallic rim that makes it look real.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decorative elements&lt;/strong&gt; – Stars, arrows, text — all the little flourishes that make your coin pop.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;createCoin() {
    const coinGroup = new THREE.Group();

    const coinRadius = 1.0;
    const coinThickness = 0.16;
    const faceZOffset = coinThickness / 2;

    // Create both faces
    this.createCoinFaceContent(coinGroup, faceZOffset, false);  // Front
    this.createCoinFaceContent(coinGroup, -faceZOffset, true);  // Back

    // Create coin edge
    this.edgeMesh = this.createCoinEdge(coinRadius, coinThickness);
    coinGroup.add(this.edgeMesh);

    this.coin = coinGroup;
    this.coin.rotation.x = Math.PI / 2; // Stand upright
    this.scene.add(this.coin);
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The edge itself is a simple cylinder with a nice metal material. We rotate it so it wraps around the coin properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;createCoinEdge(radius, thickness) {
    const edgeGeometry = new THREE.CylinderGeometry(radius, radius, thickness, 64, 1, true);
    const edgeMaterial = new THREE.MeshPhongMaterial({
        color: this.simpleEdgeColor,
        side: THREE.FrontSide,
        shininess: 30,
    });

    const edgeMesh = new THREE.Mesh(edgeGeometry, edgeMaterial);
    edgeMesh.rotation.x = Math.PI / 2;
    return edgeMesh;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Pro tip: By separating faces and edges into a Group, you can rotate, flip, or animate the whole coin without worrying about individual pieces. Plus, reusing this structure makes adding particles, stars, or text a breeze.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamic Face Texture Generation&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;createFaceTexture() {
    const canvasSize = 512;
    const canvas = document.createElement('canvas');
    canvas.width = canvasSize;
    canvas.height = canvasSize;
    const context = canvas.getContext('2d');

    // Create gradient background
    const gradient = context.createLinearGradient(0, 0, 0, canvasSize);
    gradient.addColorStop(0, this.computedStyle('--coin-gradient-start'));
    gradient.addColorStop(1, this.computedStyle('--coin-gradient-end'));
    context.fillStyle = gradient;
    context.fillRect(0, 0, canvas.width, canvas.height);

    // Add decorative elements (arrows in this case)
    this.drawArrowShape(context, canvasSize);

    return new THREE.CanvasTexture(canvas);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Arrow Shape Generation&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drawArrowShape(context, canvasSize) {
    const s = canvasSize / 380; // Scale factor

    // Draw left arrow
    context.save();
    context.translate(46 * s, 262 * s);
    context.rotate(-18 * (Math.PI / 180));
    context.translate(-42 * s, -262 * s);

    context.beginPath();
    context.moveTo(5 * s, 229 * s);
    context.lineTo(42 * s, 271 * s);
    context.lineTo(93 * s, 252 * s);
    context.closePath();
    context.fillStyle = this.computedStyle('--text-color-dark');
    context.fill();
    context.restore();

    // Draw right arrow (mirrored)
    context.save();
    context.translate(canvasSize, 0);
    context.scale(-1, 1);
    // ... same drawing code for mirrored arrow
    context.restore();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ✍️ Step 6: 3D Text Implementation
&lt;/h2&gt;

&lt;p&gt;Two kinds of text give the coin depth:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Central text&lt;/strong&gt; → beveled, feels engraved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Curved text&lt;/strong&gt; → wraps around the rim.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both use &lt;code&gt;TextGeometry&lt;/code&gt; and a loaded font.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;createCentralText(text, x, yOffset, zOffset, color, size, fontObject) {
    if (!fontObject) {
        console.warn("Font not loaded. Skipping text creation for:", text);
        return null;
    }

    const textColor = new THREE.Color(color);
    const height = size * 0.15;
    const bevelThickness = size * 0.03;

    const textGeometry = new THREE.TextGeometry(text, {
        font: fontObject,
        size: size,
        height: height,
        curveSegments: 8,
        bevelEnabled: true,
        bevelThickness: bevelThickness,
        bevelSize: size * 0.02,
        bevelSegments: 3
    });

    // Center the geometry
    textGeometry.computeBoundingBox();
    textGeometry.center();

    const material = new THREE.MeshPhongMaterial({
        color: textColor,
        specular: 0xffffff,
        shininess: 60,
        flatShading: false
    });

    const textMesh = new THREE.Mesh(textGeometry, material);
    textMesh.position.set(x, yOffset, zOffset);

    return textMesh;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Curved Text Around Coin Perimeter&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;generateCurvedTextMesh(parentGroup, textString, radius, startAngle, endAngle, zOffset, color, fontObject, textWorldHeight) {
    if (!fontObject) return;

    const textColor = new THREE.Color(color);
    const textGroup = new THREE.Group();
    const sizeFactor = 0.25;

    let accumulatedAngle = 0;
    let lastCharHalfWidth = 0;

    for (let i = 0; i &amp;lt; textString.length; i++) {
        const char = textString[i];

        if (char === ' ') {
            // Handle spaces with angular spacing
            const spaceWidth = (textWorldHeight * 0.05);
            const angularChange = (lastCharHalfWidth + spaceWidth) / radius;
            accumulatedAngle += angularChange;
            lastCharHalfWidth = spaceWidth;
            continue;
        }

        const textGeometry = new THREE.TextGeometry(char, {
            font: fontObject,
            size: textWorldHeight * sizeFactor,
            height: textWorldHeight * 0.08,
            bevelEnabled: true,
            bevelThickness: textWorldHeight * 0.015,
        });

        textGeometry.computeBoundingBox();
        const charWidth = textGeometry.boundingBox.max.x - textGeometry.boundingBox.min.x;
        const charHalfWidth = charWidth / 2;

        // Calculate position along curve
        const totalWidthNeeded = (lastCharHalfWidth + charHalfWidth) * 1.2;
        const angularChange = totalWidthNeeded / radius;
        accumulatedAngle += angularChange;

        const charAngle = startAngle - accumulatedAngle;

        const material = new THREE.MeshPhongMaterial({ color: textColor });
        textGeometry.center();

        const charMesh = new THREE.Mesh(textGeometry, material);

        // Position character on circular path
        charMesh.position.x = radius * Math.cos(charAngle);
        charMesh.position.y = radius * Math.sin(charAngle);
        charMesh.rotation.z = charAngle - Math.PI / 2; // Tangent to circle

        textGroup.add(charMesh);
        lastCharHalfWidth = charHalfWidth;
    }

    parentGroup.add(textGroup);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ By computing bounding boxes + centering, you avoid awkward alignment issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⭐ Step 7: Extruded Stars
&lt;/h2&gt;

&lt;p&gt;We generate stars with Shape + &lt;code&gt;ExtrudeGeometry&lt;/code&gt;.&lt;br&gt;
The bevel + emissive glow makes them pop when the coin rotates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create3DStar(color, size) {
    const starShape = new THREE.Shape();
    const spikes = 5;
    const outerRadius = size * 0.5;
    const innerRadius = size * 0.2;

    // Create star points
    for (let i = 0; i &amp;lt; spikes * 2; i++) {
        const radius = i % 2 === 0 ? outerRadius : innerRadius;
        const angle = (i * Math.PI) / spikes - Math.PI / 2;
        const x = Math.cos(angle) * radius;
        const y = Math.sin(angle) * radius;

        if (i === 0) {
            starShape.moveTo(x, y);
        } else {
            starShape.lineTo(x, y);
        }
    }
    starShape.closePath();

    // Extrude to create 3D depth
    const extrudeDepth = size * 0.15;
    const bevelThickness = size * 0.03;

    const extrudeSettings = {
        depth: extrudeDepth,
        bevelEnabled: true,
        bevelThickness: bevelThickness,
        bevelSize: size * 0.02,
        bevelSegments: 3
    };

    const geometry = new THREE.ExtrudeGeometry(starShape, extrudeSettings);
    geometry.translate(0, 0, -extrudeDepth / 2); // Center the geometry

    const material = new THREE.MeshPhongMaterial({
        color: color,
        emissive: color,
        emissiveIntensity: 0.3,
        specular: 0xffffff,
        shininess: 60,
    });

    const starMesh = new THREE.Mesh(geometry, material);
    starMesh.userData.totalDepth = extrudeDepth + bevelThickness;

    return starMesh;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ They also serve as a subtle light indicator — showing reflections as the camera moves.&lt;/p&gt;

&lt;h2&gt;
  
  
  🌌 Step 8: Particle System with Shaders
&lt;/h2&gt;

&lt;p&gt;Flat meshes would kill performance, so instead we go shader-based with &lt;code&gt;Points&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each particle drifts/fades based on lifetime.&lt;/li&gt;
&lt;li&gt;Ring distribution → looks like dust/glow orbiting the coin.&lt;/li&gt;
&lt;li&gt;Lightweight enough for mobile.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ Bonus: GLSL shaders let us animate thousands of points without bottlenecks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;createParticles() {
    const particleCount = 50;
    const particlesGeometry = new THREE.BufferGeometry();

    // Initialize buffers
    const positions = new Float32Array(particleCount * 3);
    const sizes = new Float32Array(particleCount);
    const colors = new Float32Array(particleCount * 3);

    for (let i = 0; i &amp;lt; particleCount; i++) {
        const i3 = i * 3;
        const radius = 1.1 + Math.random() * 0.2;
        const angle = Math.random() * Math.PI * 2;

        // Position particles in a ring around the coin
        positions[i3] = Math.cos(angle) * radius;
        positions[i3 + 1] = Math.sin(angle) * radius;
        positions[i3 + 2] = (Math.random() - 0.5) * 0.1;

        sizes[i] = 0.01 + Math.random() * 0.03;

        // White particles
        colors[i3] = 1.0;
        colors[i3 + 1] = 1.0;
        colors[i3 + 2] = 1.0;

        // Store particle animation data
        this.particlesData.push({
            initialX: positions[i3],
            initialY: positions[i3 + 1],
            initialZ: positions[i3 + 2],
            currentY: positions[i3 + 1],
            velocity: 0.005 + Math.random() * 0.005,
            rotationSpeed: (Math.random() - 0.5) * 0.05,
            life: Math.random() * 3,
            maxLife: 3 + Math.random() * 2
        });
    }

    // Set geometry attributes
    particlesGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    particlesGeometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
    particlesGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

    // Custom shader material for particles
    const particleMaterial = new THREE.ShaderMaterial({
        uniforms: {
            pointTexture: { 
                value: new THREE.TextureLoader().load(
                    'https://threejs.org/examples/textures/sprites/disc.png'
                ) 
            }
        },
        vertexShader: `
            attribute float size;
            attribute vec3 color;
            varying vec3 vColor;

            void main() {
                vColor = color;
                vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
                gl_PointSize = size * (300.0 / -mvPosition.z);
                gl_Position = projectionMatrix * mvPosition;
            }
        `,
        fragmentShader: `
            uniform sampler2D pointTexture;
            varying vec3 vColor;

            void main() {
                gl_FragColor = vec4(vColor, vColor.r) * texture2D(pointTexture, gl_PointCoord);
            }
        `,
        transparent: true,
        blending: THREE.AdditiveBlending,
        depthWrite: false,
    });

    this.particles = new THREE.Points(particlesGeometry, particleMaterial);
    this.particles.rotation.x = Math.PI / 2;
    this.scene.add(this.particles);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🖱 Step 9: Interactivity
&lt;/h2&gt;

&lt;p&gt;Now we make the coin come alive. This is what turns a static 3D object into something playful and tactile.&lt;/p&gt;

&lt;p&gt;We handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parallax&lt;/strong&gt; tilt when the user moves the mouse or finger.&lt;/li&gt;
&lt;li&gt;“&lt;strong&gt;Breathing&lt;/strong&gt;” scale on hover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;180° flip&lt;/strong&gt; on click or tap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Click ripple&lt;/strong&gt; — purely visual, no extra GPU stress.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setupEventListeners() {
    const canvas = this.renderer.domElement;

    // Mouse movement for parallax effect
    const onPointerMove = (event) =&amp;gt; {
        if (this.isAnimating) return;

        const rect = canvas.getBoundingClientRect();
        let clientX, clientY;

        if (event.touches) {
            clientX = event.touches[0].clientX;
            clientY = event.touches[0].clientY;
        } else {
            clientX = event.clientX;
            clientY = event.clientY;
        }

        // Convert to normalized device coordinates (-1 to +1)
        const x = ((clientX - rect.left) / rect.width) * 2 - 1;
        const y = -((clientY - rect.top) / rect.height) * 2 + 1;

        // Update target rotation for smooth interpolation
        this.targetRotationY = x * 0.3;
        this.targetRotationX = y * 0.3;
    };

    // Hover effects
    const onPointerEnter = () =&amp;gt; {
        if (this.isAnimating) return;
        this.targetCoinScale = 1.05;
        this.targetEmissiveIntensity = 0.5;
    };

    const onPointerLeave = () =&amp;gt; {
        if (this.isAnimating) return;
        this.targetCoinScale = 1;
        this.targetEmissiveIntensity = 0;
        this.targetRotationX = 0;
        this.targetRotationY = 0;
    };

    // Click to flip
    const onPointerClick = (event) =&amp;gt; {
        if (this.isAnimating) return;
        this.createClickRipple(event);
        this.flipCoin();
    };

    // Register event listeners
    canvas.addEventListener('mousemove', onPointerMove);
    canvas.addEventListener('mouseenter', onPointerEnter);
    canvas.addEventListener('mouseleave', onPointerLeave);
    canvas.addEventListener('click', onPointerClick);

    // Touch events for mobile
    canvas.addEventListener('touchmove', onPointerMove, { passive: true });
    canvas.addEventListener('touchstart', onPointerEnter, { passive: true });
    canvas.addEventListener('touchend', onPointerLeave, { passive: true });

    window.addEventListener('resize', () =&amp;gt; this.onWindowResize());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Ripple Effect on Click&lt;/strong&gt;&lt;br&gt;
We make a tiny ripple where you click — no JS-heavy animation, just CSS + DOM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;createClickRipple(event) {
    const ripple = document.createElement('div');
    ripple.className = 'click-ripple';

    const rect = this.container.getBoundingClientRect();
    let clientX, clientY;

    if (event.touches) {
        clientX = event.touches[0].clientX;
        clientY = event.touches[0].clientY;
    } else {
        clientX = event.clientX;
        clientY = event.clientY;
    }

    ripple.style.left = `${clientX - rect.left}px`;
    ripple.style.top = `${clientY - rect.top}px`;

    this.container.appendChild(ripple);

    // Cleanup after animation
    setTimeout(() =&amp;gt; {
        if (ripple.parentNode) {
            ripple.parentNode.removeChild(ripple);
        }
    }, 800);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Flip Animation with Easing&lt;/strong&gt;&lt;br&gt;
Clicking flips the coin 180°, with smooth cubic easing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flipCoin() {
    if (this.isFlipping) return;

    this.isFlipping = true;
    this.isAnimating = true;

    this.flipStartTime = performance.now();
    this.flipStartRotationY = this.coin.rotation.y;
    const rotationAmount = Math.PI; // 180 degrees
    this.flipEndRotationY = this.coin.rotation.y + rotationAmount;

    // Reset visual effects during flip
    this.targetEmissiveIntensity = 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the animation loop, we interpolate rotation using cubic easing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (this.isFlipping) {
    const elapsedTime = performance.now() - this.flipStartTime;
    const progress = Math.min(elapsedTime / this.flipDuration, 1);

    // Cubic easing function for smooth animation
    const easedProgress = progress &amp;lt; 0.5
        ? 4 * progress * progress * progress
        : 1 - Math.pow(-2 * progress + 2, 3) / 2;

    this.coin.rotation.y = this.flipStartRotationY + 
        (this.flipEndRotationY - this.flipStartRotationY) * easedProgress;

    if (progress &amp;gt;= 1) {
        this.isFlipping = false;
        this.isAnimating = false;
        this.isFlipped = !this.isFlipped;
        this.coin.rotation.y %= (Math.PI * 2); // Normalize rotation
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Pro tip: Separating the animation logic (flip, scale, parallax) from input events makes the coin feel super responsive and avoids conflicts between hover and click actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎞 Step 10: Animation Loop
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;animate()&lt;/code&gt; method is the heartbeat of the coin. It handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Breathing scale pulses&lt;/li&gt;
&lt;li&gt;Smooth parallax rotation&lt;/li&gt;
&lt;li&gt;Star pulsing&lt;/li&gt;
&lt;li&gt;Particle drift/lifecycle&lt;/li&gt;
&lt;li&gt;Flip animation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything updates per frame with &lt;code&gt;requestAnimationFrame&lt;/code&gt;, keeping GPU load minimal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;animate() {
    requestAnimationFrame(() =&amp;gt; this.animate());

    // Breathing animation (subtle scale pulsing)
    this.breathingTimer += 0.02;
    const breathingScale = 1 + Math.sin(this.breathingTimer) * 0.03;

    if (!this.isFlipping) {
        // Smooth interpolation for hover scale
        this.currentCoinScale += (this.targetCoinScale - this.currentCoinScale) * 0.1;
        this.coin.scale.setScalar(this.currentCoinScale * breathingScale);

        // Smooth interpolation for rotation (parallax effect)
        this.coin.rotation.x += (this.targetRotationX - this.coin.rotation.x) * 0.1;
        this.coin.rotation.y += (this.targetRotationY - this.coin.rotation.y) * 0.1;
    } else {
        this.coin.scale.setScalar(breathingScale);
    }

    // Animate stars with pulsing effect
    this.animateStars();

    // Update particle system
    this.animateParticles();

    this.renderer.render(this.scene, this.camera);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Star Animation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stars subtly pulse in color and glow to add realism:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;animateStars() {
    const WHITE = new THREE.Color(0xFFFFFF);
    const GOLD = new THREE.Color('#ffd700');

    this.stars.forEach(starMesh =&amp;gt; {
        if (starMesh.userData.pulseDelay &amp;gt; 0) {
            starMesh.userData.pulseDelay -= 0.016;
            return;
        }

        starMesh.userData.pulseTimer += 0.01;
        const pulseFactor = (Math.sin(starMesh.userData.pulseTimer) + 1) / 2;

        // Interpolate between white and gold
        starMesh.material.color.lerpColors(WHITE, GOLD, pulseFactor);
        starMesh.material.emissive.lerpColors(WHITE, GOLD, pulseFactor);
        starMesh.material.emissiveIntensity = 0.3 + pulseFactor * 0.7;
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Particle Animation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Particles float around the coin, fading in/out for a soft orbiting effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;animateParticles() {
    if (!this.particles) return;

    const positions = this.particles.geometry.attributes.position.array;
    const colors = this.particles.geometry.attributes.color.array;

    for (let i = 0; i &amp;lt; this.particlesData.length; i++) {
        const p = this.particlesData[i];
        const i3 = i * 3;

        p.life += 0.016; // ~60fps

        // Reset particles that have completed their lifecycle
        if (p.life &amp;gt; p.maxLife) {
            p.life = 0;
            const radius = 1.1 + Math.random() * 0.2;
            const angle = Math.random() * Math.PI * 2;
            p.initialX = Math.cos(angle) * radius;
            p.initialY = Math.sin(angle) * radius;
            p.currentY = p.initialY;
        }

        // Update particle positions
        positions[i3] = p.initialX;
        p.currentY = p.initialY + (p.life / p.maxLife) * 0.3; // Rise upward
        positions[i3 + 1] = p.currentY;
        positions[i3 + 2] = p.initialZ + Math.sin(p.life * p.rotationSpeed) * 0.05;

        // Fade out based on life
        const opacity = 1 - (p.life / p.maxLife);
        colors[i3] = opacity;
        colors[i3 + 1] = opacity;
        colors[i3 + 2] = opacity;
    }

    // Mark buffers for update
    this.particles.geometry.attributes.position.needsUpdate = true;
    this.particles.geometry.attributes.color.needsUpdate = true;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;✅ Why this setup works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Separates logic for &lt;strong&gt;stars, particles, coin rotation, and scale&lt;/strong&gt;, avoiding interference.&lt;/li&gt;
&lt;li&gt;Smooth &lt;strong&gt;interpolation (lerp)&lt;/strong&gt; for hover and parallax keeps the coin responsive.&lt;/li&gt;
&lt;li&gt;Efficient &lt;strong&gt;buffer updates&lt;/strong&gt; and &lt;code&gt;requestAnimationFrame&lt;/code&gt; ensures high performance, even on mobile.&lt;/li&gt;
&lt;li&gt;Centralizes all visual updates in a single loop for maintainability.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔢 Step 11: Counting Animation
&lt;/h2&gt;

&lt;p&gt;The idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The center text &lt;strong&gt;increments smoothly&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Each number is a &lt;strong&gt;3D text mesh&lt;/strong&gt;, updated dynamically.&lt;/li&gt;
&lt;li&gt;Memory cleanup is crucial because old meshes are removed every step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Creating the Inner Content&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;createInnerContent(parentGroup, isBack) {
    const innerGroup = new THREE.Group();

    // ... create inner circle and border ...

    let initialText;
    let shouldStartAnimation = false;

    if (isBack) {
        initialText = this.targetCount.toString();
    } else {
        initialText = '1';
        shouldStartAnimation = true;
    }

    const centralTextMesh = this.createCentralText(
        initialText, 0, 0.21, 0.05, 
        this.computedStyle('--text-color-dark'), 0.42, this.primary_font
    );

    if (centralTextMesh) {
        if (shouldStartAnimation) {
            this.countTextMesh = centralTextMesh;
        }
        innerGroup.add(centralTextMesh);
    }

    if (shouldStartAnimation) {
        this.startCountAnimation(innerGroup);
    }

    parentGroup.add(innerGroup);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ The &lt;code&gt;isBack&lt;/code&gt; flag allows you to set a static number on the back face, while the front face animates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Starting the Animation&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;startCountAnimation(parentGroup) {
    if (!this.primary_font) return;

    this.currentCount = 1;
    const animationDuration = 3000; // 3 seconds
    const totalIncrements = this.targetCount - this.currentCount;
    const intervalTime = animationDuration / totalIncrements;

    this.countIntervalId = setInterval(() =&amp;gt; {
        if (this.currentCount &amp;lt; this.targetCount) {
            this.currentCount++;
            this.updateCountTextMesh(parentGroup);
        } else {
            clearInterval(this.countIntervalId);
        }
    }, intervalTime);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Computes interval time based on total increments.&lt;/li&gt;
&lt;li&gt;Smoothly updates each number until the target is reached.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Updating the Text Mesh&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;updateCountTextMesh(parentGroup) {
    if (!this.countTextMesh) return;

    // Remove old text mesh
    parentGroup.remove(this.countTextMesh);
    this.countTextMesh.geometry.dispose();
    this.countTextMesh.material.dispose();

    // Create new text mesh with updated number
    const newTextMesh = this.createCentralText(
        this.currentCount.toString(), 0, 0.21, 0.05,
        this.computedStyle('--text-color-dark'), 0.42, this.primary_font
    );

    if (newTextMesh) {
        this.countTextMesh = newTextMesh;
        parentGroup.add(this.countTextMesh);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Properly disposes geometry and material to prevent memory leaks, which is essential for smooth animation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Key Points&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic mesh creation&lt;/strong&gt; allows 3D styling, bevels, and lighting on the number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory management&lt;/strong&gt; is critical — always dispose old meshes.&lt;/li&gt;
&lt;li&gt;Works seamlessly with the &lt;strong&gt;flip animation&lt;/strong&gt; from Step 9 and the &lt;strong&gt;animation loop&lt;/strong&gt; from Step 10.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📱 Step 12: Responsive Design
&lt;/h2&gt;

&lt;p&gt;The main goals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Update camera aspect ratio&lt;/strong&gt; whenever the window resizes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resize the renderer&lt;/strong&gt; to match the container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adjust camera distance&lt;/strong&gt; so the coin fills ~90% of the viewport without clipping.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Handling Window Resize&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;onWindowResize() {
    // Update camera aspect ratio
    this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
    this.camera.updateProjectionMatrix();

    // Update renderer size
    this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);

    // Adjust camera position for new aspect ratio
    this.updateCameraPosition();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;clientWidth&lt;/code&gt; and &lt;code&gt;clientHeight&lt;/code&gt; ensure the renderer fits the container.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;updateProjectionMatrix()&lt;/code&gt; recalculates the camera’s perspective.&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;updateCameraPosition()&lt;/code&gt; to scale the coin correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Updating Camera Distance&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;updateCameraPosition() {
    const coinWorldDiameter = 2.0;       // Coin size in world units
    const targetCoinCoverage = 0.9;      // Fill ~90% of the viewport

    const fovRad = this.camera.fov * Math.PI / 180;

    let distance;
    if (this.camera.aspect &amp;gt;= 1) {
        // Landscape orientation
        distance = coinWorldDiameter / (targetCoinCoverage * 2 * Math.tan(fovRad / 2));
    } else {
        // Portrait orientation
        const hfovRad = 2 * Math.atan(Math.tan(fovRad / 2) * this.camera.aspect);
        distance = coinWorldDiameter / (targetCoinCoverage * 2 * Math.tan(hfovRad / 2));
    }

    this.camera.position.z = distance * 1.05; // Slightly further for padding
    this.camera.lookAt(0, 0, 0);              // Always center the coin
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Calculates &lt;strong&gt;camera distance&lt;/strong&gt; based on coin size and viewport.&lt;/li&gt;
&lt;li&gt;Adjusts for &lt;strong&gt;landscape vs. portrait&lt;/strong&gt; aspect ratios.&lt;/li&gt;
&lt;li&gt;Multiplying by &lt;strong&gt;1.05&lt;/strong&gt; gives a tiny buffer so the coin isn’t touching screen edges.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;✅ Key Points&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Works on &lt;strong&gt;all screen sizes&lt;/strong&gt;, including mobile and tablets.&lt;/li&gt;
&lt;li&gt;Coin scales &lt;strong&gt;automatically&lt;/strong&gt; without distortion.&lt;/li&gt;
&lt;li&gt;Keeps the &lt;strong&gt;camera centered and properly framed&lt;/strong&gt; for the best visual balance.&lt;/li&gt;
&lt;li&gt;Plays nicely with &lt;strong&gt;parallax, flip, and particle animations&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📱 Browser Compatibility
&lt;/h2&gt;

&lt;p&gt;This implementation works in all modern browsers that support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WebGL&lt;/li&gt;
&lt;li&gt;ES6 Classes&lt;/li&gt;
&lt;li&gt;CSS Variables&lt;/li&gt;
&lt;li&gt;Promises&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For older browser support, add polyfills for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Promise&lt;/li&gt;
&lt;li&gt;Object.assign&lt;/li&gt;
&lt;li&gt;requestAnimationFrame&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 Performance Optimization Tips (Extra)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Geometry Reuse&lt;/strong&gt;: Reuse geometries where possible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Material Sharing&lt;/strong&gt;: Share materials between similar objects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buffer Updates&lt;/strong&gt;: Only update buffer attributes when necessary&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object Pooling&lt;/strong&gt;: Reuse particle objects instead of recreating&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level of Detail&lt;/strong&gt;: Use simpler geometries for distant objects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Texture Compression&lt;/strong&gt;: Compress textures and use appropriate formats&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔧 Troubleshooting Common Issues (Extra)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Font Loading Problems&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
loadAssets() {
    return new Promise((resolve) =&amp;gt; {
        const fontLoader = new THREE.FontLoader();

        fontLoader.load(primaryFontUrl, (font) =&amp;gt; {
            this.primary_font = font;
            resolve();
        }, 
        undefined, 
        (error) =&amp;gt; {
            console.warn("Primary font failed, using fallback:", error);
            // Implement fallback font loading
        });
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Memory Leaks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Always clean up when removing objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;updateCountTextMesh(parentGroup) {
    // Proper cleanup
    parentGroup.remove(this.countTextMesh);
    this.countTextMesh.geometry.dispose();
    this.countTextMesh.material.dispose();
    this.countTextMesh = null;

    // ... create new mesh
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Mobile Performance&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;createRenderer() {
    this.renderer = new THREE.WebGLRenderer({ 
        antialias: false, // Disable on mobile for performance
        alpha: true,
        powerPreference: 'high-performance'
    });

    // Limit pixel ratio on mobile
    const maxPixelRatio = window.devicePixelRatio &amp;gt; 1 ? 1.5 : 1;
    this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, maxPixelRatio));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  💡 Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Three.js Fundamentals&lt;/strong&gt;: Mastering scenes, cameras, renderers, and geometries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance Optimization&lt;/strong&gt;: Efficient buffer management and object pooling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Experience&lt;/strong&gt;: Smooth animations and responsive interactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Shaders&lt;/strong&gt;: GPU-accelerated effects for complex visuals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform&lt;/strong&gt;: Responsive design that works on all devices&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🎯 Demo &amp;amp; full source
&lt;/h2&gt;

&lt;p&gt;👉 Interactive demo on CodePen:&lt;br&gt;
&lt;a href="https://codepen.io/Shahibur-Rahman/pen/myVryeK" rel="noopener noreferrer"&gt;https://codepen.io/Shahibur-Rahman/pen/myVryeK&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 Full source code on GitHub:&lt;br&gt;
&lt;a href="https://github.com/d5b94396feba3/3D-Interactive-Coin-ThreeJS" rel="noopener noreferrer"&gt;https://github.com/d5b94396feba3/3D-Interactive-Coin-ThreeJS&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🎉 Wrapping Up
&lt;/h2&gt;

&lt;p&gt;And that’s our interactive 3D flipping coin! 🪙✨&lt;/p&gt;

&lt;p&gt;This implementation provides a solid foundation for creating interactive 3D experiences that you can adapt for various use cases like product showcases, educational tools, or interactive awards.&lt;/p&gt;

&lt;p&gt;I’d love to see how you adapt it — if you build your own version, share it in the comments or drop a link. 🚀&lt;/p&gt;

</description>
      <category>threejs</category>
      <category>javascript</category>
      <category>webgl</category>
      <category>animation</category>
    </item>
    <item>
      <title>Build a 3D Flipping Coin with HTML, CSS &amp; JavaScript — deep dive</title>
      <dc:creator>Shahibur Rahman</dc:creator>
      <pubDate>Mon, 29 Sep 2025 19:19:01 +0000</pubDate>
      <link>https://forem.com/shahibur_rahman_6670cd024/build-a-3d-flipping-coin-with-html-css-javascript-deep-dive-26h2</link>
      <guid>https://forem.com/shahibur_rahman_6670cd024/build-a-3d-flipping-coin-with-html-css-javascript-deep-dive-26h2</guid>
      <description>&lt;p&gt;I built an interactive 3D coin component that flips on click, has curved SVG text and a stacked-edge illusion that gives depth. This article explains everything — the &lt;strong&gt;core 3D system&lt;/strong&gt;, &lt;strong&gt;how the coin edge is built&lt;/strong&gt;, &lt;strong&gt;SVG curved text fitting&lt;/strong&gt;, and the &lt;strong&gt;click/flip timing&lt;/strong&gt; — with clear, copy-able snippets so you (or any reader) can recreate it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Live demo (interactive):&lt;br&gt;&lt;br&gt;


&lt;iframe height="600" src="https://codepen.io/Shahibur-Rahman/embed/zxrqpGz?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;


&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Project overview (what lives where)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTML&lt;/strong&gt;: semantic container &lt;code&gt;.scene&lt;/code&gt;, &lt;code&gt;.coin&lt;/code&gt;, &lt;code&gt;.coin-face&lt;/code&gt; for front/back; SVGs for curved text; &lt;code&gt;.inner-content&lt;/code&gt; for central circle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSS&lt;/strong&gt;: CSS variables for colors and timing, perspective, &lt;code&gt;transform-style: preserve-3d&lt;/code&gt;, &lt;code&gt;backface-visibility&lt;/code&gt;, small animations (star pulse, ripple), responsive tweaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JS&lt;/strong&gt;: helpers that:

&lt;ul&gt;
&lt;li&gt;programmatically create the visual coin edge (&lt;code&gt;buildCoinEdges&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;fit SVG curved text to the arc (&lt;code&gt;fitTextToArc&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;create particles (decorative),&lt;/li&gt;
&lt;li&gt;handle click ripple + flip with careful timing and state control.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Minimal HTML skeleton (core structure)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div id="scene" class="scene" role="img" aria-label="Interactive 3D Commemorative Coin"&amp;gt;
  &amp;lt;div id="coin" class="coin"&amp;gt;
    &amp;lt;div class="coin-face coin-front"&amp;gt; ... front ... &amp;lt;/div&amp;gt;
    &amp;lt;div class="coin-face coin-back"&amp;gt;  ... back  ... &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;.scene&lt;/strong&gt; gives a local perspective (distance to the viewer).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;.coin&lt;/strong&gt; is a 3D object (children keep their 3D transforms).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;.coin-face&lt;/strong&gt; are circles, absolutely positioned, with &lt;strong&gt;backface-visibility: hidden&lt;/strong&gt; so the back of each face is invisible when rotated away.&lt;/p&gt;

&lt;h2&gt;
  
  
  ♿ Accessibility: role + aria-label
&lt;/h2&gt;

&lt;p&gt;We add ARIA attributes so assistive tech treats the coin as an image-like element. That way, a screen reader won’t just read out a bunch of &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s and &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; paths.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div id="scene"
     class="scene"
     role="img"
     aria-label="Interactive 3D Commemorative Coin"&amp;gt;
  ...
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;role="img" tells screen readers this is a visual graphic.&lt;/li&gt;
&lt;li&gt;aria-label gives a human-readable description.&lt;/li&gt;
&lt;li&gt;For production, you can localize or customize this description.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key CSS pieces — the 3D foundation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:root { --flip-duration: 2.8s; }

body { perspective: 1200px; }   /* global depth context */
.scene { perspective: 1000px; } /* local depth for coin area */

.coin {
  transform-style: preserve-3d;
  transition: transform var(--flip-duration) ease-in-out;
}
.coin.is-flipped {
  transform: rotateY(180deg) !important;
}

.coin-face {
  position: absolute;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  backface-visibility: hidden;
  transform-style: preserve-3d;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why these matter
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;perspective&lt;/strong&gt; provides depth; lower values make the element appear more foreshortened.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;transform-style&lt;/strong&gt;: &lt;strong&gt;preserve-3d&lt;/strong&gt; keeps children transformed in 3D space (instead of flattening them).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;backface-visibility&lt;/strong&gt;: hidden prevents mirrored text from showing when a face rotates away.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;.is-flipped&lt;/strong&gt; class toggles a &lt;strong&gt;rotateY(180deg)&lt;/strong&gt; on the &lt;strong&gt;.coin&lt;/strong&gt; to flip it (CSS transition animates the flip).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Deep dive: core building blocks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) 3D transform system
&lt;/h3&gt;

&lt;p&gt;This tiny set of CSS + DOM rules is the backbone. Without correct &lt;strong&gt;perspective, transform-style&lt;/strong&gt;, and &lt;strong&gt;backface-visibility&lt;/strong&gt;, you won't get a convincing coin.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;body { perspective: 1200px }&lt;/strong&gt; — sets the viewer-camera distance; larger → weaker perspective.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.scene { perspective: 1000px }&lt;/strong&gt; — sets perspective for the coin area specifically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.coin&lt;/strong&gt; performs rotations. When you rotateY(180deg) the whole coin, front/back switch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common gotcha&lt;/strong&gt;: If &lt;strong&gt;backface-visibility&lt;/strong&gt; is omitted, the backside of a face can be visible mirrored when rotated.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Coin edge illusion: buildCoinEdges() (why it matters)
&lt;/h3&gt;

&lt;p&gt;A coin is thin, but visually convincing if you programmatically place many thin layers (.coin-edge) along Z. The project fakes thickness by creating many concentric rings each translated by small translateZ offsets.&lt;/p&gt;

&lt;h4&gt;
  
  
  Key function (full):
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function buildCoinEdges(coinSelector, edgeCount = 60, step = 1.2) {
  const coinElement = document.querySelector(coinSelector);
  if (!coinElement) return;

  // remove old edges
  coinElement.querySelectorAll('.coin-edge').forEach(edge =&amp;gt; edge.remove());

  for (let i = 0; i &amp;lt; edgeCount; i++) {
    const edge = document.createElement('div');
    edge.classList.add('coin-edge');

    // alternate gradient for visual texture
    if (i % 3 === 0) {
      edge.style.background = 'linear-gradient(90deg, #777 0%, #555 50%, #777 100%)';
    } else if (i % 3 === 1) {
      edge.style.background = 'linear-gradient(90deg, #666 0%, #444 50%, #666 100%)';
    } else {
      edge.style.background = 'linear-gradient(90deg, #777 0%, #555 50%, #777 100%)';
    }

    edge.style.boxShadow = 'inset 0 0 8px rgba(0,0,0,0.7)';
    edge.style.border = '1px solid #444';

    const offset = (i - edgeCount / 2) * step;
    edge.style.transform = `translateZ(${offset}px)`;

    coinElement.appendChild(edge);
  }

  // push front/back faces forward so they sit outside the edge stack
  const maxOffset = (edgeCount / 2) * step;
  const front = coinElement.querySelector('.coin-front');
  const back = coinElement.querySelector('.coin-back');

  if (front) front.style.transform = `rotateY(0deg) translateZ(${maxOffset}px)`;
  if (back) back.style.transform = `rotateY(180deg) translateZ(${maxOffset}px)`;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Why &lt;strong&gt;translateZ?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Each &lt;strong&gt;.coin-edge&lt;/strong&gt; is the same size as the coin but moved slightly on Z. Stacking many creates the visual ridged edge.&lt;/p&gt;

&lt;h4&gt;
  
  
  Numeric example
&lt;/h4&gt;

&lt;p&gt;If &lt;strong&gt;edgeCount = 24&lt;/strong&gt; and &lt;strong&gt;step = 1.2&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;edgeCount / 2 = 24 / 2 = 12.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;maxOffset = 12 * 1.2 = 14.4&lt;/strong&gt; (units are px in the style string). So front/back faces are translated &lt;strong&gt;translateZ(14.4px)&lt;/strong&gt; to sit just outside the center of the stack.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Pitfalls
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Too many edges → performance hit. Use 16–36 for a good tradeoff.&lt;/li&gt;
&lt;li&gt;Using fractional translateZ is fine; browsers accept decimal px.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3) SVG curved text + fitTextToArc() (line-by-line)
&lt;/h2&gt;

&lt;p&gt;Curved text is achieved using an SVG &lt;strong&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;/strong&gt;. To make the text fit the curve regardless of viewport changes, compute the path length and set &lt;strong&gt;textLength + lengthAdjust&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Key function:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function fitTextToArc(pathElement, textPathElement){
  if (!pathElement || !textPathElement) return;
  const pathLen = pathElement.getTotalLength();
  const padding = 5; // small breathing room
  const available = pathLen + padding;
  textPathElement.setAttribute('lengthAdjust','spacingAndGlyphs');
  textPathElement.setAttribute('textLength', available.toFixed(1));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  How it works
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;pathElement.getTotalLength()&lt;/strong&gt; returns the path arc length in px.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;textLength&lt;/strong&gt; tells the browser how much space the text should occupy along the path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;lengthAdjust="spacingAndGlyphs"&lt;/strong&gt; allows the browser to scale spacing/glyphs so the string fits visually along that length.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Do this on:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;window.load&lt;/strong&gt; (initial layout)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;window.resize&lt;/strong&gt; (when layout changes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pitfall&lt;/strong&gt;: If you have duplicate SVG &lt;strong&gt;ids&lt;/strong&gt; (e.g., same &lt;strong&gt;badgeGradient&lt;/strong&gt; or &lt;strong&gt;path&lt;/strong&gt; id on front/back), you may get unpredictable rendering. Give unique ids for each &amp;lt;&lt;strong&gt;svg&lt;/strong&gt;&amp;gt; in the document.&lt;/p&gt;

&lt;h2&gt;
  
  
  4) Click ripple + flip: state &amp;amp; timing
&lt;/h2&gt;

&lt;p&gt;The click handler needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;visually show a ripple centered on the click point,&lt;/li&gt;
&lt;li&gt;toggle the flip class,&lt;/li&gt;
&lt;li&gt;disable hover tilt while flip animation runs, and&lt;/li&gt;
&lt;li&gt;re-enable it once the flip is complete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Core snippet:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scene.addEventListener('click', function(e) {
  // create ripple
  const clickEffect = document.createElement('div');
  clickEffect.classList.add('click-effect');

  const rect = scene.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;

  clickEffect.style.left = `${x}px`;
  clickEffect.style.top = `${y}px`;

  scene.appendChild(clickEffect);
  setTimeout(() =&amp;gt; clickEffect.style.animation = 'clickRipple 0.8s forwards', 10);
  setTimeout(() =&amp;gt; scene.removeChild(clickEffect), 800);

  // flip logic
  const flipDuration = parseFloat(getComputedStyle(document.documentElement)
                       .getPropertyValue('--flip-duration'));
  // If --flip-duration: 2.8s =&amp;gt; parseFloat(...) = 2.8
  // multiply by 1000 =&amp;gt; 2800 ms

  coin.style.transform = '';          // clear any hover transform
  coin.classList.toggle('is-flipped'); // trigger flip animation

  isHovering = false;
  setTimeout(() =&amp;gt; { isHovering = true; }, flipDuration * 1000);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ✨ Particles (decorative extras)
&lt;/h2&gt;

&lt;p&gt;Particles add subtle depth and motion. They aren’t required for the coin effect, but they enhance the polish.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function spawnParticles(container, count = 20) {
  for (let i = 0; i &amp;lt; count; i++) {
    const p = document.createElement('div');
    p.classList.add('particle');
    p.style.left = Math.random() * 100 + '%';
    p.style.top = Math.random() * 100 + '%';
    container.appendChild(p);

    // fade out &amp;amp; remove
    setTimeout(() =&amp;gt; p.remove(), 4000 + Math.random() * 2000);
  }
}

// Example usage: run every few seconds
setInterval(() =&amp;gt; spawnParticles(document.getElementById('scene')), 3000);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.particle {
  position: absolute;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, transparent 70%);
  opacity: 0.7;
  animation: float 4s linear forwards;
}

@keyframes float {
  from { transform: translateY(0) scale(1); opacity: 0.7; }
  to   { transform: translateY(-60px) scale(0.4); opacity: 0; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  snippets &amp;amp; layout hints
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;CSS variables&lt;/strong&gt; (control look &amp;amp; timing from one place)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:root {
  --flip-duration: 2.8s;
  --coin-gradient-start: #173A7A;
  --coin-gradient-end: #66B7E1;
  --ripple-color: rgba(56, 189, 248, 0.6);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;SVG curved text (example):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;svg viewBox="0 0 380 380"&amp;gt;
  &amp;lt;defs&amp;gt;
    &amp;lt;path id="circle-top-front" d="M 28,190 A 162,162 0 0,1 352,190" /&amp;gt;
  &amp;lt;/defs&amp;gt;
  &amp;lt;text class="curved-text-top"&amp;gt;
    &amp;lt;textPath id="topTextPathFront" href="#circle-top-front" startOffset="50%"&amp;gt;
      20 YEARS ORLANDO &amp;amp; SURROUNDING CITIES
    &amp;lt;/textPath&amp;gt;
  &amp;lt;/text&amp;gt;
&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;countUp (brief):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function countUp(el, target, duration) {
  const startTime = performance.now();
  function update(now) {
    const elapsed = now - startTime;
    const progress = Math.min(elapsed / duration, 1);
    const current = Math.floor(progress * target);
    el.textContent = current;
    if (progress &amp;lt; 1) requestAnimationFrame(update);
    else el.textContent = target;
  }
  requestAnimationFrame(update);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📱 Responsive tweaks
&lt;/h2&gt;

&lt;p&gt;On small screens, the coin should shrink so it doesn’t overflow. A quick media query keeps it centered and contained:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    @media (max-width: 768px) {
        .scene {
            width: 85vw;
            max-width: 320px;
        }

        .curved-text-top {
            font-size: 32px !important;
            letter-spacing: 3px !important;
        }

        .curved-stars-bottom {
            font-size: 20px !important;
            letter-spacing: 6px !important;
        }

        .twenty {
            font-size: 4.5rem !important;
            margin-top: -20px !important;
            margin-bottom: -15px !important;
        }

        .years {
            font-size: 2rem !important;
            margin-bottom: -5px !important;
            margin-top: -10px !important;
        }

        .location {
            font-size: 0.9rem !important;
        }

        .inner-content-wrapper {
            width: 180px !important;
            height: 180px !important;
        }

        .inner-content {
            width: 190px !important;
            height: 190px !important;
        }

        .badge-container {
            width: 90% !important;
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This ensures the coin scales gracefully without breaking curved text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo &amp;amp; full source
&lt;/h3&gt;

&lt;p&gt;👉 Interactive demo on CodePen:&lt;br&gt;
&lt;a href="https://codepen.io/Shahibur-Rahman/pen/zxrqpGz" rel="noopener noreferrer"&gt;https://codepen.io/Shahibur-Rahman/pen/zxrqpGz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 Full source code on GitHub:&lt;br&gt;
&lt;a href="https://github.com/d5b94396feba3/3D-Flipping-Interactive-Coin" rel="noopener noreferrer"&gt;https://github.com/d5b94396feba3/3D-Flipping-Interactive-Coin&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;This project shows how a handful of CSS properties (&lt;strong&gt;perspective, transform-style, backface-visibility&lt;/strong&gt;) plus small JS utilities (edge stacking, text fitting, hover mapping) can produce a polished interactive component.&lt;/p&gt;

&lt;p&gt;Start with the &lt;strong&gt;3D foundation&lt;/strong&gt; (perspective + flip), add edge stacking for realism, then layer on &lt;strong&gt;curved text&lt;/strong&gt; and &lt;strong&gt;hover/click polish&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It’s surprisingly little code for a very convincing effect.&lt;/p&gt;

</description>
      <category>3d</category>
      <category>css</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
