Author: Omid A

  • Unlocking Enterprise-Level WordPress Development: 8 Design Patterns to Know

    Reading Time: 7 minutes

    Most WordPress agencies — even the big ones — are sitting on top of tight-coupled, spaghetti-coded monsters. You know the kind: classes that rely on hardcoded new statements, massive functions inside functions.php, and add_action() calls in constructors.

    Sure, it “works.” Until you try to write a unit test.

    Try mocking a simple dependency — boom, wpdb explodes. Try isolating a class — it bootstraps half the plugin before you reach your method. Integration hell disguised as “quick wins.”

    What’s Really Going On?

    Tight coupling and a total absence of architectural patterns.

    Most WordPress engineers never got formal exposure to software architecture. Patterns from the Gang of Four (GoF) — like Dependency Injection, Adapter, or Strategy — aren’t part of the culture. WordPress grew up as a procedural CMS, not a modern application platform. But now we’re building enterprise SaaS on it.

    And that means: we need to level up our architecture.

    Below is a ranked list of design patterns that every senior WordPress engineer should know — especially if you’re writing code for WordPress VIP, large-scale plugins. I listed the following patterns based on:

    1. Widely used in VIP-level code
    2. Improves testability and maintainability
    3. Adds performance or code clarity
    4. Works cleanly with WordPress constraints (hooks, globals, procedural legacy)

    1. Dependency Injection (DI)

    Dependency Injection (DI) is a design pattern used to implement Inversion of Control (IoC) — a core principle in modern software architecture. Instead of a class instantiating its own dependencies, those dependencies are provided from the outside, typically through the constructor.

    In plain English:
    Rather than hardcoding what a class needs internally, you let something else provide it — usually a framework or a Dependency Injection container.

    Why use this pattern?

    • Decouples your code from specific implementations
    • Makes unit testing and mocking easy
    • Enables the Single Responsibility Principle from SOLID

    Enterprise (VIP) Usage:
    Dependency Injection is mandatory in most enterprise and WordPress VIP codebases, especially those built for scale, maintainability, and automated testing.

    2. Service Provider / Registrar Pattern

    The Service Provider (or Registrar) Pattern is a structural design approach that delegates service registration and configuration to dedicated classes—commonly known as service providers or registrars.

    In plain English:
    Imagine you’re running a workshop full of tools: drills, saws, hammers. Instead of personally handing tools to every worker who needs one, you hire a Tool Manager—your Service Provider

    The Tool Manager’s responsibilities:

    • Register all the tools: “We’ve got 5 drills, 3 saws, 10 hammers.”
    • Provide tools on demand: workers simply ask for “a drill”, not a specific model hidden in a drawer.

    Now, whenever someone says:

    “Hey Tool Manager, I need a drill.”
    The Tool Manager picks the right one and hands it over.

    Why use this pattern?

    • Modular plugin architecture — keep concerns isolated
    • Centralizes setup — REST endpoints, hooks, filters, CLI, etc.
    • Test-friendly — easier to mock, swap, or isolate services
    • Works seamlessly with Dependency Injection containers

    Enterprise (VIP) Usage:
    Standard practice in Web Stories for WordPress and Altis by Human Made.
    It’s considered a best practice in any enterprise-level or VIP-compliant WordPress architecture.

    3. Hookable Interface

    The Hookable Interface is an object-oriented design pattern that allows a class to register WordPress actions and filters (hooks) in a clean, encapsulated way. Rather than scattering add_action() or add_filter() calls across procedural code, each class implements a dedicated method—typically register_hooks()—to declare its integration points.

    In plain English:
    Think of a plugin as a small machine. The Hookable Interface is like giving that machine a way to say:

    “Here’s exactly where and how I want to plug into the system.”

    For example, the plugin might say:

    “Hook me into the init phase and also into the save_post event.”

    Then, when the system boots up, it simply calls the plugin’s register_hooks() method—and the plugin wires itself up exactly as intended. No hidden side effects, no magic inside constructors.

    Why use this pattern?

    • Keeps constructors clean — no add_action() clutter
    • Encapsulates lifecycle logic inside a predictable method
    • Encourages testability and reusability
    • Improves code readability and onboarding
    • Promotes Interface Segregation — each class only implements the interfaces it needs.

    Enterprise (VIP) Usage:
    Commonly adopted in VIP-compliant codebases like Web Stories, Altis, and custom enterprise platforms. Clean separation of concerns is expected.

    4. Repository Pattern (Data Layer Abstraction)

    The Repository Pattern is a structural design pattern used to abstract data access logic from business logic. It acts as a mediator between your application and data sources—whether that’s a database, an API, or even a flat file. This decoupling leads to more modular, testable, and maintainable code.

    In plain English:

    Suppose you’re building a blogging platform where users can create, update, and manage posts. Each post includes a title, content, and author.

    Now imagine scattering raw SQL queries or direct get_posts() calls across controllers, models, and helper functions. That quickly turns into a nightmare to debug, extend, or test.

    Instead, you create a Repository class—like PostRepository—that is solely responsible for fetching, storing, and manipulating post data. Your application logic talks to the repository, and the repository handles the actual database interaction.

    Why use this pattern?

    • Isolates data access logic behind a clear API
    • Enables mocking or faking during tests
    • Prevents get_posts() or raw SQL from leaking into business logic
    • Makes it easier to switch from WordPress APIs to custom tables or external APIs later

    Enterprise (VIP) Usage:

    Widely used in VIP-compliant codebases—especially where custom tables or CPT-heavy implementations demand clean separation of concerns.

    5. Singleton Pattern (Use with Caution)

    The Singleton Pattern ensures that a class has only one instance and provides a global access point to that instance. It’s typically used when exactly one object is needed to coordinate actions across the system.

    In plain English:

    Think of a shared coffee machine in an office. There’s only one, and everyone uses the same one. No one is allowed to create a second.

    Why developers use it:

    • Provides a central place to manage global state
    • Useful for configuration managers, loggers, or factories
    • Avoids repeatedly instantiating the same resource-heavy object

    But here’s the problem:

    • Hides dependencies — breaks Dependency Injection principles
    • Makes unit testing painful
    • Encourages tight coupling across the codebase
    • Difficult to extend or swap in different implementations
    • Not thread-safe without extra care (less relevant in PHP, but still a conceptual flaw)

    When to use:

    Only when absolutely necessary—typically for global configuration, low-level factories, or shared services that cannot be reasonably scoped otherwise.

    Enterprise (VIP) Usage:

    Allowed, but heavily discouraged. VIP architecture generally favors Dependency Injection, service containers, and clear separation of concerns over global state.

    Bottom line:

    Use the Singleton pattern only when there’s a strong architectural reason. In most cases, a properly scoped service within a DI container is a safer, more testable, and more scalable alternative.

    6. Adapter Pattern (for WordPress APIs and External Services)

    The Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between systems that weren’t originally designed to communicate, enabling smoother integration without modifying existing code.

    In plain English:

    Picture this: You have a European plug, but you’re in the U.S., and the outlets are different. Rather than replacing the plug or the wall socket, you use an adapter to make them compatible.

    In software, the adapter works the same way—it “translates” one interface into another. This lets you integrate third-party libraries, legacy code, or WordPress APIs into your architecture without tightly coupling your application to them.

    Why use it:

    • Decouples your application from direct dependencies on WordPress core functions
    • Makes mocking and testing functions like wp_remote_get(), WP_Query, or transients far easier
    • Adds flexibility when switching data sources or HTTP clients
    • Enables consistent interfaces across diverse services

    Enterprise (VIP) Usage:

    Widely used and often required—especially in testable, decoupled systems. If you’re building scalable, enterprise-level WordPress code, abstracting WordPress APIs is non-negotiable.

    Bottom line:

    If you’re not wrapping WP_Query, get_option(), or HTTP calls like wp_remote_get(), you’ll hit a wall when it comes to unit testing or switching contexts (e.g., headless, CLI, API-only). The Adapter Pattern is your escape hatch from WordPress lock-in.

    7. Factory Pattern (for Flexible Object Creation)

    The Factory Pattern is a creational design pattern that provides a way to delegate the instantiation of objects to a dedicated method or class. Rather than calling constructors directly, you use a factory method to determine which object to create—especially when the object type may vary depending on the context.

    In plain English:

    Imagine you walk into a sandwich shop and say, “I want a sandwich.”
    Depending on your dietary preferences—vegan, meat lover, extra cheese—the kitchen prepares a specific sandwich without you needing to know how it’s made. That’s what the Factory Pattern does: it decides what to create and how, based on your request.

    Why use it:

    • Centralizes complex object creation logic
    • Supports polymorphic behavior without cluttering constructors
    • Keeps business logic and instantiation logic separate
    • Ideal when object creation involves conditional or contextual data

    Common caveat:

    The Factory Pattern can introduce unnecessary complexity in simple applications. If you’re creating basic objects without varying logic, a factory is likely overkill. Use it when object instantiation is conditional, dynamic, or involves multiple steps.

    Enterprise (VIP) Usage:

    • Sometimes used in modular VIP codebases where object creation needs to be dynamic
    • Often helpful in plugin architectures, especially with service managers or extensible systems

    Bottom line:

    Use the Factory Pattern when your application needs to create different objects depending on runtime conditions, such as plugin settings, request type, or content type. It shines in cases where direct instantiation would break the Single Responsibility Principle or make testing harder.

    8. Strategy Pattern (Swappable Behavior)

    The Strategy Pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one, and makes them interchangeable. It enables an object to change its behavior at runtime by delegating responsibility to a strategy object. This promotes flexibility and adheres to clean coding principles.

    In plain English:

    The Strategy Pattern lets a class switch between different behaviors or algorithms without changing its internal code. Instead of hardcoding logic, the class relies on a strategy interface. You can then provide different implementations of that interface and inject the one you need—at runtime or based on context.

    It’s like choosing which navigation app to use: Google Maps, Waze, or Apple Maps. They all solve the same problem (routing), but you can pick the one that fits best at the moment—without rewriting your trip plan.

    Why use it:

    • Cleanly separates behavior from core logic
    • Allows behavior to be swapped or extended without modifying the base class
    • Follows the Open/Closed Principle — open for extension, closed for modification
    • Great for runtime flexibility in plugins or service-oriented systems

    Enterprise (VIP) Usage:

    • Widely used in filter logic, cron handlers, and REST API responses
    • Enables dynamic decisions without bloated conditionals or duplicate code paths

    Bottom line:
    The Strategy Pattern is essential when you need runtime flexibility without compromising maintainability. It keeps your codebase clean, extensible, and aligned with solid architectural principles—especially in complex plugin or enterprise-level WordPress applications.

    Final Thoughts

    WordPress doesn’t force you to write bad code — it just doesn’t stop you.

    You can ship maintainable, testable, clean architecture in WordPress — especially when you embrace patterns like DI, Repositories, and Adapters. Your devs will ship faster. Your QA team will thank you. Your clients will notice.

    This list isn’t exhaustive. There are other useful patterns — Event Bus, Value Object, Command Bus — that I’ll cover in future posts. But start with these eight. Learn when to use them. More importantly, learn when not to.

    Overengineering is just as dangerous as underengineering. Architecture should serve the product, not the other way around.

    If this resonated with you — or if you violently disagree — leave a comment or DM me on X. I love arguing about clean code.

  • AI-First Coding: How to Lead, Not Just Prompt

    Reading Time: 2 minutes

    Welcome to the future of software development — where you don’t have to type every line of code yourself. In this new AI-first era, your role as a developer is shifting from coder to guide. You steer the process while the AI handles the heavy lifting.

    This post explores how to work with AI (like ChatGPT or GitHub Copilot) to write better code faster — without getting stuck in frustrating loops or messy rewrites.

    Why Big-Bang Prompts Often Fail

    One of the most common mistakes when using AI for coding is asking it to build everything in one go. These “big-bang” prompts often result in:

    • Lost context: Earlier instructions get overwritten or forgotten.
    • Conflicting logic: A solution to one part breaks another.
    • Unrecoverable errors: The AI gets stuck or produces broken code you can’t easily fix.

    The solution? Don’t just prompt. Lead.

    Think of AI as a “A toddler with a Master’s Degree in Code”

    AI is like a brilliant but inexperienced collaborator. It knows a lot, but it lacks judgment and context unless you give it some.

    Here’s how to communicate with it effectively:

    • Break tasks into small, clear steps.
    • Give just enough context for each step — not everything at once.
    • Be specific about what you expect (inputs, outputs, behavior).
    • Ask for reasoning — “Why this approach?”
    • Ask it to double-check — “Does this logic work as expected?”

    You’re not just prompting — you’re collaborating.

    A Simple, Repeatable AI-Driven Coding Workflow

    Here’s a proven way to structure your coding sessions with AI tools:

    1. Scaffold the Core Functionality

    Start small. Ask the AI to generate just enough code to get a working foundation (e.g., one function or a minimal module).
    Test it. Make sure it runs. Don’t move on until the basics are solid.

    2. Develop Features Iteratively

    For each requirement:

    • Ask the AI to handle just that feature.
    • Review the output.
    • Test it.
    • Then refine or revise as needed.

    Think of each feature as its own micro-task.

    3. Parameterize and Polish

    Once it works:

    • Extract hardcoded values into config files (YAML/JSON/settings).
    • Add error handling, logging, and edge case support.
    • Include documentation and basic unit tests.

    Key Takeaways

    Guide, don’t grind: Focus on guiding the AI instead of typing everything yourself. Your time is better spent reviewing and refining.

    You still matter: AI won’t catch every edge case. Human judgment is still essential for writing clean, maintainable, production-ready code.

    AI is a tool, not a replacement: It can do amazing things — when used correctly.

    Final Thoughts

    Coding with AI is like managing a junior developer with infinite energy and zero context. If you give it clear instructions, check its work, and stay in the loop, you can build faster and smarter than ever before.

    Start small. Be explicit. Iterate. And remember: you’re not just coding — you’re leading.

  • CODA (2021) Movie Review

    Reading Time: 2 minutes

    I recently watched an amazing movie—simple, low-budget, but very real, full of strong emotions and meaningful moments. It tells the story of a poor fishing family, and it’s the kind of movie that stays with you long after it ends. At its heart, it’s about family, sacrifice, and chasing your dreams.

    A Powerful Opening Scene

    The movie starts with a long shot that pulls you into the world of the main characters. The camera moves slightly, showing us that this is a working boat, a place where people work hard. Then, you hear the daughter’s voice. The fact that she has a voice is important—both in the literal sense (because she loves to sing) and in a bigger, deeper way. Her voice becomes a big part of the film, symbolizing the balance between work and family responsibilities.

    The father’s calm yet rebellious nature is very important to the story, and it plays a key role throughout the movie.

    A Real Look at Disability

    When movies show disability, they often either make it seem too perfect or unrealistic. But this film takes a more honest approach. You meet the mother later in the movie, and at first, she doesn’t get much sympathy from us. But as the movie goes on, we start to understand her better, and by the end, we feel for her. This is what good storytelling looks like.

    The Emotional Struggle

    The main character’s emotional battle is clear. You never think she’s selfish for wanting more in life. Instead, you go through her struggles with her. She feels torn between doing what she feels she has to do for her family and wanting to build a better future for herself. But the film doesn’t treat these as two opposite ideas—it shows them as part of a bigger, meaningful journey.

    One powerful moment happens after she takes a big test. She opens her laptop, staring at the screen, looking unsure. At first, it seems like she failed. But then, the scene quickly cuts to her teasing her teacher, and just moments later, she reveals that she passed. The way the scene is edited makes this moment even more emotional.

    A Powerful Silent Moment

    One of the most touching scenes in the film happens when the daughter sits in the back of a pickup truck next to her father. It’s a simple scene: the camera pulls back from a wide shot to a closer shot as she gently rests her head on his shoulder. For the first time, the father responds by placing his hand on hers—a small, quiet gesture of love. There are no big speeches or dramatic music—just a simple, powerful moment of connection.

    Final Thoughts

    This film is a real treasure. It shows family, love, sacrifice, and dreams in a way that feels true to life. It doesn’t rely on clichés or fake emotions. Instead, it gives us real, relatable characters whose struggles and successes stay with you long after the film ends.

    If you ever get the chance to watch this movie, don’t miss it. Some stories are meant to stay with you, and this is one of those stories.

  • Front-End WordPress Submission via Telegram Bot

    Reading Time: 9 minutes

    Last week, while working on our Aggregator plugin, I explored new ways to enhance its functionality. During my research, I stumbled upon Telegram’s Bot API—something I had never used before. Out of curiosity, I decided to test its capabilities. What started as simple experimentation quickly turned into a quick open source project: Why not enable front-end WordPress submission via a Telegram bot?

    This guide walks you through setting up a Telegram bot, integrating it with WordPress, and automating post submissions—all without needing to access the admin panel. In this post, I’ll share my journey of integrating Telegram with WordPress and building a bot to streamline front-end post submissions.

    I’ll walk you through my journey of integrating Telegram with WordPress and building a bot that streamlines front-end post submissions. Enough talk—let’s get started!

    1. Creating a Telegram Bot with BotFather

    Telegram provides BotFather, an official bot for managing bots. Follow these steps to create your bot:

    1. Open Telegram and search for BotFather.
    2. Start a chat with BotFather and type /newbot.
    3. Follow the instructions to set up your bot:
      • Choose a name for your bot (e.g., “WP Post Bot”).
      • Choose a username for your bot (must end with bot, e.g., “WPPostSubmissionBot”).
    4. Once created, BotFather will provide you with a Bot Token—save this token securely, as it will be needed to authenticate API requests.

    Now that we have our bot set up, let’s move on to building the WordPress plugin.

    2. Creating Your WordPress Plugin

    To interact with Telegram’s Bot API in PHP, we’ll use the Telegram Bot SDK, a well-maintained library that simplifies bot development. Before writing any code, install the SDK via Composer.

    Step 1: Create the Plugin Folder & File

    Inside the wp-content/plugins/ directory, create a new folder:

    mkdir wp-content/plugins/telegram-post-bot

    Inside this folder, create a new file named telegram-post-bot.php and open it in your code editor.

    Installing Telegram Bot SDK via Composer

    Ensure you have Composer installed on your system, then run the following command in your project directory:

    composer require irazasyed/telegram-bot-sdk

    This will download and install the SDK and its dependencies. Once installed, you can start using the library to communicate with Telegram’s API.


    Step 2: Understanding the Main Plugin File

    This file is the entry point for the plugin and initializes everything. Here’s the breakdown of the code:

    if ( ! defined( 'ABSPATH' ) ) {
        exit; // Prevent direct access
    }
    
    if ( file_exists( __DIR__ . '/vendor/autoload.php' ) ) {
        require_once __DIR__ . '/vendor/autoload.php';
    }
    
    use Telegram\Bot\Api;
    use Dotenv\Dotenv;
    
    try {
        $dotenv = Dotenv::createImmutable( __DIR__ );
        $dotenv->safeLoad();
    } catch ( Exception $e ) {
        error_log( 'Telegram Post Bot: Failed to load .env file - ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    }
    
    define( 'TELEGRAM_BOT_TOKEN', $_ENV['TELEGRAM_BOT_TOKEN'] ?: ''  );
    define( 'TELEGRAM_AUTHORIZED_USERS', $_ENV['TELEGRAM_AUTHORIZED_USERS'] ?: ''  );
    • The ABSPATH check ensures the file is accessed only within WordPress, preventing direct execution.
    • This checks if Composer dependencies are installed (including the Telegram SDK).
    • I use Dotenv to include tokens from a .env file. You can find examples in the repository.
    • If found, it loads them automatically, ensuring we can use the SDK in our plugin.

    Step 3: Registering a Telegram Webhook Route

    This snippet creates a custom REST API endpoint in WordPress to receive messages from Telegram.

    add_action( 'rest_api_init', 'register_telegram_webhook_route' );
    
    /**
     * Registers the Telegram webhook REST API endpoint.
     *
     * @return void
     */
    function register_telegram_webhook_route() {
        register_rest_route(
            'telegram/v1',
            '/webhook/',
            array(
                'methods'             => 'POST',
                'callback'            => 'handle_telegram_update',
                'permission_callback' => '__return_true',
            )
        );
    }

    Explanation:

    • register_rest_route: Registers a new REST API route under /wp-json/telegram/v1/webhook/.
    • methods => 'POST': This endpoint only accepts POST requests.
    • callback => 'handle_telegram_update': When Telegram sends data, this function processes it.
    • permission_callback => '__return_true': Allows open access (you may want to restrict it for security).

    This ensures that Telegram messages are received at /wp-json/telegram/v1/webhook/. the next step is to handle incoming Telegram messages and process post submissions. which we’ll cover in the next section.


    Step 4: Handling Telegram Messages (In-Depth Breakdown)

    The function below processes incoming messages from Telegram, collects blog post details, and stores them in a temporary session.

    /**
     * Handles incoming Telegram messages and processes user input for post creation.
     *
     * @param WP_REST_Request $request The REST API request object.
     *
     * @return WP_REST_Response REST response indicating success or failure.
     */
    function handle_telegram_update( WP_REST_Request $request ) {
        if ( empty( TELEGRAM_BOT_TOKEN ) ) {
            return new WP_REST_Response( esc_html__( 'Bot token missing', 'telegram-post-bot' ), 400 );
        }
    
        $authorized_users = array_map( 'absint', explode( ',', TELEGRAM_AUTHORIZED_USERS ) );
        $update = $request->get_json_params();
        $message = $update['message'] ?? null;
    
        if ( ! $message ) {
            return new WP_REST_Response( esc_html__( 'No message received', 'telegram-post-bot' ), 400 );
        }
    
        $chat_id = absint( $message['chat']['id'] );
        $user_id = absint( $message['from']['id'] );
        $text    = sanitize_text_field( $message['text'] ?? '' );
    
        if ( ! in_array( $user_id, $authorized_users, true ) ) {
            send_telegram_message( $chat_id, esc_html__( 'Unauthorized user.', 'telegram-post-bot' ) );
            return new WP_REST_Response( esc_html__( 'Unauthorized user', 'telegram-post-bot' ), 403 );
        }
    
    	if ("/start" === $text ) {
    		$keyboard = [
    			'keyboard' => [
    				[['text' => '/post'], ['text' => '/endsession']]
    			],
    			'resize_keyboard' => true,
    			'one_time_keyboard' => false
    		];
    
    		send_telegram_message( $chat_id, esc_html__( 'Welcome! Use the menu below to navigate: /post - Begin a new session /endsession - Cancel the current session.', 'telegram-post-bot' ), array('reply_markup' => json_encode($keyboard)) );
    
    		return new WP_REST_Response('Session started', 200);
    	}
    
    	if ($text === "/endsession") {
            delete_transient("telegram_post_{$user_id}");
    		send_telegram_message( $chat_id, esc_html__( 'Session ended. Use /start to begin again.', 'telegram-post-bot' ) );
    
            return new WP_REST_Response('Session ended', 200);
        }
    
        $user_state = get_transient("telegram_post_{$user_id}") ?: [];
    
        if ( "/post" === $text ) {
            send_telegram_message( $chat_id, esc_html__( 'Send the post title.', 'telegram-post-bot' ) );
        } elseif ( ! isset( $user_state['title'] ) ) {
            $user_state['title'] = $text;
            set_transient( "telegram_post_{$user_id}", $user_state, HOUR_IN_SECONDS );
            send_telegram_message( $chat_id, esc_html__( 'Title saved. Now, send tags (comma-separated).', 'telegram-post-bot' ) );
        } elseif ( ! isset( $user_state['tags'] ) ) {
            $user_state['tags'] = array_map( 'sanitize_text_field', explode( ',', $text ) );
            set_transient( "telegram_post_{$user_id}", $user_state, HOUR_IN_SECONDS );
            send_telegram_message( $chat_id, esc_html__( 'Tags saved. Now, send a category.', 'telegram-post-bot' ) );
        } elseif ( ! isset( $user_state['category'] ) ) {
            $user_state['category'] = $text;
            set_transient( "telegram_post_{$user_id}", $user_state, HOUR_IN_SECONDS );
            send_telegram_message( $chat_id, esc_html__( 'Category saved. Now, send the content.', 'telegram-post-bot' ) );
        } elseif ( ! isset( $user_state['content'] ) ) {
            $user_state['content'] = $text;
            set_transient( "telegram_post_{$user_id}", $user_state, HOUR_IN_SECONDS );
            send_telegram_message( $chat_id, esc_html__( 'Content saved. Type publish to submit your post.', 'telegram-post-bot' ) );
        } elseif ( 'publish' === strtolower( $text ) ) {
            create_wordpress_post( $user_state, $chat_id );
            delete_transient( "telegram_post_{$user_id}" );
            return new WP_REST_Response( esc_html__( 'Post submitted and session ended', 'telegram-post-bot' ), 200 );
        } else {
            send_telegram_message( $chat_id, esc_html__( 'Invalid command. Type publish to submit your post.', 'telegram-post-bot' ) );
        }
    
        return new WP_REST_Response( esc_html__( 'Request processed', 'telegram-post-bot' ), 200 );
    }
    

    Now that we have our Telegram bot set up and a webhook to receive messages, we need to process those messages, validate users, and guide them through submitting a post to WordPress. This is done in the handle_telegram_update function.

    I’m going to explain the code in detail. Let’s break it down.

    Fetching API Credentials and Checking Authorization:

    function handle_telegram_update(WP_REST_Request $request) {
        $bot_token = TELEGRAM_BOT_TOKEN;
    
        if (!$bot_token) return new WP_REST_Response('Bot token missing', 400);
        ....
    }
    • We retrieve the bot token and the list of authorized users (who are allowed to submit posts) from the .env file.
    • If the bot token is missing, we return an error response.

    Getting the Incoming Message from Telegram:

        $update = $request->get_json_params();
        $message = $update['message'] ?? null;
    
        if (!$message) return new WP_REST_Response('No message received', 400);
    • The message sent by the user is extracted from the request payload.
    • If there’s no message, we return an error response.

    Extracting User Data:

    $chat_id = $message['chat']['id'];
    $user_id = $message['from']['id'];
    $text = $message['text'] ?? '';
    • $chat_id: This is the chat where we need to send responses.
    • $user_id: The Telegram user’s ID (used to track progress).
    • $text: The message content sent by the user.

    Checking If the User Is Authorized:

    $authorized_users = explode(',', TELEGRAM_AUTHORIZED_USERS);
    
    if ( ! in_array( $user_id, $authorized_users, true ) ) {
            send_telegram_message( $chat_id, esc_html__( 'Unauthorized user.', 'telegram-post-bot' ) );
            return new WP_REST_Response( esc_html__( 'Unauthorized user', 'telegram-post-bot' ), 403 );
    }
    • We verify if the user is in the list of authorized users.
    • If they are not, we send them a Unauthorized user message and reject the request.

    Using get_transient for Temporary Data Storage:

    $user_state = get_transient("telegram_post_{$user_id}") ?: [];

    In our transient system, we use the string telegram_post_ combined with the Telegram user ID. This ensures that each user can submit only one blog post at a time. Additionally, you could add a feature allowing users to edit their unfinished post steps based on this transient. This is one of the key advantages of using transients in this approach, and I will highlight some others in the following:

    • This acts like a temporary session for each user.
    • Instead of storing each message permanently in the database, we save data temporarily.
    • It reduces unnecessary database writes, keeping things efficient.
    • You can delete the post while it’s being submitted by using a Telegram command like /endsession.
    • The only disadvantage is that over time, transients in the options table may accumulate. However, you can use plugins to clean them up.

    Collecting Post Details in Steps:

    Here’s where the magic happens—we guide the user step by step through the post submission process:

    if ( "/post" === $text ) {
            send_telegram_message( $chat_id, esc_html__( 'Send the post title.', 'telegram-post-bot' ) );
        } elseif ( ! isset( $user_state['title'] ) ) {
            $user_state['title'] = $text;
            set_transient( "telegram_post_{$user_id}", $user_state, HOUR_IN_SECONDS );
            send_telegram_message( $chat_id, esc_html__( 'Title saved. Now, send tags (comma-separated).', 'telegram-post-bot' ) );
        } elseif ( ! isset( $user_state['tags'] ) ) {
            $user_state['tags'] = array_map( 'sanitize_text_field', explode( ',', $text ) );
            set_transient( "telegram_post_{$user_id}", $user_state, HOUR_IN_SECONDS );
            send_telegram_message( $chat_id, esc_html__( 'Tags saved. Now, send a category.', 'telegram-post-bot' ) );
        } elseif ( ! isset( $user_state['category'] ) ) {
            $user_state['category'] = $text;
            set_transient( "telegram_post_{$user_id}", $user_state, HOUR_IN_SECONDS );
            send_telegram_message( $chat_id, esc_html__( 'Category saved. Now, send the content.', 'telegram-post-bot' ) );
        } elseif ( ! isset( $user_state['content'] ) ) {
            $user_state['content'] = $text;
            set_transient( "telegram_post_{$user_id}", $user_state, HOUR_IN_SECONDS );
            send_telegram_message( $chat_id, esc_html__( 'Content saved. Type publish to submit your post.', 'telegram-post-bot' ) );
        } elseif ( 'publish' === strtolower( $text ) ) {
            create_wordpress_post( $user_state, $chat_id );
            delete_transient( "telegram_post_{$user_id}" );
            return new WP_REST_Response( esc_html__( 'Post submitted and session ended', 'telegram-post-bot' ), 200 );
        } else {
            send_telegram_message( $chat_id, esc_html__( 'Invalid command. Type publish to submit your post.', 'telegram-post-bot' ) );
    }

    How This Works:

    1. First, we check if the command /post comes from Telegram, then we begin the process.
    2. Then, we check if the title is not missing → If yes, we save the title and ask for tags.
    3. Then, we check if the tags are not missing → If yes, we save the tags and ask for the category.
    4. Then, we check if the category is not missing → If yes, we save the category and ask for content.
    5. Then, we check if the content is not missing → If yes, we save the content and ask for the “publish” command.
    6. Finally, when the user types “publish”, we create the post in WordPress and clear the session.

    Step 5: Sending a Response to the User

    $telegram->sendMessage(['chat_id' => $chat_id, 'text' => $response_message]);
        return new WP_REST_Response('Request processed', 200);
    }
    • After each step, we send a confirmation message to guide the user.
    • The function returns a successful response to Telegram.

    Creating a WordPress Post

    When the user types "publish", the following function inserts the collected data as a draft post in WordPress.

    function create_wordpress_post( $data, $chat_id ) {
    	$category_name = sanitize_text_field( $data['category'] );
    	$category_slug = sanitize_title( $category_name );
    
    	// Check if category exists.
    	$category = get_category_by_slug( $category_slug );
    
    	if ( !$category ) {
    		$category_id = wp_insert_term( $category_name, 'category', array( 'slug' => $category_slug ) );
    
    		if ( is_wp_error( $category_id ) ) {
    			send_telegram_message( $chat_id, esc_html__( 'Error creating category: ', 'telegram-post-bot' ) . $category_id->get_error_message() );
    			return;
    		}
    
    		$category_id = $category_id['term_id'];
    	} else {
    		$category_id = $category->term_id;
    	}
    
        $post_id = wp_insert_post(
            array(
                'post_title'    => sanitize_text_field( $data['title'] ),
                'post_content'  => wp_kses_post( $data['content'] ),
                'post_status'   => 'draft',
                'post_type'     => 'post',
                'tags_input'    => array_map( 'sanitize_text_field', $data['tags'] ),
                'post_category' => ( $category_id ) ? array( $category_id ) : array(),
            ),
            true
        );
    
        if ( is_wp_error( $post_id ) ) {
            send_telegram_message( $chat_id, esc_html__( 'Error creating post: ', 'telegram-post-bot' ) . $post_id->get_error_message() );
            return;
        }
    
        send_telegram_message( $chat_id, esc_html__( 'Post submitted successfully! View: ', 'telegram-post-bot' ) . esc_url( get_permalink( $post_id ) ) );
    }
    • Creates a post using wp_insert_post().
    • Sanitizes input to prevent security issues.
    • Assigns or create a category & tags based on user input.
    • Handles errors and returns an error response if post creation fails.
    • Sends a Telegram message with the post link upon success

    Sending Messages via Telegram API in WordPress:

    The send_telegram_message function plays a crucial role in our Telegram bot integration, allowing it to send messages to users. Here’s how it works:

    /**
     * Sends a message to a Telegram chat.
     *
     * @param int    $chat_id Telegram chat ID.
     * @param string $message The message to send.
     *
     * @return void
     */
    function send_telegram_message( $chat_id, $message, $data = array() ) {
        if ( empty( TELEGRAM_BOT_TOKEN ) ) {
            return;
        }
    
        $telegram = new Api( TELEGRAM_BOT_TOKEN );
    
        try {
            $telegram->sendMessage(
                array(
                    'chat_id' => absint( $chat_id ),
                    'text'    => $message,
    				extract($data)
                )
            );
        } catch ( Exception $e ) {
            error_log( 'Telegram Post Bot: Failed to send message - ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
        }
    }

    In each step, we send a message to this function, which creates an instance of the Telegram\Bot\Api library bot and handles sending responses to the Telegram bot. While this library offers many capabilities, this is a simple use case demonstrating how you can interact with and utilize the WP REST API to create a Telegram bot. With some help from AI and the library’s repository, you can explore more advanced examples.

    3. Register the REST API to Telegram Bot webhook

    Once you’ve set up your custom WordPress REST API endpoint (/wp-json/telegram/v1/webhook/), you need to register it with Telegram so that the bot can receive messages. This can be done using Postman or the terminal (cURL).

    Enter the following URL in Postman:

    https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook?url=<YOUR_WEBHOOK_URL>

    Replace:

    • <YOUR_BOT_TOKEN> with your Telegram bot token (generated from BotFather).
    • <YOUR_WEBHOOK_URL> with your WordPress endpoint (https://yourwebsite.com/wp-json/telegram/v1/webhook/).

    or simply use terminal

    curl -X POST "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook" -d "url=<YOUR_WEBHOOK_URL>"

    Conclusion

    With front-end WordPress submission via a Telegram bot, users can publish posts without logging in. Setting up a Telegram bot using a custom WordPress REST API route is a powerful way to integrate automated messaging with your website. In this guide, I provided a quick overview of the process, including creating a custom REST endpoint, registering the webhook with the Telegram API, and verifying that the integration works.

    However, this is just a basic demonstration to show the core functionality. In a real-world implementation, there are many improvements needed, including:

    • Security Enhancements – The REST API should validate requests properly, including verifying Telegram tokens to prevent unauthorized access.
    • Caching – Implementing caching mechanisms to avoid redundant requests and improve performance.
    • Error Handling & Fault Tolerance – Adding proper logging, retry mechanisms, and fallback strategies to ensure stability.
    • Capability & Permission Checks – Ensuring only authorized users can interact with the bot and access the API endpoint.
    • Advanced REST API Optimization – Improving response times and managing rate limits for better efficiency.

    In conclusion, you can use AI to refine and expand this plugin with additional features or even create your own version. However, I generally don’t recommend using ChatGPT to generate code and deploy it in production without fully understanding how it works. I’ve created a repository for this blog post as a quick demonstration of WP REST API and post submission via a Telegram bot. You’re free to fork, download, and contribute to the repository, but please use it cautiously, as it’s not intended for production. Feel free to check out the open-source repository: Telegram Post Bot on GitHub.

  • How Past Conversations Can Cloud Communication and Create Misunderstandings

    Reading Time: 4 minutes

    Talking with your team at a tech company can sometimes feel like fixing a bug—you think you explained everything clearly, but someone still gets confused. Suddenly, you’re stuck in a loop of clarifications. Misunderstandings happen, but they don’t have to slow down teamwork or cause frustration.

    Today, I had an experience that showed me how past conversations can affect how we think and react. Some time ago, my managers told me I needed to improve my communication skills and overcome my language barrier. I took their advice seriously. At the start of the year, I started using different tools and courses to improve. I felt like I had made great progress.

    Then, today, something happened that made me doubt myself. I sent a simple message to a coworker: I told her I would reply in an hour on Github and I need to fix a thing during that time and asked her to test the product in a different way. That was it. But when I saw my coworkers reacting with confusion, I felt frustrated. Did I make any progress after a month of working on my skills?

    I re-read my message many times, trying to find my mistake. During that time, I sent a few more messages that were not really necessary. But then I realized—the problem was not my message. It was the way I was thinking about the situation.

    I always believed that good communication means being able to work with different kinds of people, even those who don’t communicate well. But at that moment, my past conversations with my managers made me feel like I had failed again. I wasn’t just responding to my coworker—I was reacting to my fear that I still wasn’t good enough at communication.

    After a few more messages, I understood something important: my coworkers were not confused because my message was unclear. They were confused because they expected something different based on past assumption. Their reaction had nothing to do with my communication skills.

    This was a valuable lesson. Past feedback helps us improve, but it should not make us doubt ourselves in every situation. Sometimes, the problem is not in what we say—it’s in how we think about the response we get.

    The Science Behind Miscommunication

    Misunderstandings don’t just happen because of unclear wording. Cognitive biases and psychological factors play a huge role. Here are a few key concepts that explain why past conversations can cloud new ones:

    • Confirmation Bias – We tend to interpret new information in a way that aligns with what we already believe. If someone previously thought a feature was implemented, they may subconsciously reject any message suggesting otherwise.
    • Negativity Bias – Our brains are wired to remember negative experiences more strongly than positive ones. If you’ve struggled with communication before, you might assume any confusion means you failed again—even if that’s not the case.
    • Cognitive Load Theory – When people are processing too much information at once, they’re more likely to misunderstand details. If a chat is long or involves multiple topics, key points can get lost.
    • Shannon-Weaver Communication Model – This classic theory explains that communication isn’t just about sending a message—it’s also about how the receiver decodes it. Factors like noise (distractions, assumptions, or past conversations) can distort meaning.

    Here are some practical strategies to prevent misunderstandings caused by past conversations and assumptions:

    1. Take a Moment Before Responding

    Before jumping into a response, step away for a moment—make a coffee, take a deep breath, or do something unrelated. There’s no need to reply immediately. A brief pause can help you process the situation better and respond with more clarity and patience.

    2. Be Clear, But Also Consider the Reader’s Perspective

    Sometimes, what’s obvious to you isn’t obvious to others. When pointing out an issue, make sure to provide just enough context to avoid misinterpretation. Instead of saying something broad like “This feature doesn’t exist,” be specific: “We don’t have this functionality on the PHP side yet, but I’m working on it now.”

    3. Don’t Let Past Conversations Cloud Your Thinking

    If you’ve been given feedback about your communication in the past, don’t let it make you overly self-conscious. Just because you faced a challenge before doesn’t mean you’re making the same mistake again. Each conversation is a fresh start—focus on the present discussion instead of assuming past issues are repeating.

    4. Don’t Assume Confusion Means Poor Communication

    If someone is confused, it doesn’t always mean you explained it badly. Sometimes, the information itself is surprising or unexpected. Before jumping to conclusions, ask yourself: “Are they confused by what I said, or just surprised by the problem?” That small shift in thinking can prevent unnecessary frustration.

    5. Stay Open and Avoid Defensive Responses

    When someone asks for clarification, it’s easy to feel like you’re being challenged. But instead of responding with “I don’t know what confused you,” try something like “Let me clarify—here’s what I meant.” A friendly and patient tone makes discussions more productive.

    6. Recognize That Improving Communication is a Process

    If you’re actively working on your communication skills, don’t be too hard on yourself when misunderstandings happen. Clarity takes practice, and even the best communicators experience occasional misinterpretations. The key is to learn from each conversation and keep refining how you explain things.

    Final Thoughts

    Work chats should be about collaboration, not confusion. By being precise, considering different perspectives, and keeping a friendly tone, we can all make communication smoother and more effective. Misunderstandings will still happen—but with the right approach, they won’t turn into roadblocks.