Half-Elf on Tech

Thoughts From a Professional Lesbian

Category: How To

  • My Super Secret .htaccess File

    My Super Secret .htaccess File

    This came up back in April in the comments of WordPress Login Protection With .htaccess, where I remarked my .htaccess was pretty long and weird. It came up again when I was doing a MeetWP presentation about hacked sites and some security.

    So what is it? Actually less long and weird these days. I’ve been trimming stuff out. But since people ask, here it is, broken out into ‘chunks.’

    Security

    Everything in this section is for security purposes. That is, I feel it helps my site be safer.

    # Tinfoil Hat Stuff
    Options +Includes
    Options +FollowSymLinks -Indexes
    

    This is basic .htaccess stuff, says to allow includes and symlinks, but stop indexes. This means if you go to halfelf.org/wp-content/uploads/ you don’t see anything, even if I don’t have an index file.

    ### Blocking Spammers Section ###
    <files wp-config.php>
    order allow,deny
    deny from all
    </files>
    

    Now we’re into a little odder bits. This stops anyone from surfing to my wp-config.php file. It shouldn’t matter, PHP won’t let it load the content, but if my PHP is off, it protects me just in case!

    # Stop protected folders from being narked. Also helps with spammers
    ErrorDocument 401 /401.html
    

    This is because of the next section. It gives a nice error for 401s, which WP normally gets gitty over. And not the fun way.

    <IfModule mod_rewrite.c>
    # Stop spam attack logins and comments
    	RewriteEngine On
    	RewriteCond %{REQUEST_METHOD} POST
    	RewriteCond %{REQUEST_URI} .(wp-comments-post|wp-login)\.php*
    	RewriteCond %{HTTP_REFERER} !.*(ipstenu.org|halfelf.org|ipstenu.org|otherplace.net).* [OR]
    	RewriteCond %{HTTP_USER_AGENT} ^$
    	RewriteRule (.*) http://%{REMOTE_ADDR}/$ [R=301,L]
     # SVN & Git protection
    	RewriteRule ^(.*/)?(\.svn|\.git)/ - [F,L]
    	ErrorDocument 403 "Access Forbidden"
    </ifModule>
    

    Ahhh, yes. Here I say “If you’re coming to wp-comments-post OR wp-login and you are NOT refereed by one of my domains, sod off.” And then it says “Oh and if you’re looking for .svn or .git files? Go away.” This isn’t perfect, but it works for some of the botnets. The fun part is that the rewrite sends them back to themselves, which should cause annoying things to happen. Don’t want that? Redirect them to fbi.gov. Actually, if some tool had a page “Redirect botnets here…” I would use that, but generally I send them to http://lmgtfy.com/?q=wordpress+botnet because I’m that sort of kid.

    Speed and Bandwidth

    Now that I’m safer, lets speed this stuff up!

    <IfModule mod_rewrite.c>
     RewriteEngine on
    # ultimate hotlink protection
     RewriteCond %{HTTP_REFERER}     !^$
     RewriteCond %{REQUEST_FILENAME} -f
     RewriteCond %{REQUEST_FILENAME} \.(gif|jpe?g?|png)$               [NC]
     RewriteCond %{HTTP_REFERER}     !^https?://([^.]+\.)?(ipstenu.org|taffys.org|halfelf.org|poohnau.us|ipstenu.org) [NC]
     RewriteRule \.(gif|jpe?g?|png)$                                 - [F,NC,L]
    </ifModule>
    

    First up, stop the hotlinks! I got the idea from Perishable Press, and it stops you from embedding my images. This means my site is faster, as you’re not sucking up my bandwidth. I get 5G so it’s not too much of a concern right now, but it’s the principle of the thing. Don’t hotlink images!

    ### Caching Section ###
    # mod_pagespeed
    <IfModule pagespeed_module>
    	ModPagespeed on
    	ModPagespeedEnableFilters defer_javascript,combine_javascript,move_css_to_head,insert_dns_prefetch,insert_image_dimensions,inline_preview_images,resize_mobile_images
    	ModPagespeedDisallow */FOLDERNAME/*
    	ModPagespeedEnableFilters insert_ga
    	ModPagespeedAnalyticsID UA-MYCODE-4
    </IfModule>
    

    I use Pagespeed on my server, so here I’ve added in my extra rules. Not everything is active for all sites. This is my default WP rule-set though, and it works well. I have it skipping a couple non WP folders, who have their own rules inside on their own .htaccess files anyway. If you don’t have pagespeed? Skip this section.

    # Expired
    <IfModule mod_expires.c>
    <Filesmatch "\.(jp?eg|png|gif|ico|woff)$">
        ExpiresActive on
        ExpiresDefault "access 1 year"
    </Filesmatch>
    
    <Filesmatch "\.(css|js|swf|mov|mp3|mpeg|mp4|ogg|ogv|ttf|xml|svg|html)$">
        ExpiresActive on
        ExpiresDefault "access 1 month"
    </Filesmatch>
    
        ExpiresDefault "access 2 days"
    </IfModule>
    ## END EXPIRES ##
    

    Oy. There are a couple ways you can control all these things. One is the way I did (filesmatch) and the other is ExpiresByType image/jpg "access plus 1 year". Is one better than they other? I don’t know. Not that I’ve managed to see, but I find the filesmatch to be easier to read and add things too. It’s shorter. Does that make it better? Only in so far as my management goes.

    #Gzip
    <IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/text text/html text/plain text/xml text/css application/x-javascript application/javascript text/javascript font/opentype font/truetype font/eot application/x-font-ttf
    </IfModule>
    #End Gzip
    

    Finally we have gzip, which compresses and makes things smaller and thus faster. Using gzip saves me about 80% in filesize, so it makes things faster to download and, thus, display. If you’re using this in your .htaccess, do not also try to use it in plugins/extensions for other web apps, that way likes double compression and garbage on your pages.

    Add support!

    This is a really short bit to add in support for filetypes I use that aren’t always standard:

    # Add filetypes
    AddType application/x-mobipocket-ebook mobi
    AddType application/epub+zip epub .epub
    AddType video/ogg .ogv
    AddType video/mp4 .mp4
    AddType video/webm .webm
    

    Rewrites

    In general, this is useless to everyone else, save as an example.

    ### Massive Redirect Section! ###
    <IfModule mod_rewrite.c>
    RewriteEngine On
    
    # Apple Touch Icons
    RewriteRule ^(.*)-precomposed.png /code/images/apple/$1.png [L,R=301]
    RewriteRule ^apple-touch-icon(.*) /code/images/apple/apple-touch-icon$1 [L,R=301]
    
    # Ipstenu Moves
    RewriteCond %{HTTP_HOST} ^blog\.ipstenu\.org
    RewriteRule ^(.*) https://ipstenu.org/$1 [L,R=301]
    RewriteCond %{HTTP_HOST} ^ipstenu\.org
    RewriteRule ^blog/([0-9]{4})/([0-9]{2})/(.*)$ https://ipstenu.org/$1/$3 [L,R=301]
    RewriteCond %{HTTP_HOST} ^ipstenu\.org
    RewriteRule ^blog/(.*)$ https://ipstenu.org/$1 [L,R=301]
    RewriteCond %{HTTP_HOST} ^ipstenu\.org
    RewriteRule ^wp-content/blogs.dir/1/files/(.*)$ https://ipstenu.org/wp-content/uploads/sites/1/$1 [L,R=301]
    RewriteCond %{HTTP_HOST} ^ipstenu\.org
    RewriteRule ^(.*)favicon.(ico|png)$ /code/images/favicons/ipstenu.ico [L,R=301]
    
    [NB: This sort of thing is duplicated for other domains which also had things moved around]
    </IfModule>
    

    I cut out a couple of sections, where what I did with the ‘Only redirect ipstenu.org’ stuff is repeated for each site. I built much of that after reading my 404 logs and determining what needed to be redirected. Everything is commented and in logical sections, so I can easily find and remember what the heck I was doing.

    This is the longest section, the stuff under # Ipstenu Moves and such, because they’re accounting for files that moved a million years ago. But the moves section is pretty straightforward too, as you can see where things went. I try to keep it as compact as I can. Sometimes I go through and make them more and more efficient, as I learn new tricks.

    What used to be here?

    I used to include the 5G Blacklist 2013 and/or the 2013 User Agent Blacklist (or whatever the current versions are), but now I don’t have it on all my sites because of the work I’ve been putting in on my firewall and ModSecurity instead. Every once in a while, someone tells me I’m putting too much work on Apache to handle the hackers and spammers, and I generally reply “Better Apache than WordPress.”

    Regularly, I go through my .htaccess and see what I can push over to ModSec. I also trim down my PageSpeed rules into things that work on most sites, things that only work on this site, and things that work for everything. This is why there’s no blacklisting here, it’s all handled by my firewall and mod_security and that’s that. I like to take the load off of apache and .htaccess and PHP and make the server do the work.

    Why not nginx?

    Per site configuration is still a pill. No, really that’s it. It’s a hassle to re-do everything I have in htaccess, I can’t just toss a .nginx file in there on the fly without restarting nginx, which means for shared hosts it’s just not gonna happen. Sucks. For managed hosting, where you don’t allow users to make those changes, sure, it’s great. But that’s not my use case. I may use it for a Varnish in front server one day, after I rebuild everything from scratch.

  • Polyphemus Problem Pans Out

    Polyphemus Problem Pans Out

    I use PHP in DSO mode, which is (woefully) insecure because it lives to save files as ‘nobody.’ Now, previously I found a ‘fix’ to this with WordPress, by tweaking some file permissions and a define in my wp-config.php file, and all was well. But if you know me, you know I’m polyamorus with my CMS, and WordPress ain’t the only game in town.

    I love, love, love Zenphoto for a gallery that is larger than my thumbdrive. But I’ve been having an annoyance that acrylian and sbillard have been really patient with me ranting about for a while now. See, when you upgrade Zenphoto, it narked at me that I had bad permissions:

    zenphoto-perms

    Oh how I cried. My work-around became to make those 777, upgrade, and change ’em back to 755, because that did work for nobody, but when Zenphoto moved the config file and had it be writable by the upgrader, I had to make that owned by nobody and the slippery slope was happening. This was not good. I brought it up again, but we all agreed this was very much a me-problem.

    A new PHP

    Now my choices were to deal with it, or change to a new PHP. Well there are problems with that, and neither suPHP nor FastCGI met my needs for speed and memory consumption. Also I tend to get error 500s when I try any of them on this server, which means I would have to do a total overhaul which I don’t have time for. Instead, I decided to look into why mod_php (aka DSO) likes to have nobody own this stuff. In my research, I stumbled across mod_ruid2, which is included in EasyApache.

    Gruppo_di_polifemo,_sperlonga_0In reading up on cPanel’s notes on mod_ruid2, I hit the incompatibilities list and winced. Right there near the top was MemCache. When I switched over to ZendOptimizer, I also switched to MemCache, and I really was not ready to give it up and go to XCache. Worse? It’s not compatible with mod_security. Epic fail. I absolutely cannot use this. Back to the drawing board until cPanel figures out a way to force it to work with those.

    More searching introduced me to MPM-itk. This was something categorically not supported by cPanel (they backed mod_ruid2), but they still had some directions on how to do this in their forums: Using MPM ITK as a Custom Opt Module

    I need to stress two very important things here:

    1) This is NOT supported by cPanel.

    They do include it in their Custom Mods, but they don’t support it’s use, and won’t include it by default because of issue number 2 (see below). Mind you, I’m no stranger to unsupported installs. I have ImageMagick in a higher version than they do, I installed wp-cli, and I have Pagespeed. So I pretty much run around always upgrading what I need. I am smarter than I used to be, and I have all this documented in a Word Doc called “Custom Installs on my server” which lives on Dropbox, for emergencies. Everything is written with code examples and as much copy/pasta as I can.

    2) There is a security risk with mpm-itk because it runs as root.

    I will quote the author:

    Since mpm-itk has to be able to setuid(), it runs as root (although restricted with POSIX capabilities and seccomp v2 where possible) until the request is parsed and the vhost determined. This means that any code execution hole before the request is parsed will be a potential root security hole. (The most likely place is probably in mod_ssl.) This is not likely to change in the near future, as socket passing, the most likely alternative solution, is very hard to get to work properly in a number of common use cases (e.g. SSL).

    Obviously this is a choice you need to make yourself. Perhaps ironically, by using setuid(), you’re protected from users cross-contaminating but this is not really a perfect fix for everyone. And frankly, this is not my preferred long term fix. My long term fix is this: Build a brand new server, from scratch, with FastCGI, and move sites over one at a time, testing as I go. That’s not today. Instead, it took me about an hour to figure this stuff out and install. And it worked out of the box. Well, except for one thing which I’ll get to.

    Installing mpm-itk

    These directions are pretty easy for cPanel/WHM. You install:

    cd ~/tmp
    wget http://docs.cpanel.net/twiki/pub/EasyApache3/CustomMods/MPMitk.tar.gz
    tar -C /var/cpanel/easy/apache/custom_opt_mods -xzf MPMitk.tar.gz
    

    Then you run EasyApache (/scripts/easyapache) and select mpm-itk from the Exhaustive Options list for PHP (it will give you a warning about the dangers, Will Robinson). Once the update is done, make sure all your normal settings are back in place, if you have anything special, and now you have to actually tell every virtual host what ID to use.

    mkdir -p /usr/local/apache/conf/userdata/std/2/username
    echo &quot;AssignUserID username username&quot; &gt;&gt; /usr/local/apache/conf/userdata/std/2/username/mpm.conf
    /scripts/ensure_vhost_includes --user=username
    

    Replace ‘username’ with your user name (you saw that coming, right?) and off you go. Of course, I had 10 users, so instead I scripted it:

    #!/bin/bash
    for user in `ls /var/cpanel/users`; do
        mkdir -p /usr/local/apache/conf/userdata/std/2/${user}
        echo &quot;AssignUserID ${user} ${user}&quot; &gt;&gt; /usr/local/apache/conf/userdata/std/2/${user}/mpm.conf
        /scripts/ensure_vhost_includes --user=${user}
    done
    

    Huzzah!

    Cleanup, Aisle PHP

    Once I had it installed, and it really was painless, I tested uploads on WordPress and everything worked. But I remembered what I had done back in 2011:

    The last step I had was chowning the folder for uploads and 2011 to nobody:nobody.

    This time I did it in reverse and chowned everything back to my user IDs. I did this for all sites, for all users, and all cache folders. Then I decided to look for all files and folders that were 777 (which I do at work when scanning for hacks) just in case I’d been stupid. I try to not be, but…

    find . -type d -perm 0777
    

    That listed all directories, and I was appalled to find some! That’s right. Up until recently, there were folders permission’d as 777 on my server. I bow my head in shame and embarrassment. Please forgive me, as I run this command to fix that:

    find . -type d -perm 777 -print -exec chmod 755 {} \;
    

    I also ran find . -group nobody to see if I had anything left over, and it happily came up empty. Then I went to double check everything worked. When I’d tested before, I did it on my single install of WP, my wiki, my gallery, another blog, and it worked. So I came here to post and I couldn’t upload images. Horror! Shock! I decided to scan my error log, and right away got a warning on cPanel: Out of disk quota.

    Well that was an easy fix!

    Now everything’s owned by the user it runs for, and nobody owns anything. Everything is secure (except for that ‘running setuid as root for a millisecond’ issue, and yes I’m keeping tabs on that), and everyone is happy. Especially me.

    Bonus Internet points if you get the joke with the title.

  • Upgrading Multiple Macs

    Upgrading Multiple Macs

    So Mavericks came out and it’s about 5 gigs. You’re looking at your three computers and crying at your bandwidth caps.

    p8eul

    So check this out. Go ahead and download the new OS:

    App Store

    Now BEFORE you run it, go to your Applications folder (mine is mid-download, but you get the idea):

    Mid Download

    By the way, that fake date is a very important date in Mac history. Cute, Mac.

    Copy that 5 gig app somewhere else. Maybe a thumbdrive if you have one big enough. You can then copy that to any other Mac and upgrade. Or make a bootable DVD and use that to install. Enjoy.

    Now I’ll be off to download once and upgrade thrice. Wish I could do it for iOS.

  • ShareDaddy Genericons

    ShareDaddy Genericons

    I have a sneaky feeling that after I publicize this, it may end up as an option in Jetpack. After all, they already include Genericons.

    jetpackI’m using Sharing (from Jetpack) on a couple sites, and it works fairly well (the metrics were surprising that one one site they excel and on others they’re never used). That said, I didn’t like the images loaded for ShareDaddy, and it was a real Golidlocks moment. The ‘icon’ buttons were buttons within buttons, and with or without the text, that didn’t make me smile. The official buttons were too large and not matchy enough since everyone has their own design.

    sharing-buttons1

    So what’s a girl to to but fall back on her favorite toy in all the world, Genericons!

    There are only three steps, and the third is to have a drink. You ready?

    Change Sharing Links to “Text Only”

    It’s easier to do this via text only, though I’m sure you can do it with the others. I switched to Text. One click. Done.

    Edit your CSS

    Now I want to hide the text, put a Genericon before the link, and set the colors to ‘true’ for each item (that is, use Twitter Blue for Twitter, Facebook Blue for Facebook, Google Orange for Google+ etc). I picked silvery-grey for email. Also I think it’s correct to use display:none to hide the text, since screenreaders will still read it, and that’s okay. Could be wrong. CSS is not my super-power.

    div.sharedaddy a.sd-button {
        padding: 2px!important;
    }
    
    div.sharedaddy .sd-content li a::before {
        font-family: 'Genericons';
    	font-size: 16px;
    	color: #fff;
    }
    
    div.sharedaddy a.sd-button>span {
        display: none;
    }
    
    div.sharedaddy .sd-content li.share-twitter a::before {
        content: '\f202';
        color: #4099FF;
    }
    div.sharedaddy .sd-content li.share-facebook a::before {
        content: '\f204';
        color: #3B5998;
    }
    div.sharedaddy .sd-content li.share-google-plus-1 a::before {
        content: '\f218';
        color: #DD4B39;
    }
    div.sharedaddy .sd-content li.share-tumblr a::before {
        content: '\f214';
        color: #2C4762;
    }
    div.sharedaddy .sd-content li.share-email a::before {
        content: '\f410';
        color: #666666;
    }
    

    This isn’t perfect, since there’s no Genericon for Printing, Digg, Reddit, StumbleUpon, or Pocket at this time. I’ll be suggesting it to Joen soon. Interestingly, Font Awesome (which could also be used for this) doesn’t have Reddit or the other social networks either, but it does have a print icon. Me? I don’t need them for this site, so it’s okay.

    myshare

    Looks great, scales well on high-def devices, and it pleases me. By the way Pinterest’s red is #C92228 from what I can tell.

    Have a drink

    Like I said, step three to was to have a drink. You’re done!

  • Genesis: Static Nav Bar

    Genesis: Static Nav Bar

    One of my sites got a facelift, and mid-stream I thought “This site would be the perfect place to have one of those floating nav bars.” You know, like how Twitter has this?

    sticky bar example

    Well guess what? It was easy! Presuming you already have a Primary Navigation Menu (like the one I have here with the Genericons), it’s two steps. Of note, you don’t have to do this in your functions.php, but since this will be a part of your theme, it probably belongs there. I tested, and it works in an mu-plugin as well. I should also point out that there are some official directions in the My.StudioPress.com site, but I didn’t use them. I like making it hard.

    Step One: Put the nav menu above your header

    This one was super easy, it’s even in one of the official Genesis Snippits under Navigation Menus. Put this in your functions.php file:

    //* Reposition the primary navigation menu
    remove_action( 'genesis_after_header', 'genesis_do_nav' );
    add_action( 'genesis_before_header', 'genesis_do_nav' );
    

    That puts the primary navigation menu above the header. If you want to use the secondary menu, it would be genesis_do_subnav but you can sort that out.

    Step Two: Make it stick

    This is pure CSS, so into your style.css goes:

    .nav-primary {
        position:fixed;
        z-index:99;
        top: 0;
        width: 100%
    }
    

    At first I didn’t add in the top: 0; but I found out that without it, a fixed position meant my header was suddenly under the navbar all the time. Oops. So I moved that to on, after spending an hour trying to math out the permutations with margins, and ended up with my navbar under the WordPress toolbar! Don’t worry, CSS to the rescue!

    body.admin-bar .nav-primary {
        top: 28px!important;
    }
    body.admin-bar .site-container {
        margin: 30px 0 0 0;
    }
    

    This simply says that for the body class of admin-bar, bump everything down by 28 pixels.

    Step Three: Return to Top

    I know, I know, I said two steps. This one is optional. I made a menu item called ‘Top’ with a link of #top and a CSS label of ‘top’ and it looks like this:

    Top Menu

    Now since I called this menu ‘primary’ and I’m using Genericons, I made this my CSS (keep in mind .nav-primary would also work):

    .menu-primary top {
        float: right;
    }
    
    .menu-primary li.top a {
        font-size: 0;
    }
    
    .menu-primary li.top a::before  {
        vertical-align: middle;
        padding: 0 5px 0 0;
        font-family: 'Genericons';
        content: '\f435';
    }
    

    This gives me a happy little top arrow that, when clicked on, takes people to the top. If you want to mess with colors, remember that to be specific for just the before calls, it’s a:hover:before (the pseudo-element is last).

  • WP Comments ReplyTwo

    WP Comments ReplyTwo

    WPTavern has this cool thing where, without threaded comments, you still have a reply link, AND it creates an automatic link back to the original comment.

    Let me rewind. If you have threaded comments, then you get a ‘reply’ link on the bottom of a comment, and it lets you make a threaded reply. Yay! There are problems with this, though, as after a while, if you get nested deep enough, you can’t reply under anymore, and have to go up to click reply on the previous post and on and on.

    Threaded PipeBack eons ago, WPTavern solved this with a function, and like all great sites documented it! The problem? The documentation was busted. Now, yes, I did ping Jeff about this and asked him how it went, but in the meantime, I was impatient and looked up a plugin that almost fit my bill.

    Enter @ Reply (aka Reply To), which add in a reply link to all comments! Plus is gives you ‘Twitter like’ @replies, so when you comment, it starts “@foo:” automatically. This is just like what WPTavern has, perfect! Except… not quite.

    My problems became two:

    1. I want all comments to have a ‘reply’ link.
    2. I don’t want the hover over image.

    And I solved this with two code chunks: a plugin and a theme.

    See, by default WP stops showing you the ‘reply’ link when you can’t nest anymore. To change that, you have to edit how your theme calls comments. Or rather, you have to change the comment_reply_link() call.

    The Theme Code

    I’m already customizing my comments in Genesis so this was surprisingly simple.

    What was this:

    &lt;div class=&quot;comment-reply&quot;&gt;
        &lt;?php comment_reply_link( array_merge( $args, array( 'depth' =&gt; $depth, 'max_depth' =&gt; $args['max_depth'] ) ) ); ?&gt;
        &lt;/div&gt;
    

    Becomes this:

    &lt;div class=&quot;comment-reply&quot;&gt;
        &lt;?php comment_reply_link( array_merge( $args, array( 'depth' =&gt; 1, 'max_depth' =&gt; 2 ) ) ); ?&gt;
    &lt;/div&gt;
    

    Basically we’re lying to WP and saying we always show the link. I got that idea from this stackexchange post, and as I understand it, we’re tricking WP by saying that every post is depth of 1, and has a max of 2, instead of letting it figure it out on it’s own. This is probably inelegant, but I couldn’t find another way to have the reply link always on.

    The Plugin Code

    nested-commentsThis was easier, in that I took the existing code, cleaned it up and removed the reply image, and that was pretty much it. Downside is that this does not work on every site. Notably, it doesn’t work unless threaded comments are turned on. When they’re not, it does a double-refresh.

    That said, this is probably never going to be a plugin for the masses, so I’m content with having it work this way for me. Even if I only thread one comment at a time, it would let me group them together and get the @-reply. It’s already rather popular on the site I intended it for, and people like it.

    So here’s the code:

    class AtReplyTwoHELF {
        public function __construct() {
            add_action( 'init', array( &amp;$this, 'init' ) );
        }
    
        public function init() {
    		if (!is_admin()) {
    			 add_action('comment_form', array( $this, 'r2_reply_js'));
    			 add_filter('comment_reply_link', array( $this,'r2_reply'));
    		}
    	}
    
    	public function r2_reply_js() {
    	?&gt;
    		&lt;script type=&quot;text/javascript&quot;&gt;
    			//&lt;![CDATA[
    			function r2_replyTwo(commentID, author) {
    				var inReplyTo = '@&lt;a href=&quot;' + commentID + '&quot;&gt;' + author + '&lt;\/a&gt;: ';
    				var myField;
    				if (document.getElementById('comment') &amp;&amp; document.getElementById('comment').type == 'textarea') {
    					myField = document.getElementById('comment');
    				} else {
    					return false;
    				}
    				if (document.selection) {
    					myField.focus();
    					sel = document.selection.createRange();
    					sel.text = inReplyTo;
    					myField.focus();
    				}
    				else if (myField.selectionStart || myField.selectionStart == '0') {
    					var startPos = myField.selectionStart;
    					var endPos = myField.selectionEnd;
    					var cursorPos = endPos;
    					myField.value = myField.value.substring(0, startPos) + inReplyTo + myField.value.substring(endPos, myField.value.length);
    					cursorPos += inReplyTo.length;
    					myField.focus();
    					myField.selectionStart = cursorPos;
    					myField.selectionEnd = cursorPos;
    				}
    				else {
    					myField.value += inReplyTo;
    					myField.focus();
    				}
    			}
    			//]]&gt;
    		&lt;/script&gt;
    	&lt;?php
    	}
    
    	public function r2_reply($reply_link) {
    		 $comment_ID = '#comment-' . get_comment_ID();
    		 $comment_author = esc_html(get_comment_author());
    		 $r2_reply_link = 'onclick=\'return r2_replyTwo(&quot;' . $comment_ID . '&quot;, &quot;' . $comment_author . '&quot;),';
    		 return str_replace(&quot;onclick='return&quot;, &quot;$r2_reply_link&quot;, $reply_link);
    	}
    }
    
    new AtReplyTwoHELF();
    

    Most of the javascript stuff is copy/pasta for me (just left of witchcraft), and I left it barebones (no configurations) on purpose. Simple is as simple does.

    Suggestions and tweaks welcome!