Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: code

  • Image Attribution

    Image Attribution

    Did you know you could add fields to the media uploader?

    In my case, I was at a convention and a fellow reporter (welcome to my new weird life) muttered she wished it was easier to have just a photo ‘credit’ line when she uploaded media from networks. I asked what system she used to run her site and when she said WordPress, I gave her my biggest smile.

    Image Filters

    There are two things we need to filter here.

    1. Add our attribution field to the editor
    2. Save the attribution data

    That’s it. WordPress handles the rest.

    add_filter( 'attachment_fields_to_edit', 'halfelf_add_attachment_attribution', 10000, 2);
    add_action( 'edit_attachment', 'halfelf_save_attachment_attribution' );
    
    function halfelf_add_attachment_attribution( $form_fields, $post ) {
    	$field_value = get_post_meta( $post->ID, 'HALFELF_attribution', true );
    	$form_fields[ 'HALFELF_attribution' ] = array(
    		'value'    => $field_value ? $field_value : '',
    		'label'    => __( 'Attribution' ),
    		'helps'    => __( 'Insert image attribution here (i.e. "NBCUniversal" etc)' )
    	);
    	return $form_fields;
    }
    
    function halfelf_save_attachment_attribution( $attachment_id ) {
    	if ( isset( $_REQUEST['attachments'][$attachment_id]['lwtv_attribution'] ) ) {
    		$attribution = $_REQUEST['attachments'][$attachment_id]['HALFELF_attribution'];
    		update_post_meta( $attachment_id, 'HALFELF_attribution', $attribution );
    	}
    }
    

    End Result?

    It shows up a little lower down than I’d like (I’d prefer it to be up where the URL is) but it works:

    An example of Image Attribution

    Oh and yes, I emailed her the code as a stand-alone plugin. Her IT person was thrilled.

  • A Sample Block: Spoilers

    A Sample Block: Spoilers

    Before I jump into this, I have to say .. this post was not written in Gutenberg.

    Sadly the plugin I use for code display, when I need it to be colored and easy to read, isn’t yet Gutenberg friendly. And, worse IMO, there’s a bug that causes multiline shortcodes to be smashed into one line, which makes a code block unusable. My two fixes are either the plugin I use be updated (which is beyond my skills at the moment) or I find a new plugin (looking). Or I wait.

    Anyway.

    Let’s make a simple block that you can’t edit!

    There are Four Files Needed

    1. spoilers.php – This is the main PHP file
    2. spoilers/index.js – This is the code
    3. spoilers/editor.css – This formats what you see on the editor
    4. spoilers/style.css – This formats what you see on the front end

    The last two aren’t required but I use them to make things pretty.

    The PHP

    The PHP file will need to be included in your code however you want to do it. I have a file called _blocks.php which includes all my Gutenblocks. In turn, it is included in my plugin’s main file.

    function lp_spoilers_block_init() {
    	$dir = dirname( __FILE__ );
    
    	$index_js = 'spoilers/index.js';
    	wp_register_script(
    		'spoilers-block-editor',
    		plugins_url( $index_js, __FILE__ ),
    		array( 'wp-blocks', 'wp-i18n', 'wp-element' ),
    		filemtime( "$dir/$index_js" )
    	);
    
    	$editor_css = 'spoilers/editor.css';
    	wp_register_style(
    		'spoilers-block-editor',
    		plugins_url( $editor_css, __FILE__ ),
    		array( 'wp-blocks' ),
    		filemtime( "$dir/$editor_css" )
    	);
    
    	$style_css = 'spoilers/style.css';
    	wp_register_style(
    		'spoilers-block',
    		plugins_url( $style_css, __FILE__ ),
    		array( 'wp-blocks' ),
    		filemtime( "$dir/$style_css" )
    	);
    
    	register_block_type( 'library/spoilers', array(
    		'editor_script' => 'spoilers-block-editor',
    		'editor_style'  => 'spoilers-block-editor',
    		'style'         => 'spoilers-block',
    	) );
    }
    add_action( 'init', 'lp_spoilers_block_init' );
    

    The JS

    Here’s the weird stuff. And I’m going to be honest here … I don’t understand all of this yet. But I picked this very simple example because it lets you see what’s going on in a concrete way.

    ( function( wp ) {
    	var registerBlockType = wp.blocks.registerBlockType;
    	var el = wp.element.createElement;
    	var __ = wp.i18n.__;
    
    	registerBlockType( 'library/spoilers', {
    
    		title: __( 'Spoiler Warning' ),
    		icon: 'vault',
    		category: 'widgets',
    		supportHTML: false,
    
    		edit: function( props ) {
    			return el(
    				'div',
    				{ className: props.className },
    				__( 'Warning: This post contains spoilers!' )
    			);
    		},
    
    		save: function() {
    			return el(
    				'div',
    				{ className: 'alert alert-danger'},
    				__( 'Warning: This post contains spoilers!' )
    			);
    		}
    	} );
    } )(
    	window.wp
    );
    

    The CSS

    There are two CSS files.

    editor.css

    .wp-block-library-spoilers {
    	border: 1px dotted #f00;
    	background-color: pink;
    	font-weight: bold;
    }
    

    style.css

    .wp-block-library-spoilers {
    	font-weight: bold;
    }
    

    Back in the js there was a bit that looked like this: { className: props.className },

    That’s what tells it to use these. In addition, I use this to call some theme specific code: { className: 'alert alert-danger'},

    The Output

    On the editor, I see this:

    Example of what the spoiler text looks like on the editor

    I included the block icon so you could see the little vault I used to indicate the block.

    On the front end, everyone sees this:

    A nice looking spoiler warning

    What’s Next?

    Making it editable, of course!

  • Structured Data

    Structured Data

    Every month around the 15th and 30th, I check Google Webmaster to see if there are any 'errors' I need to address. Most of the time this is 404s on pages that were wrong for 30 minutes, and Google decided to crawl me right then. In January, I got a bunch of new alerts about 'structured data.'

    What is it?

    Google uses structured data that it finds on the web to understand the content of the page, as well as to gather information about the web and the world in general. Which is a fancy way of saying Google looks for specific classes and attributes in your page to determine what's what. 

    Most of the time, your theme handles this for you. It's why you see hentry and authorcard in your page source. This is part of telling Google what the content is, contextually.

    Bad Errors

    Sadly, Google's report just said I had "missing fn" errors and gave me this:

    itemtype: http://microformats.org/profile/hcard
    photo: https://secure.gravatar.com/avatar/CODE?s=118&d=retro&r=g

    That's not very helpful unless you already know what you're looking for. And even then… There was a link to test live data and I clicked on it, only to get a report back that I had no errors. Frustrating, right?

    Then I checked a different error:

    Missing: author
    Missing: entry-title
    Missing: updated

    That was a little better, I thought, and I found out that since we'd removed author and updated from those posts, for a valid reason mind you, that was showing an error. The 'easy' fix was to make this function:

    function microformats_fix( $post_id ) {
    	$valid_types = array( 'post_type_authors', 'post_type_characters', 'post_type_shows' );
    	if ( in_array( get_post_type( $post_id ), $valid_types ) ) {
    		echo '<div class="hatom-extra" style="display:none;visibility:hidden;">
    			<span class="entry-title">' . get_the_title( $post_id ) . '</span>
    			<span class="updated">' . get_the_modified_time( 'F jS, Y', $post_id ) . '</span>
    			<span class="author vcard"><span class="fn">' . get_option( 'blogname' ) . '</span></span>
    		</div>';
    	}
    }

    And then I just tossed it into my post content.

    Does This Matter?

    Kind of. It doesn't appear to actively hurt your SEO but it can help a little. In my case, I'm not actually using 'Author' so it's an inconvenience. And I don't want to make public when a page was last updated, since it's meant to be static content, but also it gets 'saved' a lot more than it gets updated, due to how I process some data. Basically it would lie to users.

    But. Apparently I get to lie to Google.

    Yaaay.

  • Data Structure

    Data Structure

    At WordCamp US, Tracy turned to me and said “I want to do something about the actors on our site.”

    Her idea was that, based on the traffic on our sites, people wanted to know a little more about the actors. The way I’d built out the site, you could get a list of all of an actor’s characters, but you couldn’t really get at everything. Tracy explained to me what people were searching for (actors who were queer, or not) and I mulled over the possibilities, sketching out three solutions.

    1. Facet and Smart PHP

    We originally added in actors as a plain-text field, saved as an array, for all names associated with a character. In using this, with FacetWP, it was trivial to look for all characters played by Ali Liebert. We also already had in a repeatable field, so we could put the ‘most prominent’ actor on top (see “Sara Lance”).

    However what was not trivial was the idea of identifying if an actor was queer. You see some characters have multiple actors, and while today all are either queer or not, one day they may not be. I pointed this out to Tracy using the Sara Lance conundrum and Tracy cried ‘Whhhyyyyyyyy?’ and lay down on the floor.

    2. Taxonomies

    Characters already have a bunch of custom taxonomies, and I considered extending that to a new taxonomy for actors. That would immediately provide organizations, and adding new actors is easy on the fly. With auto-complete, we could get away from the drama of my inability to spell names (or autocorrect’s inability to believe me that in this case, I is before C).

    But… Taxonomies lack extendability. Even with a mess of custom meta added in for featured images, we wouldn’t have an ‘easy’ way to track all trans actors. And we wouldn’t have enough of a future.

    3. Custom Post Types

    This is what we actually decided to use. A custom page for each actor. We added in two new taxonomies for actor gender identity and sexual orientation, which are then used to determine if the actor is queer or not. It gives us the most control and extendability of the choices, and the nice permalink.

    There are two significant downsides to this. First, you have to add in a page for the actor before you add in the character page. Second, I had to ‘reproduce’ a loop to list all the characters played by an actor. However I was reusing the same logic as I do for shows which made it easier than it might have been.

    What It Means

    Understanding and predicting an unknown future is hard. It’s near impossible. You have to guess what you want things to be, and as I have said many times with the building out of LezWatchTV, you must be alright with being wrong.

  • Alternate Symbols

    Alternate Symbols

    I like to remotely host my SVGs and then call them in my code. For the most part, this works well, and I wrote out some basic code to check if they’re defined and accessible before displaying.

    But what if you wanted your fallback to be a font-icon?

    And remember, you want your code to be DRY as a bone. So no repeating chunks of code all over the place, please and thank you.

    The Code

    There is but ONE requirement here. You have to define HALFELF_SYMBOLICONS_PATH as the path to your SVGs. In my case, mine is something like http://my-cool-icons.objects-us-west-1.dream.io/svgs/ because I’m using DreamObjects for them. Any cloud host works great for this, mind you.

    function halfelf_symbols( $svg = 'square.svg', $fontawesome = 'fa-square' ) {	
    
    	$icon = '<i class="fa ' . $fontawesome . '" aria-hidden="true"></i>';
    
    	if ( defined( 'HALFELF_SYMBOLICONS_PATH' ) ) {
    		$response      = wp_remote_get( HALFELF_SYMBOLICONS_PATH );
    		$response_code = wp_remote_retrieve_response_code( $response );
    		
    		if ( $response_code == '200' ) {
    			$get_svg      = wp_remote_get( LP_SYMBOLICONS_PATH . $svg );
    			$response_svg = wp_remote_retrieve_response_code( $get_svg );
    			$icon         = ( $response_svg == '200' )? $get_svg['body'] : 'square.svg';
    		}
    	}
    
    	return $icon;
    }
    

    This checks for the existence of the server used and if the actual icon exists before it sets things.

    Usage Example

    In my code, I define it like this:

    $icon  = halfelf_symbols( 'users.svg', 'fa-users' );
    $title = '<span role="img" aria-label="users" title="Users" class="TEMPLATE users">' . $icon . '</span>';
    

    This sets the icon and then calls the span which will help make this more accessibility friendly. I could have coded the span into the function, but since I often have it all dynamically generated, it worked more sustainably this way.

    In this example, I call it like this:

    the_archive_title( '<h1 class="page-title">' . $title . '</h1>' );
    

    And voila. Icons in my page title.

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