Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

  • FacetWP: Making Sorting Suck Less

    FacetWP: Making Sorting Suck Less

    Sorting data in WordPress is generally done in the most basic of ways. You want to see all posts that are in a specific category, you go to example.com/category/drinks/ and there you are. But if you want to see everything in the category ‘drinks’ with the tag ‘bourbon’ and the custom taxonomy of ‘ingredients’ and a value of ‘mint’ AND ‘simple syrup’ to get the recipe for a mint julep, then you have a pretty crazy complex query.

    Enter FacetWP

    FacetWP is a premium plugin that, for $79 a year, handles all that crazy sorting for you. And yes, it’s worth it.

    FacetWP introduces advanced filtering to WordPress, which lets you do things like get that list of all drinks made with bourbon that include a simple syrup, in a dynamic way! It’s incredibly fast, since it’s using ajax and javascript, and as long as you have enough server memory to index all the data in the first place, it’s faster than reloading a new category page.

    Downsides

    In order to be that fast, you do not get pretty URLs. Let’s say you have your drinks category at `example.com/category/drinks’ and you want to list all those things. Your URL will look like this:

    example.com/category/drinks/?fwp_alcohol=bourbun&fwp_ingredients=simple+syrup%2Cmint

    The realistic reason they don’t try to make it ‘pretty’ is that it would create a lot more rewrite rules than would be sustainable, if you have a lot of facets. The number of checks would slow your site down, and that would kind of suck.

    Compatibility Notes

    If you use CMB2 you’ll need FacetWP + CMB2.

    If you use Genesis themes, there are two tricks. First, you’ll want to use the following function to add FacetWP’s CSS to your theme:

    function YOURTHEMENAME_facetwp_class( $atts ) {
        $atts['class'] .= ' facetwp-template';
        return $atts;
    }
    add_filter( 'genesis_attr_content', 'YOURTHEMENAME_facetwp_class' );
    

    Second, if you’re like me and you use a lot of custom loops, they may not behave as expected. If you call the loop multiple times on a page (which is bad behavior in the first place and I know it), FacetWP has a bit of trouble knowing what javascript to apply to what section. That should be expected, and once I cleaned it up, it worked great.

    Should you use it?

    If you have a lot of complex intersectional queries to sort through, yes.

    If you need dynamic result updates, yes.

    It works.

  • Sharing WordPress Content with any PHP App

    Sharing WordPress Content with any PHP App

    Last week I explained how I shared my WordPress content with Hugo. Now, that was all well and good, but there is an obvious way I can do this when I don’t have a tool that understands dynamic content?

    I’ve got PHP so the answer is “You bet your britches, baby!”

    The First Version

    If you just want to grab the URL and output the post content, you can do this:

    $curl = curl_init();
    curl_setopt_array( $curl, array(
    	CURLOPT_FAILONERROR    => true,
    	CURLOPT_CONNECTTIMEOUT => 30,
    	CURLOPT_TIMEOUT        => 60,
    	CURLOPT_FOLLOWLOCATION => false,
    	CURLOPT_MAXREDIRS      => 3,
    	CURLOPT_SSL_VERIFYPEER => false,
    	CURLOPT_RETURNTRANSFER => true,
    	CURLOPT_URL            => 'https://example.com/wp-json/wp/v2/pages/12345'
    ));
    
    $result = curl_exec( $curln);
    curl_close( $curl );
    
    $obj = json_decode( $result );
    
    echo $obj->content->rendered;
    

    Now this does work, and it works well, but it’s a little basic and it doesn’t really sanitize things. Plus I would be placing this code in multiple files per site (not everyone themes as nicely as WordPress, ladies and gentlemen). So I wanted to write something that was more easily repeatable.

    The Advanced Code

    With that in mind, I whipped up a PHP file that checks and validates the URL, makes sure it’s a wp-json URL, makes sure it’s JSON, and then spits out the post content.

    <?php
    
    /* This code shows the content of a WP post or page.
     *
     * To use, pass the variable ?url=FOO
     *
     */
    
    if (!$_GET || !$_GET["url"]) return;
    
    include_once( "StrictUrlValidator.php" );
    
    $this_url = (string) filter_var( $_GET['url'], FILTER_SANITIZE_URL );
    
    if (strpos( $this_url, 'wp-json') == FALSE ) return;
    
    function do_curl ( $url ) {
    	$curl = curl_init();
    
    	curl_setopt_array( $curl, array(
    		CURLOPT_FAILONERROR    => true,
    		CURLOPT_CONNECTTIMEOUT => 30,
    		CURLOPT_TIMEOUT        => 60,
    		CURLOPT_FOLLOWLOCATION => false,
    		CURLOPT_MAXREDIRS      => 3,
    		CURLOPT_SSL_VERIFYPEER => false,
    		CURLOPT_RETURNTRANSFER => true,
    		CURLOPT_URL            => $url
    	) );
    
    	return curl_exec( $curl );
    	curl_close( $curl );
    }
    
    if ( StrictUrlValidator::validate( $this_url, true, true ) === false ) {
    	$return = "ERROR: Bad URL";
    } else {
    	$obj = json_decode( do_curl ( $this_url ) );
    
    	if ( json_last_error() === JSON_ERROR_NONE ) {
    		$return = $obj->content->rendered;
    	} else {
    		$return = "ERROR: Bad JSON";
    	}
    }
    
    echo $return;
    

    You can see I have some complex and some basic checks in there. The URL validation is done via a PHP Library called StrictUrlValidator. If I was using WordPress, I’d have access to esc_url() and other nifty things, but since I’m running this out in the wild, I make do with what I have.

  • Local: For When You Don’t Need The Kitchen Sink

    Local: For When You Don’t Need The Kitchen Sink

    The app formerly known as Pressmatic was bought by Flywheel and converted to be Local.

    I’d been wanting to try Pressmatic for a while, but had some ethical concerns about paying when I knew the owner wasn’t paying his other employees. At this point, however, the damage has been done in a way I can live with, so I downloaded Local to play with.

    Local Is an App

    The best thing about Local is that it’s an app. Vagrant is awesome, but it’s 100% command line, and while I’m fine with that, not everyone else is. The learning curve for VVV is steep and, given what you can do with it, it’s not a bad thing. I love it but it can be overkill when I want to work on my plugins. Local is an app, it looks like a Mac app and behaves like a Mac app, so it makes it more obvious how I use it and what I do with it.

    Syncing Data

    My biggest issue with VVV (and VVV2) is I can’t sync folders from Vagrant to my desktop. This is a workflow issue and I know it. For me, I have dedicated folders for my plugin development, all saved in ~/Development/wordpress/plugins-git/ (with a folder per plugin obviously). That makes it easy for me to script updates by saying “For every git repository in this folder, do a pull.”

    Since I do my development work on multiple computers, having as much of this automated as possible is important for my sanity, no matter how much Sara Lance tries to destroy it. Every time I log in to my computer, it runs the update which pulls everything down and syncs.

    And all that means, for VVV, I have two options.

    1. checkout a git repository into the plugin locations for my site and edit those, syncing them back up.
    2. manually copy files over every time I update them

    But with Local, I can use the add-on Volumes which lets me map a folder on Local’s virtual machine to my local desktop. Now it’s not perfect, but with this script by Andy Fragen, I was able to set up my site with actual honest to goodness symlinks:

    [gist]https://gist.github.com/afragen/748e4780b6057d4c41cf9e466557042a[/gist]

    Now I just edit my repositories and magically they’re updated on Local. Faster development for the win.

    (My own fork of Andy’s code is available here on Github – I made some changes but not much.)

    Conclusion

    I use both.

    VVV is great. I love it when I’m working on WordPress core, or god help me, WordPress.org itself.

    But when it’s just me developing my own software and testing? I like Local. It’s good.

  • Sharing WordPress Content with Hugo

    Sharing WordPress Content with Hugo

    When your life is just WordPress, there’s not a lot of headaches involved in making some posts and updating widgets and keeping your whole site in sync.

    When your life isn’t just WordPress, it gets a little weird.

    When you want to use WordPress to run your life a little more, you un-weird it by making it weirder.

    Rethinking Where the Content Lives

    Normally we think about content living on its own site. Well, I have a ‘message’ that needs to be the same on five different domains. Just work with me here. The point is, if I want to update the header message on the five sites, I have to update five sites. Yuck.

    Now, there are a lot of solutions to this. I decided I wanted one, and only one, place to update the header message. The most obvious is making a static text file that I could update when needed and import/include it in everywhere. But as I started looking into how I do that, my eyes drifted to WordPress.

    What if I used JSON? What if instead of a file, I made a page on WordPress, grabbed the content from https://example.com/wp-json/wp/v2/pages/12345 and parsed that so I didn’t have to do a whole mess of editing anywhere but on WordPress?

    Hugo

    Guess what. That works. And it works extra well for Hugo (a static site generator I’m fond of) because Hugo understands dynamic content. The one drawback is that it can’t live refresh remote data, so I will always have to push a change to the site to trigger this rebuild.

    However if you ever wondered how to include WordPress’ JSON data into a Hugo theme, here’s what I have in my template for utility-bar.html:

    {{ $wordpressURL  := "https://example.com/wp-json/wp/v2/pages/12345" }}
    {{ $wordpressJSON := getJSON $wordpressURL }}
    
    <div class="utility-bar">
    	<div class="wrap">
    		<section id="text-16" class="widget widget_text">
    			<div class="widget-wrap">
    				<div class="textwidget">
    					{{ $wordpressJSON.content.rendered | safeHTML}}
    				</div>
    			</div>
    		</section>
    	</div>
    </div>
    

    The reason safeHTML is there is that otherwise Hugo wants to escape my HTML. Which is a wise choice! Default to not trusting.

    This outputs the post content and I have a happy (enough) day. The more I look at it, the more I realize how much I can do with WordPress and Hugo, since regenerating the site just takes a push of Hugo content.

  • Genesis Themes: Author Box Shortcode

    Genesis Themes: Author Box Shortcode

    In building out a network of sites, I was struck upon by a feature of multisite I love, a feature of a theme I adore, and an inconvenience of the combination.

    Author Box

    StudioPress’ Genesis themes include a feature called “Author Box” which allows authors to create bios from their profiles and show them at the bottom of posts. When you have multiple authors on a site, this is a great way to make sure everyone gets credit and that they can control it.

    The code to make this show up is included in most (if not all) StudioPress themes, but if you need to add it for your post archives and single posts, it looks like this:

    add_filter( 'get_the_author_genesis_author_box_single', '__return_true' );
    add_filter( 'get_the_author_genesis_author_box_archive', '__return_true' );
    

    Multisite Magic

    Once the code is enabled, and once someone’s written a bio, their author box shows up for all sites on the network. This is great for what I needed, as it meant everyone had control and I could just set it and forget it. The only ‘annoying’ part is it’s the same bio for all sites, so if you have wildly different sites on your network, this may not be right for you.

    This does harken back to my age old comment: WordPress Multisite is for a network of somewhat related sites.

    By this I mean if all the sites on your network are related, let’s say for a school, then it doesn’t matter that everyone’s bio talks about their school work. But if you combine the school with hobbies, then it gets weird to announce that the champion archer has a PhD in neuroscience. Although that is pretty cool.

    Display The Author Box Anywhere

    The other problem with the author box is you can only use it on pages or posts as context. Which is not what I wanted here. So I made it a shortcode.

    function author_box( $atts ) {
    
    	$user = username_exists( sanitize_user( $atts['user'] ) );
    
    	if ( !$user ) return;
    
    	wp_enqueue_style( 'author-box-shortcode', plugins_url( 'author-box.css', __FILE__ ) );
    
    	$authordata    = get_userdata( $user );
    	$gravatar_size = 'genesis_author_box_gravatar_size' ;
    	$gravatar      = get_avatar( get_the_author_meta( 'email', $user ), $gravatar_size );
    	$description   = wpautop( get_the_author_meta( 'description', $user ) );
    	$username      = get_the_author_meta( 'display_name' , $user );
    
    	$author_box    = '
    		<section class="author-box author-box-shortcode">'
    		. $gravatar
    		. '<h4 class="author-box-title"><span itemprop="name">' . $username . '</span></h4>
    		<div class="author-box-content" itemprop="description">'. $description .'</div>
    		</section>
    	';
    
    	return $author_box;
    }
    

    This is obviously skewed towards Genesis themes, but realistically other than the code in $gravatar_size you can use this for any theme anywhere. The benefit of Genesis here is that most, if not all, of the CSS is done for you. The shortcode is [author-box user="ipstenu"] and it dumps out a full width user box of your named author.

    Display Multiple Boxes Instead

    But… What if you wanted a grid? Or a group of IDs? Taking advantage of the fact that Genesis comes with columns, the code looks like this:

    function author_box( $atts ) {
    
    	if ( $atts['users'] == '' ) return;
    
    	wp_enqueue_style( 'author-box-shortcode', '/wp-content/mu-plugins/css/author-box.css' );
    
    	$users = explode(',', $atts['users'] );
    	$user_count = count( $users );
    
    	$columns = 'one-half';
    	if ( $user_count == 1 ) $columns = '';
    	if ( $user_count % 3 == 0 ) $columns = 'one-third';
    
    	$author_box = '<div class="author-box-shortcode">';
    
    	foreach( $users as $user ) {
    		$user = username_exists( sanitize_user( $user ) );
    		if ( $user ) {
    			$authordata    = get_userdata( $user );
    			$gravatar_size = 'genesis_author_box_gravatar_size' ;
    			$gravatar      = get_avatar( get_the_author_meta( 'email', $user ), $gravatar_size );
    			$description   = wpautop( get_the_author_meta( 'description', $user ) );
    			$username      = get_the_author_meta( 'display_name' , $user );
    
    			$author_box   .= '
    				<section class="author-box '. $columns .'">'
    				. $gravatar
    				. '<h4 class="author-box-title"><span itemprop="name">' . $username . '</span></h4>
    				<div class="author-box-content" itemprop="description">'. $description .'</div>
    				</section>
    			';
    		}
    	}
    
    	$author_box .= '</div>';
    
    	return $author_box;
    }
    

    The shortcode here is [author-box users="ipstenu, liljimmi"] and that puts out a column of either fullwidth, half, or a third. The default is a half, and if there’s only one item, it goes to full width, but I only put in a 1/3rd check because I didn’t feel the need to cover everything. If you want to nick the CSS, StudioPress put it up online, and you can extend it as you want.

  • The New SEO Scam

    The New SEO Scam

    The email looked innocuous.

    I was just browsing Ipstenu.Org and saw in this post (link from 2002) you were interested in tech, and so I thought you might also be interested in linking to a resource we put together on the ways technology is improving health.

    It went on to tell me about how they were comprehensive, up to date, etc etc. I admit, I wondered how they got my email in the first place, since it’s not listed on my site on purpose. But barring anything nefarious, I assumed they guessed, and since it wasn’t important to me that a personal blog post from 2002 get updated, I deleted the email.

    Every five days after then I got a ‘follow up’ email from this person, Camilla Hathaway, and it was strange. I didn’t reply so why would they keep doing that?

    But then I got an email from another company about a different post, asking me if I wanted to link to their article about ddos protection. And another from a third company for a post about cPanel.

    They all sent follow up emails and they all were very ‘nice’ about it, praising my writing and telling me about broken links.

    Spam by any other name…

    If the email was about running a banner ad on CNN for $725, you’d know it was spam.

    If it was from the FBI telling you the corrupt government owed you millions, you’d know it was spam.

    This appeared to be from a real person, a real reader. Except for the fact that there was no way they should have been able to find that particular email address. Except for the fact that they kept email. Except for the fact that who the heck reads old posts on a personal site from as far back as 2001 (I’ve been blogging a long time) and tell me that a link is broken or the information is out of date.

    It’s weird, isn’t it?

    Well, it’s spam.

    The New Spam Game

    The old SEO spam was a lot more overt.

    We are a Leading SEO & Web Development Company and one of the very few companies which offer organic SEO Services with a full range of supporting services such as one way themed text links, blog submissions, directory submissions, article writing and postings, etc.

    or

    I was doing some research on [Subject] and landed on your website.

    You know the obvious ones. These new ones are more clever. They sound more like people. And the worst part is they aren’t all fake people.

    You see … A real company, a legit company, run by real people in the UK, spammed the hell out of me with offers like this. Every day for almost two weeks before I blocked the accounts. This was after I pinged them on Twitter and asked them to leave me alone.

    I shouldn’t have to.

    If I don’t reply, I’m probably not going to. But I surely am not going to reply within a day if you email me daily. The new spam game, the new scam game is to be nice and hammer you with a request over and over and over.

    If It Looks Too Good To Be True, It Is

    The bottom line is that if it looks too good to be true, it is. No probably about it.