Tag: wordpress

  • Ross has the same journey as me with WordPress themes

    Just like Ross I’ve had to dance around the current state of WordPress themes and find the docs to be bad with lots of edge cases once you start looking through Github issues and Stack Exchange posts…and god knows what else.

    When I look at the current state of building WordPress themes I don’t think I ever would have started with WordPress if this is where I had to start. It’s so much more difficult now and did I mention the docs suck.

  • Deleting Gravity Forms Spam Entries

    Over the last week we had a form get noticed by spam bots and get over 100,000 entries without us noticing. While we added Gravity Forms CAPTCHA to stop the spam I still have all those entries in the database to remove. I was only able to do around 1000 via the UI before the server timed out and the process was stopped, but Gravity Forms has a CLI plugin which gives me wp gf as a command I can run against the site.

    I started with small batches (the default page_size is 20) and once --page_size=10000 worked I looked up the number of entries and matched it to --page_size which deleted all the spam entries on the form.

    wp gf entry delete $(wp gf entry list <form_id> --format=ids --page_size=10000) --force
    

    The command takes the output of wp gf entrie list <form_id> and sends it to the wp gf entry delete <entry_id> --force command. Thus listing all the entries based on the --page_size value and deleting them.

    It took about 40 minutes for the command to finish running on around 100,000 entries.

    You can find the Gravity Forms CLI docs for entries here.

  • LocalWP and Homebrew Conflicts

    Like an idiot while I was going to do a “quick” bit of work to get ahead for next week on Friday. First I decided it was time to update Homebrew before I really got into diagnosing a problem for a client. Of course mayhem ensued with the wpcli commands no longer working with LocalWP after I ran brew update && brew upgrade.

    There were two main errors to search. First the icu4u library wasn’t loaded. This library is used for unicode characters and is a requirement of PHP, MySQL and a number of other things that Homebrew was managing. The specific error is below.

    Library not loaded: /opt/homebrew/opt/icu4c/lib/libicuio.73.dylib

    Further down in the same error block there was a note about PHP 8.0 not being installed.

    Error: php@8.0 has been disabled because it is a versioned formula!

    After searching and finding these general directions to remove and reinstall LocalWP, I was still out of luck. Worse, I no longer had any development sites installed locally as I removed them all during the uninstall process.

    Fixing with Homebrew

    Ultimately after a bunch of fruitless searching I came across this article saying that PHP 8.0 was no longer supported in Homebrew. To continue to have it installed I needed to get it from a different repository with the following commands.

    • Add the new repository to Homebrew – brew tap shivammathur/php
    • install php 8.0 – brew install shivammathur/php/php8.0

    Now my wpcli commands work as expected and I no longer see the errors from icu4c. The big problem with this, and anything installed with Homebrew, is that we’re messing with my main PHP version in my shell. I’m always running PHP 8.0 now, even on projects that may need earlier or later versions of PHP.

    Fixing it with nix-shell

    Of course you’ll need to install nix-shell first with these instructions.

    A far more portable and repeatable way to fix my issue is to use nix-shell. Instead of messing with the main versions of software running on my machine, I’m only messing with the current instance of the terminal that I’m working in.

    The easy way to test this is to run the following command which temporarily installs php8.1 and wpcli in my terminal.

    nix-shell -p php81 wp-cli
    

    You’ll still get an error connecting to the database because the shell doesn’t understand how to make the connection to LocalWP but that can be fixed by going to the Database tab in LocalWP and copying the socket value into the DB_HOST value. It should look something like this:

    define( 'DB_HOST', 'localhost:/Users/curtismchale/Library/Application Support/Local/run/CiNKEaTuy/mysql/mysqld.sock' );
    

    Now when I run a wp-cli command I have a connection which means the next step is to create a shell.nix file I can place in the root directory of my project which has the values I need setup already so I don’t have to manually run them each time. This is what my shell.nix file looks like.

    let
      nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11";
      pkgs = import nixpkgs { config = {}; overlays = []; };
    in
    
    pkgs.mkShellNoCC {
      packages = with pkgs; [
        wp-cli
    	php81
      ];
    }
    

    Then when I open up my terminal I run nix-shell and the current shell is setup with php81 and wp-cli every time. I can move the same shell.nix file to a new site, or a new computer and run nix-shell and I’ll get the same environment in my shell every time.

  • MailPoet Wastes Developer Time

    I use MailPoet on my personal site to run my email lists and that means I sometimes have it active when I’m developing locally. When I have any site set up to develop locally I use the WP_ENVIRONMENT_TYPE constant to tell WordPress that I’m on a local site, but unfortunately MailPoet doesn’t respect this.

    I found out that MailPoet is monitoring my emails (fine) and found that I had some URL’s that were unreachable at curtismchale.test because they’re on my local computer and can’t be reached by anyone but me. Thus my account was blocked from sending and marked as spam. I got a fairly pleasant but strongly worded email about sending spam and my account is now under review until I confirm that I’ve removed the curtismchale.test domain.

    So instead of using built in features of WordPress to make my life easy MailPoet wants me to always make sure I disable the plugin locally so I don’t send emails. That sounds good, but I’ve found bugs in MailPoet during local development that I would not have found in a live environment. Bugs I was able to fix or work around because I was developing locally.

    What MailPoet should do is what WooCommerce Subscriptions does, notice I’m on a local environment and then put up a nag about being disabled because I’m probably local and if I want it to process payments or emails anyway I have to click a button.

    I never have to manually worry about WooCommerce Subscriptions trying to charge my users twice because they built a plugin that anticipates developer needs.

    I’ll forget to turn MailPoet off some other time when I’m doing development and I’ll get another email about it. Maybe next time they’ll shut my account down…but that could be fixed if they added the local check.

    Yes I did mention that to the support agent, but I likely won’t wait for them to add the feature. I’ll likely build a plugin that lets you choose plugins to turn off locally that will automatically turn them off if it detects WP_ENVIRONMENT_TYPE set to local.

  • Reuben Asks – Is WordCamp Relevant to the PHP Development Community?

    His overall answer is no, WordPress is JavaScript focused. While I think there are a few interesting sessions that Reuben highlighted I feel disappointed to still see WP going down the React route as it is so slow.

    As I wrote a few days ago, we should send HTML to users then use some JavaScript to make the interactions a bit nicer. Your whole site should work without JavaScript involved in any fashion.

    All this JS crap continues to feel to me like we’re slowly heading back to the Flash days where you had to wait for loading screens.

  • Deleting 20,000 Spam WordPress Users with WP CLI

    While doing a site inspection I noticed that we had over 20,000 spam subscribers on the site with addresses that lead to telegram. The WordPress admin is a terrible way to try and delete users when you have this many to do, so we turned to wp cli.

    First I noted that the site couldn’t delete all the users at once because the command would time out so I used wp list user --role=subscriber --number=10 --field=ID to list 10 users and then increased the number of returned results till I was able to retrieve 5,000 users at once. Then it was a matter of combining this with wp delete user to delete the listed users. Then I ran the command a few times to clean up the site.

    Our final command looked like this.

    wp user delete $(wp user list --role=subscriber --field=ID --number=5000) --reassign=1
  • Adding Plugin Version Number to Enqueued Scripts and Styles

    For a long time, I manually changed the $version parameter when I was using wp_enqueue_script or wp_enqueue_style. At some point, I’d forget to make the change though and then some caching plugin would have issues because it wouldn’t realize that the script/style had been updated and I’d waste time wondering why I was seeing a cached version of scripts.

    To combat this I started adding the version number of the plugin or theme as the $version argument in wp_enqueue_script so let’s look at how I do that.

    get_file_data

    My first stab at solving this problem was while writing a plugin and dealing with admin scripts and styles only. If this is your sole use case then get_plugin_data( __FILE__ ) will get you the information you need.

    add_action( 'admin_enqueue_scripts', 'admin_enqueue' );
    
    function admin_enqueue(){
    
    	$plugin_data = get_plugin_data( __FILE__ );
    	$version = $plugin_data['Version'];
    
    	wp_enqueue_script( 'my_script_name', plugins_url( '/plugin-folder/path-to/script.js' ), array( 'jquery'), esc_attr( $version ), true );
    
    }
    

    Unfortunately, if you want a method that works when you’re adding scripts to the frontend and to the admin area of WordPress you’re going to be let down by get_plugin_data as this function is only defined in the WordPress admin area.

    To get reliable access to this data anytime you want to add $version to your enqueued scripts you need to use get_file_data . This is available on both the frontend and the admin area in WordPress.

    add_action( 'wp_enqueue_scripts', 'frontend_enqueue' );
    
    function frontend_enqueue(){
    
    	$plugin_data = get_file_data( __FILE__, [
    		'Version' => 'Version',
    	], 'plugin' );
    	$version = $plugin_data['Version'];
    
    	wp_enqueue_script( 'my_script_name', plugins_url( '/plugin-folder/path-to/script.js' ), array( 'jquery'), esc_attr( $version ), true );
    
    }
    

    get_file_data reads the first 8kb of a file for metadata embedded in the file. Then you can define the metadata values you want to be passed through in an array. The final declaration of plugin above will translate into a filter that will be named extra_plugin_headers that you could modify if needed.

    Caveats

    The biggest caveat to using get_file_contents is that you need to be doing your enqueue work in the main plugin file that has the standard WordPress plugin headers as that is what the plugin is reading.

    That works for me, and using get_file_contents I can use a standard snippet to enqueue scripts and make sure I have the version number appended all the time.

  • Your Code Should Gracefully Degrade

    One of the biggest tips I’d give to any new programmers is that your code should gracefully degrade. Let’s look at a simplified example in a recent WordPress project I was working on.

    In a plugin there was a line similar to what you’d see below that includes a file from a theme.

    require_once plugin_dir_path(__FILE__) . '../../themes/<theme-name>/lib/assets.php';
    

    In our default install this isn’t a problem because the theme exists and assets.php also exists. So the file is included and we have no issues.

    But this week I’ve been building a system to check which theme is active and force activate a specific theme for a site if it’s not active. It also lets us know if the theme isn’t present so it can’t be activated. While testing this I renamed the theme folder to have it deactivate itself and then the line above breaks.

    This is where we should fail gracefully and check if we have the file in the expected location before we try and include it. Further, outside of standard PHP notices, there is no reporting at any level about the issue.

    I’ve added a task to fix this, but this is what the fix would look like in pseudo-code if you’re curious.

    • Check if the theme exists with wp_get_themes()[^1]
    • If theme exists, check if the file in question exists
    • If either of the above fail, let someone know about it in your reporting system so that the issue can be resolved.

    In the case above, I’d send a message to the our dev slack channel along with some information about what the fix should be and if a developer should be involved. We have a number of errors that can be handled by support staff without involving a developer because of the tooling we’ve already built.

    In general, before you try to use some custom functionality, make sure it exists and then deal with the case if it doesn’t exist so your whole site doesn’t die.

    Yes we should not even have a plugin/theme dependency like this. That is a different topic to address and one of the many items of technical debt we need to dig out of.

    [^1]: This can be an expensive function so be clear about it’s cost and that the cost of the function increases as you add more themes.

  • Adding Random Content to WordPress via WP CLI

    Right now I’m working on a Google Analytics 4 integration that needs some data and views. To get the clicks/views I’m using the Selenium IDE Firefox Extension which records my clicks and the replays them. But to even have content for those clicks, I need to generate a bunch of it.

    Enter WP CLI Random Content by Bryan Richards.

    I’ve used two commands so far. wp random generate --count=50 to generate 50 posts. Then wp random generate --count=50 --post_type=question to generate 50 questions for the WP FAQ Manager plugin.

    WP CLI Random Content has far more options than I’ve described above. You can have it add taxonomies to your generated content, use --with-terms=true to have it generate terms first then generate content and attach it to those terms.

    Overall, it’s a one stop shop for generating basic content for testing your WordPress sites.