Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: coding

  • NoEmbed: Embedding What’s Not oEmbed

    NoEmbed: Embedding What’s Not oEmbed

    We all love oEmbed with WordPress. Want to include a YouTube video? Paste in the URL and call it a day!

    The magic is that WordPress sends data to the YouTube oembed endpoint (i.e. a special app on YouTube that translates URLs to embed things) and says “Hi, I have this video URL here, what can I use to embed it?” And YouTube replies back “This javascript, my fine friend!” WordPress tips it hat and carries on, swapping the URL out for javascript on the fly.

    Awesome.

    Except when it doesn’t work because it can’t because there isn’t an oEmbed endpoint.

    What Now?

    Usually I make a shortcode. But my man Otto always grumbles and tells me to make an embed instead.

    The concept of the embed is that we register a ‘fake’ oembed. It’s not an endpoint, it’s just saying “Hey, WordPress. When you see this kind of URL all alone, let’s make magic.”

    It’s surprisingly straightforward. All we need to know are two things:

    1. What is the URL people are going to paste in?
    2. What is the output supposed to look like?

    Since we’ve already done this as a shortcode, we’re ready to go.

    Register the Handler

    First we have to tell it that we’re making a new handler, called indiegogo and this is the kind of URL to expect:

    wp_embed_register_handler( 'indiegogo', '#https?://www\.indiegogo\.com/projects/.*#i', 'indiegogo_embed_handler' );
    

    The first part, indiegogo, is the name. It should be unique.

    The second part, '#https?://www\.indiegogo\.com/projects/.*#i' is saying “http OR https” and “as long as the URL starts with www.indigogo.com/projects” — it’s ‘basic’ regex.

    The last bit, indiegogo_embed_handler is the function name we’re going to need.

    Write the Embed Function

    This is very similar to the shortcode. We take the data, make sure it’s formatted correctly, and output the (in this case) iFrame:

    function indiegogo_embed_handler( $matches, $attr, $url, $rawattr ) {
    	$url   = esc_url( $matches[0] );
    	$url   = rtrim( $url, "#/");
    	$url   = str_replace( 'projects/', 'project/', $url );
    	$embed = sprintf( '<iframe src="%1$s/embedded" width="222" height="445" frameborder="0" scrolling="no"></iframe>', $url );
    	return apply_filters( 'indiegogo_embed', $embed, $matches, $attr, $url, $rawattr );
    }
    

    I’ve left the filters in place in case I decide I want to do more weird things to it, without having to edit the core part of my code.

    A GutenGotcha

    For some reason, this doesn’t work in Gutenberg, and it tells me it cannot embed the content. Except when you visit the page, everything is embedded perfectly.

  • Posts by Day, Every Year

    Posts by Day, Every Year

    In my previous post I mentioned a site that has a tag for a date. That is, it uses 25-march (or march-25) to tag it’s posts so someone could conceivably find all the posts made on March 25th in every single year.

    WordPress makes it easy to list all the posts on a specific date. Just visit example.com/2018/04/19/ and you’ll see all the posts made on April 19th of this year. Remove the 19 and you get everything from April and so on and so forth.

    But you can’t, out of the box, list everything on April 19th from every single year the site’s been up.

    WP_Query has Dates

    As of 3.7, WordPress has a date_query aspect to WP_Query which lets you do this:

    $all_posts_on_this_date = new WP_Query( array(
        'posts_per_page' => -1, // this will show all the posts
        'date_query' => array(
            array(
                'month' => date( 'n', current_time( 'timestamp' ) ),
                'day'   => date( 'j', current_time( 'timestamp' ) )
            ),
        ),
    ) );
    

    That generates an array of every single post that has the same month and day of today, but does not check the year. If you don’t want all the posts, just the last ten, use 'posts_per_page' => 10, instead.

    Once you have your list of posts, you can use a normal loop to display the content:

    if ( $all_posts_on_this_date->have_posts() ){
        while( $all_posts_on_this_date->have_posts() ) {
            $all_posts_on_this_date->the_post();
            the_title();
        }
    }
    
  • Stacked Charts Part 2: Rebuilding the Array

    Stacked Charts Part 2: Rebuilding the Array

    I’ve talked about this before in category statistics, but in order to get the data from a simple array into a Chart.js consumable one, we have to rebuild the array.

    All Arrays are not Equal

    In order to save the data in a way I could use and reuse, I had to aim at the lowest common denominator. But also I had to save the arrays at a per show basis, which is not the same as what I was going to need to output.

    Instead of just outputting the averages for the show, I needed to combine all this into a ‘by nation’ statistic. That is, I needed to get a list of all shows that were associated with a taxonomy value for that country (easy) and combine all their arrays (not quite easy) and order the data in a way that would make sense (not easy).

    So again we start with understanding the array. Here’s a show that happens to air in Argentina:

    Array
    (
        [cisgender]    => 2
        [trans-woman]  => 0
        [trans-man]    => 0
        [non-binary]   => 0
        [gender-fluid] => 0
        [gender-queer] => 0
        [agender]      => 0
    )
    

    This is the data for one show. Argentina has 2, oddly both with the same stats breakdown by gender identity. What I need to do is loop through both those shows and add the arrays to be this:

    Array
    (
        [cisgender]    => 4
        [trans-woman]  => 0
        [trans-man]    => 0
        [non-binary]   => 0
        [gender-fluid] => 0
        [gender-queer] => 0
        [agender]      => 0
    )
    

    Get the Base Arrays

    Just like before, we make an array of the base data as we have it in the gender, sexuality, and romantic orientations. In this case, we’re adding in a query to change the order to be largest to smallest overall from the taxonomy. While this may not be true for all nations in the future, it is today:

    $taxonomy = get_terms( 'lez_nations' );
    foreach ( $taxonomy as $the_tax ) {
    }
    

    I need to pause here. Everything from here out goes in that foreach. We’re going to be looping for each nation in the list of nations. Now… I re-use this code for multiple taxonomies, so lez_nations is actually lez_' . $data and it dynamically changes based on how I call this function.

    On we go!

    	$characters = 0;
    	$shows      = 0;
    	
    	// Create a massive array of all the character terms we care about...
    	$valid_char_data = array( 
    		'gender'    => 'lez_gender',
    		'sexuality' => 'lez_sexuality',
    		'romantic'  => 'lez_romantic',
    	);
    
    	if ( isset( $subdata ) && !empty( $subdata ) ) {
    		$char_data = array();
    		$terms     = get_terms( $valid_char_data[ $subdata ], array( 'orderby' => 'count', 'order' => 'DESC' ) );
    
    		if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) {
    			foreach ( $terms as $term ) {
    				$char_data[ $term->slug ] = 0;
    			}
    		}
    	}
    

    Now that we have those base arrays, again set to zero,

    By the way, $subdata and $data are parameters sent to the function that runs this. $subdata is for the taxonomy we’re calculating (sexuality etc) and $data is for the overall taxonomy (Nations or perhaps Stations or genres – we use a lot of those).

    This gets us started.

    Queery the Posts

    Next we need a WP_Query of all the posts in the taxonomy.

    	$count = wp_count_posts( 'post_type_shows' )->publish;
    	$queery = new WP_Query ( array(
    		'post_type'              => 'post_type_shows',
    		'posts_per_page'         => $count,
    		'post_status'            => array( 'publish' ),
    		'tax_query'              => array( array(
    			'taxonomy' => 'lez_' . $data,
    			'field'    => 'slug',
    			'terms'    => $the_tax->slug,
    			'operator' => '=',
    		),),
    	) );
    	wp_reset_query();
    

    Remember, this is still within that foreach above. And once we have the posts, let’s query all the shows:

    	if ( $queery->have_posts() ) {
    		foreach( $queery->posts as $show ) {
    
    			$shows++;
    			// Get all the crazy arrays
    			$gender = get_post_meta( $show->ID, 'lezshows_char_gender' );
    			if ( isset( $subdata ) ) { 
    				$dataset = get_post_meta( $show->ID, 'lezshows_char_' . $subdata );
    			}
    
    			// Add the character counts
    			foreach( array_shift( $gender ) as $this_gender => $count ) {
    				$characters += $count;
    			}
    
    			if ( !empty( $dataset ) ) {
    				foreach( array_shift( $dataset ) as $this_data => $count ) {
    					$char_data[ $this_data ] += $count;
    				}
    			}
    
    		}
    	}
    

    The weird section you see, // Add the character counts is there because every character has a gender, but not everyone has a sexuality or romantic orientation. Because of that, I decided it was safest to use that as my baseline count.

    The second section that checks if ( !empty( $dataset ) ) {...} is what adds things up for the array.

    Speaking of…

    Output the New Array

    Once I have those counts, I generate different arrays depending on what I’m outputting. The basic barchart is different from a percentage, which is different from the stacked bar.

    	// Determine what kind of array we need to show...
    	switch( $format ) {
    		case 'barchart':
    			$array[] = array (
    				'name'  => $the_tax->name,
    				'count' => $shows,
    			);
    			break;
    		case 'percentage':
    			$array = self::taxonomy( 'post_type_shows', 'lez_' . $data );
    			break;
    		case 'count':
    			$array = count( $taxonomy );
    			break;
    		case 'stackedbar':
    			$array[$the_tax->slug] = array(
    				'name'       => $the_tax->name,
    				'count'      => $shows,
    				'characters' => $characters,
    				'dataset'    => $char_data,
    			);
    	}
    

    And all of this is so I could get that silly stacked bar, which will have the count of total characters, shows, and the data.

    Whew.

  • Reordering Sort Order Redux

    Reordering Sort Order Redux

    Earlier this year I talked about removing stopwords from sort queries.

    Sadly I ran into a problem where the code wasn’t working.

    The Original Code

    Here’s the original code.

    add_filter( 'posts_orderby', function( $orderby, \WP_Query $q ) {
        if( 'SPECIFIC_POST_TYPE' !== $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 );
    }
    

    This worked, mostly, but it somehow broke pagination. Every page restarted the order. This had to do with complications with the Genesis theme but more importantly it messed up the order on the back of WordPress and it didn’t play well with FacetWP. So I rewrote it a little to be more specific:

    The New Code

    if ( !is_admin() ) {
    	
    	add_filter( 'posts_orderby', function( $orderby, \WP_Query $q ) {
    		
    		// If this isn't an archive page, don't change $orderby
    		if ( !is_archive() ) return $orderby;
    		
    		// If the post type isn't a SPECIFIC_POST_TYPE, don't change $orderby
    		if ( 'SPECIFIC_POST_TYPE' !== $q->get( 'post_type' ) ) return $orderby;
    
    		// If the sort isn't based on title, don't change $orderby
    		$fwp_sort  = ( isset( $_GET['fwp_sort'] ) )? sanitize_text_field( $_GET['fwp_sort'] ) : 'empty';
    		$fwp_array = array( 'title_asc', 'title_desc', 'empty');
    		if ( !in_array( $fwp_sort, $fwp_array ) ) return $orderby;
    
    		// Okay! Time to go!
    		global $wpdb;
    
    		// Adjust this to your needs:
    		$matches = [ 'the ', 'an ', 'a ' ];
    
    		// Return our customized $orderby
    		return sprintf(
    			" %s %s ",
    			MY_CUSTOM_posts_orderby_sql( $matches, " LOWER( {$wpdb->posts}.post_title) " ),
    			'ASC' === strtoupper( $q->get( 'order' ) ) ? 'ASC' : 'DESC'
    		);
    
    	}, 10, 2 );
    
    	function MY_CUSTOM_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 lwtv_shows_posts_orderby_sql( $matches, $sql );
    	}
    }
    

    First of all, I got smart about only loading this when it needed to be loaded. Next I told it to only sort on archive pages, because I was also outputting recently added lists in other places. Finally I forced it to understand Facet, and that if I wasn’t sorting by alphabetical, it didn’t matter at all.

  • Make WordPress Gay

    Make WordPress Gay

    In September, WordPress.com changed their admin bar from the normal black or blue to a rainbow color. Why?

    Australia will be holding a national survey on marriage equality over the next two months. To show our support as a company for marriage equality, we’re showing the rainbow bar in the WordPress.com admin bar to all logged-in Australian visitors. You can read more about the marriage equality campaign here: http://www.equalitycampaign.org.au/

    And this was super cool. They’d done it before when Gay Marriage was legalized in the US and it’s much appreciated as a show of solidarity. But… The menu bar only shows if you’re visiting WordPress.com from Australia. This causes two problems for me:

    1) I’m in the US
    2) I self host

    Don’t worry, there’s a solution.

    The ‘Official’ Solution

    The code for what WordPress.com does can be found on the Github repository for Calypso. But that’s all mixed in with a lot of extra ‘stuff’ that has to be there for their services, like geo-location and so on.

    The rest of us don’t need that, and since Gary (the fellow who wrote the ticket) is a friend of mine, I asked him if the code was available. It is.

    Rainbow Bar Code

    <?php
    
    /*
     * Plugin Name: Rainbow Bar!
     */
    
    function rainbow_bar() {
    ?>
    	<style type="text/css">
    		#wpadminbar {
    			background: linear-gradient(
    				to bottom,
    				#e24c3e 0%,
    				#e24c3e 16.66667%,
    				#f47d3b 16.66667%,
    				#f47d3b 33.33333%,
    				#fdb813 33.33333%,
    				#fdb813 50%,
    				#74bb5d 50%,
    				#74bb5d 66.66667%,
    				#38a6d7 66.66667%,
    				#38a6d7 83.33333%,
    				#8c7ab8 83.33333%,
    				#8c7ab8 100% );
    		}
    
    		#wpadminbar,
    		#wpadminbar .quicklinks > ul > li {
    			-webkit-box-shadow: unset;
    			-moz-box-shadow: unset;
    			box-shadow: unset;
    		}
    
    		#wpadminbar .ab-top-menu > li > a {
    			background-color: rgba( 50, 55, 60, .85 );
    		}
    	</style>
    <?php
    }
    add_action( 'wp_before_admin_bar_render', 'rainbow_bar' );
    

    Install that to get this:

    Rainbow Pride WP Admin Bar

    Small Changes

    In order to make it look ‘right’ for me, I changed two things

    I removed this:

    	#wpadminbar .ab-top-menu > li > a {
    		background-color: rgba( 50, 55, 60, .85 );
    	}
    

    And I added this:

    	#wpadminbar .ab-item, #wpadminbar a.ab-item, #wpadminbar > #wp-toolbar span.ab-label, #wpadminbar > #wp-toolbar span.noticon,
    	#wpadminbar .ab-icon, #wpadminbar .ab-icon:before, #wpadminbar .ab-item:before, #wpadminbar .ab-item:after {
    		color: #000;
    	}
    

    Caveats

    Even with my changes, the color for my Jetpack stats looks wrong. It’s too washed out. And that’s because it’s apparently hardcoded into the plugin in a way I can’t overwrite. I can live with that problem.

    The other issue is that this will only show for logged in users (unless you’re using some code to always show the admin bar). That begs the question, of course, of why would you do this if only logged in users?

    If you’re running a queer themed website that happens to use WordPress … like, say, Autostraddle, then this makes perfect sense. For me, it just makes me feel happy to see that pride rainbow all the time.

  • 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.