Half-Elf on Tech

Thoughts From a Professional Lesbian

Category: How To

  • Shortcode Example: Reviews

    Shortcode Example: Reviews

    Sometimes people want to have reviews mean ‘people leave reviews on my work.’ But the other kind of reviews are the ones where I review other peoples’ works. And for that, I found it helps to have some kind of standard.

    Let’s say I’m reviewing a TV show for overall quality but also overall gayness. That is, I want to be able to write up a post and then, at the bottom, put up a shortcode to say “This show is really good but has no gay characters.” To do that, I made a list of the important factors to distill:

    • Name: The name of the TV show
    • Summary: A short, 140 character summary of the overall show.
    • Queer: A 1-5 rating of how queer the show is
    • Rating: A 1-3 scale (yes, meh, no) for how good the show is overall
    • Warning: Is there a trigger warning (or CW) to be aware of

    The intent is to make it easy for someone to scroll down and find what they want to watch. Right?

    The Code

    A bit of warning. There are two specific to my site bits of design in here. First are the icons. While I’ve generalized them for you as Emoji, keep in mind you probably want to have your own style here. Second, I’m using Bootstrap, so I’ve leveraged some of their default code. You’ll want to tweak the CSS.

    class TV_Shortcodes {
    
    	/**
    	 * Constructor
    	 */
    	public function __construct() {
    		add_action( 'init', array( $this, 'init' ) );
    		add_filter( 'widget_text', 'do_shortcode' );
    	}
    
    	/**
    	 * Init
    	 */
    	public function init() {
    		add_shortcode( 'review', array( $this, 'review' ) );
    	}
    
    	/**
    	 * Reviews.
    	 */
    	public function review( $atts ) {
    
    		$attributes = shortcode_atts( array(
    			'title'   => 'Coming Soon',
    			'summary' => 'Coming soon ...',
    			'queer'   => '3',
    			'rating'  => 'meh',
    			'warning' => 'none',
    		), $atts );
    
    		$queer = (float) $attributes['queer'];
    		$queer = ( $queer < 0 )? 0 : $queer;
    		$queer = ( $queer > 5 )? 5 : $queer;
    
    		$worth = ( in_array( $attributes['worth'], array( 'yes', 'no', 'meh' ) ) )? $attributes['worth'] : 'meh';
    		switch ( $worth ) {
    			case 'yes':
    				$worth_icon = '??';
    				$worth_color = 'success';
    				break;
    			case 'no':
    				$worth_icon  = '??';
    				$worth_color = 'danger';
    				break;
    			case 'meh':
    				$worth_icon  = '?';
    				$worth_color = 'warning';
    				break;
    		}
    
    		// Get proper triger warning data
    		$warning = '';
    		$trigger = ( in_array( $attributes['trigger'], array( 'high', 'medium', 'low' ) ) )? $attributes['trigger'] : 'none';
    
    		if ( $trigger != 'none' ) {
    			$warn_image    = '⚠️';
    			switch ( $trigger ) {
    				case 'high':
    					$warn_color = 'danger';
    					break;
    				case 'medium':
    					$warn_color = 'warning';
    					break;
    				case 'low':
    					$warn_color = 'info';
    					break;
    			}
    
    			$warning = '<span data-toggle="tooltip" aria-label="Warning - This show contains triggers" title="Warning - This show contains triggers"><button type="button" class="btn btn-' . $warn_color . '"><span class="screener screener-warn ' . $warn_color . '" role="img">' . $warn_image . '</span></button></span>';
    		}
    
    		$output = '<div class="bd-callout"><h5 id="' . esc_attr( $attributes['title'] ) . '">Screener Review on <em>' . esc_html( $attributes['title'] ) . '</em></h5>
    		<p>' . esc_html( $attributes['summary'] ) . '</p>
    		<p><span data-toggle="tooltip" aria-label="How good is this show for queers?" title="How good is this show for queers?"><button type="button" class="btn btn-dark">Queer Score: ' . $queer . '</button></span> <span data-toggle="tooltip" aria-label="Is this show worth watching? ' . ucfirst( $worth ) . '" title="Is this show worth watching? ' . ucfirst( $worth ) . '"><button type="button" class="btn btn-' . $worth_color . '">Worth It? <span role="img" class="screener screener-worthit ' . lcfirst( $worth ) . '">' . $worth_icon . '</span></button></span> ' . $warning . '</p>
    		</div>';
    
    		return $output;
    
    	}
    }
    new TV_Shortcodes();
    

    The Future

    I’m thinking about changing the scores from numbers to stars, and adding in a link if the show has been added to the site. But it being a shortcode, it’s reasonably extensible.

    Enjoy, and export to your own review sites.

  • Genesis Theme: Anonymize Posts

    Genesis Theme: Anonymize Posts

    Actually, I anonymize my pages.

    See, I have multiple authors on a site, and one of the things I like is to celebrate them when they post. Awesome. But I don’t want to highlight who wrote the pages on the site. Or who posted the videos. Those are informational and don’t need any ego attached.

    When using StudioPress’ Genesis theme, you can edit the post authors by filtering genesis_post_info

    The Code

    add_filter( 'genesis_post_info', 'EXAMPLE_genesis_post_info' );
    function EXAMPLE_genesis_post_info( $post_info = '' ) {
    	if ( is_singular( array ( 'videos', 'page' ) ) )
    		$post_info = 'By the Barbarians [post_edit]';
    	return $post_info;
    }
    

    Now all my posts are by Barbarians!

  • Light Fingered Fish

    Light Fingered Fish

    To explain the joke before we get too far, Jetpack’s contact form was originally called Grunion. The book “Memory” by Lois McMaster Bujold uses the phrase “light fingered fish” to talk about fish who elude hooks.

    I was building a site for my father and he wanted the contact form to redirect to another page. Thankfully you can do this with a filter on grunion_contact_form_redirect_url (see? Grunion? Fish?)

    The Code

    If you use the official code, then you’re going to need to know two things:

    1) What is the page ID you’re redirecting from
    2) What is the page slug you’re redirecting to

    Yes, it’s weird that you have to know those, but … well. That’s what we’ve got. I tried to come up with a reason why, and I think it’s just that searching for posts by slug is hard.

    function EXAMPLE_grunion_custom_form_redirect_url( $redirect, $id, $post_id ){
    
        $redirected_urls = array(
            '123' => home_url( 'contact' ),
            '456' => home_url( 'about' ),
            '789' => 'https://wordpress.org/surprise/',
        );
     
        foreach ( $redirected_urls as $source => $destination ) {
            if ( $id == $source ) {
                return $destination;
            }
        }
     
        // If there's no custom redirect, return the default
        return $redirect;
    }
    add_filter( 'grunion_contact_form_redirect_url', 'EXAMPLE_grunion_custom_form_redirect_url', 10, 3 );
    

    Buuuuut what if you wanted to do it by slug?

        $redirected_urls = array(
            'contact'  => home_url( 'contact-success' ),
            'about'    => home_url( 'about-success' ),
            'surprise' => 'https://wordpress.org/surprise/',
        );
     
        $slug = get_post_field( 'post_name', $id );
    
        foreach ( $redirected_urls as $source => $destination ) {
            if ( $slug == $source ) {
                return $destination;
            }
        }
    

    The benefit to this is you can change the post ID and, as long as it has the same slug, you’re good to go. Also let’s say you have a bunch of separate contact pages (contact-me, contact-mom and so on). You could use the logic to redirect all pages that have the word ‘contact’ or ‘about’ or ‘surprise’ …

        foreach ( $redirected_urls as $source => $destination ) {
            if ( strpos( $source, $slug ) !== false
                return $destination;
            }
        }
    
  • Google Auto Ads

    Google Auto Ads

    After upgrading my theme, I saw a note that I could add my Google Adsense ID, but “Auto Ads must be enabled in your AdSense account for this feature to work properly.”

    Auto Ads?

    Auto Ads

    A month ago Google introduced a new way to handle ads, and simply that is you don’t have to mess around with placing adds. You put your code in and then you tell Google “Gimme them auto ads!” and they add in … well … ads.

    Depending on your options, you can show in-article ads or just section ones, and it comes out looking a bit like this:

    An example of in-article ads

    Not Perfect

    There are some issues with this.

    There are obvious pros to this, and mostly it’s that I don’t have to think about where an ad is going to go. I can tell Google “Show a medium amount of ads where you think is best” and walk away. I don’t have to worry about which ads to use. Also they use Google’s ‘what fits with the content’ magic algorithm.

    But.

    I can’t exclude certain areas.

    Which means on one of my sites has an extra hunk of ads in the headers, as Google inserted an ad in each section. And I can’t tell it not to put adds in specific sections. I can tell it not to put ads on specific pages, and with Genesis I can do that from within WordPress, but the options just aren’t quite where I want. Yet.

    Setting It Up

    Like all

    1. In the left navigation panel, visit My ads and select Get Started.
    2. On the “Choose your global settings” page, select the ad formats that you’d like to show and click Save.
    3. On the next page, click Copy code.
    4. Paste the ad code between the < head > and </ head > tags of each page where you want to show Auto ads.

    It takes about 20 minutes for ads to show up

    If you’re using Genesis themes, upgrade to 2.6 and paste your Publisher ID in the new setting field for Auto Ads, in either the Theme Settings, or the new Customizer panel.

    If you don’t want that at all, Gary Jones made a plugin to remove it entirely from Genesis Themes. Though I’d point out you don’t have to use it if you don’t want.

  • Safari and SameOrigin

    Safari and SameOrigin

    I was updating a site I’ve been neglecting. Due to reasons, it’s been about four months since I’ve looked at it, let alone done any work. But as I was cleaning up the data, moving ads around, and messing with widgets, I found myself stumped.

    Customizer was a blank screen.

    Debugging Javascript

    Now I happen to know that the WordPress customizer makes heavy use of javascript, so I popped open the console and found this error:

    [Error] Multiple 'X-Frame-Options' headers with conflicting values ('ALLOW-FROM https://example.com/wordpress/wp-admin/customize.php, SAMEORIGIN') encountered when loading 'https://example.com/2017/post-name/?customize_changeset_uuid=0774a706-2a5b-4700-a8fb-2c294708687c&customize_theme=utility-pro&customize_messenger_channel=preview-0'. Falling back to 'DENY'.
    
    [Error] Refused to display 'https://example.com/2017/post-name/?customize_changeset_uuid=0774a706-2a5b-4700-a8fb-2c294708687c&customize_theme=utility-pro&customize_messenger_channel=preview-0' in a frame because it set 'X-Frame-Options' to 'ALLOW-FROM https://example.com/wordpress/wp-admin/customize.php, SAMEORIGIN'.
    

    Right away, I knew that the problem wasn’t Javascript.

    Safari is Special

    A quick search netted me two possible tickets. First, there’s the problem with customizer failing to load if there was a home/siteurl domain mismatch and second, there’s the issue that customizer fails to load in Safari due to X-Origin Header mismatch.

    In reading those tickets, I determined that it was possible that since this site has WordPress in a folder (yes, named wordpress) but runs from the main domain, that could break it. There was also a possibility that NGINX or Apache were set to restrict SAMEORIGIN. Finally, there was the absolutely daft problem that Safari was special and didn’t like extra rules.

    Now, since this server runs multiple sites, and only this one was having any problems, I threw out all possible server related causes. I also discarded any Multisite related causes, as the site wasn’t the network. Next I determined it absolutely was Safari, by testing on Firefox and Chrome. That left me with the following probable causes:

    1. Some code on my site was breaking customizer

    No, really. That was it.

    The Solution

    Two choices here. Figure out what was broken or stop using Safari.

    I have reasons for using Safari, so I knuckled down. I searched all lines of my code for anything related to X-Frame-Options and came up empty. I tested by commenting out the relevant lines in WordPress core, which worked, but wasn’t tenable.

    Then I changed my search and looked for X-Frame-Options in all .htaccess files, and found this:

    <IfModule mod_headers.c>
         Header set X-Frame-Options "SAMEORIGIN"
    </IfModule>
    

    Removed that, and done.

    Why Was It There?

    I actually put that code in to prevent clickjacking. Clickjacking is what’s called a “UI redress attack.” It happens when a malicious attacker uses transparent or opaque laters to trick someone into clicking a button or a link on page, when they were intending to click another. Usually this is to steal private information.

    WordPress already does this for the login page, and for this site, that’s actually the only time ‘private’ data is sent on this site. Which means it’s mostly safe enough to leave be. There are ways I could do this in PHP code, but since WordPress isn’t the only tool on the site, it’s harder to maintain.

    The better fix would be to, with Apache or NGINX, check the domain and URL, and only apply clickjacking when I’m not in Customizer.

    Of course I have no idea how to do that. Yet.

  • Remoteless SVG

    Remoteless SVG

    I spent about half a year working on being able to remote load my SVG, and I wanted to for a few reasons:

    1. Having only one place to update my icons is more efficient
    2. They don’t need to be in code versioning control (like GitHub)
    3. Licensing (not all are free)

    I solved those problems when I finally figured out SVG Injection (via javascript) and CORS to allow the scripts from my objects.

    It’s not all lollipops and sunshine

    However. There was, and is, one really big issue. The icons being remote mean if the remote server isn’t accessible but my site is, I got no icons. Now, the smart dev in me knows that I should have a check. If the server is up, use the icons remotely. Otherwise, use a fallback.

    There was one big, and in the end insurmountable, problem with that. There was no way to do that without slowing my site down, which was the headache in the first place. If I check the uptime for every icon on a page, the site went from a 1 second load to up to 20 seconds. In the best solution, I added 2-5 second load to every page checking if the server was up on every page load. Which is kind of the problem with wp_remote_get() in the first place.

    Make ’em local again

    This meant the ‘best’ solution was to make them local. Again. Yaaay.

    But I really didn’t want to have them stashed in GitHub. I wanted my ‘base’ to be the cloud storage, and then that needed to sync. Normally I would use rsync for that but you can’t rsync from CEPH storage.

    Don’t panic. There are answers.

    Boto-Rsync

    If you’re on DreamPress or any DreamHost server, they’ve forked boto-rsync and (if you set up a .boto file) you can do this:

    boto-rsync --endpoint objects-us-west-1.dream.io s3://my-cool-icons/ /home/my_user/mydomain.com/wp-content/uploads/my-cool-icons/
    

    Using cron or some other scheduling tool, setting up a server to run that at regular intervals isn’t too terrible.

    AWS CLI

    There’s also the option of AWS CLI, which is very similar, only it’s actually maintained. The advantage with boto-rsync is that it’s installed on the servers at DreamHost. If you have AWS CLI on your server, you can do this:

    aws s3 --endpoint-url https://objects-us-west-1.dream.io sync s3://my-cool-icons/ /home/my_user/mydomain.com/wp-content/uploads/my-cool-icons/
    

    Keep in mind, you need to configure this either with aws configure or an ~/.aws/credentials files.

    Pushing from the outside inside

    You may have noticed that, unlike rsync, I can’t tell it to push to another server. That is, I can say “sync files from server A to server B” with rsync and I can’t with either boto-rsync or the AWS CLI.

    And no, you just can’t.

    Unless you use rclone. Which is a little messy but totally do-able.

    There’s one other option though… I’ve been using Codeship to push code. That is, every time my GitHub repos update, it triggers a Codeship deploy. And in that deploy I usually just say “Rsync a git clone, k’thnx’bai.”

    Now I tell it this too:

    # Download from S3
    pip install awscli
    aws s3 --endpoint-url https://objects-us-west-1.dream.io sync s3://my-cool-icons/ ~/my-cool-icons/
    # Copy up to Server
    rsync -avz -e "ssh" ~/lezpress-icons/ USER@DOMAIN:/home/USER/DOMAIN/wp-content/uploads/my-cool-icons/ --delete
    

    I will note there’s one extra trick. You have to add Environment Variables for AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY on Codeship.

    This works for me since there are few times I push an icon without also making some code changes to the server code anyway.

    Hopefully if gives you some directions.