Half-Elf on Tech

Thoughts From a Professional Lesbian

Category: How To

  • A Case Sensitive Headache

    A Case Sensitive Headache

    Like most people, I pull my site down to develop locally. After all, it’s safer. But also like most people, my live machine is a linux box and my personal machine is not. In fact, it’s a Mac, running whatever the latest OS X is. But don’t worry, today’s drama happens on Windows as well.

    You see, I downloaded my local data, imported my database, changed my URLs to local, and stared at a page that had the wrong data. Or rather, the right data and the wrong image. The page for “Sam” was showing me a photo for another character named “Sam.” But on my live site it was fine.

    Commence the Debugging

    The first thing I did was copy the image to a new folder to try and re-upload it. Only that didn’t work out the way I thought it would. You see, I knew the filename was Sam.jpg so I tried to copy that over:

    cp ~/sites/example.com/wp-content/uploads/2018/05/Sam.jpg ~/Downloads

    Only when I went to look, it was again the bad image. In fact, when I looked at all the files in the folder, there was only one file named ‘sam’ when there should be two

    A list of files in local and live sites. The one of the left is local, and it's missing a bunch of files.
    Local site is on the left, live is on the right

    Things were starting to become clear. Next I copied it and renamed it sam.jpg and got an error:

    Error: The name "sam" with extension ".jpg" is already taken. Please choose a different name.

    I’ve got it now. Mac doesn’t understand the different between upper and lower case. It’s not case sensitive.

    Bad News: No Easy Fix

    I’m really sorry about this. There isn’t an easy fix.

    First of all, if you wanted to fix your main hard drive, you would have to reformat it. That’s a pain in the ass. Second? Your Mac will not be happy and things will break. For example, the Steam app doesn’t like it if your Mac is case sensitive. Seriously, I have no idea why. But it’s what it expects.

    This means the ‘fix’ is to partition your hard drive. Since I’m using AFPS, I opted to make a volume instead of a partition, which feels pretty much the same but it’s not. I opted for AFPS with case sensitive and encrypted because I’m generally a neurotic. Making a volume is very fast, and once I was done, I copied everything in my ~/Sites/ folder over to /Volumes/websites/ (where websites is the name of my new volume).

    And now it looks great!

    Mac OS screenshot showing the files are now case sensitive! Yay!

    Except… I still only see one in terminal. I freaked out for a moment and then I realized that while the GUI is smart enough to know that Sam and sam are both S’s, terminal put the capital letters above the lowercase. So all the S’s came before all the s’s.

    Miss Piggy bashing her head into a table. Which is how I felt about now.

    The last step was to move my copy of Chassis over to the new websites volume, spin it back up, and finally it was working properly.

    The Moral?

    Don’t use case-sensitive filenames. Or filenames with special characters like á or í because operating systems are stupid.

    No seriously, that’s it. Haven’t you wondered why people advocate we all use all lower-case for filenames in our code repositories? Because different operating systems are stupid. They don’t always talk properly to each other, they have different ideas of right and wrong, and they’re never going to agree. If you work with multiple operating systems, aim at the lowest common denominator, pick a style, and stick the hell to it.

    That’s your moral.

  • CMB2: Conditional Meta Fields

    CMB2: Conditional Meta Fields

    Even though Gutenberg is on the rise, and every day gets us closer to using a whole new editor, we still use the ‘classic’ editor and we’re still beholden to it’s space limitations. I have strong feelings about how to properly utilize space when using CMB2, but not included in that specific post is this.

    Fields You Don’t (Always) Need

    There are three types of fields for CMB2.

    1. Fields you always need to use
    2. Fields that are optional
    3. Fields that are needed only in specific situations

    Most of the time we use option 2. Option 3 is the tricky one, though, since most of the time we end up having things show based on actions. That is, I save a post, and new options show up. When it comes to CMB2, we really don’t want to have to save and then edit and save and edit.

    Yuck!

    Thankfully this is all possible.

    Practical Example: Affiliates

    Today we’re making a box to handle affiliate links. There are three types of links: Amazon, Genric, and ‘Unique.’

    The Amazon one will link directly to a signup link and the generic one links to the same thing only on Click Junction. Both of those links will have affiliate details backed in so I don’t have to look them up later. This also means any partners I have on the site don’t need to know all the gory details. Bazinga.

    The last one, though ‘Unique’ is tricky. You see, when someone picks that, I want them to be able to put in a specific URL that may be affiliate linking somewhere else. But let’s start out with how it normally works.

    Make Your Fields

    function my_site_cmb2_metaboxes() {
    	// prefix for all custom fields
    	$prefix = 'my_sites_';
    
    	// Metabox Group: Must See
    	$cmb_affiliate = new_cmb2_box( array(
    		'id'           => 'affiliate_metabox',
    		'title'        => __( 'Affiliate Details', 'my-domain' ),
    		'object_types' => array( 'post_type_shows' ),
    		'show_in_rest' => true,
    	) );
    
    	// Field Box: Affiliate Type
    	$field_affiliatetype = $cmb_affiliate->add_field( array(
    		'name'             => __( 'Type', 'my-domain' ),
    		'id'               => $prefix . 'affiliate',
    		'type'             => 'select',
    		'options'          => array( 
    			'amazon'  => 'Amazon',
    			'generic' => 'Generic',
    			'url'     => 'Unique Link',
    		);
    		'show_option_none' => true,
    	) );
    	// Field Box: Affiliate Links
    	$field_affiliateurl = $cmb_affiliate->add_field( array(
    		'name'    => __( 'Link', 'my-domain' ),
    		'id'      => $prefix . 'affiliateurl',
    		'type'    => 'text_url',
    	) );
    );
    

    That’s pretty normal for CMB2 and looks like this:

    CMB2: Affiliate Details

    Normal, but … I want to hide that Link field unless a specific option is selected. Enter javascript.

    Hide That Field (Conditionally)

    Make a file for your javascript. I’ve called mine cmb2.js and put it in the same folder as my file that will enqueue the scripts.

    // Either create a new empty object, or work with the existing one.
    window.MySite_CMB2 = window.MySite_CMB2 || {};
    
    (function( window, document, $, app, undefined ) {
    	'use strict';
    
    	app.cache = function() {
    		app.$ = {};
    		app.$.select = $( document.getElementById( 'my_sites_affiliate' ) );
    		app.$.field = $( document.getElementById( 'my_sites_affiliateurl' ) );
    		app.$.field_container = app.$.field.closest( '.cmb-row');
    	};
    
    	app.init = function() {
    		app.cache();
    		app.$.select.on( 'change', function( event ) {
    			if ( 'url' === $(this).val() ) {
    				app.$.field_container.show();
    			} else {
    				app.$.field_container.hide();
    			}
    		} ).trigger( 'change' );
    	};
    
    	$( document ).ready( app.init );
    })( window, document, jQuery, MySite_CMB2 );
    

    And then call the enqueue, but only when appropriate (because we want to keep the load low):

    add_action( ‘admin_enqueue_scripts’, ‘my_site_admin_enqueue_scripts’ );

    function my_site_admin_enqueue_scripts( ) {
    $screen = get_current_screen();
    if ( ! isset( $screen->post_type ) || ‘post_type_shows’ !== $screen->post_type ) return;

    wp_enqueue_script( 'custom-js', plugins_url( '/cmb2.js' , __FILE__ ), array( 'jquery' ) );
    

    }

    And that works like this:

    GIF of how it shows and hides things

    A Word of Warning

    While all this is great for the sighted people, hiding things is not actually all that great for those who use screen-readers. For that you’d want to toggle the field to be disabled.

  • It’s Just Math

    It’s Just Math

    Recently I posted about how I added new features to a show scoring system, over on LezWatchTV. We added in what we call Intersectionality, which is rewarding shows for positive representation of diversity in the world. Naturally that begs the question of what is show scoring (answer: it’s calculative a qualitative value of how good a TV show on a scale of 0-100). And that makes people ask …

    How do you do that?

    It’s really math

    Look, the bare answer to all of this is that it’s math. I’m taking the meta data we assign to shows, like how much screen time characters get and how good the show is for them, and so on, and assigning numbers to those values. Then I add up the numbers, in various ways, and determine what the over all score is.

    It’s a little more complex than that, and if you’re super interested in how it all works, we wrote up an explanation as to the framework I created to value shows.

    But most people who ask me how I did this aren’t asking about the math, they want to know about the code. That is, how did I get WordPress to automate all this, because you know I don’t do it all by hand. No, I do it when the post saves.

    Magical Hooks

    Let’s take a moment. What’s a hook anyway?

    A hook is an action or a filter in WordPress, which allows you to write code that ‘hooks’ into the rest of WordPress code. You can use this to trigger your code to run at specific times.

    For example, if I had a hook called publish_post (which we do – WordPress has that built in), and I wanted to run my show calculations when I publish a post, I would do this:

    add_action ( 'publish_post', 'shows_calculations' );

    That would of course require me to have a function called show_calculations() that does the math, which is fine. But. I don’t want to do this on post publish. I want to do it on post save, and only if the post is of the shows post type. And that sounds like a lot of if/then statements until you learn about dynamic hooks.

    You see, I’m using the save_post_ hook, which runs when a post is saved, and it’s a little special. Unlike a hook like publish_post(), this hook lets me customize it how I want by being save_post_{$post->post_type} …

    Yeah those curly brackets are weird, right? That’s the dynamic part. That part changes.

    Dynamic Saving

    To understand how the hook name changes, you should know that in my case, I have a post type called post_type_shows — logical right? Well the subsequent hook is called save_post_post_type_shows — that is, I’ve replaced those weird brackets with the post type.

    Once you know your new hook name, it’s like every other hook:

    add_action( 'save_post_post_type_shows', 'shows_calculations', 10, 3 );

    What that does is call the function post_type_shows_calculations on every post save. And that function calls another one I called do_the_math() which passes the post ID to the myriad complications of calculations I perform.

    But the magic literally is in that post save hook.

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

  • A Slightly More Complex Sample Box: Spoilers

    A Slightly More Complex Sample Box: Spoilers

    Round two!

    Once I made a simple, sample spoiler box, it was time to jump into the next stage. Customizable, editable, not simple, sample spoiler boxes!

    Here’s the best part. All I had to change was the index.js file from the previous example.

    The Javascript

    ( function( blocks, i18n, element ) {
    	var el = element.createElement;
    	var __ = i18n.__;
    	var RichText = blocks.RichText;
    
    	blocks.registerBlockType( 'library/spoilers', {
    
    		title: __( 'Spoiler Warning' ),
    		icon: 'vault',
    		category: 'widgets',
    		supportHTML: false,
    
    		attributes: {
    			content: {
    				type: 'array',
    				source: 'children',
    				selector: 'div',
    			},
    		},
    
    		edit: function( props ) {
    			var content = props.attributes.content || 'Warning: This post contains spoilers!';
    			var focus = props.focus;
    			function onChangeContent( newContent ) {
    				props.setAttributes( { content: newContent } );
    			}
    
    			return el(
    				RichText,
    				{
    					tagName: 'div',
    					className: props.className,
    					onChange: onChangeContent,
    					value: content,
    					focus: focus,
    					onFocus: props.setFocus
    				}
    			);
    		},
    
    		save: function( props ) {
    			return el(
    				'div',
    				{ className: 'alert alert-danger'},
    				props.attributes.content
    			);
    		}
    	} );
    } )(
    	window.wp.blocks,
    	window.wp.i18n,
    	window.wp.element
    );
    

    What’s Different?

    A few things. A lot of things. The idea is that we need to pass the content attribute (i.e. our warning) to be processed. This is very much like how you pass attributes through shortcodes, so don’t panic.

    var content = props.attributes.content || 'Warning: This post contains spoilers!';
    var focus = props.focus;
    function onChangeContent( newContent ) {
    	props.setAttributes( { content: newContent } );
    }
    

    This says “Set the content to ‘Warning: This post contains spoilers!’ if there’s nothing there. Then watch the focus. If the focus changes, use the new content to replace the original content.”

    Down in our return el section, we’ve changed it to be Rich Text (so people can use bold and so on… even though it’s always bold), and the value: content, is the variable content we edited above.

    What’s It Like?

    It’s just like any other Block. It inserts itself when you click the icon, it loads the default, and you can edit it:

    An example Gif of spoilers

    And that is a simple, editable, Gutenberg Block