Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: coding

  • Sortable Custom Columns

    Sortable Custom Columns

    I have a lot of custom columns going on over at LezWatch TV. And there are a million tutorials on how to make your columns sortable. So here’s another one.

    Backstory

    I have a custom post type for TV characters, and the characters have extra meta data. The kind I want to show is the TV show (or shows) associated with the character and the role type.

    I should note that ‘role type’ can be weird, since there are characters who will be guests on one show and regulars on the other, and really I should go back and change all of that but when you get to 1000 characters, you really don’t want to… Anyway. Let’s just do this. Keep in mind the following things.

    1. The custom post type was registered as post_type_characters
    2. The data I wish to list is ‘tv shows’ and ‘role type’
    3. TV Shows is an array of IDs related to another post type (shows), stored in a meta key called lezchars_show
    4. Role Type is a plain text value stored in lezchars_type

    Now we’re ready to go

    New Column Headers

    We want to make a new column for your custom post types. For this to work, you need to know the name of your custom post type – post_type_characters remember – and the format for the column code – manage_{POSTTYPE}_posts_columns … See how that works?

    // Add Custom Column Headers
    add_filter( 'manage_post_type_characters_posts_columns', 'set_custom_edit_post_type_characters_columns' );
    function set_custom_edit_post_type_characters_columns($columns) {
    	$columns['cpt-shows']		= 'TV Show(s)';
    	$columns['postmeta-roletype']	= 'Role Type';
    	return $columns;
    }
    

    The name of the columns (cpt-shows and postmeta-roletype) are semi-arbitrary. I picked cpt-TYPE and postmeta-META because they were logical to me. I always use those formats.

    Column Content

    Giving the column content is a little more difficult because I’m listing TV shows associated with a character. Plural. And some characters are on three shows. Usually people are only on one, thankfully.

    This time the function name is going to be based on manage_{POSTTYPE}_posts_custom_column

    // Add Custom Column Content
    add_action( 'manage_post_type_characters_posts_custom_column' , 'custom_post_type_characters_column', 10, 2 );
    function custom_post_type_characters_column( $column, $post_id ) {
    	// Since SOME characters have multiple shows, we force this to be an array
    	if ( !is_array( get_post_meta( $post_id, 'lezchars_show', true ) ) ) {
    		$character_show_IDs = array( get_post_meta( $post_id, 'lezchars_show', true ) );
    	} else {
    		$character_show_IDs = get_post_meta( $post_id, 'lezchars_show', true );
    	}
    
    	// Show Title is an array to handle commas
    	$show_title = array();
    
    	foreach ( $character_show_IDs as $character_show_ID ) {
    		array_push( $show_title, get_post( $character_show_ID )->post_title );
    	}
    
    	switch ( $column ) {
    		case 'cpt-shows':
    			echo implode(", ", $show_title );
    			break;
    		case 'postmeta-roletype':
    			echo ucfirst(get_post_meta( $post_id, 'lezchars_type', true ));
    			break;
    	}
    }
    

    And this results in this:

    Character Custom Post Types with Special Columns

    Making it Sortable

    But as you’ve noticed, it’s not actually sortable. That is, if I have one character with no ‘role’, I have to surf through 47 pages until I find her. So the magic here is to make these columns sortable.

    This time the filter is named manage_edit-{POSTTYPE}_sortable_columns and there’s some extra mess going on.

    // Make columns sortable
    add_filter( 'manage_edit-post_type_characters_sortable_columns', 'sortable_post_type_characters_column' );
    function sortable_post_type_characters_column( $columns ) {
    	unset( $columns['cpt-shows'] ); 	  // Don't allow sort by shows
    	$columns['postmeta-roletype']	= 'role'; // Allow sort by role
        return $columns;
    }
    

    The column names are required from the previous steps, but the sort by being ‘role’ is something I made up. It doesn’t have to match anything from before however it has to be remembered, as we’re going to re-use it in a moment.

    You see, I have to tell it what ‘sortable’ means by filtering pre_get_posts. I’m grabbing the meta value for lezchars_type and I’m going to tell it to order by the value. This happens to be alphabetical. If I was using a number, it would have to be meta_value_num instead of meta_value.

    // Create Role Sortability
    function post_type_characters_role_orderby( $query ) {
    	if( ! is_admin() ) return;
    
    	if ( $query->is_main_query() && ( $orderby = $query->get( 'orderby' ) ) ) {
        	switch( $orderby ) {
    			case 'role':
    				$query->set( 'meta_key', 'lezchars_type' );
    				$query->set( 'orderby', 'meta_value' );
    				break;
    		}
    	}
    }
    

    Remember how I said to remember we called the order-by ‘role’? Well this was why. The case check is on ‘role’ but also the meta_key is our key name.

    And yes it works:

    Showing sorted columns

  • Post Meta, Custom Post Meta, and Statistics

    Post Meta, Custom Post Meta, and Statistics

    I do a lot with weird stats. I do a lot with CMB2 and custom meta. I do a lot with CMB2 and weird post meta.

    Is it any surprise I wanted to add my new year dropdown data to my site for some interesting statistics?

    Of course not. But I started small. I have a saved loop called $all_shows_query

    // List of shows
    $all_shows_query = new WP_Query ( array(
    	'post_type'       => 'post_type_shows',
    	'posts_per_page'  => -1,
    	'no_found_rows'   => true,
    	'post_status'     => array( 'publish', 'draft' ),
    ));
    

    This really just gets the list of all the shows and holds on to it. The meat of the code runs with this:

    $show_current = 0;
    
    if ($all_shows_query->have_posts() ) {
    	while ( $all_shows_query->have_posts() ) {
    		$all_shows_query->the_post();
    		$show_id = $post->ID;
    		if ( get_post_meta( $show_id, "shows_airdates", true) ) {
    			$airdates = get_post_meta( $show_id, "shows_airdates", true);
    			if ( $airdates['finish'] == 'current' ) {
    				$show_current++;
    			}
    		}
    		// More IF statements are here.
    	}
    	wp_reset_query();
    }
    

    As mentioned, there are more if statements, like if ( get_post_meta( $show_id, "shows_stars", true) ) {...} which gets data for if a show has a star or not. And if ( get_post_meta( $show_id, "shows_worthit", true) ) {...} which checks if a show is worth it or not. I was already doing a lot of this, so slipping in one more check doesn’t hurt.

    To show the data, without Chart.js, it looks like this:

    	<h3>Shows Currently Airing</h3>
    
    	<p>&bull; Currently Airing - <?php echo $show_current .' total &mdash; '. round( ( ( $show_current / $count_shows ) * 100) , 1); ?>%
    	<br />&bull; Not Airing - <?php echo ( $count_shows - $show_current )  .' total &mdash; '. round( ( ( ( $count_shows - $show_current ) / $count_shows ) * 100) , 1); ?>%
    	</p>
    

    With Chart.js, it looks like this:

    <canvas id="pieCurrent" width="200" height="200"></canvas>
    
    <script>
    // Piechart for Currently Airing Data
    var pieCurrentdata = {
        labels: [
            "On Air (<?php echo $show_current; ?>)",
            "Off Air (<?php echo ($count_shows - $show_current ); ?>)",
        ],
        datasets: [
            {
                data: [ <?php echo '
    	            "'.$show_current.'",
    	            "'.($count_shows - $show_current ).'",
    	            '; ?>],
                backgroundColor: [
    	            "#4BC0C0",
    	            "#FF6384",
    	            "#E7E9ED"
                ]
            }]
    };
    
    var ctx = document.getElementById("pieCurrent").getContext("2d");
    var pieTriggers = new Chart(ctx,{
        type:'doughnut',
        data: pieCurrentdata,
        options: {
    		tooltips: {
    		    callbacks: {
    				label: function(tooltipItem, data) {
    					return data.labels[tooltipItem.index];
    				}
    		    },
    		},
    	}
    });
    </script>
    

    Right now the data’s a bit off, since I’ve only updated 200 of the 325 shows, but it’s enough to get the information.

  • Passing Variables in WordPress Templates

    Passing Variables in WordPress Templates

    I don’t theme, generally speaking. I’m not bad at it, I just don’t have that particular aspect of ‘design’ in my head where I can create out of nothing. What I can do (and do do) is tweak the heck out of themes. This means I’m sure, if you theme, you know all this and think I’m a bit silly. That’s okay.

    One thing I hate about code is duplicated effort. I don’t mind including the same library in 100 different projects, but I hate when I have to use the exact same code over and over in multiple files. Like I did in my theme.

    I talked about using Chart.js in my theme before, and in that work I made use of get_template_part() to show the includes. It actually doesn’t look like that any more. Here’s the original code:

    function lez_stats_footer() {
        get_template_part( 'stats' );
    }
    

    And here’s the current code:

    function lez_stats_footer() {
    	if ( is_page( 'stats' ) ) {
    		get_template_part( 'inc/statistics', 'base' );
    	}
    	if ( is_page( 'death' ) ) {
    		get_template_part( 'inc/statistics', 'death' );
    	}
    	if ( is_page( 'characters' ) ) {
    		get_template_part( 'inc/statistics', 'characters' );
    	}
    	if ( is_page( 'shows' ) ) {
    		get_template_part( 'inc/statistics', 'shows' );
    	}
    }
    

    While get_template_part( 'stats' ) calls the file stats.php, the call to get_template_part( 'inc/statistics', 'shows' ); gets /inc/statistics-shows.php instead.

    This lets me organize things a little better. My /inc/ folder is filled with this:

    archive-characters.php
    archive-shows.php
    excerpt-characters.php
    excerpt-shows.php
    sidebar-characters.php
    sidebar-shows_archive.php
    sidebar-shows_single.php
    statistics-base.php
    statistics-characters.php
    statistics-code.php
    statistics-death.php
    statistics-shows.php
    

    As the help doc says, this makes it easy for a theme to reuse sections of code and that’s actually what I’m doing.

    I have a call to get_template_part( 'inc/archive', 'characters' ); in multiple files, including some page templates but also in my category and taxonomy lists.

    if ( get_post_type( get_the_ID() ) === 'post_type_characters' ) {
    	get_template_part( 'inc/archive', 'characters' );
    }
    
    if ( get_post_type( get_the_ID() ) === 'post_type_shows' ) {
    	get_template_part( 'inc/archive', 'shows' );
    }
    

    That means if it’s a show, use one format, and if it’s a character use another.

    But…

    One of the things I do is a little weird. On the single display pages for shows, I loop through all the characters related to the show and display them. There’s custom post meta to link the two, and what I do on the single page (single-post_type_shows.php) is grab the value for the post-meta of ‘shows’ on every single character page, and if the show ID is the same as the post ID of the page you’re on, add the character to an array.

    This is not very efficient. I know. It’s basically running an extra loop on every ‘show page’ load and collecting this query. What’s important here though is the array:

    $havecharacters[$char_id] = array(
    	'id'      => $char_id,
    	'title'   => get_the_title( $char_id ),
    	'url'     => get_the_permalink( $char_id ),
    	'content' => get_the_content( $char_id ),
    );
    

    This is important because I needed to pass this data through to my template part. On the template page for characters, I call get_template_part( 'inc/excerpt', 'characters' ); and it works fine because it knows exactly what page it’s on and calling get_the_post_thumbnail() will work because of what show it is. But when I call it from the shows page, though, it tried to use the values based on the show page, not the character.

    What I needed to do was pass the parameters of the character array to the excerpt. And you can’t do that with get_template_part but you can with a different call.

    include(locate_template('inc/excerpt-characters.php'));

    This passed through my array, which let me call it with a nice for-loop :

    if ( empty($havecharacters) ) {
    	echo '<p>There are no characters listed for this show.</p>';
    } else {
    	foreach( $havecharacters as $character ) {
    	?>
    		<ul class="character-list">
    			<li class="clearfix"><?php include(locate_template('inc/excerpt-characters.php')); ?></li>
    		</ul>
    	<?php
    	}
    }
    

    And now my excerpt template can use $character['title'] and everything looked great on the show pages.

    They did not, however, look so great on the character page, which used get_template_part remember. This was simply because the array for $character was empty. In order to make this work for the single character page, I had to recreate the array. Only I did it a little differently.

    if ( empty($character) ) {
    	$character = array(
    		'id'      => get_the_ID(),
    		'title'   => get_the_title(),
    		'url'     => get_the_permalink(),
    		'content' => '',
    		'ischar'  => true,
    		'shows'  => get_post_meta( get_the_ID(), 'lezchars_show', true ),
    	);
    }
    

    The reason I wiped out the content (which held get_the_content in the original array, remember) is that on a single character page, WordPress knows to show the content, so I don’t need to double that work. I also added in new array items for ischar and shows so I could check if ( $character['ischar'] !== true ) {...} in some places and show slightly different content depending on what page I’m on.

    Whew.

  • Keep A Name In Mind

    Keep A Name In Mind

    When you’re making your own project for small things, it’s not a huge deal when you try to think up a name. As soon as you realize your project is going to be shared with the world, however, the game changes.

    Project Names

    A project can be as massive as a new release of an operating system (Longhorn anyone?) or as small as a new plugin for WordPress. If could be a library for PHP or JS, or maybe a simple NPM add on. In all cases, the name you pick should be unique.

    This gets hard when you want to name a tool something like “Foo for Bar” like “Color Coding for Quickbooks.” Wy is that hard? Well while your name is certainly descriptive, it’s not unique. Because someone else can make the same tool. “Joe’s Color Coding for Quickbooks.” Or worse… “Color Coding 4 Quickbooks.” And the problem here is that neither of you really have the right to the name, do you? You’re both leveraging ‘Quickbooks’ and their brand, so where do you have a leg to stand on when someone uses a similar name?

    A unique name, though, like “Color Me Quickly,” would be so much better. Think of displaying it like this: “Color Me Quickly – A tool to colorize Quickbooks” and then having a description that talks about the idea and how to use it. “We love Quickbooks, just like you, but we hated the color schemes. We were always mixing up Receipts and Refunds. That’s why we came up with the Color Me Quickly tool. One simple install and we could see, right away, what was what. With our accessibility friendly default color schemes, and fully customizable colors, you can make your Quickbooks look how you need.”

    The name is unique, the description is SEO friendly, and you will be easily able to stand out in a crowd.

    Function Names

    Oh but then we have function names.

    If you’re a library, please please please remember to wrap your code in “If this code is already included, do not include it again” checks. PHP has function_exists() and class_exists().

    Using Javascript?

    if (typeof obj === "function") { 
        // Function is safe to use
    }
    

    The point here is that if you’ve made a library, always make sure it’s not already running before you try to run. This is a huge issue in WordPress land. With over 45,000 plugins, the odds that two will include the same libraries are pretty high. The odds that two will have conflicting versions? Right, you got it.

    But you should watch out with those checks. I’ve seen a lot of plugins use a check that if the function doesn’t exist, run their plugin. That’s a great idea, except that it’s not. If you name your function wp_get_post() and check for it’s existence before loading, what happens if it does exist? Your code won’t be called. And what happens if your code doesn’t get called? Your function won’t run. Your plugin won’t work as expected.

    Function and class names have to consider their world. A WordPress plugin should never use a non-prefixed’ anything. As Nacin says:

    It’s a simple concept. Anything you create in the global namespace has the potential to conflict with a theme, another plugin (including one you wrote), and WordPress core itself. Thus, prefix everything with a unique-enough character set.

    Be Unique

    Consider the environment that you code for when you name things. Always check for trademarks and possible conflicts before you name something you plan to release to the world. Remember to be unique.

    Also remember it’s 2016. All the good two and three character prefixes are probably taken.

  • Hiding Custom Taxonomy Parents

    Hiding Custom Taxonomy Parents

    I have a few custom taxonomies that I want to be shown as textboxes and in order to do that, the simplest way is in the custom taxonomy, you set them up as hierachical true. This makes them behave like categories. The problem is I really don’t want these things to be hierarchical. That is, I don’t want people adding in a parent/child relationship.

    The simplest way around this is to cheat with CSS:

    select#newtropes_parent {
        display: none;
    }
    .form-field.term-parent-wrap {
        display: none;
    }
    

    That hides the parent value. In order to have it show on the proper pages, I put it in a file called shows.css, in the same folder as my shows.php file that controls all the settings for the shows CPT (this includes the custom taxonomies used by shows) and wrapped it in this:

    add_action( 'admin_enqueue_scripts', 'shows_my_scripts', 10 );
    function shows_my_scripts( $hook ) {
    	global $current_screen;
    	wp_register_style( 'shows-styles', plugins_url('shows.css', __FILE__ ) );
    	if( 'post_type_shows' == $current_screen->post_type || 'tropes' == $current_screen->taxonomy ) {
    		wp_enqueue_style( 'shows-styles' );
    	}
    }
    

    Now you can’t see that there are parents. Perfect. Done.

  • WordPress Multisite: Block Site

    WordPress Multisite: Block Site

    This came up when I was looking at WordPress.com, where one has the freedom to post anything within their ToS, and I saw someone’s moronic blog about how specific people were evil. Pick whatever you want, it doesn’t matter except assume it was something offensive to a minority.

    The Terms of Use says this:

    In particular, make sure that none of the prohibited items (like spam, viruses, or serious threats of violence) appear on your website.

    This was not a serious threat of violence, it was just ignorant, offensive, and stupid. I looked at the site and thought “What I want most in this moment is a big ass button to block this person from posting on my .com site, and to prevent them from ever being able to comment on any blog I own.”

    It doesn’t exist. (I will note I found BuddyBlock but I have no idea how well that would work, and it’s for BuddyPress only.)

    Part of the cool thing about WordPress Multisite is that you can run your own social network. With that power comes responsibility though. Users should be able to protect themselves while remaining on your network, allowing them to block other users they just don’t want to talk to.

    So why don’t we? Well effective blocking is hard. As I mentioned in my post about how (most) contact forms fail at this, the biggest issue is people can just fake who they are are try again. This is a little harder on a Multisite, where a legitimate email and account can be required to comment, but by default all members of a network can comment on any blog on the network. This means we’re opening ourselves up to the potential to more abuse.

    How would that big block work? There are a few approaches and I think the best route would be two fold.

    Blocking Users

    Everyone should have the ability to mute or block a user. As an end user, if I never want to see comments from John Smith again, I should be able to press ‘block.’ Then I would just see a note like [comment hidden] whenever I run into a comment from him on any blog on the network. On a non Multisite, I’d actually like to see that for any site that requires registration. Allow users to mute each other.

    As an admin, if I block John Smith, then his comments are immediately discarded. If you wanted to get fancy, then you’d hide his comment from everyone who isn’t him, so he thinks he’s still talking to people and just being ignored. A silence mode. Use some JS so an admin has to click to expand and see what’s going on, so if John Smith is escalating, he can be banned.

    That would be the other thing. Banning users from your sites on a Multisite should be totally possible. And on .com a way to report “User X keeps working around my blocks.” would help a lot.

    Also for admins, perhaps they should be able to see “X people have blocked this user” on the Dashboard. That said, I can see a massive possibility for abuse with that. If John Smith was an admin of his own blog and saw ’10 people blocked you…’ it could cause problems. It would be trivial to hide it from the user, so you could never know how many people blocked you, but I can think of a few fast workarounds. Easiest is to add a second admin account to my own blog on the network and check.

    Blocking Blogs

    This is mostly an issue on WordPress.com, since it’s one of the few places I know of that has a ‘reader’ that shows you blogs that you might be interested in. That’s how I found the offending blog, by the way. A friend runs a religious blog on .com and the one we both found appalling was a recommended blog to her. I’ve already talked to some people behind the scenes of .com about that and how the algorithm may need some turning. But even if she had stumbled on to it via a search, should she not be able to say “Ew! Block!”

    I would write it so that if someone clicked ‘block blog’ the following things happen:

    1. The owner of the blog is blocked from commenting on any blog I own
    2. The URL of the blog is placed on my blacklist
    3. Optionally, all admins of the blog are added to my blacklist

    Now I don’t have to see anything anymore.