Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

  • You’ll Probably be Fine with Gutenberg

    You’ll Probably be Fine with Gutenberg

    With WordPress 5.0 getting closer and closer, I know a lot of people are worried about Gutenberg and what that will mean for their themes and plugins.

    Most Themes Will Be Fine

    The majority of themes, every single one you can download from WordPress.org, and the ones from reputable theme shops like StudioPress, will be just fine. Nothing will break, and other than getting a new editor experience, there’s no change.

    What will happen, and yes people will call this breaking, is that not all features will be supported

    Right now, most themes don’t support some of the more outré block types. And a lot of themes don’t support text columns like this:

    And then again, Genesis? Does!

    This is a left column

    This is a right column

    And every theme is going to support repeatable blocks which means you’ll be able to build out some nice defaults and standards.

    The reason for all this is that if you’re just using Gutenberg as an editor, not a whole lot has changed with your content. It remains the same as it ever was, just with a fresh coat of paint.

    There are exceptions, like themes that rely heavily on features that alter the post editor. I’ll get there in a minute.

    Most Plugins Will be Fine, Too

    Here again, unless your plugin interest with the post editor, you are going to be just fine. You use Jetpack to publicize your posts? Cool. No change. AMP? No change.  Go look at most of your plugins. Caching? No changes. Google Analytics? No change.

    The reason here is, again, most plugins don’t mess with post editing. I should know. I review them.

    I know what you’re thinking, though. What about plugins like Yoast SEO, which add in a bunch of extra boxes at the bottom and sides of a post? The boxes are called Meta Boxes, and they’re already supported. Plus Yoast went to the extra effort of being on the forefront of Gutenberging.

    The colours to indicate how good your post’s SEO is, however, are missing right now. So is the field to customize your Jetpack publicize content. Basically everything that went on that sidebar on the post edit screen (the Publish Box) isn’t done yet.

    Which brings me to…

    The Exceptions Will Hurt

    Okay.  I won’t sugar coat this. If you use a complex post editor, like Visual Composer or Bold Grid, or if you use a plugin with a lot of custom meta boxes, the world will be very different, and probably unhappy.

    Now that said, Visual Composer will work with Gutenberg! And so will ACF and CMB2 is well on it’s way. But. The problem is people who have bundled those plugins in their themes. And those themes will have to update their packages and make sure users update.

    Anyone who just had a mental image of how many premium themes bundle Visual Composer and tried to calculate the number of users who don’t properly update themes regularly… welcome to my world. I’d like to say I’m ‘against’ the bundle, but the reality is that I’m against premium products using libraries and then not managing those libraries properly. Not that it’s easy, and that’s a different topic.

    The problem in the exception is that there will be a non-insignificant number of people who have no idea they’re using a problematic library that needs an update.

    Where Do We Go From Here?

    This problem is WordPress’ own doing, make no mistake. If the ability to add custom meta boxes and settings and the like was less complicated to begin with, we wouldn’t have needed tools like these to work around it. We still have no decent meta box API, and the settings API is something I regularly call “as intelligent as a bag of wet hair.”

    But. Innovation is necessary. We can’t just not move forward because we didn’t do things right the first time. And in WordPress’ defence, there was no way to know or even write the ‘right’ way back 15 years ago when all this started. The best tools didn’t exist. And all those ways we built to hack around the problem made it more clear what the right way needed to be.

    Which brings us here.

    Most of what you do won’t be impacted by Gutenberg. What is adversely impacted won’t break most things, but it will be really annoying. And then those rare edge case exceptions? Well. We’re back to the words (paraphrased) of Hecht.

    When infrequently used systems break, they do so in big ways.

  • Hashtag your Jetpack with Taxonomies

    Hashtag your Jetpack with Taxonomies

    So maybe you read my post about hash tagging Jetpack Publisher posts with a complex check of “Is this tag also a custom post type?” and you thought “Mika, that’s awesome. But I don’t have a database of shows!”

    To my reporter friend who lamented this to me, I have a solution.

    The Real Problem

    I think most of the problem with Jetpack’s custom Publicize message is that it’s (rightly) hidden by default. Most people don’t need it. But those that do want to see it so we remember “and do this…”

    Publicize Settings on your post sidebar

    And let’s be honest, this visibility issue is going to be worse when we move to Gutenberg.

    Again, I don’t think Jetpack is in the wrong here. The majority of users don’t need to be so aggro about their messages. There are a lot of WordPress sites that we would classify as ‘smaller’ sites. And those who aren’t tend to need very bespoke/custom solutions, which is the problem.

    My solution works for me because it’s easily automated and checkable. We’re very pedantic about tags (seriously, Tracy went through all our tags and cleaned them up), we have a great system to match tag to show, and, most significantly, we know that our social media engagement relies on not just content, but the appropriate hashtags. That is, the shows we’re talking about.

    This means there are two types of ways to do this:

    1) Make all your tags (and/or categories) into your hashtags
    2) Make a custom taxonomy for your hashtags

    Since using all the tags might be a bit much, I went with option 2.

    The Code

    <?php
    /*
     * Jetpack tweaks
     * @version 1.0
     * @package mu-plugins
     */
    
    class HalfElf_Jetpack {
    
    	public function __construct() {
    		add_action( 'publish_post', array( $this, 'custom_message_save' ) );
    		add_action( 'init', array( $this, 'register_taxonomy_hashtag' ) );
    	}
    
    	public function register_taxonomy_hashtag() {
    
    		//parameters for the new taxonomy
    		$arguments = array(
    			'label'                 => 'Hashtags',
    			'hierarchical'          => false,
    			'public'                => false,
    			'show_ui'               => true,
    			'update_count_callback' => '_update_post_term_count',
    			'rewrite'               => false,
    		);
    
    		register_taxonomy( 'flf_hashtags', 'post', $arguments );
    	}
    
    	public function publicize_hashtags() {
    		$post      = get_post();
    		$hash_tags = '';
    
    		// If the post isn't empty AND it's a post (not a page etc), let's go!
    		if ( ! empty( $post ) && 'post' === get_post_type( $post->ID ) ) {
    
    			update_post_meta( $post->ID, '_wpas_mess', 'test' );
    
    			// First let's add the hashtags
    			$post_tags = get_the_terms( $post->ID, 'flf_hashtags' );
    			if ( ! empty( $post_tags ) ) {
    				// Create list of tags with hashtags in front of them
    				foreach ( $post_tags as $tag ) {
    					// Change tag from this-name to thisname and slap a hashtag on it.
    					$tag_name   = str_replace( '-', '', $tag->slug );
    					$hash_tags .= ' #' . $tag_name;
    				}
    			}
    
    			// Next we add a category in specific situations.
    			$post_cats = get_the_category( $post->ID );
    			if ( ! empty( $post_cats ) ) {
    				// Create list of tags with hashtags in front of them
    				foreach ( $post_cats as $cat ) {
    					if ( 'MAINCAT' === $cat->slug ) {
    						// Change slug from this-name to thisname and slap a hashtag on it.
    						$cat_name   = str_replace( '-', '', $cat->slug );
    						$hash_tags .= ' #' . $cat_name;
    					}
    				}
    			}
    		}
    
    		// Loop back. If there are hashtags, we add them.
    		if ( '' !== $hash_tags ) {
    			// Create our custom message
    			$custom_message = 'New post! ' . get_the_title() . $hash_tags;
    			update_post_meta( $post->ID, '_wpas_mess', $custom_message );
    		}
    	}
    
    	// Save that message
    	public function custom_message_save() {
    		add_action( 'save_post', array( $this, 'publicize_hashtags' ) );
    	}
    
    }
    
    new HalfElf_Jetpack();
    

    A Little Explanation

    You may notice I added in a bit that looks for a specific category:

    if ( 'MAINCAT' === $cat->slug ) { ... }
    

    The reason here is that on the specific site I wrote this for, they have four (yes four!) categories:

    1) Announcements
    2) News
    3) Fandom (actually named FOR the fandom)
    4) Miscellaneous

    They wanted item to be a tag, so it would always #JanelleMonae (for example). For them I did a super basic ‘if the fandom, then the hashtag’ but a slightly more common situation would be someone having a category for ‘Fandoms’ and then subcategories our fandom. For that you’ll want something like this:

    foreach ( $post_cats as $cat ) {
    	$cat_mom = $cat->category_parent;
    	if( $cat_mom > 0 && 'fandoms' === $cat_mom->slug ) {
    		// Change slug from this-name to thisname and slap a hashtag on it.
    		$cat_name   = str_replace( '-', '', $cat->slug );
    		$hash_tags .= ' #' . $cat_name;
    	}
    }
    

    Enjoy your hash tagging!

  • Hashtag Your Jetpack with Custom Post Types

    Hashtag Your Jetpack with Custom Post Types

    The brunt of this code comes from Jeremy Herve, who was explaining to someone how to add Category hashtags prefixed to Jetpack Publicize tweets.

    That is, someone wanted to take a category for a post (say ‘How To’) and convert that into a hashtag (say #howto).

    I too wanted to do this, but like my weirdly related posts, I needed to do the following:

    1) Get the tags
    2) Check if the tag slug was the same as a post slug for a specific custom post type
    3) Output the hashtag as all one-word, no spaces, no hyphens

    Here’s The Code

    No more explaining, here’s the code.

    class Hashtag_Jetpack {
    
    	public function __construct() {
    		add_action( 'publish_post', array( $this, 'custom_message_save' ) );
    	}
    
    	public function publicize_hashtags() {
    		$post = get_post();
    
    		// If the post isn't empty AND it's a post (not a page etc), let's go!
    		if ( ! empty( $post ) && 'post' === get_post_type( $post->ID ) ) {
    			$post_tags = get_the_tags( $post->ID );
    			if ( ! empty( $post_tags ) ) {
    				// Create list of tags with hashtags in front of them
    				$hash_tags = '';
    				foreach ( $post_tags as $tag ) {
    					// Limit this to shows only.
    					$maybeshow = get_page_by_path( $tag->name, OBJECT, 'post_type_shows' );
    					if ( $maybeshow->post_name === $tag->slug ) {
    						// Change tag from this-name to thisname and slap a hashtag on it.
    						$tag_name   = str_replace( '-', '', $tag->slug );
    						$hash_tags .= ' #' . $tag_name;
    					}
    				}
    
    				// Create our custom message
    				$custom_message = 'New post! ' . get_the_title() . $hash_tags;
    				update_post_meta( $post->ID, '_wpas_mess', $custom_message );
    			}
    		}
    	}
    
    	// Save that message
    	public function custom_message_save() {
    		add_action( 'save_post', array( $this, 'publicize_hashtags' ) );
    	}
    
    }
    
    new Hashtag_Jetpack();
    

    The only ‘catch’ you may stumble on is that I’m checking against the post type of post_type_shows – just change that as you need to.

    Voila! Instant hashtags.

    Oh and if you’re wondering why I didn’t put in a check for “Is Jetpack active…” the reason is that this is adding a post meta, and doesn’t actually depend on Jetpack being active at all. Will it ‘clutter up’ your database if Jetpack isn’t active? Yes. But it won’t break your site so it’s safe enough for me.

  • Custom Admin and Tool Bar Icons

    Custom Admin and Tool Bar Icons

    When you’re making a plugin, sometimes you need a sidebar icon. And adding those in is relatively simple, depending on how you do it. But when you’re adding in something to the toolbar, things get a little messier…

    Dashicons and the Admin Sidebar

    The easiest way to add in an icon is to use a Dashicon – part of a set of icons developed by WordPress itself. These are designed to just work with WordPress, and you can include it in your call to add a menu page like so:

    add_menu_page( 'HalfElf', 'HalfElf', 'manage_options', 'varnish-page', 'halfelf_settings_page', 'dashicons-carrot', 75 );
    

    These will automatically change colors for you when people change their profile colors.

    Dashicons and the Toolbar

    But what if you want to add a menu to the toolbar? Well this is a little bit more complicated, due to the fact that there just isn’t a built in way to add that icon.

    When you add in a toolbar menu, it looks like this:

    add_action( 'admin_bar_menu', 'halfelf_add_admin_bar_menu', 999 );
    function halfelf_add_admin_bar_menu( $wp_admin_bar ) {
    	$args = array(
    		'id'    => 'halfelf_add_admin_bar_menu',
    		'title' => 'HalfElf',
    		'href'  => 'https://example.com',
    	);
    	$wp_admin_bar->add_node( $args );
    }
    

    While I think the array should accept ‘icon’, it doesn’t, which means you have two choices to add in your icon:

    1) Put <span class="dashicons dashicons-carron"></span> in front of the ‘HalfElf’ on the title line
    2) Use some CSS

    Personally I pick option 2, because that will allow the icon to show when the screen is in mobile view, and all you see are the icons.

    For that to work, I have the following custom CSS:

    #wpadminbar #wp-admin-bar-purge-varnish-cache .ab-icon:before {
    	content: '\f511'; 
    	top: 4px;
    }
    
    @media screen and (max-width: 782px) {
    	#wpadminbar li#wp-admin-bar-purge-varnish-cache{
    		display: block!important;
    	}
    }
    

    I call it on the front and back end, and this works pretty well.

    Adding a Custom SVG

    But. What happens when your coworker doesn’t like your joke about the carrot? Well now we’re into the weird land of “I want to use a Custom SVG for my admin bar and my toolbar.” And this is strange because while you can just echo out the SVG, the color can be a bit of a mess. Unlike a font-icon, SVGs don’t always play nicely with the sidebar. You have to define their colors in order for the fill replacement to work, and even then I found out that it doesn’t go well with the toolbar!

    I lifted a page from Yoast SEO, who uses a function get_icon_svg() and calls it in place of 'dashicons-carrot', making my menu this:

    add_menu_page( 'HalfElf', 'HalfElf', 'manage_options', 'varnish-page', 'halfelf_settings_page', get_icon_svg( true, '#82878c' ), 75 );
    

    Yoasties, if you’re wondering why I have the extra variable, let me explain.

    In their default, they have a one parameter, the true/false, and it defaults to true, so they just use get_icon_svg() and call it a day. But I had cases where I wanted things to have specific colors, so I added in a parameter for the color. In addition, I put in some extra checks on how to determine the color based on what admin colors the user had selected:

    function get_icon_svg( $base64 = true, $icon_color = false ) {
    	global $_wp_admin_css_colors;
    
    	$fill = ( false !== $icon_color )? sanitize_hex_color( $icon_color ) : '#82878c';
    
    	if ( is_admin() && false === $icon_color  ) {
    		$admin_colors  = json_decode( json_encode( $_wp_admin_css_colors ), true ) ;
    		$current_color = get_user_option( 'admin_color' );
    		$fill          = $admin_colors[$current_color]['icon_colors']['base'];
    	}
    
    	$svg = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" style="fill:' . $fill . '" viewBox="0 0 36.2 34.39" role="img" aria-hidden="true" focusable="false"><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path fill="' . $fill . '" d="M24.41,0H4L0,18.39H12.16v2a2,2,0,0,0,4.08,0v-2H24.1a8.8,8.8,0,0,1,4.09-1Z"/><path fill="' . $fill . '" d="M21.5,20.4H18.24a4,4,0,0,1-8.08,0v0H.2v8.68H19.61a9.15,9.15,0,0,1-.41-2.68A9,9,0,0,1,21.5,20.4Z"/><path fill="' . $fill . '" d="M28.7,33.85a7,7,0,1,1,7-7A7,7,0,0,1,28.7,33.85Zm-1.61-5.36h5V25.28H30.31v-3H27.09Z"/><path fill="' . $fill . '" d="M28.7,20.46a6.43,6.43,0,1,1-6.43,6.43,6.43,6.43,0,0,1,6.43-6.43M26.56,29h6.09V24.74H30.84V21.8H26.56V29m2.14-9.64a7.5,7.5,0,1,0,7.5,7.5,7.51,7.51,0,0,0-7.5-7.5ZM27.63,28V22.87h2.14v2.95h1.81V28Z"/></g></g></svg>';
    
    	if ( $base64 ) {
    		return 'data:image/svg+xml;base64,' . base64_encode( $svg );
    	}
    
    	return $svg;
    }
    

    Personally I find it pretty crazy, and the icon on the toolbar doesn’t reflect the changes until the page is reloaded, but that’s a small price to pay since it doesn’t happen all that often.

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

  • Protect My Plugins, Please

    Protect My Plugins, Please

    I have a site that literally requires a specific plugin always be active for things to work. It has a lot of very complicated code, and two things must never ever happen to this:

    1. It should never be disabled
    2. It should never be updated by WordPress

    Well. Okay. Let’s do that.

    A Caveat

    If you don’t have a way in place to update and secure the plugin you’re hiding, DON’T DO THIS. Once you hide it, people won’t know to update it and, in fact, won’t be able to. Because you’re also preventing traditional updates. In my case, I have a git repository that triggers a push (plus some other magic).

    Again. Unless you’ve got something set up to handle updates, don’t do this.

    Now let’s do this.

    Never Disable Me (by Hiding)

    To do this, we need to know the folder name and filename of the plugin, such as ‘my-plugin/my-plugin.php. If you want to hide the plugin you've put the code in, you can useplugin_basename( FILE )` instead, which is what I’ve done.

    add_action( 'pre_current_active_plugins', 'mysite_hide_my_plugins' );
    function mysite_hide_my_plugins() {
    	global $wp_list_table;
    
    	$hide_plugins = array(
    		plugin_basename( __FILE__ ),
    	);
    	$curr_plugins = $wp_list_table->items;
    	foreach ( $curr_plugins as $plugin => $data ) {
    		if (in_array( $plugin, $hide_plugins ) ) {
    			unset( $wp_list_table->items[$plugin] );
    		}
    	}
    }
    

    If you were writing a plugin to hide a lot of things, you would change the array to list all of them. But since this is a ‘hide myself’ deal, it’s easier to put it in the plugin itself.

    The added bonus to making the file path dynamic is that if someone gets clever and renamed the folder or file, it would still work. Neener.

    Stop My Updates

    Now this one again is easier if you put it in the plugin you’re disabling updates for, because again you can use plugin_basename( __FILE__ ) to detect the plugin. If you’re not, then you’ll need to make an array of names. But that should get you started.

    add_filter( 'http_request_args', 'mysite_disable_my_plugin_update', 10, 2 );
    function mysite_disable_my_plugin_update( $return, $url ) {
    	if ( 0 === strpos( $url, 'https://api.wordpress.org/plugins/update-check/' ) ) {
    		$my_plugin = plugin_basename( __FILE__ );
    		$plugins   = json_decode( $return['body']['plugins'], true );
    		unset( $plugins['plugins'][$my_plugin] );
    		unset( $plugins['active'][array_search( $my_plugin, $plugins['active'] )] );
    		$return['body']['plugins'] = json_encode( $plugins );
    	}
    	return $return;
    }
    

    What Does It Look Like?

    Nothing, really. You’ve hidden everything. Congratulations. Now you don’t have to worry about admins or WordPress updating your plugin. Or worse.