Half-Elf on Tech

Thoughts From a Professional Lesbian

Category: How To

  • Updating Multiple Posts’ Meta

    Updating Multiple Posts’ Meta

    I had 328 posts that I needed to add a meta field value to. Thankfully they all had the same value at the moment, so what I really needed was to tell WP “For all posts in the custom post type of ‘show’ add the post_meta key of ‘tvtype’ with a value of ‘tvshow’.”

    That sounded simple. It wasn’t.

    Googling “Updating multiple posts” or “Bulk Update Multiple Posts” (with WordPress in there) was frustratingly vague and told me to do things like the bulk edit from the post lists page. Well. Sure. If I added my post meta to the bulk editor (which I do know how to do) and felt like updating them 20 shows at a time, I could do that. Heck, I could make my page list 50 and not 20, and do it in 5 ‘cycles.’

    But that wasn’t what I wanted to do. No, I wanted to figure out how to do it faster forever, so that if I had to update 32,800 posts, I could do it in the least CPU intensive way.

    PHP

    If I was to do this in PHP, it would look like this:

    $ids = array(
    	'post_type' 	=> 'post_type_shows',
    	'numberposts'	=> -1,
    	'post_status'	=> array('publish', 'pending', 'draft', 'future'),
    );
    
    
    foreach ($ids as $id){
        add_post_meta( $id, 'tvtype', 'tvshow' );
    }
    

    I picked add_post_meta instead of update_ because while the update will add the meta if it’s not found, I didn’t want to update any of the shows I’d manually fiddled with already. And to run this, I’d have to put it in an MU plugin and delete it when I was done.

    Which… Yes. That could work. I’d want to wrap it around a user capability check to make sure it didn’t run indefinitely, but it would work.

    WP-CLI

    Doesn’t a nice command line call sound better, though? Spoiler alert: It’s not.

    I knew I could get a list of the IDs with this:

    $ wp post list --post_type=post_type_shows --fields=ID --format=ids
    

    That gave me a space-separated list

    And I knew I could add the meta like this for each show:

    $ wp post meta add 123 tvtype tvshow
    

    But who wants to do that 328 times?

    The documentation for wp post meta update said “Update a meta field.” A. Singular. Now it was possible that this could be for multiple posts, since the information on wp post update said “Update one or more posts” and “one or more” means one or more. But the example only had this:

    $ wp post update 123 --post_name=something --post_status=draft
    

    Notice how there’s no mention of how one might handle multiple posts? In light of clear documentation, I checked what the code was doing. For the update function, I found this:

    	public function update( $args, $assoc_args ) {
    		foreach( $args as $key => $arg ) {
    			if ( is_numeric( $arg ) ) {
    				continue;
    			}
    

    The check for if ( is_numeric( $arg ) ) is the magic there. It says “If this is an ID, keep going.” And no spaces. So the answer to “How do I update multiple posts?” is this:

    $ wp post update 123 124 125 --post_name=something --post_status=draft
    

    Great! So can I do that with post meta? Would this work?

    $ wp post meta add 123 124 125 tvtype tvshow
    

    Answer: No.

    So I took that list, used search/replace to turn it into 328 separate commands, and pasted them in (in 50 line chunks) to my terminal to update everything.

    Yaaaay.

  • HTTPS and HSTS

    HTTPS and HSTS

    HTTP Strict Transport Security (HSTS) is a standard to protect visitors by ensuring that their browsers always connect to a website over HTTPS.

    Basically you have your server say “Everyone who accesses my server should use secure connections.” This matters because it prevents man-in-the-middle attacks that change HTTPS to HTTP and steal your credentials. Bad days. If you are using HTTPS/SSL on all your domains you should totally enable HSTS.

    Okay, great. How?

    Well if you have one domain, this is as easy as tossing this into your htaccess:

     <IfModule mod_headers.c>
        Header set Strict-Transport-Security max-age=16070400;
     </IfModule>
    

    But … I have 20+ domains on this server. That would suck to have to edit! In fact, this is really closely related to my issues combatting referrer spam server wide. This stuff isn’t always obvious. For cPanel, I just added that code to my pre_virtualhost_global.conf file, same as I did for a certain referrer spam company.

    If you’re using NGINX, you should read their blog post on the subject for full details but the basic code is this:

    server {
        listen 443 ssl;
    
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    
        # This 'location' block inherits the STS header
        location / {
            root /usr/share/nginx/html;
        }
    
        # Because this 'location' block contains another 'add_header' directive,
        # we must redeclare the STS header
        location /servlet {
            add_header X-Served-By "My Servlet Handler";
            add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
            proxy_pass http://localhost:8080;
        }
    }
    

    And if all else fails and you can’t set this on the server, you can always edit your .htaccess or nginx.conf file locally.

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

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