Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: coding

  • Customize Network Toolbars

    Customize Network Toolbars

    This came up in the WordPress Support Forums. If you use Multisite, the WordPress toolbar (at the top of your site when logged in) has a special item called “My Sites” which shows all the sites of which you are an administrator. This is great and works as a quick jump to get to a different site really fast. It has problems, though, in that if you make a network with a bunch of sites named the same thing, it’s hard to tell which site you’re on.

    Now I know what you’re thinking! “Mika!” You say. “Mika, come on, no one has 100 sites with the same name unless they’re doing what you say is a terrible idea, and duplicating sites!!”

    Au contraire, mon frère. There are a few totally understandable reasons why this might happen. Fairly recently I was helping a school sort out Multisite, and they wanted a site for each classroom (easy) and the names of the sites would all be the same: Super Cool School – Class Frog

    And their ‘My Sites’ list was all the same.

    An example of a site list where the names aren't really readable because they're too long

    As you can see, WordPress wisely puts a practical limit on the title length, which makes sense. Now when I was faced with this problem, I remembered something that had come up in the forums, where someone wanted the ‘language’ of the site to show up in the site list, so his sites would be showing as “SiteName (en)” and so on. Since he was using the site path (en, de, etc) as the site’s slug, it was easy for him to come up with this, once I (accidentally) pointed him the right way:

    <?php
    /*
    Plugin Name: Show Site Path
    Description: Show Site Path in My Sites Menu
    */
    
    function helf_customize_my_sites( $wp_admin_bar ) {
    
        $mysites = $wp_admin_bar->user->{'blogs'};
    
        foreach($mysites as $site) {
          $site->blogname .= ' (' . $site->path . ')';
        }
    }
    
    add_action('admin_bar_menu', 'helf_customize_my_sites');
    

    I say it was an accident because I did read the question wrong, but it actually gave me the answer to my schools. They too used the classroom name as the site path, so for them I changed one line:

        foreach($mysites as $site) {
          $site->blogname = '$site->path';
        }
    

    That was it. Now the sites showed up the way they wanted.

  • Mailbag: One Analytics to Bind Them

    Mailbag: One Analytics to Bind Them

    Mailbag on Monday because Angie Meeker asks:

    Do you have an article about getting Google Analytics right on Multisite, so SA can see indiv stats for each site, but also parent. Where the SAdmin owns the entire GA account (site owners don’t need their own GA account)

    There are a few ways to do this, and they’re all pretty easy.

    Google Mod_PageSpeed

    This is the ‘easiest’ way if you already have PageSpeed installed. You can put in your GA filter in the .htaccess and be done with it:

    ModPagespeedEnableFilters insert_ga
    ModPagespeedAnalyticsID <Analytics ID>
    

    That’s actually what I do here, because I’m incredibly lazy and I have Pagespeed set up on my server. I can even make this a little more special by using if statements in Apache 2.4:

    <If "$req{Host} == 'www.domain.com'">
        ModPagespeedEnableFilters insert_ga
        ModPagespeedAnalyticsID <Analytics ID>
    </If>
    

    Graph Background

    MU Plugin

    But if you’re still on Apache 2.2 or don’t want to mess with .htaccess for whatever reason, then you should try an mu-plugin, my favorite things in the world. And all you have to do is this:


    // Paste your Google Analytics code here

  • Mailbag: Translations

    Mailbag: Translations

    The second hardest thing about translations is trusting the translator.

    I sometimes joke that I barely speak English, so when someone said he translated my entire ebook about Multisite into French, I was delighted and scared. While I do kind of understand French, I’m not qualified to translate it, so having someone else do it would be a fantastic offering. But since I can’t translate it, I have no way of knowing how to gauge if they understood my meaning, which is hard enough to figure out in English.

    After a while, I decided to tell him that I’d like to see them, but I wasn’t sure if I’d want to put them up online to sell or give away. Of course he was welcome to give them away all he wanted!

    French Fries

    The problem isn’t that I trust him, or not, but that I don’t have a failsafe. With coding, I have coworkers who can spot check me. With blog posts I could use an editor, and it’s the same with books. If this was a contracted book, I’d be able to let my publisher find someone we all agree fits the bill. When you’re on your own, it’s a lot harder.

    The same goes with my plugins. I don’t actually package anything in my plugins by way of translations. The closest I have is my Varnish Plugin, which has a folder on github for people to store translations. Since they don’t have to be included in core, it’s easy enough for me to say “Use at your own risk.”

    With code, there’s a lot more you have to do in order to make your code translatable though. With my books, I just write. With my code, I have to remember to escape properly. Which I nearly never get correct the first time out. With code, you have to remember from the start to write your words in a way that can be translated, and you have to worry every time you change things that it will be broken for everyone on the next update.

    It’s chaining, really, to realize I can’t just ‘write’ in my plugin like I do on my blog.

    So what’s the question and the answer? Should you translate your work? Maybe. You should always make it translatable, but whether or not you should manage the translations is a really strange question without a perfect answer. Unless you’re fluent in two languages.

    I keep the following links bookmarked, just to keep me on track when I start editing any plugin, and I try to work backwards to fix all my old ones, but it’s really slippery.

  • All Comments By Email

    All Comments By Email

    By default, when you look at the list of comments on your WP Admin dashboard, you get a list like this:

    The Edit Comments page

    On that list, if you click on the IP address of the commenter, you go to all comments by that IP, but if you click on the email you get a mailto link, to let you email the person. That’s great, but as my friend, and fellow fansite runner, Liv pointed out, a lot of people post from multiple IP addresses these days, but only one email. What she wanted was for the icon that gave you the number of approved comments to link to that person’s approved comments.

    Me, being the sort to poke around, decided to see if that could be done. I already knew how to filter columns and tables, after all. What I learned was that there actually isn’t a filter for those columns, and the only way around it was to replace it. This means I was going to have to rebuild everything, and in doing so, I wanted that email address to be a link to the search. While annoying, it was pretty easy:

    &lt;?php
    /*
    	Plugin Name: Easy Comment Search By Email
    	Description: Changes the default link for emails in the comment lists from mailto links to search results for that address.
    	Author: Mika A Epstein (ipstenu)
    	Author URI: https://halfelf.org
    
    Credit: https://wordpress.stackexchange.com/questions/83769/hook-to-edit-an-column-on-comments-screen
     */
    
    class ecsbePlugin {
    
    	public function __construct() {
    		add_action( 'admin_head' , array( &amp;$this, 'column_style') );
    
    	add_filter( 'manage_edit-comments_columns', function($columns) {
    		unset($columns[&quot;author&quot;]);
    		$columns_one = array_slice($columns,0,1);
    		$columns_two = array_slice($columns,1);
    		$columns_one[&quot;author-new&quot;] = &quot;Author&quot;;
    		$columns = $columns_one + $columns_two;
    		return $columns;
    	});
    
    	add_filter( 'manage_comments_custom_column', function($column, $column_id) {
    		
    		global $comment_status;
    				$author_url = get_comment_author_url();
    					if ( 'http://' == $author_url )
    							$author_url = '';
    					$author_url_display = preg_replace( '|http://(www\.)?|i', '', $author_url );
    					if ( strlen( $author_url_display ) &gt; 50 )
    							$author_url_display = substr( $author_url_display, 0, 49 ) . '&amp;hellip;';
    	
    					echo &quot;&lt;strong&gt;&quot;; comment_author(); echo '&lt;/strong&gt;&lt;br /&gt;';
    					if ( !empty( $author_url ) )
    							echo &quot;&lt;a title='$author_url' href='$author_url'&gt;$author_url_display&lt;/a&gt;&lt;br /&gt;&quot;;
    	
    					if ( current_user_can( 'edit_posts' ) ) {
    						$author_email = get_comment_author_email();
    					
    							if ( !empty( $author_email ) ) {
    									echo '&lt;a href=&quot;edit-comments.php?s=';
    									echo $author_email;
    									echo '&amp;amp;mode=detail';
    						   		if ( 'spam' == $comment_status )
    									echo '&amp;amp;comment_status=spam';
    								echo '&quot;&gt;';
    								echo $author_email;
    								echo '&lt;/a&gt;&lt;br /&gt;';
    							}
    							
    							echo '&lt;a href=&quot;edit-comments.php?s=';
    							comment_author_IP();
    							echo '&amp;amp;mode=detail';
    							if ( 'spam' == $comment_status )
    									echo '&amp;amp;comment_status=spam';
    							echo '&quot;&gt;';
    							comment_author_IP();
    							echo '&lt;/a&gt;';
    					}
    		
    		}, 10, 2 );
    
    	}
    
     	public function column_style() {
    		echo '&lt;style type=&quot;text/css&quot;&gt;
    			#comments-form .fixed .column-author-new {
    				width: 20%;
    			}
    		 &lt;/style&gt;';
    	}
    
    }
    
    new ecsbePlugin();
    

    The plugin needs a way better name, though, because this is just … bad. The array slice in the beginning was to remove the first item and replace it, without having to do a lot of overly wrought arguing with possible columns.

    That said, this is the sort of thing I may submit a patch for in core, since IPs change a heckuvalot more now, and while that’s a great way to find some serial-accounts and sockpuppets, sorting by email helps you find people being trolls. Both would be good, and I don’t think a lot of us email people. If anything, I’d change the author NAME to be a mailto link.

    Food for thought.

  • Mailbag: Pinging Pingbacks

    Mailbag: Pinging Pingbacks

    I run a fan site, and so does a friend of mine. Liv and I were chatting about wishlists in WordPress for fansites, and she mentioned this:

    I also like seeing who has linked to my site from other WP blogs because that helps me create fandom connections with other bloggers. I wish there was a quick button I could hit that would allow me to email those bloggers with a quick note of thanks for the connection

    When you’re running a fan website, communicating and connecting with those other sites is a killer feature. We network and that’s how we make our communities bloom, after all, since most of us can’t afford a budget for ‘real’ advertising, and it’s probably not entirely legal for us to do that anyway. So outside of spending days tracking everyone down, what about using the power of ping-backs for ourselves?

    Table Tennis

    I’m sure Liv has an unshakable confidence in my ability to code her things (and I love the requests she makes, they stretch my brain) but this one kicked my patootie a lot. Getting a list of pingbacks isn’t all that hard. There’s a plugin called Commenter Emails by Scott, which nicely lists all the email addresses used to make comments. Using that logic, it’s pretty easy to list all the pingbacks. I mean, hey, we can already do that!

    If you go to /wp-admin/edit-comments.php?s&comment_status=all&comment_type=pings you’ll see all your pings:

    All pings listed

    Just looking at that, however, made me notice a horrible problem. There are no emails listed in pingbacks. This makes perfect sense. The emails aren’t (generally) listed on a page that links to your site. That means without doing some serious site-scraping, there’s no way to get that email.

    Putting that aside, the other option is to, perhaps, list the ‘parent’ domain that pinged you. So I went back to Scott’s plugin and forked it into this:

    &lt;?php
    
    /*
    Plugin Name: Pingers List
    License: GPLv2 or later
    License URI: http://www.gnu.org/licenses/gpl-2.0.html
    Description: List all pingbacks with links to their main domain.
    
    	Quasi fork of http://wordpress.org/plugins/commenter-pings/
    
    	Copyright (c) 2007-2014 by Scott Reilly (aka coffee2code)
    	Copyright (c) 2014 by Mika Epstein (aka ipstenu)
    */
    
    defined( 'ABSPATH' ) or die();
    
    if ( is_admin() &amp;&amp; ! class_exists( 'PingersList' ) ) :
    
    class PingersList {
    
    	private static $plugin_basename = '';
    	private static $plugin_page     = '';
    
    	/**
    	 * Returns version of the plugin.
    	 *
    	 * @since 2.1
    	 */
    	public static function version() {
    		return '2.2.1';
    	}
    
    	/**
    	 * Constructor
    	 */
    	public static function init() {
    		self::$plugin_basename = plugin_basename( __FILE__ );
    
    		// Register hooks
    		add_action( 'admin_menu', array( __CLASS__, 'admin_menu' ) );
    		add_action( 'admin_menu', array( __CLASS__, 'do_init' ), 11 );
    	}
    
    	/**
    	 * Initialize hooks and data
    	 */
    	public static function do_init() {
    		// Currently empty
    	}
    
    	/**
    	 * Query database to obtain the list of commenter email addresses.
    	 * Only checks comments that are approved, have a author email, and are
    	 * of the comment_type 'comment' (or '').
    	 *
    	 * Only one entry is returned per email address.  If a given email address
    	 * has multiple instances in the database, each with different names, then
    	 * the most recent comment will be used to obtain any additional field data
    	 * such as comment_author, etc.
    	 *
    	 * @param array $fields  The fields to obtain from each comment
    	 * @param string $output (optional) Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants. See WP docs for wpdb::get_results() for more info
    	 * @return mixed List of email addresses
    	 */
    	public static function get_pings( $fields = array(  'comment_post_ID', 'comment_author', 'comment_author_url' ), $output = ARRAY_N ) {
    		global $wpdb;
    
    		// comment_author_url must be one of the fields
    		if ( ! in_array( 'comment_author_url', $fields ) )
    			array_unshift( $fields,  'comment_author_url' );
    
    		$fields = implode( ', ', $fields );
    		$sql = &quot;SELECT $fields
    				FROM {$wpdb-&gt;comments} t1
    				INNER JOIN ( SELECT MAX(comment_ID) AS id FROM {$wpdb-&gt;comments} GROUP BY comment_author_url ) t2 ON t1.comment_ID = t2.id
    				WHERE
    					comment_approved = '1' AND
    					comment_type = 'pingback'
    				GROUP BY comment_author_url
    				ORDER BY comment_author_url ASC&quot;;
    		$pings = $wpdb-&gt;get_results( $sql, $output );
    		return $pings;
    	}
    
    
    	/**
    	 * Creates the admin menu.
    	 *
    	 * @return void
    	 */
    	public static function admin_menu() {
    		add_filter( 'plugin_action_links_' . self::$plugin_basename, array( __CLASS__, 'plugin_action_links' ) );
    		// Add menu under Comments
    		self::$plugin_page = add_comments_page( __( 'Pinger List', 'pinger-list' ), __( 'Pinger List', 'pinger-list' ),
    			apply_filters( 'manage_commenter_pings_options', 'manage_options' ), self::$plugin_basename, array( __CLASS__, 'admin_page' ) );
    	}
    
    	/**
    	 * Adds a 'Settings' link to the plugin action links.
    	 *
    	 * @param array $action_links The current action links
    	 * @return array The action links
    	 */
    	public static function plugin_action_links( $action_links ) {
    		$settings_link = '&lt;a href=&quot;edit-comments.php?page=' . self::$plugin_basename.'&quot; title=&quot;&quot;&gt;' . __( 'Listing', 'pinger-list' ) . '&lt;/a&gt;';
    		array_unshift( $action_links, $settings_link );
    		return $action_links;
    	}
    
    	/**
    	 * Outputs the contents of the plugin's admin page.
    	 *
    	 * @return void
    	 */
    	public static function admin_page() {
    		$pings = self::get_pings();
    		$pings_count = count( $pings );
    
    		echo '&lt;div class=&quot;wrap&quot;&gt;';
    		echo '&lt;h2&gt;' . __( 'Ping List', 'pinger-list' ) . '&lt;/h2&gt;';
    		echo '&lt;p&gt;' . sprintf( __( 'There are %s unique ping locations for this site.', 'pinger-list' ), $pings_count ) . '&lt;/p&gt;';
    		echo '&lt;/div&gt;';
    
    			echo '&lt;div class=&quot;wrap&quot;&gt;';
    			echo '&lt;h2&gt;' . __( 'All Pings', 'pinger-list' ) . '&lt;/h2&gt;';
    			echo '&lt;table padding=2&gt;';
    			echo '&lt;tr&gt;&lt;th&gt;' . __( 'Post', 'pinger-list' ) . '&lt;/th&gt;&lt;th&gt;' . __( 'Source', 'pinger-list' ) . '&lt;/th&gt;&lt;th&gt;' . __( 'Direct Link', 'pinger-list' ) . '&lt;/th&gt;&lt;/tr&gt;';
    
    			foreach ( $pings as $item ) {
    			
    				$pings_url = parse_url(esc_html( $item[2] ));
    				$ping_url = $pings_url[scheme].'://'. $pings_url[host];
    			
    				echo '&lt;tr width=&quot;20%&quot;&gt;&lt;td&gt;&lt;a href=&quot;' . get_permalink( $item[0] ) . '&quot;&gt;'. get_the_title($item[0]) .'&lt;/a&gt;&lt;/td&gt;';
    				echo '&lt;td width=&quot;20%&quot;&gt;' . make_clickable($ping_url).'&lt;/td&gt;';
    				echo '&lt;td&gt;&lt;a href=&quot;'.esc_html( $item[2] ).'&quot;&gt;'. esc_html( $item[1] ) . '&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;';
    			}
    
    			echo '&lt;/table&gt;';
    			echo '&lt;p&gt;' . sprintf( __( '%s pings listed.', 'pinger-list' ), $pings_count ) . '&lt;/p&gt;';
    			echo '&lt;/div&gt;';
    
    
    	}
    } // end PingersList
    
    PingersList::init();
    
    endif; // end if ! class_exists()
    

    The plugin’s crazy basic. It simply checks for unique ping sources and lists them. So if the same ‘main’ site links to you 10 times from 10 separate posts, it lists that. Probably a nice tweak would be to order them by domain, list the posts they link to and from where, and have a group by sort of list, but I didn’t get that far into it. Forks welcome, as are full blown plugins!

  • Genesis Favicons Multistyle

    Genesis Favicons Multistyle

    For the longest time I used .htaccess to shunt my favicons to the right place based on the requesting domain. This basic logic worked well, but was starting to get weird and it didn’t always work depending on if I was using pagespeed to trim out leading domains or not, and of course when I tightened security it got weirder.

    A whole mess of favicons

    Now I’ve know you can use custom favicon in Genesis and that’s pretty simple. But … I have to make my life complicated. See I use Multisite and I don’t edit my themes. No, really, this theme? I only edited it to update it. I don’t edit them, I use custom CSS and functions to edit via hooks and filters, the way you’re supposed to.

    So how do I override favicons for five sites on a Multisite? Why with an MU Plugin!

    #2, shows a specific favicon, and does it for and before defaulting to the main site. I ‘hard code’ in the full URL to make sure I don’t mess it up and there isn’t any crazy cross site shenanigans. Obviously this will get a little long over time, and if I ever stop using Genesis for a site, I’ll instead hook into wp_head() and toss in the link for shortcut icon.