Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: wordpress

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

  • Making HTTPS Everywhere

    Making HTTPS Everywhere

    With the advent of Let’s Encrypt, introducing free and easy SSL certificates for everyone, and the fact that Plesk, cPanel, and home grown Panels like DreamHost’s all providing easy ways to install certs, renew them, and support them, we’re finally inching our way to the dream of HTTPS Everywhere.

    Why HTTPS?

    The S makes it secure, and the green lock on a browser tells a person that their visit is safer, encrypted, and obscures sensitive data. It means a visit is confidential. It means the site is the real site. It cannot be easily monitored, modified, or impersonated.

    While this blog has no sensitive data of yours, it does accept (require) your email when you leave a comment. You don’t want everyone knowing that, I suspect. You probably don’t want everyone grabbing your IP.

    Why do we care about security in general? Because nothing is non-sensitive anymore. Everything we do and say on the Internet can be used against us. Entering in your mother’s maiden name on a form over HTTP? Someone can snipe that and use it to steal your identity. Use the same password on multiple accounts, one of which is HTTP? Your code can be stolen. The list goes on and one.

    How I Turned This Site HTTPS Everywhere

    Every single domain on ipstenu.org is now https. Everyone either has the Let’s Encrypt certificate or a Comodo one. First I turned on Let’s Encrypt. Then I used WP-CLI to search and replace my urls:

    $ wp search-replace http://halfelf https://halfelf
    $ wp search-replace http://ipstenu https://ipstenu
    

    And so on and so forth down the line.

    Next I checked my mu-plugins folder and my content folder to make sure none of my home grown code was hardcoding in http (it wasn’t), and updated my wp-config.php to include this:

    define('FORCE_SSL_ADMIN', true);
    define('FORCE_SSL_LOGIN', true);
    

    That probably wasn’t required but why not? Finally I tossed this into my .htaccess:

    # Force non WWW and SSL for everyone.
    <IfModule mod_rewrite.c>
    	RewriteEngine On
    
    	RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
    	RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
    
    	RewriteCond %{HTTPS} off
    	RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
    <IfModule mod_rewrite.c>
    

    Really. That’s all it took to swap it all to https everywhere here.

    Gotchas

    Not all my plugins were happy about this.

    Most were, actually, which was nice, but a couple did some incredibly stupid things with hardcoded http resources. Fixing them for myself is trivial. For others… I recommend WordPress HTTPS or Really Simple SSL, both of which will let you force https for all URLs or block the http ones.

    For the most part, with WordPress, you don’t need to worry about this. In recent years, the ability to force SSL from within WP itself has gotten better and better. The problem has always been our themes and plugins.

    Other than that, it’s been pretty smooth going.

    Non WordPress

    But… what about my non-WordPress sites? Yeah, you know I have them.

    Well my ZenPhoto20 site doesn’t run any extensions, so I just checked the box for using SSL and went on my way. I’d cleverly written all my themeing with protocol-less URLs (\\example.com\my\path\file.css). While that’s really an anti-pattern, and https should be used whenever possible, I had everything in one file so I search/replace’d that and it was done.

    My Hugo site required two changes to a config file. It looked like this:

      StaticURL = "https://example-static.net"
      HomeURL = "https://example.net"
    

    The reason I did that was so my templates could look like this:

    <link rel="shortcut icon" href="{{ .Site.Params.StaticURL }}/images/favicon.ico">
    

    Once I saved the variables in my config, I could push the site (which automatically rebuilds and deploys) and be done.

  • Elasicsearch as a Service

    Elasicsearch as a Service

    Search is hard. Searching when you have custom meta data in post is harder. By default, WordPress does not search your custom meta data. And my LezWatchTV site is 75% custom meta data.

    I’d been using Google Search, but that has a lot of issues of it’s own like privacy, ads, accuracy, and most importantly, no way to tune it. I decided to try out ElasticSearch since I knew that was what WordPress.org’s internal search engine was going towards. After I added custom post meta to my search content, this post was going to be about how to install Elasticsearch on an ELK stack on DreamCompute, which turned out to be rather easy if time consuming and messy. And getting WordPress to work with it was as easy as installing the ElasticPress plugin (thank you 10up).

    What was complicated was making Elasticsearch work remotely. By default, it wants to only be accessible locally for your own security. But adding in Shield and still having all the logs and pretty things to understand what was happening and how to manage it when it was all new escalated quickly. It was simply too much all at once for me. Instead I decided to look into Elasticsearch as a service.

    There are a lot of options here,

    Self Managed

    I know I said ‘as a service’ but you really can use DigitalOcean or DreamCompute to do this. And there’s all sorts of documentation about how to do it available (like DigitalOcean’s ‘How to install the ELK Stack on Ubuntu 14.04’ which works on DreamCompute too). And Amazon Elasticsearch is also an option here.

    But… they’re all very self-managed. They require you to jump into servers, run a lot of commands, and they’re not new user friendly. Look, I get that this is complicated stuff, but people aren’t going to know if they want to learn all this if you make it monumental to get into.

    Services

    You can break these down into two main types.

    Enterprise Level:

    Free ‘Trials’:

    I wanted to use something ‘free’ to get started so I could figure out what I wanted to do and how to properly use Elasticsearch before deciding if I wanted to pay. But also I wanted to figure out exactly to do with search. Therefore I needed something ‘free’ to test with, something with logs, that would help me understand it all. I ended up trying both Bonsai and Searchly. While Bonsai gave me more room, Searchly had more information to the interface, but neither had a ‘Hey, here’s how you tune Elasticsearch!’ page.

    Neither had Kibana 4 though, which is a little sad.

    So when you don’t know how to do ‘anything’ with Elasticsearch, what can you test? The same search. I checked which was faster, which was more accurate, and which had the results I wanted. Bonsai was the winner here, so that’s what I went with.

    Integrating WordPress

    Thankfully this is the easy part.

    Install the ElasticPress plugin. Go to Settings -> ElasticPress and add in the URL from your Bonsai panel as your Host. It should look like https://username:password@yourcluster.us-west-2.bonsai.io (with some variation based on location). Save, press the ‘Run Index’ button, and you’re done.

    The nice thing about the plugin is if it breaks (like the service goes down), the plugin reverts to WordPress search! Which isn’t great, but … well.

    Next? How do I tune Elasticsearch?!

  • Search Options for Custom Post Data

    Search Options for Custom Post Data

    I use CMB2 to add in a bunch of custom meta data for my posts on a site. Seeing as I’m using it to allow layouts and formats to be consistent, it’s not surprising that I’ve chosen to split out my data like that. In another world, maybe it would be done differently, but this works.

    Except that search sucks. WordPress doesn’t search custom post meta out of the box which just kills me. That meant all the data I stored in for names and dates was never getting searched. There are two ‘easy’ solutions for this at least.

    Google Search

    Ew. I know. But ew. Since I’m using Genesis as my theme, it’s not super hard, just a little weird. Assuming you already have a Custom Search Engine set up, and you’re using Genesis, here’s what to do next.

    First I added this into my functions-site.php (note: I made a functions-site.php file so I can easily update my functions.php file on the rare occasion I need to update the child theme – it’s really rare – but also so I always know what’s me and what was Genesis):

    /* Google Custom Search Engine */
    add_filter( 'genesis_search_form', 'helf_search_form', 10, 4);
    function helf_search_form( $form, $search_text, $button_text, $label ) {
        $onfocus = " onfocus=\"if (this.value == '$search_text') {this.value = '';}\"";
        $onblur = " onblur=\"if (this.value == '') {this.value = '$search_text';}\"";
        $form = '<form method="get" class="searchform search-form" action="' . home_url() . '/search" >' . $label . '
    <input type="text" value="' . esc_attr( $search_text ) . '" name="q" class="s search-input"' . $onfocus . $onblur . ' /><input type="submit" class="searchsubmit search-submit" value="' . esc_attr( $button_text ) . '" /></form>';
        return $form;
    }
    

    Then I made a custom page template thanks to Rick Duncan:

    <?php
    /*
     * Template Name: Google Custom Search Engine
     *
     * This file adds the Google SERP template to our Genesis Child Theme.
     *
     * @author     Rick R. Duncan
     * @link       http://www.rickrduncan.com
     * @license    http://www.opensource.org/licenses/gpl-license.php GPL v2.0 (or later)
     *
     */
    
    //* Force Full-Width Layout
    add_filter( 'genesis_pre_get_option_site_layout', '__genesis_return_full_width_content' );
    
    //* Add Noindex tag to the page
    add_action( 'genesis_meta', 'lez_noindex_page' );
    function lez_noindex_page() {
    	echo '<meta name="robots" content="noindex, follow">';
    }
    
    //* Insert Google CSE code into <head> section of webpage
    add_action( 'genesis_meta', 'lez_google_cse_meta', 15 );
    function lez_google_cse_meta() { ?>
    
    	<script>
    	  (function() {
    	    var cx = '017016624276440630536:tpoclrwnxyy';
    	    var gcse = document.createElement('script');
    	    gcse.type = 'text/javascript';
    	    gcse.async = true;
    	    gcse.src = 'https://cse.google.com/cse.js?cx=' + cx;
    	    var s = document.getElementsByTagName('script')[0];
    	    s.parentNode.insertBefore(gcse, s);
    	  })();
    	</script><?php
    }
    //* Add custom body class
    add_filter( 'body_class', 'lez_add_body_class' );
    function lez_add_body_class( $classes ) {
    
       $classes[] = 'google-cse';
       return $classes;
    
    }
    //* Remove standard Genesis loop and insert our custom page content
    remove_action( 'genesis_loop', 'genesis_do_loop' );
    add_action( 'genesis_loop', 'lez_custom_content' );
    function lez_custom_content() { ?>
    
    	<div itemtype="http://schema.org/SearchResultsPage" itemscope="itemscope">
    		<header class="entry-header">
    			<h1 itemprop="headline">
        			<?php echo get_the_title($ID); ?>
        		</h1>
        	</header>
        	<div class="entry-content" itemprop="text">
        		<?php echo get_the_content();
        		//* Obtain querystring value if present and display on-screen
    			if ((isset($_REQUEST['q'])) && (!empty($_REQUEST['q']))) {
        			$query= $_REQUEST['q'];
        			echo '<strong>You Searched For:</strong> <em>'.$query.'</em>';
    			}
    			else {
    				echo 'Please enter a search phrase.';
    			}
    			if ( is_active_sidebar( 'google-cse' ) ) {
    				dynamic_sidebar( 'google-cse' );
    			}
    			?>
        		<gcse:searchresults-only></gcse:searchresults-only>
        	</div>
        </div>
    
    <?php }
    genesis();
    

    Finally I added a page called “Search Results” and assigned it this template. Done. Google, who searches the whole page content, will get everything. It just looks like Google.

    Having WordPress search your Custom Post Meta

    This was surprisingly annoying, but not as hard as all that. Adam Balee wrote Search WordPress by Custom Fields without a Plugin which, I know, is ‘without a plugin’ and sort of silly, but I put that in as an MU plugin and it worked perfectly!

    <?php
    /**
     * Extend WordPress search to include custom fields
     *
     * http://adambalee.com
     */
    
    /**
     * Join posts and postmeta tables
     *
     * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_join
     */
    function cf_search_join( $join ) {
        global $wpdb;
    
        if ( is_search() ) {    
            $join .=' LEFT JOIN '.$wpdb->postmeta. ' ON '. $wpdb->posts . '.ID = ' . $wpdb->postmeta . '.post_id ';
        }
        
        return $join;
    }
    add_filter('posts_join', 'cf_search_join' );
    
    /**
     * Modify the search query with posts_where
     *
     * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_where
     */
    function cf_search_where( $where ) {
        global $pagenow, $wpdb;
       
        if ( is_search() ) {
            $where = preg_replace(
                "/\(\s*".$wpdb->posts.".post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
                "(".$wpdb->posts.".post_title LIKE $1) OR (".$wpdb->postmeta.".meta_value LIKE $1)", $where );
        }
    
        return $where;
    }
    add_filter( 'posts_where', 'cf_search_where' );
    
    /**
     * Prevent duplicates
     *
     * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_distinct
     */
    function cf_search_distinct( $where ) {
        global $wpdb;
    
        if ( is_search() ) {
            return "DISTINCT";
        }
    
        return $where;
    }
    add_filter( 'posts_distinct', 'cf_search_distinct' );
    

    This is not the most efficient search, I know. But it works and gets my data where it’s needed.

  • Pagination and Static Front Pages

    Pagination and Static Front Pages

    This is a post skewed towards the Genesis Framework. Actually, if you’re not using the Genesis Metro Pro theme, I don’t know how well this will work.

    My problem was simple. I used the Metro Pro Static Front Page to show some widgets and then custom displays of posts via those widgets. It works pretty darn well and looks like this:

    Metro Pro's Static Front Page

    There was just one small issue. It doesn’t show me pagination at the bottom of the page. Oh and the normal method of example.com/page/2/ just showed me the same front page over and over. Not what I wanted.

    One way I could work around this would be to treat the front page as a static front page and make a “blog” page. Except then my urls would be example.com/blog/page/2 and I’d have duplicate content on example.com/blog/ which is not desirable. Causing me more frustration was the fact that the documentation said this:

    If no widgets are placed in any of the home page specific widget areas, a blog-style home page will be displayed.

    What I wanted was that blog-style page on the sub pages, along with navigation.

    Show Navigation Links

    This part was easy. In the file front-page.php I edited the function function metro_homepage_widgets() to have this at the bottom:

    genesis_posts_nav();

    Really, that was it. Now I had navigation! But (as I already knew) the navigation didn’t work properly.

    Fix Paged Content

    At the top of the front-page.php file is a call to add an action with all the metro_home_genesis_meta content. I wrapped that in a check to see if the page we’re on is a ‘paged’ page using is_paged(), which specifically checks if the query is for paged result and not for the first page.

    if ( !is_paged() ) {
    	add_action( 'genesis_meta', 'metro_home_genesis_meta' );
    }
    

    Again, really, that was it.

  • CMB2 And The Dropdown Years

    CMB2 And The Dropdown Years

    At WordCamp Montreal, I mentioned the database of dead lesbians that Tracy and I maintain. The camper looked at it and said “You know it would be awesome if you showed the shows airdates.”

    Good point! Except I just plain struggled with the concepts and how to do them in CMB2. I knew I could make multiple fields in one ‘metabox’ as I read up on the snippet for an address field, but try as I might, I couldn’t make it work.

    I tweeted my headache and ended up talking to Justin Sternberg who asked me if I could explain my use case better.

    I have 300+ posts, all of which have a start and end date. Some may have an end date of “current” however.

    Examples of valid data:

    • 1977-1979
    • 2016-current
    • 2000-2016

    I also need to sort by start and end year. So I can search for all posts with a start of 2014.

    I could have two year-sorts, easily, but that makes for a clunky interface as it would be separate fields. I know CMB2 can have a combined field (like addresses) but while I got it to save, it wouldn’t properly display on the edit page.

    This only needs to be editable on the WP admin edit post.

    That night, he replied and asked if this year-range field type would work.

    Mind? Blown. It works exactly how I need it to. I tweaked the code (and threw in a pull request) to set up a way to reverse the years (show newest first) which is more useful for my needs.

    Now? Editing 319 show entries.