Half-Elf on Tech

Thoughts From a Professional Lesbian

Category: How To

  • Remotely Hosting SVGs

    Remotely Hosting SVGs

    I prefer to use SVGs whenever possible instead of pngs or font icons. They can be resized, they can be colored, and they’re relatively small. But I also have a set of 700 icons I use and I don’t want to have to copy them to every single site. Instead, I’d like to have them all on a central repository and call them remotely.

    To The Cloud

    You can use S3 or really anything for this, but I chose DreamObjects since it’s open source and I work for DreamHost.

    For this to work, you create a bucket in DreamObjects with whatever name you want. I tend to name them after the website I’m working on, but in this case I’m making what is essentially a media library so a better bucket name would be the service. Keep in mind, while you can make a DNS alias like media.mydomain.com for this, you cannot use that with https at this time. Sucks. I know.

    On DreamObjects, the accessible URL will be BUCKET.objects-us-east-1.dream.io — hang on to that. We’ll need it in a bit.

    Upload Your Files

    Since I plan to use this as a collective for ‘media’ related to a network of sites, I named the bucket for the network and then made subfolders:

    • backups
    • site1
    • site2
    • svg
    • svgcolor

    Backups is for everything you’re thinking. Site1 and Site2 are special folders for files unique to those domains only. Think large videos or a podcast. Since they’re not updated a lot, and I want them accessible without draining my server resources, the cloud is perfect. I separated my SVG files into two reasonable folders, because I plan to generate a list of these later.

    Calling An Individual File

    This is the much easier part. In general, PHP can use file_get_contents() for this. But we’re on WordPress and there is a more reliable way about this.

    $svg = wp_remote_get( 'https://BUCKET.objects-us-east-1.dream.io/svg/ICON.svg' );
    if ( $svg['response']['code'] !== '404' ) {
    	$icon = $svg['body'];
    }
    

    By using wp_remote_get it’s possible to check if the file exists before displaying the content of the body. Which is, indeed, how one displays the content.

    The reason, by the way, we want to use wp_remote_get and not file_get_contents is that there’s no way to check if the file exists with the latter. You could still use file_get_contents to display the file, but once you’ve done the remote get, you may as well use it.

    Getting a List Of All SVGs

    I’d done this before with local images, getting a list of all the SVGs. You can’t do a foreach of a remote folder like that. So this is a little messier. You’ll need the Amazon AWS SDK for PHP for this. I’ve tested this on the latest of the 2 branch – 2.8.31 – and the 3 branch – 3.32.3 and the code is different but can work.

    In both cases, I downloaded the .phar file and included it in a function that I used to save a file with a list of the image names in them. By doing that, I can have other functions call the file and not have to query DreamObjects every time I want to get a list.

    function get_icons() {
    	include_once( dirname( __FILE__ ) . '/aws.phar' );
    
    	$svgicons = '';
    
    	// AWS SDK Code Goes Here
    
    	$upload_dir = wp_upload_dir();
    	$this_file  = $upload_dir['basedir'] . '/svgicons.txt';
    	$open_file  = fopen( $this_file, 'wa+' );
    	$write_file = fputs( $open_file, $svgicons );
    	fclose( $open_file );
    }
    

    I left out the SDK code. Let’s do that now.

    SDK V2

    The V2 is actually what’s still officially supported by CEPH, but the PHAR file is larger.

    	Version 2
    	$client = Aws\S3\S3Client::factory(array(
    		'base_url' => 'https://objects-us-east-1.dream.io',
    		'key'      => AWS_ACCESS_KEY_ID,
    		'secret'   => AWS_SECRET_ACCESS_KEY,
    		'version' => 'latest',
    		'region'  => 'us-east-1',
    	));
    
    	$files = $client->getIterator( 'ListObjects', array(
    		'Bucket' => 'BUCKET',
    		'Prefix' => 'svg/',
    	));
    
    
    	foreach ( $files as $file ) {
    		if ( strpos( $file['Key'], '.svg' ) !== false ) {
    			$name         = strtolower( substr( $file['Key'] , 0, strrpos( $file['Key'] , ".") ) );
    			$name         = str_replace( 'svg/', '', $name );
    			$symbolicons .= "$name\r\n";
    		}
    	}
    

    SDK V3

    I would recommend using V3 if possible. Since you’re only using it to get data and not write, it’s fine to use, even if it’s not supported.

    	// Version 3
    	$s3 = new Aws\S3\S3Client([
    		'version' => 'latest',
    		'region'  => 'us-east-1',
    		'endpoint' => 'https://objects-us-east-1.dream.io',
    		'credentials' => [
    			'key'    => AWS_ACCESS_KEY_ID,
    			'secret' => AWS_SECRET_ACCESS_KEY,
    		]
    	]);
    
    	$files = $s3->getPaginator( 'ListObjects', [
    		'Bucket' => 'BUCKET',
    		'Prefix' => 'svg/',
    	]);
    
    	foreach ( $files as $file ) {
    		foreach ( $file['Contents'] as $item ) {
    	
    			if ( strpos( $item['Key'], '.svg' ) !== false ) {
    				$name = strtolower( substr( $item['Key'] , 0, strrpos( $item['Key'] , ".") ) );
    				$name = str_replace( 'svg/', '', $name );
    				$symbolicons .= "$name\r\n";
    			}
    		}
    	}
    

    Side Note…

    If you’re on DreamHost, you’ll want to add this to your phprc file:

    extension=phar.so
    detect_unicode = Off
    phar.readonly = Off
    phar.require_hash = Off
    suhosin.executor.include.whitelist = phar
    

    That way it knows to run phar files properly. And by the way, that’s part of why phar files aren’t allowed in your WordPress plugins.

    Which is Better?

    That’s a difficult question. When it comes to loading them on the front end, it makes little difference. And having them on a CDN or a cloud service is generally better. It also means I don’t have to push the same 400 or so files to multiple servers. On the other hand, calling files from yet another domain gets you a down check on most site speed checks… But the joke’s on them. The image is loaded in PHP.

    Of course the massive downside is that if the cloud is down my site can be kinda jacked. But that’s why using a check on the response code is your lifeline.

  • Deploying from GitHub with Codeship

    Deploying from GitHub with Codeship

    For the longest time I self-hosted all my git repositories not so much because I enjoyed doing so but because there were limited options for easily pushing code from git to my servers. Invariably you will end up using an intermediary because GitHub and their peers have no real reason nor inclination to make those things easy for you. After all, if they can keep you in their systems, more money to ’em.

    And while that’s perfectly understandable and logical, it’s annoying. And if you’re on a budget and not a Git Expert, it’s extremely frustrating. The deployment API was written in High Level Geek, and I found it a headache to decipher and test.

    Thankfully there are tools for that, and one of them in Codeship. Codeship is free for 100 builds a month, and unlike DeployHQ, it uses rsync, which meshes with my preferred way of moving data.

    The Plan

    My plan is simple. I want to push code to a Github Repository and, when it’s a push to master, it would rsync the files over to my server. This will allow multiple people to work on code, among other things.

    The Setup

    First make an account with Codeship. You can log in as Github which is useful since you’ll want to connect your Github account with Codeship anyway. When you create a project, you’ll be prompted which SCM you want to use:

    Create a Project in Codeship

    I’m using Github, but it’s nice to see GitLab in there as well. Once you pick your SCM, paste in the clone URL.

    Examples:

    • git@github.com:<username>/<repository_name>.git
    • https://github.com/<username>/<repository_name>.git
    • https://github.com/codeship/<repository_name>

    Finally you can pick Codeship Pro or Basic – I picked Basic because it required the least amount of know-how. Not that it’s easy, but I don’t have to configure the server or anything annoying. In fact, Basic is so basic, than you can just accept the default setup commands and go. Which is what I did.

    The Settings

    Once you’ve done all that, you’re on a new screen that tells you to push code. Of course, you can’t do that until you set up a couple more things. Like tell it where to push the code.

    Click on “Project Settings” and go to the General tab. You’ll need to get that SSH key and add it to your server’s ~/.ssh/authorized_keys to allow passwordless deployment. I strongly recommend that.

    Now you can click Deploy and add a Deployment Pipeline. Pick ‘master’ unless you’re using something else.

    Now you have to add a deployment to your pipeline. There are a lot of options here, but for my plan of an rsync, the choice is “Custom Script”. Since I’m pushing code to my DreamPress server, the rsync looks like this:

    # DreamPress
    rsync -aCz -e "ssh" ~/clone/ wp_e413xh@mysite.dream.press:/home/wp_e414xh/mysite.dream.press/wp-content/themes/my-theme-name/ --delete
    

    You can customize your rsync commands however you want. I like mine to delete files I’ve removed.

    Deploy the Ships

    Everything is set up so go back to your project page and push some code to Github

    Magic happens.

    The code deploys.

    Huzzah.

    On the free version you only get 100 private builds and five private projects. Mine are public (so’s the Github repo for that matter) so it doesn’t matter. The only downside is only getting one push at a time, but since they take less than five minutes, it’s perfectly acceptable for a free solution.

  • Demi Related Post Types

    Demi Related Post Types

    In my last post, I talked about relating posts to a custom post type. That is, how I listed the blog posts tagged with a show on the show page. Let’s do the reverse!

    Continuing the Click Hole

    While we’re very good about remembering to tag the posts we write with the shows they’re about, we’re not always as good with remembering to link the first instance of the show name to the show. That is, when we write about Supergirl we sometimes forget to link that to the page for her.

    That said, we still want to make it ‘easy’ for people to figure out where they can read more about the show. The ‘simplest’ way to do that would be to have a block at the bottom of each blog post. “Want to know more about the shows in this post?” or something along those lines.

    Relations Back Across Post Types

    The difficulty here is that if you want to get post data based on a post name, it’s easy. There’s a handy little function get_page_by_title() where you can put in this: get_page_by_title( 'Supergirl', OBJECT, 'post_type_shows' ); where Supergirl is the name of the show and post_type_shows is the post type.

    That’s well and good, but I knew there would be cases where the tag wasn’t going to match the name. People name shows pretty weird stuff (#hastag for example is the name of a show). Instead, I knew that I needed the tag slug and for that, I needed a different function: get_page_by_path()

    The Code

    Here’s the magic sauce:

    function related_shows( $content ) {
        if ( is_singular( 'post' ) ) {
    		$posttags = get_the_tags( get_the_ID() );
    		$shows = '';
    		if ( $posttags ) {
    			foreach ( $posttags as $tag ) {
    				if ( $post = get_page_by_path( $tag->name, OBJECT, 'post_type_shows' ) ) {
    					$shows .= '<li><a href="/show/' . $post->name . '">'. $tag->name . '</a></li>';
    				}
    			}
    		}
    		if ( $shows !== '' ) {
    			$content .= '<section class="related-shows"><div><h4 class="related-shows-title">Read more about shows mentioned in this post:</h4><ul>' . $shows . '</ul></div></section>';
    		}
    	}
    	return $content;
    }
    

    This has a few failsafes in there, like it only runs on single posts, it has to have at least one tag, and it has to have at least one tag that’s a show. Whew.

    After that, it’s just the CSS.

  • Semi Related Posts

    Semi Related Posts

    Once in a while you write some code that you’re pretty sure will never be useful to anyone else on the planet, but the idea is cool, so you want to share it.

    Hence this post.

    The Power Of Relations

    I was making coffee one Sunday morning, thinking about how I could keep turning a site into a wiki-level click hole, where a person could get lost for hours and days reading article after article. The reason WikiPedia works so well is that the content is all interlinked. Now, if you’ve never been a wiki editor, and you’ve just noticed that there are a billion links on every page, taking you around the universe, I’ll let you in on a secret. Those links are all human-made. A person goes in and makes those links, giving you ‘related’ posts.

    But WikiPedia really only works well due to the massive amounts of people who know weird shit and are willing to help and share. When it’s just you and your crazy friend running a site, that’s a lot of work. And you have wives and pets and kids to take care of. No, you want– you need to automate the heck out of this stuff.

    Related Posts are Hard

    Making related posts is a hard thing. There are myriad algorithms out there to calculate ‘relatedness’ and they’re all crazy complex. Worse, they all require a lot of processing power. There’s a reason Jetpack’s related posts feature runs on their servers. They’ve got more power than you do, and it stops people on shared from being nasty neighbors.

    This code is not a replacement for that code. This code is very specific and very targeted. It relies on people being clever (but not too clever). But when it works, it makes the interwoven data sets of a site work well. Because you see, we’re not talking about ‘If you like this article, read this other one…’ No. No we’re talking about crossing custom post types.

    Relations Across Post Types

    Hold on. I know what you’re thinking.

    The point of separate post types is not to cross the streams. Yes, I know. But that’s actually not true. Menus, for example, are a post type, and you a great many people want to have specific menus to their post types. That sounds logical, doesn’t it? Well, in this case, I have a post type for TV Shows, and I have regular old blog posts.

    My lightbulb moment was three-fold:

    1. When we write articles about the TV Shows, we logically use tags of the show name.
    2. Those show name tags match the slugs of the TV shows.
    3. If we link back to the articles from the TV Show page, then we’re getting people down the click hole.

    This meant my logic was simple: On the TV show page, check if there are any blog posts tagged with the show name and, if so, that post was ‘related’ to the show and therefore we should link back to it.

    The Code

    I put all this into a function so I could re-use it in a couple places. I like not repeating myself with code. I also have the function call another function, because I actually re-use this to connect different post types that all use the same tags. If you don’t need that, you can tighten it up.

    The Display

    This is in the theme:

    $slug = get_post_field( 'post_name', get_post() );
    $term = term_exists( $slug , 'post_tag' );
    if ( $term !== 0 && $term !== null ) { ?>
    	<section name="related-posts" id="related-posts" class="shows-extras">
    		<h2>Related Posts</h2>
    		<?php echo related_posts( $slug, 'post' ); ?>
    	</section> <?php
    }
    

    The Functions

    And here’s the function it calls:

    function related_posts( $slug ) {
    	$related_post_loop  = related_posts_by_tag( 'post', $slug );
    	$related_post_query = wp_list_pluck( $related_post_loop->posts, 'ID' );
    
    	if ( $related_post_loop->have_posts() ) {
    		$the_related_posts = '<ul>';
    		foreach( $related_post_query as $related_post ) {
    			$the_related_posts .= '<li><a href="' . get_the_permalink( $related_post ) . '">' . get_the_title( $related_post ) . '</a> &mdash; ' . get_the_date( get_option( 'date_format' ), $related_post ) . '</li>';
    		}
    
    		$the_related_posts .= '</ul>';
    	}
    
    	return $the_related_posts;
    }
    

    And here’s the function that calls:

    function related_posts_by_tag( $post_type, $slug ) {
    	$term = term_exists( $slug, 'post_tag' );
    	if ( $term == 0 || $term == null ) return;
    
    	$count = '5';
    	$query = new WP_Query( array(
    		'post_type'       => $post_type,
    		'posts_per_page'  => $count,
    		'no_found_rows'   => true,
    		'post_status'     => array( 'publish' ),
    		'tag'             => $slug,
    		'orderby'         => 'date',
    		'order'           => 'DESC',
        ) );
    
    	wp_reset_query();
    	return $query;
    }
    

    The Result?

    An unordered list of the last five related posts. You could add a link to ‘show more’ if you were so included.

  • Stopwords and Sort Queries

    Stopwords and Sort Queries

    The code I use is part and parcel from a comment Pascal Birchler made in 2015 and Birgir E. riffed on in 2016. I made one small change.

    The Problem

    People like to name TV shows with ‘The’ or ‘A’ or ‘An’ as the first word. “The Fall” and “The Good Wife” for example. However, when we order such things in a human sensible way, “A Touch of Cloth” should be listed behind both of those.

    • Frankenstein
    • The Fall
    • Grey’s Anatomy
    • The Good Wife
    • A Touch of Cloth

    WordPress, though, sees them as absolutes and you get this:

    • A Touch of Cloth
    • Frankenstein
    • Grey’s Anatomy
    • The Fall
    • The Good Wife

    Not quite right, is it?

    The Fix

    This code does two things.

    The first part is the filter on the posts_orderby function. That checks if the post type is the one I want to filter (in my case, only shows), and if so, use regex to filter out my stop words of ‘the ‘, ‘an ‘, and ‘a ‘. The extra space in each word is important. I want to reorder “The Fall” and not “Then They Fall” after all!

    The second part is the actual filter on the title, to mess with it only for the ordering of posts.

    add_filter( 'posts_orderby', function( $orderby, \WP_Query $q ) {
        if( 'post_type_shows' !== $q->get( 'post_type' ) )
            return $orderby;
    
        global $wpdb;
    
        // Adjust this to your needs:
        $matches = [ 'the ', 'an ', 'a ' ];
    
        return sprintf(
            " %s %s ",
            MYSITE_shows_posts_orderby_sql( $matches, " LOWER( {$wpdb->posts}.post_title) " ),
            'ASC' === strtoupper( $q->get( 'order' ) ) ? 'ASC' : 'DESC'
        );
    
    }, 10, 2 );
    
    function MYSITE_shows_posts_orderby_sql( &$matches, $sql )
    {
        if( empty( $matches ) || ! is_array( $matches ) )
            return $sql;
    
        $sql = sprintf( " TRIM( LEADING '%s' FROM ( %s ) ) ", $matches[0], $sql );
        array_shift( $matches );
        return MYSITE_shows_posts_orderby_sql( $matches, $sql );
    }
    

    If you’re using MariaDB, this can be even easier, but I have to test on my dev site, which uses MySQL.

  • Date, Time, and PHP

    Date, Time, and PHP

    I had a case where I needed to convert time from the format of the US centric dd/MM/YYYY to something a little more sane.

    A Very Bad (and Stupid) Way

    Since I was starting from a set position of 18/07/2017 (for example), the first idea I had was to use explode to do this:

    $date = "18/07/2017";
    $datearray = explode("/", $date);
    $newdate = $date[1].'-'.$date[0].'-'.$date[2]
    

    I did say this was stupid, right? The reason I’d want to do that, though, is that gets it into a DD-MM-YYYY format, which can be parsed a little more easily. Unless you’re an American. Which means this idea is useless.

    A Less Bad, But Still Bad, Way

    What about making it unix timestamp?

    $date = "18/07/2017";
    $date_parse = date_parse_from_format( 'm/d/Y' , $date);
    $date_echo = mktime( $date_parse['hour'], $date_parse['minute'], $date_parse['second'], $date_parse['month'], $date_parse['day'], $date_parse['year'] );
    

    Now I have a unix timestamp, which I can output any which way I want! At this point, if I’m not sure what version of PHP people are using, as long as it’s above 5.2, my code will work. Awesome.

    But we can do better.

    A Clever Way

    Instead of all that, I could use date_parse_from_format:

    $date = "18/07/2017";
    $date_parse = date_create_from_format('m/j/Y', $date );
    $date_echo[] = date_format($date_parse, 'F d, Y');
    

    And this will convert “18/07/2017” into “18 July, 2017”

    Which is far, far more understandable.

    So Which Is Best?

    If it’s your own code on your own server, I’d use date_create_from_format to control the output as you want.

    If this is code you want to run on any server, like in a WordPress plugin, use the unix timestamp, and then use date_i18n to localize:

    date_i18n( get_option( 'date_format' ), $date_echo );