AI 资讯
How to Actually A/B Test AI Avatar vs. Text Chat Conversion (A Technical Approach)
Following up on a common claim in the AI avatar space — that voice/video avatars convert better than plain text chat — there's surprisingly little rigorous testing behind it. If you're building or embedding one of these widgets, here's a practical way to actually measure it instead of trusting vendor case studies. Why This Is Harder Than a Normal A/B Test Standard A/B testing swaps one variable (a button color, a headline) while holding everything else constant. Avatar vs. text chat isn't that clean — you're changing interaction modality, response latency expectations, and visual real estate simultaneously. You need to isolate the variable that actually matters: does voice/video presence drive conversion, independent of the underlying conversation quality? A Cleaner Experimental Setup javascript // Pseudocode for variant assignment function assignVariant(sessionId) { const hash = hashSessionId(sessionId); return hash % 2 === 0 ? 'avatar' : 'text'; } Key controls to hold constant across both variants: Same LLM backend and prompt/knowledge base — the conversation logic shouldn't differ, only the presentation layer Same lead capture form and CTA placement — don't let UI differences beyond avatar-vs-text confound the result Same traffic source — segment by acquisition channel if traffic mix varies, since paid vs. organic visitors convert differently regardless of chat UI Minimum sample size before evaluating — novelty effects are real; running this for 3 days will overstate the avatar's lift. Run for at least 2-3 weeks to let novelty decay. Metrics to Track (Not Just Conversion Rate) Conversion rate alone hides why one variant wins or loses: session_start_to_first_message (engagement friction) message_count_per_session (depth of interaction) time_to_form_completion (avatar/video adds latency — does it cost or gain time?) bounce_rate_before_first_response lead_quality_score (if you can grade downstream — a lead isn't a conversion if it's junk) A common finding worth watc
AI 资讯
When "select all" checkboxes don't actually select anything — verifying after `check()`, not just trusting it
WordPress's plugin and theme update screens both have a "select all" checkbox. Calling check() on it with Playwright succeeds — no error, no exception. But look at the individual checkboxes afterward, and sometimes none of them are actually checked. Note: Playwright's check() ticks a checkbox. The click itself can succeed even if the page's JavaScript handler never fires, leaving what the form actually submits out of sync with what the screen visually shows. What actually happens The "select all" checkbox is usually wired up with a JavaScript handler: clicking it is supposed to check every individual checkbox underneath it. Playwright's check(force=True) can force the DOM state of that one checkbox — but that only changes that checkbox's own state . It doesn't guarantee the JavaScript handler that's supposed to propagate the change to the individual checkboxes actually fires. # Looks like it worked, but the individual checkboxes are still unchecked select_all . first . check ( force = True ) page . click ( ' input[type= " submit " ][name= " upgrade " ] ' ) Clicking the update button submits whatever the form's actual state is — which is "nothing checked." Nothing updates. No error is thrown, so on the surface it looks like the run completed normally. The fix — verify right after checking, every time Right after checking "select all," confirm that the individual checkboxes underneath are actually checked. If they aren't, fall back to checking each one individually. sel_all_sel = ' input[type= " checkbox " ][id^= " plugins-select-all " ] ' select_all = plugin_form . locator ( sel_all_sel ) if select_all . count () > 0 : select_all . first . check ( force = True ) page . wait_for_timeout ( 500 ) # Verification step — confirm checkboxes are actually checked chk_sel_check = ' input[type= " checkbox " ][name= " checked[] " ]:checked ' any_checked = plugin_form . locator ( chk_sel_check ). count () > 0 if not any_checked : # Select-all had no effect; switch to individual s
AI 资讯
When `update-core.php` version scraping goes wrong — telling a plugin version number apart from WordPress core
On hosting without SSH access, a common pattern is to open update-core.php (the WordPress update screen) with Playwright and read "what version is WordPress core currently running" straight off the page text. In one deployment, the recorded core version came back as something like 1.7.11 — a number that has never existed for WordPress core. Note: update-core.php is the WordPress admin's "Updates" page, listing pending updates for core, plugins, themes, and translations all on one screen. What was actually happening That page doesn't only show the core version string — it's packed with version numbers belonging to pending plugin and translation updates too. A line like "Update Plugin X to 1.7.11" is typical. # First implementation — grabs the first N.N.N-shaped number on the page match = re . search ( r ' \d+\.\d+(?:\.\d+)? ' , page_text ) version = match . group ( 0 ) if match else None This naive regex grabs whatever N.N.N -shaped number appears first on the page. Because of how the page is laid out, a plugin's pending update can render above the core version message, so 1.7.11 (a plugin's version) ended up recorded as the WordPress core version. Why this is hard to catch The bug doesn't throw an exception — the regex matches successfully, just on the wrong value. Nothing about it looks broken until someone notices the report shows a version that WordPress core has never shipped (there's no 1.x series for core). The fix — a three-stage guard Prefer a dedicated selector first — look for specific DOM locations where core's update message actually renders, like p.response > strong or #wp-version-message strong Fall back to keyword-anchored regex — if no selector matches, only accept a number immediately following the words "WordPress," "バージョン," or "Version" Validate plausibility as a final check — whichever path produced a value, run it through a function that checks the major version number falls within 4–9 def is_plausible_wp_core_version ( ver : str ) -> bool : m =
AI 资讯
Migrating 10 WordPress Sites to Cloudflare Pages: What Broke
A few months ago I moved a batch of WordPress sites off a shared LAMP host and onto Cloudflare Pages as static exports. The pitch is obvious: no PHP process to keep patched, no MySQL to babysit, effectively free hosting, and a CDN in front of everything by default. What the pitch doesn't tell you is how many small, boring things break on the way there. This post is a rundown of what actually went wrong migrating a set of ten WordPress sites — one of them is burningtribe.tokyo , which I'll use as the concrete example — and how I fixed each issue. The approach The migration itself is conceptually simple: crawl the live WordPress site, save every URL as a static HTML file plus its assets, and serve that tree from Cloudflare Pages. I used a combination of wget --mirror and a custom crawler for a couple of sites where wget choked on query-string-based pagination. The static output then gets pushed with wrangler pages deploy . No build step, no framework, just files. That simplicity is exactly why it seemed low-risk. It was not. Problem 1: relative canonical tags pointed everything at the homepage The first thing I noticed after deploying was that Google Search Console started reporting most inner pages as "duplicate, Google chose different canonical" — and the canonical it picked was the homepage. The cause was almost funny once I found it: the WordPress theme emitted <link rel="canonical" href="/"> as a relative path in a few cached page fragments, instead of an absolute URL like https://burningtribe.tokyo/some-post/ . On the original WordPress install this didn't matter because the page itself resolved the relative reference correctly at the point of caching. Once the HTML was frozen and served statically from a different origin structure (Pages serves everything from the apex), that relative canonical collapsed to the site root for every single page that had it. The fix was a straightforward but tedious pass: grep every exported HTML file for rel="canonical" , and rew
AI 资讯
Essential WordPress Plugins Every New Website Needs (And Which Ones to Avoid)
When you first install WordPress, it is easy to think that every popular plugin will improve your website. After all, the WordPress plugin directory contains tens of thousands of plugins, each promising better SEO, stronger security, faster performance, or beautiful design. That is exactly where many beginners make their first mistake. A new website does not need 30, 40, or 50 plugins. Every plugin you install adds more code that must be maintained, updated, and secured. While the number of plugins alone does not determine performance, unnecessary or poorly coded plugins increase the chances of conflicts, slowdowns, and security issues. Security experts also continue to report that plugins account for the overwhelming majority of WordPress vulnerabilities. The better approach is simple. Install only the plugins that solve an essential problem. Choose one high quality plugin for each task, avoid duplicates, and ignore everything else until you actually need it. Here are the only five to six plugins that most brand new WordPress websites need. The Minimalist Plugin Rule Before installing anything, remember this simple rule: One plugin, one job. If one plugin already handles SEO, you do not need another SEO plugin. If one caching plugin is active, never install a second one. If your hosting company already performs automatic backups, you may not need a backup plugin running every day. Keeping your plugin list small makes your website easier to manage, faster to update, and less likely to develop compatibility problems. An SEO Plugin Recommended: Rank Math SEO or Yoast SEO Every website needs an SEO plugin. Without one, you miss important features such as: XML sitemaps Meta titles and descriptions Search engine indexing controls Schema markup Social sharing previews For beginners, Rank Math's free version includes a generous feature set, while Yoast SEO remains one of the most established and beginner friendly alternatives. Either option works well. The important rule i
AI 资讯
How to Safely Update WordPress Plugins and Themes Without Breaking Your Site
If you've ever delayed updating your WordPress plugins or themes because you were afraid something might break, you're not alone. Many beginners avoid updates for weeks or even months because they've heard horror stories about websites crashing after a single click. Others do the exact opposite. They click "Update All" without preparing, then panic when their homepage displays an error or their layout suddenly changes. The good news is that updating WordPress doesn't have to be risky. With a simple maintenance routine, you can keep your website secure, stable, and running smoothly without the fear of losing your content. In this guide, you'll learn how to create reliable backups, update safely, test your website after every change, and recover quickly if something goes wrong. Why You Should Never Ignore WordPress Updates Updates exist for a reason. Plugin developers, theme creators, and the WordPress core team regularly release updates to: Fix security vulnerabilities Patch software bugs Improve compatibility with newer versions of WordPress and PHP Add useful features Improve website performance Running outdated plugins or themes leaves your website exposed to known security issues. In many cases, attackers specifically target websites that haven't been updated. At the same time, installing every available update without preparation isn't the answer either. A single incompatible plugin or poorly coded update can create conflicts that affect your website. The goal isn't to update everything as quickly as possible. The goal is to update carefully and confidently. A Safe WordPress Update Routine Follow this routine every time you update your website. Step 1: Create an Automatic Off-Site Backup Before changing anything, make sure you have a complete backup stored somewhere other than your web hosting account. If your hosting server experiences problems, a backup stored on the same server may not help. Instead, configure automatic backups to services such as: Google Dri
AI 资讯
60 AI-written WordPress plugins, and JavaScript escaping that is safe by accident
This post has two halves: what "escape your output" actually means once the obvious answer stops working, and then a study of whether current AI assistants get that harder version right. It continues a series on what coding assistants actually produce when a non-expert asks them for WordPress code. Start with a line that passes review and still leaves a hole: echo '<a href="' . esc_html ( $url ) . '">Visit</a>' ; There is an escaping function wrapped around the value. A grep for esc_ finds it. A quick review passes it. Now set $url to javascript:alert(document.cookie) . The link still runs when the visitor clicks it. esc_html() escapes characters that matter in HTML text, like < and > . It does nothing about a dangerous URL scheme. The code is escaped. It is escaped for the wrong context. From the outside, correct escaping and wrong-context escaping look identical. A URL is a different context and needs its own escaper. That escaper is esc_url() , and it returns an empty string for a javascript: URL. Which escaper goes where Escaping is context-dependent. "Sanitize on input, escape on output" is the rule the first post of this series covers, but "escape on output" hides a second decision: which escaper. The answer depends on where the value lands on the page. Where the value lands Escaper Visible text between tags ( <p>HERE</p> ) esc_html() Inside an attribute ( title="HERE" ) esc_attr() A URL slot ( href="HERE" , src="HERE" ) esc_url() , which also enforces a safe scheme Inside a tag attribute that holds JavaScript ( onclick="greet('HERE')" ) esc_js() , scoped to this slot by core's own documentation Any value handed to JavaScript inside a <script> block ( var data = HERE ) wp_json_encode() , which writes its own quotes; add JSON_HEX_TAG when the value is untrusted HTML you want to keep, like a formatted post body wp_kses() with an explicit allow-list Six functions, one job each. The mistake is almost never "forgot to escape". It is "escaped for the wrong context",
AI 资讯
Your A/B test has three goals and they disagree. Now what?
Every A/B testing tutorial ends the same way: run the test, wait for significance, ship the winner. Then you run a real test and variant B converts 12% better on newsletter signups, brings in 4% less revenue per visitor, and bounce is flat. Nothing is significant except the signups. Ship it? I spent an embarrassing amount of time on this question while building an A/B engine, and most of what I read online didn't help, because most of it assumes one metric. This post is what I ended up with. It's not novel — the statistics are decades old — but I couldn't find it written down in one place with working code, so here it is. Why the p-value doesn't answer the question you're asking Two problems, and the second one is the bad one. Multiple comparisons. Three metrics at α = 0.05 means roughly a 14% chance of at least one false positive if nothing is actually different. Bonferroni fixes this, but now you need α = 0.017 per metric and your test needs to run three times as long. On a site doing 300 conversions a month that's not a fix, it's a refusal. The p-value is answering a different question. It tells you the probability of your data assuming no difference exists. What you actually want to know is: if I ship B, how much do I expect to lose if I'm wrong? Those are not the same question and no amount of Bonferroni turns one into the other. There's also the peeking problem — everyone checks the dashboard daily and stops when it goes green, which quietly inflates the false positive rate well past whatever α you wrote down. I'll come back to that, because Bayesian methods do not magically solve it, whatever you may have read. Posterior first, decision second For a conversion rate, the Beta-Binomial conjugate pair gives you the posterior in one line. With a uniform prior, after c conversions out of n visitors: p | data ~ Beta ( 1 + c , 1 + n - c ) That's it. No closed-form comparison between two Betas that's worth implementing, so sample. PHP has no Beta sampler in core, and
开发者
What "18 years in web dev" actually means when your clients are small businesses, not startups
Most dev-to content about longevity comes from people who scaled one product for a decade. My version of 18 years is different: 235+ separate small projects, each with a different client, budget, and expectation. That produces a completely different set of lessons. Every project restarts the trust clock. In a startup, trust compounds — the team, the codebase, the client relationship all carry forward. In agency work for small businesses, you start from zero credibility on every single engagement. The client has no idea if you're competent until you prove it, usually within the first draft. That reality shaped how we scope: front-load a visible win early, even a small one, rather than saving the "impressive part" for the end. Most client requests aren't really about the website. "Can we change the homepage headline" is often actually "I'm nervous this won't generate leads" or "my business partner didn't like it." Treating every request as a literal design brief instead of what it's actually about leads to a lot of pointless revision cycles. Asking one clarifying question — "what's this in response to?" — before touching the page has cut our revision count more than any process change. Consistency beats innovation for this client base. A small business owner doesn't want a novel UX pattern. They want their site to look like the successful competitor's site, load fast, and not embarrass them. Chasing design trends for this audience is optimizing for the wrong judge — they're not evaluating craft, they're evaluating "does this look like it'll work." The real skill is saying no to the wrong project, not saying yes to more of them. Early on I took every lead. Now the highest-leverage thing I do is a 10-minute pre-call that filters out projects where the client's expectations and budget don't match — before either of us spends real time on it. That single filter has done more for margin than any pricing change. None of this shows up in a portfolio. But if you're a develope
AI 资讯
🛡️ Building AbilityGuard: Monitoring the WordPress Abilities API in Production
A few weeks ago I wrote about the WordPress Abilities API — what it is, why WordPress 6.9 shipped it, and what it means for how plugins will talk to each other and to AI agents going forward. That post was theory. This one is the part where theory meets a composer.json file and a stubborn bug at 1 AM. This is the story of building AbilityGuard — a plugin that monitors Abilities API usage in production, so you actually know what's happening when abilities get registered, called, and (occasionally) abused. 👀 Why monitoring, and why now Here's the thing about the Abilities API that got me nervous the first time I really understood it: it's a capability surface . Any plugin can register an ability. Any authorized caller — a human-triggered action, an automation, or increasingly, an AI agent — can invoke one. That's the whole point of the API, and it's genuinely exciting. But it also means your site now has a growing list of "things that can be done to it programmatically," and most WordPress admins have zero visibility into that list. I've spent enough years debugging WordPress sites in production to know what happens when you can't see something: you find out about it during an incident, not before. Slow queries, rogue cron jobs, plugin conflicts — they all follow the same pattern. Nobody notices the small thing until the small thing becomes the outage. So the idea for AbilityGuard was simple: give site owners a dashboard and a log for every ability registered on their site, every time one gets called, and by whom. Not another abstract "security scanner" — just honest, readable visibility into a part of WordPress that's brand new and mostly invisible right now. 🔌 Where I started: hooking into the registry, not fighting it The Abilities API exposes a central registry ( wp_get_ability_registry() under the hood, with helper functions layered on top). My first instinct was to intercept ability calls by wrapping core functions — and I killed that idea within the hour. Wrapp
AI 资讯
Why scheduled posts don't publish on time — inspecting WP-Cron with WP-CLI
A post scheduled to publish at a specific time doesn't go live when expected. A plugin's recurring email notification never arrives. This tends to happen on low-traffic sites, and there's a specific reason for it. Note: WP-Cron is WordPress’s built-in scheduling system. It sounds like the OS-level cron daemon, but the underlying mechanism is quite different. WordPress’s WP-Cron doesn’t work like a real OS cron daemon. On every page load, WordPress checks whether any scheduled task is past its due time and, if so, runs it. This is what's known as "pseudo-cron" — and its weakness is that nothing runs without a page visit . Schedule a post to publish at 3am on a site with little overnight traffic, and the publish task can sit unexecuted until the next visitor happens to load a page. WP-CLI lets you look inside this otherwise invisible system and run exactly the task you need, right now. Listing what's scheduled wp cron event list hook next_run_gmt recurrence publish_future_post 2026-06-20 03:00:00 - wp_version_check 2026-06-20 06:12:00 12 hours wp_scheduled_delete 2026-06-21 00:00:00 daily hook is the task's identifier, next_run_gmt is the next scheduled run time in UTC, and recurrence is the repeat interval. If publish_future_post is still listed despite its time having already passed, that confirms the task is overdue simply because no page load has triggered it yet. Running a task right now To trigger a specific task immediately: # Run a specific hook right now wp cron event run publish_future_post # Run every overdue task at once wp cron event run --due-now --due-now finds every task whose scheduled time has passed but hasn't run yet, and executes all of them. Instead of waiting for a visitor to trigger the check, this one command runs the post publish, the email notification, or whatever else is pending. Confirming WP-Cron itself is working wp cron test This checks whether the WP-Cron scheduler is functioning at all. On sites where wp-config.php has define('DISABL
AI 资讯
Latency Is the Real UX Problem in AI Avatars, Not the Voice
Everyone evaluating AI avatar platforms focuses on voice quality. The bigger UX killer is almost always latency — and it's a harder problem than picking a good TTS provider. Where the delay actually comes from: User speaks/types → STT (if voice input) → LLM generates response (streaming helps, but first-token latency matters) → TTS converts text to audio → Audio playback + lip-sync rendering Each hop adds latency. A naive implementation that waits for the full LLM response before starting TTS can easily hit 2-4 seconds of dead air — long enough for a user to assume the bot is broken. How production systems actually solve this: Token streaming into TTS — start synthesizing audio on partial LLM output (sentence-by-sentence chunks) instead of waiting for the full response Speculative rendering — start lip-sync animation slightly ahead of audio using predicted phoneme timing WebSocket/SSE persistent connections — avoid the overhead of repeated HTTP round-trips per turn Regional API routing — TTS/LLM provider latency varies a lot by user geography; this matters more than most benchmarks show A practical note: platforms that advertise "real-time" avatars but load all logic behind a single request/response cycle will feel noticeably worse than ones built around streaming pipelines, even if they use the identical LLM and TTS providers underneath. If you're evaluating a platform (or building one), test with realistic network conditions, not office wifi — that's where the architecture differences actually show up. Bottom line: the voice provider matters less than people think. The orchestration around it — how aggressively you stream and pipeline each stage — is what separates a "wow" demo from a production-ready conversational agent.
AI 资讯
Locked out of wp-admin? Why WP-CLI works when `wp-login.php` doesn’t
A forgotten password, a security plugin that blocked your own IP by mistake, a plugin bug that turns the admin screen white — the causes vary, but the result is the same: you can’t log in to wp-admin. Note: WP-CLI is a command-line tool for managing WordPress, invoked as wp . It operates directly on the server, without going through a browser. This is exactly the situation where WP-CLI is useful. It works here because it never touches wp-login.php — it reads and writes the WordPress database and filesystem directly, so a broken login screen doesn’t affect it at all. Why WP-CLI keeps working when wp-admin doesn't A normal login follows the path: browser → wp-login.php → authentication → wp-admin. If anything along that path is broken — a plugin throwing a fatal error during authentication, a security plugin blocking your IP, a fatal error in the admin theme — the login itself can’t complete. WP-CLI connects over SSH and reads/writes the wp_users and wp_options tables (and the filesystem) directly. The code in wp-login.php is never executed, so problems on that path don’t matter. This does require that SSH access itself still works — on most hosting providers, SSH is a separate access path from the admin dashboard, so it usually still works even when wp-admin doesn’t. Scenario 1: Forgotten password # List administrator accounts wp user list --role = administrator --fields = ID,user_login,user_email # Overwrite the password directly wp user update 1 --user_pass = 'a-strong-new-password' wp user update writes the new password directly to the database row — no password-reset email, no token, no waiting on a delivery that might land in spam or not arrive at all. Scenario 2: A security plugin blocked your own IP Login-attempt-limiting plugins occasionally misclassify legitimate activity as an attack and add the working IP to a block list. # Deactivate the plugin responsible for the block wp plugin deactivate <plugin-causing-the-lockout> # Re-enable it later, after reviewin
AI 资讯
WordPress Plugin Cross Post for Dev.to: my solution to abandoned and unmaintained plugins
I’ve released Cross Post for Dev.to , a WordPress plugin that mirrors a post to Dev.to the moment you publish it on your own site. The plugin itself is simple: one hook, one API call, one published mirror. The interesting part of this release isn’t the feature. It’s what had to be true for a zero-dependency plugin to stay trustworthy over time without me babysitting it. The problem Most small WordPress plugins rot over time. They’re built against whatever WordPress version happens to be current, they lean on a third-party library or two to save time, and neither the plugin nor the library gets revisited until something breaks. Usually a major WordPress release, sometimes a PHP version bump. At that point the maintainer generally abandons the project and so many of these lesser used plugins become useless for users. Cross-Post for Dev.to needed to cross-post reliably from WordPress to Dev.to without letting that happen so easily. No bundled third-party SDKs, no external service sitting between the plugin and the Dev.to API, no dependency that could go unmaintained while the plugin using it kept shipping. Why zero-dependency isn’t the whole answer Removing dependencies fixes one kind of rot but not the other. A plugin with no third-party packages can still quietly break against a new WordPress release; deprecated hooks, changed REST behaviour, PHP version drift. Zero dependencies means less that can fail underneath you; it says nothing about whether the plugin still works today. Those are two different guarantees, and conflating them is how “lightweight” plugins end up just as unmaintained as heavy ones, only with fewer visible warning signs. So the actual problem wasn’t “avoid dependencies.” It was “know, continuously and automatically, whether this plugin still works”. Which is a validation problem, not a packaging problem. The validation loop Two layers of the same idea, one local and one continuous. Locally , a pre-push hook runs before any commit leaves my machin
AI 资讯
Why phpMyAdmin migrations break plugin settings — and why `wp search-replace` doesn’t
After a domain migration or HTTPS switch, "all plugin settings are gone" or "Elementor layouts are broken" is a common outcome. The cause, in most cases, is running a string replacement against the WordPress database without accounting for PHP serialized data. WordPress stores plugin configurations, custom field values, and widget settings in PHP’s serialized format. Standard SQL replacements — phpMyAdmin’s find-and-replace, raw UPDATE statements, sed on a .sql dump — rewrite the string value without updating the length metadata that serialization embeds alongside it. The result is a database that appears intact but returns false on every read of the affected values. wp search-replace handles this correctly. Understanding why makes the pre- and post-execution steps more deliberate. What PHP serialization stores alongside the value A serialized entry in WordPress looks like this: a : 2 : { s : 4 : "home" ; s : 22 : "http://example.com/top" ; s : 5 : "title" ; s : 8 : "My Site" ;} The segment s:22:"http://example.com/top" means "a string of 22 bytes." The s:N: prefix records the byte length. When a simple string replacement changes http://example.com to https://example.com : Before: s:22:"http://example.com/top" (22 bytes) After: s:22:"https://example.com/top" (23 bytes) The s:22 stays unchanged even though the actual string is now 23 bytes. PHP’s unserialize() detects this mismatch and returns false . The plugin reads false instead of its configuration array and behaves as though the settings were never saved. phpMyAdmin’s find-and-replace executes a SQL UPDATE at the storage layer. No PHP context exists there — it can’t know the column contains serialized data, and it doesn’t adjust the length prefix. How wp search-replace handles it wp search-replace operates at the PHP layer, not the SQL layer: Reads each column value Checks whether it’s serialized using is_serialized() If serialized: calls unserialize() to expand it into a PHP array or object Applies the string r
开发者
I'll be speaking at WordCamp US 2026 🎉
A few months ago, I submitted a talk proposal to WordCamp US without really knowing what to expect. Today, I'm happy to say that it was accepted, and I'll be speaking at one of the largest WordPress conferences in the world. WordCamp US has always been one of the events I've looked up to in the WordPress ecosystem. As someone who has spent over a decade building content platforms with WordPress, contributing to open source, and working with teams across different countries, having the opportunity to share my experience on that stage is something I don't take for granted. My session is: Stop Blaming WordPress: Building a Real Editorial Workflow Without Leaving the Ecosystem Throughout my time working with WordPress, I've noticed a recurring pattern. When editorial teams struggle to publish content efficiently, WordPress often gets the blame. But after working with organizations of different sizes, I've learned that the CMS is rarely the real problem. The real challenges are usually: disconnected editorial processes; unclear content ownership; missing approval workflows; inconsistent governance; too much reliance on manual work. In this session, I'll share practical strategies for building scalable editorial workflows while keeping WordPress at the center of the ecosystem. The goal isn't to introduce another platform, it's to make the existing one work better. Speaking at WordCamp US is especially meaningful because I've been part of the WordPress ecosystem for many years. Being able to give something back to this community is an opportunity I'm genuinely grateful for. If you'll be at WordCamp US 2026 in Phoenix, I'd love to connect. 🎟️ Get your ticket: https://us.wordcamp.org/2026/tickets/ 💸 Use my speaker discount: speaker-friend20 during checkout for a discount on your ticket. See you at WCUS! 🚀
AI 资讯
Another day, another VPS breach
I woke up to two emails that immediately caught my attention. One was from my website monitoring service (I use UptimeRobot, no affiliation) reporting that a client's website was down. The other was from my VPS provider informing me that they had suspended my VPS due to abuse. I logged into the control panel and immediately noticed a massive CPU spike. The server had gone from its usual 15–20% CPU usage to a sustained 100% for nearly four hours before the provider shut it down under their fair usage policy. My first clue was xmlrpc.php . It was consuming a significant amount of resources, so I started researching it. I'm not primarily a WordPress/PHP developer, and I was surprised to learn that XML-RPC exposes functionality for remote management of WordPress. I disabled XML-RPC, brought the VPS back online, and thought the problem was solved. It wasn't. The next day I woke up to the exact same two emails. This time my VPS provider had already imposed CPU limits on the server. I noticed a few kernel-looking processes consuming CPU, assumed they were related to the throttling, and restarted the VPS. A few hours later, it was offline again. At that point I knew I was dealing with a compromise rather than a performance issue. I began investigating the WordPress installation and immediately found obvious signs of infection. There were numerous malicious PHP files ( index.php , cache.php , etc.) buried inside recursively nested directories such as: image / image / image / image / cache . php The deeper I looked, the worse it became. The attackers had created: A rogue WordPress administrator account An unauthorized SSH key A root-level user on the VPS An administrator account inside CyberPanel This wasn't just a compromised website anymore. It was a full VPS compromise. My working theory was that the attackers exploited a vulnerable WordPress component (likely allowing arbitrary PHP upload or remote code execution), established persistence, and pivoted into the operating s
AI 资讯
Integrating AI and WordPress: From Idea to Execution, Real-World Challenges, and Practical Workflows
Integrating AI and WordPress: From Idea to Execution, Real-World Challenges, and Practical Workflows The rise of artificial intelligence has fundamentally transformed our definition of an effective website. The era of static websites that merely served as digital placeholders is over. Today, Content Management Systems like WordPress—backed by custom AI capabilities—are evolving into intelligent, automated, and interactive assistants. Below is a detailed overview of our practical experience, architecture, completed implementations, and the technical hurdles we overcame while building custom AI tools for WordPress. 1. Why Integrate AI with WordPress? (Beyond Simple Plugins) Many people view AI in WordPress as limited to off-the-shelf content generation plugins or generic chatbots. However, real value is unlocked when custom AI tools are tailored specifically to a business's unique workflow and ecosystem. Our focus when implementing AI tools relies on three core principles: Complex Process Automation: Reducing human intervention in repetitive tasks, such as automatic categorization, SEO optimization, and metadata generation. Personalized User Experience: Delivering smart, exclusive responses to users based on real-time behavior and stored data. Direct and Secure Connectivity: Seamlessly bridging Large Language Models (LLMs) with the WordPress database and native hooks via APIs. 2. Featured Projects and Case Studies Throughout our development journey, we have brought several practical AI use cases from concept to live production environments: A) Intelligent Content Engine A custom tool integrated into the WordPress admin panel that analyzes article topics to: Generate an optimized SEO structure (Headings and target keywords). Draft initial content alongside image metadata (Alt text and Descriptions). Automatically suggest internal links based on existing posts inside the wp_posts database table. B) Context-Aware AI Support Agents Upgrading basic chatbots into smart agen
AI 资讯
Getting ready for WordPress 7.0 — three things to check now: PHP requirements, FSE migration, and editor extensions
Five failure patterns from past WordPress major upgrades ended with the observation that "the same structures will recur in some form in the next major (7.0 or 8.0)." This post applies that pattern knowledge to WordPress 7.0 specifically — not as a list of things that will happen, but as a preparatory checklist for what you can verify now. This sits alongside the seven-item pre-flight checklist from W1 rather than replacing it. Think of it as the 7.0-specific supplement. Item 1 — PHP minimum requirement increase (W3 pattern 3 again) W3’s pattern 3 was "older plugins break with parse errors when the PHP minimum requirement is raised." This pattern repeats on every major upgrade cycle. WordPress 7.0 is expected to follow the same trajectory. Background: PHP 7.4 reached end-of-life from the PHP project in November 2022 (security support ended November 2023) WordPress 6.6 requires PHP 7.2 minimum WordPress 7.0 is expected to raise the minimum to PHP 8.0 or later When this happens, sites running PHP 7.x will encounter plugins and themes that "worked fine before" but break with syntax or fatal errors after the upgrade. The mechanism is the same as past cycles — the plugin hasn’t updated its code to handle PHP 8.x stricter type handling, and the errors surface only at runtime. What you can check now: # Confirm the server’s PHP version php -v # List all plugins with their current version and update status wp plugin list --fields = name,version,update,update_version --format = table # Run a JSON output check — noisy output here means PHP warnings already leaking wp plugin list --format = json The pattern to watch for: plugins with an old "Tested up to" value and no updates in the past year. These are the highest risk candidates for PHP compatibility issues when the minimum requirement moves. The post on PHP 8.2 Deprecated warnings leaking into WP-CLI JSON output covers a related symptom — even when PHP compatibility doesn’t cause a hard break, it can silently corrupt o
AI 资讯
How to Actually Protect Yourself From wp2shell (Not Just "Update WordPress")
Everyone's telling you the same thing right now: update WordPress. Which is, fine, yes, obviously but that's not enough. Patching closes the hole. What is someone is already In your system? So let's actually walk through this properly what to do right now, what to do if you can't patch immediately, and what to do if you think you were already hit. Need more context? I wrote a summarising one here. wp2shell: The Bug That Turned an Empty WordPress Install Into a Shell Aditya Pidurkar Aditya Pidurkar Aditya Pidurkar Follow Jul 22 wp2shell: The Bug That Turned an Empty WordPress Install Into a Shell # wordpress # wp2shell # cybersecurity # cve Add Comment 4 min read S1: Stop assuming you're patched. Go check. WordPress flipped on forced automatic background updates for this one because of how serious it is. That's great, except "forced" doesn't mean "guaranteed", VERIFY IT. Go look at your actual running version right now: In the dashboard: wp-admin > Updates Via WP-CLI, if you have shell access: wp core version You want to see 7.0.2 if you're on the 7.0 line, or 6.9.5 if you're on 6.9. If you see anything else — 6.9.0 through 6.9.4, or 7.0.0/7.0.1 — you're still exposed. Update it now!! Come back when done. A few situations where the forced update quietly doesn't happen, in what I read on internet: Managed hosting environments where the host controls the update pipeline and hasn't pushed it yet Sites with a "disable auto-updates" plugin installed months ago for stability reasons and never revisited wp-config.php with WP_AUTO_UPDATE_CORE explicitly set to false Staging or dev environments nobody thinks of as "production" but that are still internet-facing If any of that describes you, don't wait on the automation. Update manually. S2: If you genuinely can't update right now, here's your stopgap aybe you're facing a compatibility issue, a change freeze, or a custom plugin that isn't ready yet. It happens. If you need a little more time before applying the real fix, here'