Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

  • Whose Fault Is The Hack?

    Whose Fault Is The Hack?

    Your site was hacked.

    Welcome to the worst day of your webmastering career. This is worse than the time you accidentally rebooted the server in the middle of processing the largest orders ever. This is worse than the time you cowboy coded something stupid at 1am on your phone from the bar. This is worse than the time you typed rm -rf ./* in the wrong window. This is worse than the time your credit card expired and you put off fixing it in the billing system until your site was down.

    Why? It’s worse because you have no idea what the hell just happened, why, or who to blame.

    Let me tell you who to blame.

    Your Web Software

    Oh yes. They are to blame. They left you vulnerable. They made it all sound so super easy and simple that you installed a site and walked away, assuming all was well. They didn’t push those security updates like they promised and they left you ready to be hacked!

    Except… Did you update everything promptly? Did you use the code only in the ways it was intended? Did you add on extensions that weren’t vetted by security experts. Did you limit the administrative access to your site to only people who knew how to do things and what was, and was not, safe?

    Your Web Host

    Can’t forget these idiots, right? They’re supposed to be locking your server down for your own protection and making sure no one can see anything. They take care of everything, like server updates and network upgrades and those zero-day SSL alerts. Clearly they dropped the ball.

    Except … Did the OS they’re using actually push the update needed for security? Did they not and now your host had to decide if they fork and support more or they wait and only support the legit things? One is getting you fixed faster, but it’s also making it harder to make sure all security patches are applied. Oh and hey, there are 40 other people on your slice of the network, and one of them has a down-time requirement. And did you remember to only use the secure access to your server? Did you maybe, the one time, turn on FTP (even though they told you not to) and use a clear-texted password?

    Yours

    Hey you. If you can’t tell… The answer is it’s everyone’s fault.

    Of course everyone can do better to make the world more secure, but we have to accept the fact that it’s not ever any one person’s fault. Very few bits of code are written by one person and never looked at. Very few situations are clearcut. We forget to lock the door, we leave a window cracked, we assume and don’t check.

    But at the end of the day, the fault for our hacks lies on the person who cares the most when the hacks happen. If your website is your life, if it’s the way you make business and survive, you cannot just take it all on a hope and prayer that you got it right. And if the effort of upkeep and maintenance is too much, you’ll have to compensate with paying for experts who do that.

    The fault is ours.

  • Multisite Emails and Redirects

    Multisite Emails and Redirects

    I wrote a plugin that allows people to Join A Multisite on a Per-Site Basis.

    There are some things it doesn’t do that I have no intention of adding into the plugin, but people often ask me how to do them. Personally, I think people should always know they’re on a network, and hiding this will only lead to complaints later one, but my way is not the only way.

    That said. I am aware of things people try to do that my plugin won’t. All of these snippets should go in a file in mu-plugins. I’d name it multisite-registration.php personally. That way I know what it is right away.

    Emails By Site

    When you register for a network site, you always get emailed from the network. This means even if I go to halfelf.org to reset my password, the email always comes from ipstenu.org. To change the password reset emails to be from the one where you’ve actually pressed the reset link is pretty easy:

    add_filter( 'retrieve_password_message', function ($message, $key) {
      	return str_replace(get_site_url(1), get_site_url(), $message);
    }, 10, 2);
    
    add_filter( 'retrieve_password_title', function($title) {
    	return "[" . wp_specialchars_decode(get_option('blogname'), ENT_QUOTES) . "] Password Reset";
    });
    

    But when we talk about the activation it’s a little messier. If you’re using my plugin, you can have users sign up on a specific site. You probably want to have new user activations come from that site and if you do, you need to do this:

    add_filter( 'wpmu_signup_blog_notification_subject', function($subject) {
    	return "[" . wp_specialchars_decode(get_option('blogname'), ENT_QUOTES) . "] Activate Your Account";
    });
    
    add_filter( 'wpmu_signup_blog_notification_subject', function($subject) {
    	return "[" . wp_specialchars_decode(get_option('blogname'), ENT_QUOTES) . "] Activate Your Account";
    });
    

    That subject can, obviously, be changed.

    Redirect Lost Password Pages

    So you may have noticed that the lost password page on a network always points to the network and never the site you’re on.

    Screenshot of my login screen showing halfelf in the URL bar, but the reset link points to ipstenu.org

    That can actually be fixed by doing this:

    add_filter( 'lostpassword_url', function ($url, $redirect) {	
    	
    	$args = array( 'action' => 'lostpassword' );
    	
    	if ( !empty($redirect) )
    		$args['redirect_to'] = $redirect;
    	return add_query_arg( $args, site_url('wp-login.php') );
    }, 10, 2);
    
    add_filter( 'network_site_url', function($url, $path, $scheme) {
      
      	if (stripos($url, "action=lostpassword") !== false)
    		return site_url('wp-login.php?action=lostpassword', $scheme);
      
       	if (stripos($url, "action=resetpass") !== false)
    		return site_url('wp-login.php?action=resetpass', $scheme);
      
    	return $url;
    }, 10, 3 );
    

    This simply filters the URL and if you’re on a site’s login page, use that site for the URL.

    Redirect Logins to Their Site

    This one is messier. If you always want a user to be redirected to ‘their’ site, you have to know what their primary blog is on the network. You can do this, and for the most part, this works:

    add_filter('login_redirect', function ( $redirect_to, $request_redirect_to, $user ) {
        if ($user->ID != 0) {
            $user_info = get_userdata($user->ID);
            if ($user_info->primary_blog) {
                $primary_url = get_blogaddress_by_id($user_info->primary_blog) . 'wp-admin/';
                if ($primary_url) {
                    wp_redirect($primary_url);
                    die();
                }
            }
        }
        return $redirect_to;
    }, 100, 3);
    

    If they don’t have a primary_blog, they’ll be punted to the main for the network, as it should be.

  • Apple Watch UX: Too Small

    Apple Watch UX: Too Small

    The biggest issue I have with my watch is the UX to perform an action.

    I use the WebMD app to remind me to take my pills every morning. At 7:15 it pings my watch and says to take them. I have to scroll down and tap ‘take’ but it gives me three options: Skip, Take, Dismiss.

    This is not the same as the alert they show on their Apple page:

    WebMD's  'skip/take' screen

    That shows up when you miss the alert and go check what you need to take.

    This strikes me as a bit off. The information is too small and the buttons are not as clear as they should be, and I have to scroll down.

    So let’s think what does someone want when they get that alert? They either take the pill or not. Or they dismiss the alert. We want them to take the pills, so we should make that button bigger and green. We don’t want them to not, so make that normal and red. As for dismissing, we can swipe the alert down, so you can leave that button off completely and use the built in UX.

    When we consider the Human Interface Guidelines of the Watch, I think they’ve aimed too small. While they say to have buttons be “large enough to be tapped easily” and “Create buttons that are easy for the user to tap.” the minimum sizes they provide are shockingly small.

    The minimum button sizes are only 50 px tall!

    A 42mm Watch has 390px of usable vertical height. 52px (which is the minimum size for the 42mm) is 13.3% of the height. That’s 13mm. Hold on to that number.

    There was a study by the MIT Touch Lab which investigated Human Fingertips in the Mechanics of Tactile Sense and they determined the average width of an adult human index finger is 1.6 to 2 cm (16 – 20 mm).

    Now Apple’s saying the minimum height is 13mm when the smaller end of average is 16mm. That means if your button is the minimum, most of us will have trouble tapping your button.

    Apple Watch's UX kit with button examples

    I’m not a graphic designer. I call myself a monkey with a crayon. I’m not a UX expert. But I am is a very experienced user and I’m someone who understands how users think. That’s distressingly more rare than you’d think. I understand how a user goes from A to B and gets lost on C.

    When I first got my Apple Watch, I struggled. I had a lot of confusion over force touch (something I still find a little difficult to get correct). But I’ve learned the one thing. Those buttons need to be bigger.

    Buttons on Alarms

    These are the buttons for alarms. The one I don’t have is the nightstand mode button but I use my Watch for an alarm and I don’t have a snooze. Sometimes I have trouble tapping the button in the morning, but I don’t think that’s a function of button size. That said… Those buttons could be larger.

    The Watch tries to separate functions on the Watch, alerts, by ones you look at and ones you interact with. I think the interactions need to have bigger buttons in order for more meaningful, easier, usability.

    Some of this will be addressed in WatchOS 3, which will have us swiping less. But the majority of apps are still trying too hard to cram more information onto small screens. WebMD included. We need to get better about separating information from alerts on our notifications and devices. We need to have everything be easily dismissible with one gesture.

    Thankfully we’ll keep iterating and getting better.

  • Multisite And Theme Activation Checks

    Multisite And Theme Activation Checks

    Earlier this week I talked about how you can’t actually network activate all plugins. As it happens, I have a plugin for a specific theme, and it doesn’t like being Network Activated.

    The plugin is Genesis Simple Hooks and, logically, it only works if you have a Genesis theme installed and active.

    How It Works

    When the plugin is activated it properly runs the following activation hook:

    register_activation_hook( __FILE__, 'simplehooks_activation' );
    /**
     * This function runs on plugin activation. It checks to make sure Genesis
     * or a Genesis child theme is active. If not, it deactivates itself.
     *
     * @since 0.1.0
     */
    function simplehooks_activation() {
    
    	if ( ! defined( 'PARENT_THEME_VERSION' ) || ! version_compare( PARENT_THEME_VERSION, '2.1.0', '>=' ) )
    		simplehooks_deactivate( '2.1.0', '3.9.2' );
    
    }
    

    First this looks to see if there’s no parent theme or if the parent theme version is less than 2.1.0, and if either of those are the case (that is if there’s no parent theme, and the version is less than…) it calls simplehooks_deactivate.

    /**
     * Deactivate Simple Hooks.
     *
     * This function deactivates Simple Hooks.
     *
     * @since 1.8.0.2
     */
    function simplehooks_deactivate( $genesis_version = '2.1.0', $wp_version = '3.9.2' ) {
    
    	deactivate_plugins( plugin_basename( __FILE__ ) );
    	wp_die( sprintf( __( 'Sorry, you cannot run Simple Hooks without WordPress %s and <a href="%s">Genesis %s</a>, or greater.', 'genesis-simple-hooks' ), $wp_version, 'http://my.studiopress.com/?download_id=91046d629e74d525b3f2978e404e7ffa', $genesis_version ) );
    
    }
    

    And you know what? That sucks. It never checks for Genesis as the parent theme name, based on the assumption that only Genesis themes use the define of PARENT_THEME_VERSION which probably made sense a few years ago, but doesn’t anymore. And more importantly for this explanation, you can’t network activate it because unless you’re lucky enough to be running a Genesis child theme on the main site of your network, it won’t activate.

    Worse, it will throw errors on sites that aren’t.

    How I’d Fix It

    First I made a list of the action/function calls that require a Genesis Child Theme to be active in order to run properly: simplehooks_load_textdomain, simplehooks_init, simplehooks_execute_hooks

    Then I moved them all to their own section and wrapped them in a check:

    if ( simplehooks_can_run() ) {
    	add_action( 'plugins_loaded', 'simplehooks_load_textdomain' );
    	add_action( 'genesis_init', 'simplehooks_init', 20 );
    	add_action( 'genesis_init', 'simplehooks_execute_hooks', 20 );
    }
    

    What’s simplehooks_can_run? That’s a new function I’ve created to check for the requirements:

    /**
     * Can Simple Hooks Run
     *
     * This function checks if the requirements for Simple Hooks are met.
     *
     * @since 2.3.0
     */
    function simplehooks_can_run() {
    	
    	global $wp_version;
    	$my_theme = wp_get_theme();
    	$genesis_theme = wp_get_theme( 'genesis' );
    
    	if ( ( version_compare( $genesis_theme->get( 'Version' ), '2.1.0', '>=' ) && !is_null( $genesis_theme->get( 'Version' ) ) && !empty( $genesis_theme->get( 'Version' ) ) ) && ( $my_theme->get( 'Name' ) == 'Genesis' ||  $my_theme->get( 'Template' ) == 'genesis' ) ) {
    		return true;
    	}
    		
    	return false;
    	
    }
    

    What this checks is if the parent theme is 2.1.0 or higher and, if it is, if the theme or it’s parent is named Genesis. By having it use return I don’t have to check if it’s true or false, the if-check is smart enough for me not to do it explicitly.

    Finally I changed the if check in simplehooks_activation to look like this:

    	if ( !is_multisite() && !simplehooks_can_run() )
    

    What this isn’t doing is checking for WordPress 3.9.x anymore, as it’s 2016 and that was 2014 and I’m not worried about it. If I was, I’d toss && version_compare( $wp_version, '3.9.2', '>=' ) to the if-check in simplehooks_can_use to CYA.

    This also isn’t giving you an error on Multisite. That means if you network activate the plugin and, on Multisite, go to a site that does not have Genesis running, you don’t get an error. This is by design. There’s no point in telling a site-admin “Hey there’s this Genesis thing you can’t have because you’re not using it! NEENER!”

    I’m actually using this here on this site. If you’re interested at testing it out, grab it from my Github repo.

    Pull requests welcome.

  • WordPress Multisite Control

    WordPress Multisite Control

    When you write a plugin for WordPress Multisite, you have three options for how to let users control the plugin options. It comes down to the manipulation of the ways we have to activate a plugin on Multisite, which are per-site or network only.

    I’m a firm adherent of having the network control as much as it logically should, but allowing each site to pick unique features. Never should someone be shocked to find out they’re on a network. A network is, after all, a collection of WordPress sites. Now your collection may or may not be related, but at the end of the day, someone should never be surprised to find out the site they signed up for is on a network.

    With this in mind, I separated the ‘control’ of the plugins into three groups.

    Network Only

    A Network Only plugin is one that should be controlled via the Network Admin. While the Settings API is a terrible bag of wet hair for Multisite, if you have a network plugin, then it should be for the network. The plugins that have no interface at all should be network activated. This is really simple, but in general if you’re adding this feature to your network, you probably want it on for everyone. There are some rare exceptions, but in general, network only is the key.

    Most network only plugins are clever enough to use Network: true in their plugin headers, which makes this much easier. If you think your code should only be activated by the network, use that.

    Per-Site

    A per-site plugin is activated on each site, controlled from each site, and the network admins have no authority save uninstalling the plugin. These plugins are things that each site should decide how to use. When I look at my own sites, I have a few that are like this. Like @Reply Two – when you look at it, you’d think it should be network only, but since it requires some per-site configuration with regards to comments, it’s best left as optional for each site.

    There is no Network: false setting, I’m afraid.

    Network Only Activation with Per-Site Control

    Here’s where it gets sticky, and plugins like Jetpack actually handle this better than most others. Take, for example, something like a plugin that adds features to a specific theme. If that theme isn’t active, the plugin shouldn’t error out. But a lot of us code our plugins to say “If this other plugin or theme isn’t active, don’t activate.” That sounds like a great idea except when you want to have it network activated. In those cases, the checks get weird and don’t run as expected.

    And then you have to consider what should control what. I mentioned Jetpack because it has a network admin screen.

    Jetpack's Network Override

    There you can enforce connections from your network admin, or not, as you see fit.

    Which One Is Right?

    While I’ve postulated this is very simple, it’s not. For example, when you have Jetpack, do I want everyone to edit every setting or just some? I’d want them to have the ability to use the CSS editor per-site, but maybe not VaultPress or Stats. The checks for that code is not as logical as it should be. The whens for running those checks, the priorities and weight given to who is more important, is not obvious.

    I would say that the Network Admins should have final say. But many people don’t agree with me on that. Many people think each site on a network would be best to exist on it’s own and stand alone, a part of a secret.

    And that too deserves room for thought.

  • Boundaries

    Boundaries

    My friend and coworker tipped me on to this post about The Asshole Filter which begs the question: “Why is everyone I deal with an asshole?”

    The post goes on to talk about how the issue is that if we draw a line in the sand but then allow people to cross that line, or worse reward them for doing that, we’re hurting ourself. In the example, a fellow named Fred used to accept personal emails about a project but now asks them to be sent to a group email.

    […] some people use fredsstaff@fredsconvention.tld and some people use Fred’s personal email.

    Who uses the officially designated email address?

    • People who feel strongly about following rules.
    • People who feel following the rules is generally a good idea.
    • People who respect Fred’s request because they’re generally respectful.
    • People who respect Fred’s request because they like Fred personally.
    • People who don’t want to antagonize Fred.
    • People who realize the problem Fred is trying to solve and want to be cooperative to reduce the burden on Fred.
    • People who feel it important to respect role boundaries.
    • People who are concerned that overwhelming Fred will cause their request to get lost.

    Who uses Fred’s personal email address?

    • People who can’t be bothered to learn and follow procedures.
    • People who feel rules are for other people.
    • People who feel they should get to cut in line.
    • People who don’t feel keeping track of what other people prefer is all that important.
    • People who aren’t troubled by the thought of pissing off Fred, either because they don’t care whom they piss off or because they think Fred is of no account.
    • People who feel entitled to get their way.
    • People who feel satisfaction when they find an illicit “shortcut” to getting what they want, that “suckers” are too “chicken” to use.

    In short, the decent, cooperative, law-abiding people all use the departmental email address, even though it doesn’t work a well as they might like, while the assholes continue emailing Fred directly.

    Did that just sound familiar to a lot of you who are thinking “I can just email Mika about plugins…”

    People often ask me why I sound angry when I tell you “Please email plugins@wordpress.org, don’t tweet/DM/Slack me asking for status. Just use the email and sit on your hands a bit.” I’m actually not angry. I’m annoyed. There’s a huge difference.

    Angry me logs off and calls it a day.

    Annoyed me rants a bit to someone who understands, or maybe says something passive/aggressive on Twitter about “Please use the plugins@ email…”

    In both cases, though, I probably waste at least an hour of my time not getting to the things everyone wants be to get to. In both cases, I feel incredibly disrespected and used. Yes, used. Because even when my friends say “Hey I have a quick plugin question…” the answer has once been a quick one. That question? Someone asked if he could adopt the plugin of a friend of ours who had died.

    First of all, that’s just an unexpected question by anyone’s standards (and it’s why we came up with a policy about handling death among developers). Second of all, it’s a touchy subject on it’s best day, so asking how to handle it was respectful. Third… He actually asked if he should just email the group.

    This was someone who clearly understood the reality, the situation, and the fact that there will always be exceptions.

    The sidebar of this issue is that, even if I ask someone to email a group, nine times of out ten it’s me who replied.

    I can give you a really long explanation, including how I plan to use those emails to train up new reviewers so you don’t have to wait on me, but let me ask you this instead.

    • Are you more or less important than everyone else who is waiting for their plugin to be reviewed?

    In general, if you get the ‘snippy’ reply of “Dude stop fucking things up and use the right channel” then I’ve already asked you, at least once, to “please” use the email. I’m very careful to ask nicely since I know there’s no possible way for everyone to know things. Mistakes are totally okay in my book. Intentionally trying to jump a queue just makes you an asshole, no matter your intentions. You’re disrespecting everyone, me and every other plugin developer out there, by demanding you get attended to first.

    Okay.

    So what would be a good reason to ping directly? Well about the only reason people do it that I consider thoughtful and respectful is this:

    Hey it’s been a week and I didn’t hear back about X. Did the email get lost or are you guys super backlogged?

    And maybe…

    I think [plugin reviewer] is treating me unfairly. Can you help or do you know who can?

    Both of those are totally perfect reasons to step off book. One is you not being sure if the email was received. The other is an issue with someone who might read the email.

    But do you see how they’re both asking, briefly, without a lot of drama or accusations, a simple question? Well. Not a simple question. But they’re asking in a way that shows they understand the situation of the world in general, they understand they’re asking for an exception, and they will respectfully accept the answers.

    By the way. The answer to the first one is 90% ‘backlogged’ and 10% ‘goddamn email!’