Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

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

  • CORS – Not Beer

    CORS – Not Beer

    I’ve been struggling with the idea of remote loading SVGs. There are a lot of problems with this, mostly due to the DOM structure of how your browser loads an SVG file.

    What’s an SVG and the DOM?

    Scalable Vector Graphics, or SVG, is a special XML file. It’s generally used like an image, like JPG or PNG, and contains vector graphics. This makes it smaller than PNGs and scalable without resolution loss. Pretty nifty for icons. In fact, better than my beloved Fonts.

    This is a part of the Document Object Model, or DOM. In the file, you tell it how you want the content to render. It’s why an SVG can be so powerful, having all the code you need inside a small file. It’s also why it’s so dangerous. Specifically if can contain Javascript code, which can be embedded and hidden in an image. And that can do a lot of damage. If you’re not paying attention, you can upload an SVG with javascript that runs code on the user’s browser. Bad times all around.

    Making this worse, if you use <img> tags to embed the SVG, many browsers will execute the code. Even today. Yeah… Not really a good thing.

    Embedding SVGs externally has a cost

    If you want to include an SVG file in your web page, you can use that dangerous <img> tag, but you can also use <svg> code right in the document. The problem there is that requires the SVG to essentially be pasted into the code. For PHP, you can use code like file_get_contents() (which doesn’t always work remotely) or if you’re WordPressy, wp_remote_get()

    The catch there? They’re slow if you list, oh, 69 images on a page. You can, of course, use <img> tags and that loads the SVG remotely, perfectly happily. If you’re sure about your SVGs, this is okay. You’re just going to lose one big, super big feature about SVGs.

    You can’t use CSS to edit them (color, resizing, etc) anymore.

    And that, my friends, is a big problem.

    Javascript End Run

    I wasn’t willing to compromise speed or flexibility. I’m particular like that. So I looked up how you can use javascript to hack the DOM. I quickly found SVGInjector, a fast, caching, dynamic inline SVG DOM injection library. Installing and configuring was easy buuuuut…

    You saw that but coming, right?

    Right.

    It didn’t work. Because of CORS.

    What the heck is CORS?

    Cross Origin Resource Sharing, CORS, is that magical thing that says “Hey you can’t run this resource remotely, yo!” Generally it’s related to Fonts (if you’ve ever tried to embed your own fonts from your own servers, you’d have run into this). I happened to hit it on SVGs, and basically the server where I host the icons wasn’t allowing my servers (local or otherwise) to load the icons.

    Thankfully! I use DreamObjects, and you can totally set up CORS on DreamObjects. You can do it on Amazon S3, and I think all the various CEPH-esque services let you.

    The catch here (hah you saw that coming, right?) is that not all clients let you do it. Lucky for me, I have s3cmd on my laptop (you should too if you’re going to work with S3 type services – it’ll make your life easier). Installing is easy, and you just have to make a .s3cfg file (DreamHost has some instructions).

    Once that’s set up, you can make your rules file like this:

    <CORSConfiguration>
    <CORSRule>
        <ID>Allow My Icons</ID>
        <AllowedOrigin>https://www.example.com</AllowedOrigin>
        <AllowedOrigin>http://www.example.com</AllowedOrigin>
        <AllowedOrigin>https://example.com</AllowedOrigin>
        <AllowedOrigin>http://example.com</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>HEAD</AllowedMethod>
        <AllowedHeader>Content-*</AllowedHeader>
        <AllowedHeader>Host</AllowedHeader>
        <ExposeHeader>ETag</ExposeHeader>
        <MaxAgeSeconds>86400</MaxAgeSeconds>
    </CORSRule>
    </CORSConfiguration>
    

    and then you can set (and check) your CORS:

    ipstenu@Uhura:~$ s3cmd setcors Development/example.com/cors-rules.xml s3://my-icons
    ipstenu@Uhura:~$ s3cmd info s3://my-icons
    s3://my-icons/ (bucket):
       Location:  us-west-1
       Payer:     BucketOwner
       Expiration Rule: none
       Policy:    none
       CORS:      <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><CORSRule><ID>Allow My Icons</ID><AllowedMethod>GET</AllowedMethod><AllowedMethod>HEAD</AllowedMethod><AllowedOrigin>http://example.com</AllowedOrigin><AllowedOrigin>http://example.com </AllowedOrigin><AllowedOrigin>https://www.example.com </AllowedOrigin><AllowedOrigin>https://www.example.com </AllowedOrigin><AllowedHeader>Content-*</AllowedHeader><AllowedHeader>Host</AllowedHeader><MaxAgeSeconds>86400</MaxAgeSeconds><ExposeHeader>ETag</ExposeHeader></CORSRule></CORSConfiguration>
       ACL:       lezpress: FULL_CONTROL
    

    And now you can embed your SVGs remotely.

  • Reordering Facet Displays … For Death

    Reordering Facet Displays … For Death

    Isn’t that a catchy title?

    I’m using FacetWP to help me order and sort archives in ways that are reflective of the content. One of the things I sort are characters (yeah yeah yeah) and some of those characters are dead. It occurred to me that wouldn’t it be nifty if I could sort the characters by when they died?

    There was just one problem. Actually there were two. One was Sara Lance, and she’s my personal demon. The other was my own stupidity and lack of foresight. Neither were insurmountable.

    How FacetWP Changes Sort Order

    Before I get into the weeds, let’s have a moment to talk about sort order. FacetWP has a way to filter the sort orders.

    So for an example, I have post meta value for the number of characters saved as lezshows_char_count for all shows. If I wanted to sort shows by the most characters to least, I can add this in:

    $options['most_queers'] = array(
    	'label' => 'Number of Characters (Descending)',
    	'query_args' => array(
    		'orderby'  => 'meta_value_num', // sort by numerical custom field
    		'meta_key' => 'lezshows_char_count', // required when sorting by custom fields
    		'order'    => 'DESC', // descending order
    	)
    );
    

    It looks very similar to WP_Query and that’s what makes it easy. Except for my two problems…

    Problem One: Formats

    The first problem was not the Sara Lance problem. It was the ‘Mika didn’t think about things 4 years ago’ problem. I was saving the dates of death in the format of MM/DD/YYYY

    If you’re an American, you’re wondering “So what?” and if you’re anyone else, you’re giving me a death glare because “08/05/2010” could be August 05 OR May 08, and damn it, I knew better. For what it’s worth, the output on the front end is always “05 August 2010” but that’s not here nor there.

    You see, the issue isn’t that I was using stupid date/time formats, the issue is sorting.

    In the previous example, I have an order of meta_value_num which is literally a number. What’s the one for dates? You get meta_value_date or meta_value_datetime and neither of them work with the date format I’d chosen.

    So for this to work, I had to go and change everything from MM/DD/YYYY to YYYY/MM/DD – Not fun, but doable. And it led me to my Sara Lance Drama…

    Problem Two: Arrays

    How many times has Sara Lance died? Right now, three.

    When I decided to sort by the date of death, which one did I pick? Long pause.

    I decided to pick the last one. That is the most recent death. If someone’s actually still dead, the most recent death is the one that stuck. If they’re not, then death was pretty arbitrary to begin with and there ya go.

    The question became how and where did I save the death? I went with a post meta of lezchars_last_death and had it auto update on post save, like this:

    add_action( 'save_post_post_type_characters', 'characters_update_meta', 10, 3 );
    function characters_update_meta( $post_id ) {
    
    	// unhook this function so it doesn't loop infinitely
    	remove_action( 'save_post_post_type_characters', 'characters_update_meta' );
    		
    	// get the most recent death and save it as a new meta
    	$character_death = get_post_meta( $post_id, 'lezchars_death_year', true );
    	$newest_death    = 0000-00-00;
    	foreach ( $character_death as $death ) {
    		if ( $death > $newest_death ) $newest_death = $death;
    	}
    
    	if ( $newest_death !== 0000-00-00 ) {
    		update_post_meta( $post_id, 'lezchars_last_death', $newest_death );
    	}
    
    	// re-hook this function
    	add_action( 'save_post_post_type_characters', 'characters_update_meta' );
    }
    

    If there is a latest death, we get to set it as the YYYY-MM-DD value and off we go. But…

    Problem Three: Orderby Hellscape

    Surprise! I ran into a third problem! Remember how I was using the orderby of meta_value_num in my example? And I mentioned that I wanted to use meta_value_date or meta_value_datetime to sort by date?

    Yeah no.

    If I’d converted the date into unix time, sure. But I was reusing this logic in a couple places, and I didn’t want to re-save everything like that. I also use a query to grab all deaths in a year, and basically I do need to keep it with the format I have. That just messed up my sort until I found the magic of orderby' => 'meta_value',

    End Result?

    It works. It’s got yet another post meta, which I’m not super happy about, but sometimes that’s really just the simplest way to solve a problem. The data is always updating itself, and it’s relatively easy for me to tweak it. Also now I can do a lot more searches in different ways.

    Since I don’t have to worry about database size at the moment, nor speed, since I’ve designed it well, this works for me.

  • The Invisible Facets

    The Invisible Facets

    I’ve been using FacetWP for a year or so and it’s, hands down, the smartest WordPress plugin I’ve ever bought. Certainly I could have coded it, but not having to and being able to extend it to do what I need has saved me months of work and support.

    This is not to say it’s perfect. I’ve run into multiple quirks and headaches that resulted in me writing weird code to solve. And the solution to the null entry was no different. But it was, at the end, solvable.

    A Null Entry

    The majority of my data is saved in custom taxonomies. This makes it easy for me to grab and process. It’s also easy to list, because I can point people at /taxonomy/term and FacetWP magically populates properly in the sidebar.

    However. In one case, I have a checkbox. This is a simple post-meta to say if we love a show or not, and that check box, if it exists, is detected by FacetWP and I can easily get a list of all loved shows. The reverse is, sadly, not easy.

    That’s because if the checkbox is empty, there is nothing saved. No post meta. And if there’s no data, then when FacetWP builds out it’s list, there’s nothing saved for the non-existent data, and therefore no way to list nothing.

    An Imaginary Entry

    The other problem, related to this, is that I use Taxonomies in a different way. That is, while I use them like everyone else does with tags and categories, I also use them to track ‘stars’ – gold or silver etc. Obviously that makes it easy to track with /stars/gold/ buuuuuuut what if I wanted to list all the shows without any stars?

    How do I tell FacetWP ‘if there’s no taxonomy data saved for this, use a default?’

    A Fake Facet

    The answer lies within making a fake, that is unused, facet.

    The tl;dr to how FacetWP works is that it generates it’s own table with the data it collects from it’s facets. In general, there’s a 1-to-1 relationship with the facet and how it outputs. If you’re saving the terms of a taxonomy (like star colors), then there’s an entry in the database for the show and it’s star. If something has multiple values (like tags) then it has multiple entries.

    You can then alter the

    The Facet

    In order to make entries for my null or imaginary values, I made a facet that I didn’t use that I called all_the_missing and I gave it the data source of “Post Types”:

    An example of the facet

    The rest doesn’t matter. I’m not planing to display this, and I picked post types because it’s a quick bit to add the database without making it too heavy or complicated. Also I know it’ll exist for all my data.

    The Filter

    The magic to all this is my filter for facetwp_index_row:

    if ( 'all_the_missing' == $params['facet_name'] ) {
    	// If we do not love the show...
    	$loved = get_post_meta( $params['post_id'], 'shows_worthit_show_we_love', true);
    	if ( empty( $loved ) ) {
    		$params_loved = $params;
    		$params_loved['facet_name']           = 'show_loved';
    		$params_loved['facet_source']         = 'cf/shows_worthit_show_we_love';
    		$params_loved['facet_value']          = 'no';
    		$params_loved['facet_display_value']  = 'No';
    		$class->insert( $params_loved );
    	}
    	// If there are no stars
    	$stars = get_the_terms( $params['post_id'], 'the_stars' );
    	if ( empty( $stars ) ) {
    		$params_stars = $params;
    		$params_stars['facet_name']           = 'show_stars';
    		$params_stars['facet_source']         = 'tax/the_stars';
    		$params_stars['facet_value']          = 'none';
    		$params_stars['facet_display_value']  = 'None';
    		$class->insert( $params_stars );
    	}
    	return false; // skip default indexing
    }
    

    Now this is also wrapped in an if ( get_post_type( $params['post_id'] ) == 'post_type_shows' ) {...} check so this particular one only runs when shows are saved. But you can see what I do is when I run that specific facet, I check if the other data is available and if so, save it.

    Improvements

    I’d like to actually not have to save data when I don’t need it, but the need is enough that having this work was paramount. I can now sort when there’s no data, and that was what I needed.

  • Hey, Twitter, Why Do You Hate Us?

    Hey, Twitter, Why Do You Hate Us?

    Hi, Twitter.

    I know we fight a lot. You know I report a lot of abuse and harassment, and you do nothing about the Nazis, and we have our differences. But this isn’t about that. I mean, yeah, I’m salty about the Russian thing, but we need to talk about something else.

    We need to talk about using Twitter on a desktop when you have multiple accounts.

    Multiple Twitter Accounts Happen

    I have a legit reason to have multiple accounts. A good one, in fact. I have my personal account, but I have two others for brands I manage. And that means I kind of need to be able to log in to all three at once and wrangle things.

    If you use Twitter on the web, your choices are regular Twitter or Tweetdeck. The latter makes you sign up via a very convoluted process in order to grant access to accounts. Basically, you have to give your ‘main’ account access to the ones you want to manage. It’s not very obvious.

    And there are weird things missing from Tweetdeck. Like … no decent notifications. You can’t tell what you’ve read or when people @ you or anything like that. Not easily. Oh, and there’s no GIF button.

    Finally … with three accounts I get to have NINE columns. Three each for ‘home,’ ‘mentions,’ and ‘messages.’ Thanks. A lot.

    No Great Desktop App

    Here’s my problem. There’s no good Twitter desktop app. Your own app went unloved until you pulled the plug. In a tweet. Nice. Really nice. That leaves me with a few choices.

    TweetBot: I like Tweetbot, except that I can’t see polls in it, and I can’t navigate to embed Gifs. But it has a pretty decent interface. The biggest issue is that you can’t see group DMs. Sometimes keep on top breaks. Sometimes not.

    Twitterific: This is a wonderful app except that scrolling sucks. If you switch to a different account, keep on top stops working, and ⌘↑ (which should take you to the top of whatever you’re on) doesn’t scroll right. Oh and no embedding Gifs. And again, no group DMs and no polls.

    What about TweetDeck’s desktop app? It hasn’t been updated since 2015. The best version I’ve seen is Tweeten but again, I’m back to 3 columns per account.

    What I Want Is Simple

    I want the iOS app, but for the desktop. I want to have the following features:

    1. Multiple Account Support
    2. One visual ‘column’ per account (it can have sub tabs, whatever)
    3. The ability to insert and read polls
    4. Support for multi-person DMs
    5. Notifications
    6. A damn GIF button

    Instead, I get to use Tweetdeck in my browser. At least, until Twitter dumps that too.