AI 资讯
The Whale Metaphor: How OOP's Four Pillars Actually Work in WordPress
In the age of AI engineering and vibe coding, almost nobody mentions OOP anymore. But what if children were never taught prefixes, roots, and suffixes — the architecture of words — or how a sentence is properly built? Will AI agents really be enough for the specialists of tomorrow, if those specialists never learned the grammar underneath? Dive into OOP in WordPress development practice → (original source, featuring the four whales example) Most WordPress developers learn Object-Oriented Programming the hard way: by staring at WP_Widget, WP_Query, or WP_Post and reverse-engineering why core is built the way it is. Textbooks explain encapsulation, abstraction, inheritance, and polymorphism with abstract diagrams that rarely survive contact with real code. Here's a different way to think about it — using a whale. A whale keeps its vital organs protected inside its body, dives into depths where the mechanics of survival are invisible from the surface, passes traits down to its calf, and adapts its behavior differently depending on the environment it's in. Swap "whale" for "class," and you've basically described the four pillars of OOP. Let's walk through each one with WordPress-specific code, then look at how the same principles scale from a five-page brochure site to an enterprise platform. Why OOP Matters in WordPress at All WordPress was procedural for most of its early life, and plenty of plugins still are. But once your project outgrows a handful of files, procedural code starts fighting you: global state leaks everywhere, the same logic gets copy-pasted into three different hooks, and a single typo in a variable name three files away breaks something unrelated. OOP fixes this by grouping data and behavior together into objects instead of scattering functions and passing arrays between them. WordPress core made this bet a long time ago — WP_Widget, WP_Query, and WP_Post are all classes — and the four principles below are the foundation that makes classes trustwort
AI 资讯
How to generate WCAG-compliant ALT text for WordPress images without sending them to a vendor's black-box API
If you've ever tried to fix accessibility on an old WordPress site, you know the drill: hundreds of images in the Media Library, most with empty alt attributes, and a WCAG 2.1 audit (or a client demanding one) breathing down your neck. Writing alt text by hand for 400 images is not a fun Tuesday. Every "AI alt text" SaaS I looked at wanted a monthly subscription, routed my images through their own servers, and gave me zero control over which model actually looked at the picture. This post is about the plugin I built to fix that for my own sites, and the handful of implementation details that turned out to matter more than expected. The actual problem WCAG 2.1 Success Criterion 1.1.1 requires non-text content to have a text alternative. In WordPress terms: every attachment post of MIME type image should have _wp_attachment_image_alt set to something meaningful, not "IMG_4821.jpg" and not empty. Doing this with a vision-capable LLM is trivial in principle — send the image, ask for a short description, save it as the alt attribute. The part that's not trivial, if you don't want another recurring SaaS bill and don't want to hand a third party your whole media library, is: whose API key, which model, and where does the image actually go. Design decision: BYOK, not a hosted service The plugin ( Alt Text BYOK ) doesn't call any server of mine. It calls whatever OpenAI-compatible chat/completions endpoint you configure, with your own API key. That's the entire trust model: your images go from your WordPress install directly to the provider you already chose (OpenAI, or any of the growing list of OpenAI-compatible vision endpoints), and nowhere else. The settings are deliberately just four fields: function atbyok_default_settings () { return array ( 'api_base' => 'https://api.openai.com/v1' , 'api_key' => '' , 'model' => 'gpt-4o-mini' , 'language' => 'English' , 'overwrite_existing' => '0' , 'license_key' => '' , ); } api_base is the detail that matters most for portability:
AI 资讯
I mapped every WordPress plugin CVE since 2023. Here's what the data says — and how I built it.
Most "is this plugin safe?" advice is vibes. I wanted numbers, so I built a dataset. Here's what it found, and exactly how, so you can check my work or build your own. The finding first Of 8,010 WordPress plugins with a publicly documented vulnerability since 2023 (15,534 vulnerability records in total): 3,780 have been removed from the wordpress.org plugin directory. Removal stops updates but doesn't uninstall — affected sites keep running the code. 277 carried a critical (CVSS ≥ 9.0) flaw on record before removal. 2,115 are still installable today with a known vuln and no update in 12+ months — roughly 6.7M active installs combined. The part that surprised me most: "removed from the directory" is nearly invisible to a site owner. No dashboard warning, no email. The plugin just quietly stops getting fixes while sitting on the site. How I built it (no paid APIs) The whole thing runs on two public sources and no API keys. 1. Vulnerability data — the GitHub Advisory Database. It mirrors CVE records including the Patchstack and Wordfence CNA assignments that cover almost all WordPress plugin CVEs. It's a git repo, so a shallow, sparse clone of the advisories/unreviewed/{year} folders gets you the raw JSON: git clone --depth 1 --filter = blob:none --sparse \ https://github.com/github/advisory-database.git Each advisory carries the CVE ID, a CVSS vector string, CWE IDs, and reference URLs. The plugin slug isn't a first-class field — you recover it from the Patchstack/Wordfence reference URLs with a couple of regexes. That alone attributes the large majority of WordPress advisories to a specific plugin. 2. Maintenance signals — the wordpress.org plugin API. For each slug: https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request[slug]=SLUG That gives install count, last-updated date, tested-up-to version, and support-thread resolution ratio. A 404 (or an {error} body) means the plugin isn't in the directory — but that's ambiguous: it could be removed ,
开发者
WordPress PHP-Only Block Registration
Seven and half years after blocks arrived in Core, WordPress introduces a way to build blocks without React annd build pipelines. All you need is PHP. WordPress PHP-Only Block Registration originally handwritten and published with love on CSS-Tricks . You should really get the newsletter as well.
AI 资讯
Building a Custom REST API in WordPress the Right Way
WordPress is often treated as a traditional CMS, but its REST API makes it possible to use WordPress as the backend for applications, dashboards, mobile clients, automation systems, and external services. The difficult part isn't registering an endpoint. The difficult part is designing the endpoint so that authentication, authorization, validation, error handling, and data access are all handled correctly. A production API needs a contract. It needs to know: Who can access it What data they can access What input is accepted What output is returned What happens when something fails Here's a practical approach. Register a Custom Route A basic WordPress REST API route can be registered with register_rest_route() . add_action ( 'rest_api_init' , function () { register_rest_route ( 'myplugin/v1' , '/posts' , [ 'methods' => WP_REST_Server :: READABLE , 'callback' => 'myplugin_get_posts' , ]); }); This creates an endpoint similar to: /wp-json/myplugin/v1/posts The namespace matters. Using: myplugin/v1 gives the API a version boundary. If the response structure changes later, a new version can be introduced without immediately breaking existing clients. Don't Put Authorization Inside the Callback A common beginner implementation does everything inside the callback: function myplugin_get_posts () { if ( ! current_user_can ( 'manage_options' )) { return new WP_Error ( 'forbidden' , 'Access denied' , [ 'status' => 403 ] ); } // Query data... } This works, but WordPress provides a cleaner place for the permission decision. Use permission_callback . register_rest_route ( 'myplugin/v1' , '/posts' , [ 'methods' => WP_REST_Server :: READABLE , 'callback' => 'myplugin_get_posts' , 'permission_callback' => function () { return current_user_can ( 'manage_options' ); }, ]); Now the endpoint has a clearer separation: Request ↓ Permission check ↓ Callback ↓ Data That separation becomes increasingly valuable as an API grows. Authentication Is Not Authorization These concepts are easy to m
AI 资讯
The excluded-plugin setting that Playwright ignored — fixing browser-mode updates and false residual warnings
The symptom In browser-mode maintenance (Playwright, no SSH), plugins marked as "excluded from update checks" were still being updated. After the run, a "plugin updates remaining" WARNING email arrived every time. The excluded plugins were intentionally left behind, but the residual check treated them as unfinished updates and fired a warning — a two-part problem: wrong behavior and a misleading alert. SSH path vs. Playwright path On SSH-capable sites, WP-CLI's --skip-plugins flag carries the ignored_plugins list into the update command. That path already excluded them correctly. The Playwright path was different. browser_update_remaining_plugins() worked by clicking the "select all" checkbox on update-core.php and submitting the form — no filtering at all. # Before: select-all, ignored_plugins never consulted page . check ( ' input[name= " action " ][value= " update-selected-plugins " ] ' ) for cb in page . query_selector_all ( ' input[name= " checked[] " ] ' ): cb . check () This function is the chokepoint for two flows: pure browser-mode updates ( run_browser_update_flow ) and the browser residual pass that runs after SSH updates ( run_browser_residual_update , on by default). So even SSH sites could have excluded plugins updated by the residual pass. Browser-driven bulk updates are also outside the scope of pinpoint rollback, meaning there is no automatic recovery if a wrong update goes through. Fix 1 — _ignored_plugin_slugs() and _plugin_slug_from_checkbox_value() When excluded plugins are configured, the fix replaces the select-all approach with per-checkbox evaluation. def _ignored_plugin_slugs ( site : dict ) -> set [ str ]: raw = site . get ( " ignored_plugins " , "" ) return { s . strip (). lower () for s in raw . split ( " , " ) if s . strip ()} def _plugin_slug_from_checkbox_value ( value : str ) -> str : return value . split ( " / " )[ 0 ]. strip (). lower () if " / " in value else value . strip (). lower () Plugin checkboxes on update-core.php use a va
开源项目
WordPress.com Student Plan
As someone who teaches beginning web development, I find that building a WordPress site makes for a great final project. Hosting those projects has always been a roadblock though. WordPress.com Student Plan originally handwritten and published with love on CSS-Tricks . You should really get the newsletter as well.
AI 资讯
WordPress.com targets the next generation of web creators with a free student plan
WordPress.com Education lets teachers offer their students free domains, plugin support and professional website-building tools.
AI 资讯
I measured 7,032 WordPress plugins to find out how anyone gets their first install
I shipped a plugin to the WordPress.org directory. It got zero installs. That is not a complaint, it is the normal outcome. Roughly 19% of all plugins in the directory never pass zero installs , which is more than 10,500 of them. But I wanted to know why , and whether the answer was "your plugin is bad" or something structural. So instead of reading marketing advice, I queried the directory API and counted. Everything below is reproducible. The API is free, needs no key, and every query I used is in the article. The short version Search is a two phase system, and phase one is a hard filter , not a ranking. If a single word of the user's query is missing from your listing, you are excluded from that search entirely. Phase two is where you lose, and it is ranked partly on active installs . That is the cold start trap. Of the plugins that broke out recently, 88% had distribution before they started . The two behaviours that actually correlate with breaking out from nothing are release cadence and resolving support threads , which are two of the five phase-two ranking inputs and the only two a plugin with no installs can move. WordPress.org gives plugin authors no analytics whatsoever . No listing views, no impressions, no click-through. Anyone who tells you confidently what makes people click install is guessing. How search actually works The best-documented account traces to WP Tavern's 2017 coverage of the directory relaunch, quoting Greg Brown, the Automattic data engineer who built it. It runs on Elasticsearch, and it has two phases. Phase one builds the candidate pool. It matches against title, excerpt, description, tags, slug, author name and contributor names. Critically: all search keywords must appear somewhere, or the plugin is excluded from the result set. Not ranked low. Excluded. Phase two sorts that pool by last update date, compatibility with the current core version, active installs, percent of support tickets resolved, and average rating. That split ma
AI 资讯
WordPress Block Themes vs Classic Themes: Should You Switch in 2026?
If you've been developing WordPress websites for several years, there's a good chance you've spent a lot of time working with files like: header.php footer.php single.php page.php archive.php functions.php That's certainly where most of my WordPress development experience has been. But WordPress has been changing. With the Block Editor, Site Editor, block themes, patterns, and theme.json , WordPress now offers a very different approach to theme development. So I decided to take a closer look at the question: If you're already comfortable building classic WordPress themes, is it worth moving toward block themes in 2026? This isn't an article written from the perspective of someone who has spent years exclusively building block themes. Most of my own WordPress work has traditionally involved classic themes . Instead, I'm looking at block themes from the perspective of an experienced WordPress developer who is exploring how the platform is evolving—and where the newer approach fits alongside the architecture I've used extensively. Classic Themes vs Block Themes WordPress currently identifies two primary theme types: Classic themes Block themes According to the official WordPress Theme Developer Handbook, classic themes primarily use PHP, JavaScript, and CSS and can make extensive use of WordPress functions, hooks, and filters. Block themes, on the other hand, are built around block markup and HTML-based templates and allow users to edit more areas of the website through the Site Editor. A simplified comparison looks like this: Classic Theme Block Theme PHP templates HTML block templates single.php templates/single.html header.php parts/header.html footer.php parts/footer.html Template hierarchy Block-based templates Custom PHP logic Blocks + APIs + plugins Customizer / theme options Site Editor / Styles theme.json optional theme.json commonly used This doesn't mean classic themes are obsolete. They aren't. WordPress continues to maintain documentation for classic theme
AI 资讯
How to Give AI Better Evidence: Lessons From a Security Investigation That Almost Failed
Category: My AI Experiments There's a mental model most people use when working with AI: describe your problem, get a solution. It works well enough, until it doesn't. And when it fails, the failure is invisible — because AI doesn't say "I don't have enough to go on." It gives you a confident, well-reasoned, completely wrong answer. I learned this the hard way during a website security investigation. The AI and I ran a thorough analysis, reached a clear conclusion, and were wrong. Not because the AI was weak — because I gave it the wrong kind of input. When I changed the input, the same AI found the answer in seconds. That gap — between the input that produces a wrong answer and the input that produces a right one — is what I want to talk about. The Investigation That Almost Failed My website was secretly redirecting visitors to a virus site. The attack was sophisticated: it only targeted specific browsers, fired at most once per device per day using a cookie-based cooldown, and left no trace in any file. I asked AI to help investigate. I described the symptoms. We searched through files together — .htaccess , theme functions, plugin code. Everything looked clean. The AI identified the most suspicious external element in scope: a Chinese analytics script called 51.la. I removed it. The redirect stopped. I called it solved. Three weeks later, the identical attack appeared on another site I manage. No 51.la anywhere. This time, instead of describing the symptoms, I gave the AI something different: the actual rendered HTML of an affected page, fetched using the exact browser User-Agent and IP type that triggered the attack. The AI found an 83KB malicious JavaScript payload injected into every page. Inside it: a WeChat browser detector, a link-click hijacker, a cookie-based daily cooldown. The payload was stored in the WordPress database — in plugin configuration data — where no file-level search could ever find it. Same AI. Same type of problem. Completely different ou
开发者
The ACF Block.json Migration Matrix Nobody Published
Search "acf_register_block_type to block.json mapping" and you get tutorials. Each one converts a single testimonial block and calls it done. None of them hand you the full key-by-key table: every legacy PHP argument, its block.json destination, and the handful of settings that have no destination at all. I migrated forty-plus blocks across two client themes this year. Some keys moved with zero friction. Others sat in gray areas I only resolved by testing in the block editor and watching what broke. This is the matrix I wish existed before I started. Why the gap exists ACF's own documentation covers acf_register_block_type() on one page and the block.json acf key on another. Both pages are accurate. Neither cross-references the other. A developer migrating a block has to hold both pages open and manually match render_template to renderTemplate , post_types to postTypes , and so on. Miss one, and a block silently loses a feature instead of throwing an error. Three categories of keys make this worse: Renamed keys. Same feature, different casing, different location. snake_case becomes camelCase , and the key moves from the flat settings array into a nested acf object. Relocated keys. Some legacy settings aren't ACF-specific at all. They belong to WordPress's core block registration and move to the top level of block.json, outside the acf object entirely. Orphaned keys. A few legacy settings have no documented block.json equivalent. You either drop the behavior, replicate it with WordPress's native supports API, or handle it in your render template instead. The full matrix Legacy acf_register_block_type() key block.json location Notes name Top-level name Must be namespaced, e.g. acf/testimonial instead of testimonial title Top-level title Direct match description Top-level description Direct match category Top-level category Direct match icon Top-level icon Direct match, including the array form for background/foreground colors keywords Top-level keywords Direct match p
开发者
WordPress Sitelerini Yavaşlatan 7 Yaygın Hata
WordPress sitelerinde hız problemi yaşandığında genellikle ilk refleks bir cache eklentisi kurmak oluyor. Bazen gerçekten fark yaratıyor, bazen de PageSpeed puanı biraz yükselmesine rağmen site hâlâ yavaş hissettiriyor. Bunun nedeni WordPress performansının tek bir ayardan oluşmaması. Hosting, tema, eklentiler, görseller, JavaScript dosyaları ve veritabanı aynı anda sayfanın yüklenme süresini etkileyebiliyor. Bu nedenle bir siteyi hızlandırmaya başlamadan önce asıl problemin nerede olduğunu bulmak gerekiyor. WordPress projelerinde sık karşılaştığım 7 performans hatasını aşağıda topladım. Gereğinden büyük görseller kullanmak En sık karşılaştığım problemlerden biri bu. Sayfada 700 piksel genişliğinde görüntülenecek bir görselin 4000-5000 piksel olarak yüklenmesi oldukça yaygın. Özellikle yüksek çözünürlüklü stok fotoğraflar doğrudan WordPress'e yüklendiğinde tek bir görsel birkaç megabayta ulaşabiliyor. Bu da özellikle mobil bağlantılarda ciddi yük oluşturuyor. Görselleri yüklemeden önce kullanılacağı alana uygun boyuta getirmek, sıkıştırmak ve mümkün olduğunda WebP veya AVIF gibi modern formatları tercih etmek önemli. Ancak yalnızca dosya formatını değiştirmek yeterli değil. 4000 piksel genişliğindeki bir görseli WebP'ye çevirmek, görselin gereğinden büyük olduğu gerçeğini değiştirmiyor. Her problemi cache eklentisiyle çözmeye çalışmak Cache WordPress performansında önemli bir yere sahip. Fakat cache eklentisi kurmak her hız problemini ortadan kaldırmaz. Sunucu geç cevap veriyorsa, çok fazla JavaScript çalışıyorsa veya veritabanında ağır sorgular varsa cache yalnızca problemin bir bölümünü gizleyebilir. Ayrıca aynı anda birden fazla optimizasyon eklentisi kullanmak da başka sorunlara yol açabiliyor. Örneğin bir eklentide JavaScript erteleme, başka bir eklentide tekrar JavaScript optimizasyonu ve hosting panelinde üçüncü bir optimizasyon sistemi açıldığında hangi ayarın ne yaptığını takip etmek zorlaşıyor. Ben mümkün olduğunca tek bir ana cache sistemi üzerinden ilerl
开发者
How to Secure Your WordPress Dashboard and Prevent Clients from Breaking Their Sites
A guide on using Admin Extension Access Control to lock down WordPress plugins and prevent unauthorized changes. How to Secure Your WordPress Dashboard and Prevent Clients from Breaking Their Sites If you are a freelance web developer or run an agency, you have probably experienced the dread of a client accidentally bringing down their WordPress site. You spend weeks building a robust, performant website, only for an unauthorized user to log into the dashboard, start deactivating essential plugins, or install poorly coded extensions that break everything. WordPress is fantastic because of its flexibility, but out of the box, any Administrator can touch everything . To solve this problem, I want to introduce a lightweight solution: Admin Extension Access Control . What is Admin Extension Access Control? Admin Extension Access Control is a WordPress plugin designed to give you granular control over who can see, modify, install, or delete plugins on your site. Built for modern environments (PHP 8.1+ and WordPress 6.0+), it allows you to configure strict role-based access rules without writing custom PHP functions in your functions.php file every time. Key Features Global Lockdown : Completely remove the plugins page for specific user roles. Granular Permissions : Restrict the ability to add, delete, activate, deactivate, or install plugins on a per-role basis. Exempt Users Whitelist : Designate trusted administrators (like yourself) who bypass all lockdown rules. Only exempt users can configure the access control settings. Dashboard Cleanup : Hide the plugins menu item from unauthorized users to keep the dashboard less confusing for clients. How It Works Once installed and activated, the user who activates the plugin is automatically added to the Exempt Users list. This prevents you from accidentally locking yourself out. From the settings panel, you can select which roles should be restricted from managing plugins. For example, you can give your client an "Administrat
开发者
Excited to finally join DEV!
👋 Hello DEV Community! I'm excited to finally join DEV! I'm a developer, entrepreneur, and lifelong learner who enjoys building practical web solutions with WordPress, PHP, and modern web technologies. Over the past few years I've been working on: 🚀 WordPress plugins and starter websites 💻 Affordable web solutions for individuals and small businesses 📈 Web analytics and digital marketing tools 🌱 Exploring software architecture, clean code, and open-source development I'm also building and experimenting with digital products that solve real-world problems while documenting what I learn along the way. Here you'll find posts about: WordPress development PHP programming Building and launching web products Software engineering lessons Productivity and business insights for developers Occasionally, mathematics and calculus when it connects to programming or analytics I'm looking forward to learning from this amazing community, contributing where I can, and connecting with fellow developers. Thanks for having me! 😊
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