Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: wordpress

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

  • Query Vars, Post Titles, and Yoast SEO

    Query Vars, Post Titles, and Yoast SEO

    Most people who use WordPress use query vars every single day without knowing. Back in the old days, we used to have websites with URLs like example.com/?p=123 and we called those ‘ugly’ permalinks. This encouraged everyone to make pretty ones, like example.com/2018/post-name/ instead. What many people don’t realize is that those ?p=123 calls are query variables.

    WordPress, and most modern CMS tools, take that pretty permalink, translate it back to a query variable, and go get the post ID with 123. The variable p (which stands for post) has a value of 123. It makes sense.

    And you can make your own.

    Pretty URLs Are Better

    If you have a choice of telling people to visit example.com/?p=123 or example.com/2018/post-name/, I feel confident most of you will pick the latter. And if you’re writing code and you need a consistent location to call, would you rather try to assume that everyone’s using wp-content and look for example.com/wp-content/plugins/myplugin/my-file.php or would you rather use example.com/myplugin/?

    Now. This post is not about how to make those awesome virtual pages. There are some good tutorials like Metabox.io’s to get you started there. This is about when you have a slightly different scenario.

    Using Query Variables in Titles

    For a number of reasons, I have a page built out to manage my custom query calls. This allows me to edit the content of the page like it was, well, a page! Anyone can edit the page content, change the information, and then everything else is magically generated, it can be easily tweaked to fit my theme, and it basically works for me.

    What doesn’t work well is that all the pages have the same title. And that, as we all know, is bad SEO. I don’t want all my pages to have the same title, and it’s confusing for users because they go to example.com/my-thing/sub-thing/ and example.com/my-thing/other-thing/ so logically the page title should be different, right?

    Changing the display title on the page isn’t too terrible. I have this code at the top of the page to grab the query variable:

    $my_thing = ( isset($wp_query->query['my_thing'] ) )? $wp_query->query['my_thing'] : 'main' ;
    

    And then when I echo the title, I do this:

    <h1 class="entry-title">My Thing
    	<?php 
    		$title = ( 'main' !== $my_thing )? ' on ' . ucfirst( $my_thing ) : 's';
    		echo $title;
    	?>
    </h1>
    

    Which means my titles are either “My Things” or “My Thing on That”. If I had multiple ‘words’ in my query variables (separated by a – of course) then I’d use a string replace and capitalize each word with ucwords().

    But Page Titles aren’t Title Titles

    The problem is the page title isn’t the … well … title title. Have you ever looked at your browser bar? Assuming you don’t have 94 tabs open at once…

    A browser with a lot of tabs open, to the point you may have no idea what's in any of them...

    Then your browser would look a little like this:

    A sane number of tabs open, where you can read the title of each tab

    Imagine if they all said “My Things | Half-Elf on Tech”? Well that would suck. It sucks for you reading, it sucks for screenreaders, and if you have Yoast SEO, it’s quite easy to fix.

    Custom Yoasty Variables and Titles

    Step one is to revisit something I did two years ago, making a custom Yoast SEO variable. This time I want to make a variable for %%mything%%:

    wpseo_register_var_replacement( '%%mything%%', array( $this, 'yoast_retrieve_mything_replacement' ), 'basic', 'The type of thing page we\'re on.' );
    

    And the code will be to grab the query variable and parse it:

    function yoast_retrieve_mything_replacement( ) {
    	$my_thing = get_query_var( 'my_thing', 'none' );
    	$return   = ( 'none' !== $my_thing )? 'on ' . ucfirst( $my_thing ) : '';
    	return $return;
    }
    

    Step 2 is going into the page, popping open the snippet editor, and making the title this:

    The Yoast Snippet

    And then? Pour a beer and watch some sports.