Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

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

  • The Time and The Place

    The Time and The Place

    It was the day of a big release. A major release. A release that had been announced weeks, if not months, in advance. Everyone who was anyone knew that today was the day. So why not publicly drop the news of a major issue with the project in the middle of that release?

    It was the middle of the development meeting. Everyone was talking about issues with a part of the project. They were deep into the hell that is debugging and backtracking and arguing if things should be fixed or simply noted. So why not ask for help about an issue one user was having?

    This is not about WordPress. Well. It is and it isn’t. It’s about understanding who you are, where you are, and what’s going on around you. It’s about awareness and acceptance. It’s about being a part of something greater than yourself.

    This is about common sense.

    In Festivus, there’s a time and a place for the airing of grievances. In Judaism, we have a time and place for atoning for sins and forgiving others. While you certainly can do these things at any point in time, the purpose of having set and established periods for them is to prevent people from being derailed, to stop breaking the flow.

    The time to report a security issue (which should never be ‘in public first,’ IMO) is not the middle of the release meeting. The time to report petty theft is not while your Manager is giving an announcement. The time to tell everyone that Beyoncé’s video was better is not while Taylor Swift is on stage giving her acceptance speech.

    Those moments are rude, inconsiderate, and disrespectful.

    It doesn’t matter if you’re right or not because yes, Single Ladies was a magnificent video and Beyoncé was robbed, it matters if the right people will be able to address the issue without causing harm to everyone else.

    No one is more or less important than anyone else. Saying ‘everyone’s special’ is just another way of saying no one is. As painful as that can be to hear, it’s true. Instead of arguing that ‘us’ are more special than ‘them,’ which is purely subjective anyway, we should look at the magnitude of the work we do. Who will be harmed by the choice to publicly state something now?

    The good of the many often is more important than the good of the few, or the one. That doesn’t mean you should not confront people in public. It means you should not do so recklessly. It means that you should not speak up without consideration of who you are, where you are, and when you are. It means you must be prepared to accept the consequences of your actions.

    If you decide the best place to speak up against a politician is at his rally, you must accept that you may be throw out. You must accept that protests may end with your arrest. You must accept that being vocally against a decision or an action may result in you being publicly talked back to and possibly shunned.

    At the same time, you cannot be afraid to do these things. You should speak up against wrongs. You should speak up against bad decisions. You should tell your manager that they’re making a bad choice. But you cannot do those things blindly or ignorantly.

    It’s human nature to want to be a part of a group. We’re herd animals. We like the safety it affords us. We like the security. We crave it. So when we achieve acceptance into an ‘inner circle’ we want to protect our standing and not be cast out, and that can cause a bit of a Status Quo mentality.

    Some members of the group will always be the ones to shake things up. They will be the ones to speak against the majority, to stand up and say “This is wrong and here is why.” They’re the ones who are brave enough and strong enough to accept the consequences of their actions. They don’t walk into a room, interrupting everything and everyone, to announce something. 

    They don’t get a free pass, however. They accept the consequences. And the effective ones make sure that when they choose to speak up, they do it in the right place, at the right time, with the full respect given to their group and community. And if they don’t, well again, they know what they’re getting into.

    I can’t tell you to speak unafraid. That would be incredibly unrealistic. But I can say to speak boldly and to think about the consequences of your actions. And I can tell you to ask. “Will there be a post-mortem of this deployment where we can talk about improvements to the process?” Ask. “I know this is a meeting, and I apologize for interrupting, but I have a security issue. Where would be the right place for this?” Ask.

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