Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: wordpress

  • FacetWP and Genesis Pagination

    FacetWP and Genesis Pagination

    If you use StudioPress’ Genesis themes, you may be used to that pretty numerical pagination instead of ye olde previous and next buttons. And if you use FacetWP, you may have found out you need to use their pagination to make things work right.

    Thankfully this can be fixed thanks to two people’s previous work.

    Replace the Navigation

    First we look to Sridhar Katakam who came up with an elegant way to replace the normal Genesis post navigation. The code is nearly the same, I just single-lined the returns:

    // Replace Genesis' Pagination with FacetWP's.
    // Context: Posts page, all Archives and Search results page.
    // @author Sridhar Katakam - http://sridharkatakam.com/facetwp-genesis/
    add_action( 'loop_end', 'MYSITE_replace_genesis_pagination' );
    function MYSITE_replace_genesis_pagination() {
    	if ( ! ( is_home() || is_archive() || is_search() ) ) return;
    	remove_action( 'genesis_after_endwhile', 'genesis_posts_nav' );
    	add_action( 'genesis_after_endwhile', 'MYSITE_facet_posts_nav' );
    }
    function MYSITE_facet_posts_nav() {
    	echo facetwp_display( 'pager' );
    }
    

    The nice part about this is that if you’re not using FacetWP on that page, it reverts to the default pager. I’m not doing a check on if Facet exists because I have this in a giant block that does it for me. If you need to, though, the code would be this:

    if ( function_exists( 'facetwp_display' ) ) { 
        add_action( 'loop_end', 'MYSITE_replace_genesis_pagination' ); 
    }
    

    And yes, you should always check if the plugin is loaded before you break things.

    Style the Navigation

    The styling magic comes from a slight fork of Matt Gibbs’ code to remove pagination if there’s only one post:

    // Style pagination to look like Genesis
    // @author Matt Gibbs
    // https://gist.github.com/mgibbs189/69176ef41fa4e26d1419
    function MYSITE_facetwp_pager( $output, $params ) {
        $output = '<div class="archive-pagination pagination"><ul>';
        $page = (int) $params['page'];
        $total_pages = (int) $params['total_pages'];
    
        // Only show pagination when > 1 page
        if ( 1 < $total_pages ) {
    
            if ( 1 < $page ) {
                $output .= '<li><a class="facetwp-page" data-page="' . ( $page - 1 ) . '">&laquo; Previous Page</a></li>';
            }
            if ( 3 < $page ) {
                $output .= '<li><a class="facetwp-page first-page" data-page="1">1</a></li>';
                $output .= ' <span class="dots">…</span> ';
            }
            for ( $i = 2; $i > 0; $i-- ) {
                if ( 0 < ( $page - $i ) ) {
                    $output .= '<li><a class="facetwp-page" data-page="' . ($page - $i) . '">' . ($page - $i) . '</a></li>';
                }
            }
    
            // Current page
            $output .= '<li class="active" aria-label="Current page"><a class="facetwp-page active" data-page="' . $page . '">' . $page . '</a></li>';
    
            for ( $i = 1; $i <= 2; $i++ ) {
                if ( $total_pages >= ( $page + $i ) ) {
                    $output .= '<li><a class="facetwp-page" data-page="' . ($page + $i) . '">' . ($page + $i) . '</a></li>';
                }
            }
            if ( $total_pages > ( $page + 2 ) ) {
                $output .= ' <span class="dots">…</span> ';
                $output .= '<li><a class="facetwp-page last-page" data-page="' . $total_pages . '">' . $total_pages . '</a></li>';
            }
            if ( $page < $total_pages ) {
                $output .= '<li><a class="facetwp-page" data-page="' . ( $page + 1 ) . '">Next Page &raquo;</a></li>';
            }
        }
    
        $output .= '</ul></div>';
    
        return $output;
    }
    add_filter( 'facetwp_pager_html', 'MYSITE_facetwp_pager', 10, 2 );
    

    This is somewhat obvious. It adds in <li></li> around each page item, and also wraps the entire block in the default Genesis code of <div class="archive-pagination pagination"><ul></ul></div> to produce the default Genesis display.

  • Front Facing Alerts on Posts

    Front Facing Alerts on Posts

    Once I wrote the back-end code to show admin alerts on posts, I decided to add in some front end magic.

    Since the original code was written to be called by hooking into the admin_notices action, the obvious solution was to hook into something on the front end. But where?

    Genesis

    I’m a fan of the Genesis themes. There’s a handy hook for placement called genesis_before_content_sidebar_wrap() and the call (based on last week’s code) is this:

    add_action( 'genesis_before_content_sidebar_wrap', 'MYSITE_admin_notices' );
    

    And immediately that outputs this below the menu:

    Output message above title

    Obviously there’s no CSS going on there, but that’s okay.

    Not Genesis

    That’s lovely but what if I don’t use Genesis?

    While a filter like add_filter( 'the_title', 'MYSITE_admin_notices', 10, 2 ); would work, it also calls it for every output of the title on a page. If you happen to be using a sidebar that links to your 5 most recent posts, it can get pretty messy.

    And the problem here is that WordPress doesn’t have standard template hooks like that. You can use get_header() but that puts it at the top of every page. There just isn’t a way to say “I want this above the title” and have it work universally on all themes. Bummer.

    That said, you can totally make the get_header() call work with a little CSS magic and a little extra if-checks.

    The CSS Code

    Remember our goal in life is to load as little as possible on pages, so the best thing is to check if this is even the right post type:

    add_action( 'wp_enqueue_scripts', 'MYSITE_wp_enqueue_scripts' );
    public function MYSITE_wp_enqueue_scripts( ) {
    	wp_register_style( 'cpt-shows-styles', plugins_url('shows.css', __FILE__ ) );
    
    	if( is_single() && get_post_type() == 'post_type_shows' ){
    		wp_enqueue_style( 'cpt-shows-styles' );
    	}
    }
    

    The actual CSS is up to you, but mine looks like this and is cribbed from WordPress core:

    .wrap .notice {
    	background: #fff;
    	border-left: 4px solid #fff;
    	-webkit-box-shadow: 0 1px 1px 0 rgba(0, 0, 0, .1);
    	box-shadow: 0 1px 1px 0 rgba(0, 0, 0, .1);
    	margin: 5px;
    	padding: 1px 12px;
    }
    
    .wrap .notice p {
    	margin: .5em 0;
    	padding: 2px;
    }
    
    .wrap .notice p:before {
    	margin-right: 6px;
    	vertical-align: bottom;
    }
    
    .wrap .notice-error {
    	border-left-color: #dc3232;
    }
    
    .wrap .notice-info {
    	border-left-color: #00a0d2;
    }
    
    .wrap .notice-warning {
    	border-left-color: #ffb900;
    }
    
    .wrap .notice-updated,
    .wrap .notice-success {
    	border-left-color: #46b450;
    }
    

    This basically reproduces the same CSS as you’d see on the Admin Dashboard.

    The PHP Code

    While we covered the brunt of the PHP code before, we need to make a few alterations to make sure that the output only shows on the right pages and to the right people.

    The first changes will be in this section:

    if ( $message ) {
    	printf( '<div class="notice %1$s"><p><span class="dashicons dashicons-%2$s"></span> %3$s</p></div>', esc_attr( $type ), esc_attr( $dashicon ), esc_html( $message ) );
    }
    

    While it’s possible to use is_admin() to check if a visitor is on the dashboard or not, this is going to show on both front and backend. That means a better choice is is_user_logged_in() so our code will look like if ( $message && is_user_logged_in() ) and now it only shows if you’re logged in.

    But. The code shows on every page. That meant the code showed up on the static front page because it had no code and just widgets. In order to make it only show up on the right pages, I put in a check for what was called when.

    Since I have multiple CPTs, I keep the code that is post type specific in a file that matches the name, and I have an extra file called all-cpts.php that calls common code that they all use. This helps me keep my code dry (Don’t Repeat Yourself). That makes it easy for me to use a switch based the post type and output the right code on the right pages:

    function MYSITE_CPT_admin_notices() {
    
    	if ( !get_post() ) return;
    
    	$message    = '';
    	$type       = 'updated';
    	$post       = get_post();
    
    	$content    = get_post_field( 'post_content', $post->ID );
    	$word_count = str_word_count( strip_tags( $content ) );
    
    	switch ( $post->post_type ) {
    		case 'post_type_shows':
    			$worthit = get_post_meta( $post->ID, 'shows_worthit_details', true );
    
    			if ( $worthit < '1' ) {
    				$type     = 'notice-info';
    				$message  = 'Is this show worth watching? We don\'t know. Halp!';
    				$dashicon = 'heart';
    
    				if ( $word_count < '100' ) {
    					$type     = 'notice-error';
    					$message  = 'We clearly know nothing about this show. Help!';
    					$dashicon = 'warning';
    				} elseif ( $word_count < '200' ) {
    					$type     = 'notice-warning';
    					$message  = 'This post is a stub. Please edit it and make it more awesome.';
    					$dashicon = 'info';
    				}
    			}
    			break;
    	}
    
    	if ( $message && is_user_logged_in() && ( is_single() || is_admin() ) ) {
    		printf( '<div class="wrap"><div class="notice %1$s"><p><span class="dashicons dashicons-%2$s"></span> %3$s</p></div></div>', esc_attr( $type ), esc_attr( $dashicon ), esc_html( $message ) );
    	}
    
    }
    

    Now the message shows up for logged in users only, and only on the intended pages.

  • Admin Alerts on Posts

    Admin Alerts on Posts

    As the new WordPress.org plugin directory rolls out, I know a lot of people are fussing over the front end. I’m not for a very practical reason. I’m working on making the backend functional and educational.

    Information Is Key

    A lot of the information about what a various post status means is something I understand from experience. My goal is to expand the team as broadly as possible and in order to do that, I must document. But documentation is more than just writing up a doc and telling people to follow it.

    People need contextual reminders of what the status of a ‘thing’ is to understand what they must do next. With that in mind, I leveraged some code by Obenland to make reviewer alerts better. Originally, you see, our plan was to let plugin developers edit things from the admin panel. Everything’s moving to the front end except for the reviewers you see.

    On Beyond Plugins

    That’s all well and good, but what if I want to do it on my own sites? What if I wanted to have a site message for the editors on my site, so they’d know what posts are ‘done’ and which need some love? What if I wanted to encourage my fellow editors to edit a post I’d worked on?

    I made my plan, based off the old stub template from MediaWiki, because that was always a good way to know if a post did or didn’t need some love.

    1. I needed a way to ‘flag’ a post as a stub automatically
    2. I needed it to display on the page being edited
    3. I would like it to display on the front end but only to logged in users (for now)

    Stub Qualifications

    In order to flag a post automatically, I needed to come up with criteria or qualifications that would mark a post as a stub. Obviously content was part of it, but measuring the quality of the content was going to be difficult.

    My initial criteria for a stub was the following:

    • Under 100 words in post content
    • Empty meta value for “Worth it details”

    Obviously that second item is unique to my situation, but having it meant I could measure engagement by how much someone knew about a show to enter the data. That criteria seemed too strict though. So I tweaked them to be an OR and not an AND. No matter what, if there was under 100 words it was getting flagged. And no matter what, if there was no meta for worthiness, it would be flagged.

    • Info: No meta value for Worth It
    • Warning: No meta value for Worth It and post content under 200 words
    • Alert: No meta value for Worth It and post content under 100 words

    WP Admin Code

    The original code that all this is based on is from WordPress.org (not the WordPress code but the stuff that runs the domain).

    add_action( 'edit_form_after_title', 'MYSITE_admin_notices' );
    function MYSITE_admin_notices() {
    	$message    = '';
    	$type       = 'updated';
    	$post       = get_post();
    
    	switch ( $post->post_type ) {
    		case 'post_type_shows':
    
    			$content    = get_post_field( 'post_content', $post->ID );
    			$word_count = str_word_count( strip_tags( $content ) );
    			$worthit    = get_post_meta( $post->ID, 'worthit_details', true );
    
    			if ( $worthit < '1' ) {
    				$type     = 'notice-info';
    				$message  = 'Is this show worth watching? We don\'t know. Halp!';
    				$dashicon = 'heart';
    
    				if ( $word_count < '100' ) {
    					$type     = 'notice-error';
    					$message  = 'We clearly know nothing about this show. Help!';
    					$dashicon = 'warning';
    				} elseif ( $word_count < '200' ) {
    					$type     = 'notice-warning';
    					$message  = 'This post is a stub. Please edit it and make it more awesome.';
    					$dashicon = 'info';
    				}
    			}
    			break;
    	}
    
    	if ( $message ) {
    		printf( '<div class="notice %1$s"><p><span class="dashicons dashicons-%2$s"></span> %3$s</p></div>', esc_attr( $type ), esc_attr( $dashicon ), esc_html( $message ) );
    	}
    
    }
    

    The reason it uses edit_form_after_title and prints the error instead of add_settings_error is that I don’t actually want these alerts to be dismissible and I only want it to show on post editor pages.

    The Output

    This post needs some attention

    There’s some improvements to be made, but this is a start.

  • Multi Faceted Connections

    Multi Faceted Connections

    That’s a pun because I’m using FacetWP y’all.

    As you probably know by now, I have a site that has a lot of weird data. And one of the problems I ran into with FacetWP and my site was that I saved data in a serialized manner.

    Simple Arrays

    Since characters can have multiple actors, the data is saved in a serialized array like this: a:1:{i:0;s:13:"Lucy Lawless";}

    I want to be able to search for all the characters Lucy Lawless plays, even if someone else also played the role, so to do that I need to tell FacetWP to save the data twice, an entry for each actor. To do that, I use this code:

    add_filter( 'facetwp_index_row', 'filter_facetwp_index_row', 10, 2 );
    function filter_facetwp_index_row( $params, $class ) {	
    	// Actors
    	// Saves one value for each actor
    	if ( 'char_actors' == $params['facet_name'] ) {
    		$values = (array) $params['facet_value'];
    		foreach ( $values as $val ) {
    			$params['facet_value'] = $val;
    			$params['facet_display_value'] = $val;
    			$class->insert( $params );
    		}
    		return false; // skip default indexing
    	}
    	return $params;
    }
    

    That saves two entries in the FacetWP table like this:

    An example of two actors for one character

    Complex Arrays

    Buuuuut I also have a complex array where I list a show’s airdates: a:2:{s:5:"start";s:4:"1994";s:6:"finish";s:4:"2009";}

    Now that doesn’t look too weird, I know, but the problem is I wanted to be able to compare the start and end dates, so you could get a list of, say, all shows that were on air between 1950 and 1960 (two, by the way). In order to do that, I had to break the array apart into not only two values, but two separate sources!

    In order to make that work, I do this:

    // Airdates
    // Splits array value into two sources
    if ( 'show_airdates' == $params['facet_name'] ) {
    	$values = (array) $params['facet_value'];
    
    	$start = ( isset( $values['start'] ) )? $values['start'] : '';
    	$end   = ( isset( $values['finish'] ) )? $values['finish'] : date( 'Y' );
    
    	$params['facet_value']         = $start;
    	$params['facet_display_value'] = $start;
    	$class->insert( $params );
    
    	$params['facet_source']        = 'cf/lezshows_airdates_end';
    	$params['facet_name']          = 'show_airdates_end';
    	$params['facet_value']         = $end;
    	$params['facet_display_value'] = $end;
    	$class->insert( $params );
    	
    	return false; // skip default indexing
    }
    

    That gives me two database entries like so:

    An example of two values in two separate sources

    The reason this is done is because I have a facet that compares the datasets for lezshow_airdates_end with lezshow_airdates and if the numbers are between them, that’s what it shows.

    And this works because of this filter:

    // Filter Facet sources
    add_filter( 'facetwp_facet_sources', function( $sources ) {
        $sources['custom_fields']['choices']['cf/lezshows_airdates_end'] = 'Airdates End';
        return $sources;
    });
    

    That creates a new custom field based on the values in cf/lezshows_airdates_end so I can compare between the two. And with a snazzy slider, I can do this:

    Aired Between ... as a slider

  • FacetWP: Making Sorting Suck Less

    FacetWP: Making Sorting Suck Less

    Sorting data in WordPress is generally done in the most basic of ways. You want to see all posts that are in a specific category, you go to example.com/category/drinks/ and there you are. But if you want to see everything in the category ‘drinks’ with the tag ‘bourbon’ and the custom taxonomy of ‘ingredients’ and a value of ‘mint’ AND ‘simple syrup’ to get the recipe for a mint julep, then you have a pretty crazy complex query.

    Enter FacetWP

    FacetWP is a premium plugin that, for $79 a year, handles all that crazy sorting for you. And yes, it’s worth it.

    FacetWP introduces advanced filtering to WordPress, which lets you do things like get that list of all drinks made with bourbon that include a simple syrup, in a dynamic way! It’s incredibly fast, since it’s using ajax and javascript, and as long as you have enough server memory to index all the data in the first place, it’s faster than reloading a new category page.

    Downsides

    In order to be that fast, you do not get pretty URLs. Let’s say you have your drinks category at `example.com/category/drinks’ and you want to list all those things. Your URL will look like this:

    example.com/category/drinks/?fwp_alcohol=bourbun&fwp_ingredients=simple+syrup%2Cmint

    The realistic reason they don’t try to make it ‘pretty’ is that it would create a lot more rewrite rules than would be sustainable, if you have a lot of facets. The number of checks would slow your site down, and that would kind of suck.

    Compatibility Notes

    If you use CMB2 you’ll need FacetWP + CMB2.

    If you use Genesis themes, there are two tricks. First, you’ll want to use the following function to add FacetWP’s CSS to your theme:

    function YOURTHEMENAME_facetwp_class( $atts ) {
        $atts['class'] .= ' facetwp-template';
        return $atts;
    }
    add_filter( 'genesis_attr_content', 'YOURTHEMENAME_facetwp_class' );
    

    Second, if you’re like me and you use a lot of custom loops, they may not behave as expected. If you call the loop multiple times on a page (which is bad behavior in the first place and I know it), FacetWP has a bit of trouble knowing what javascript to apply to what section. That should be expected, and once I cleaned it up, it worked great.

    Should you use it?

    If you have a lot of complex intersectional queries to sort through, yes.

    If you need dynamic result updates, yes.

    It works.