Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: jetpack

  • Show Feedback in “Right Now”

    Show Feedback in “Right Now”

    The “Right Now” section of the WordPress dashboard is a great way to get an overview of the goings on of your site. But it doesn’t quite list everything. What if you could add things like ‘messages’ to the the “At a Glance” section like this:

    The "At a Glance" section, with messages added in.

    Guess what? You can!

    The Code

    Presmuing you’re using Jetpack’s contact form module, you automatically get a new kind of post called ‘Feedback.’ In order to make it display it’s count in “At a Glance,” there are two parts. First we add the CSS, which does the styling. Then we add the PHP that counts and displays the number of posts in feedback.

    add_action( 'dashboard_glance_items', 'helf_dashboard_glance' );
    add_action( 'admin_head', 'helf_dashboard_glance_css' );
    
    /*
     * Show Feedback in "Right Now"
     */
    function helf_dashboard_glance() {
    	if ( class_exists( 'Jetpack' ) && Jetpack::is_module_active( 'contact-form' ) ) {
    		foreach ( array( 'feedback' ) as $post_type ) {
    			$num_posts   = wp_count_posts( $post_type );
    			$count_posts = ( isset( $num_posts->publish ) ) ? $num_posts->publish : '0';
    			if ( 0 !== $count_posts ) {
    				if ( 'feedback' === $post_type ) {
    					// translators: %s is the number of messages
    					$text = _n( '%s Message', '%s Messages', $count_posts );
    				}
    				$text = sprintf( $text, number_format_i18n( $count_posts ) );
    				printf( '<li class="%1$s-count"><a href="edit.php?post_type=%1$s">%2$s</a></li>', esc_attr( $post_type ), wp_kses_post( $text ) );
    			}
    		}
    	}
    }
    
    /*
     * Custom Icon for Feedback in "Right Now"
     */
    function helf_dashboard_glance_css() {
    	if ( class_exists( 'Jetpack' ) && Jetpack::is_module_active( 'contact-form' ) ) {
    		?>
    		<style type='text/css'>
    			#adminmenu #menu-posts-feedback div.wp-menu-image:before, #dashboard_right_now li.feedback-count a:before {
    				content: '\f466';
    				margin-left: -1px;
    			}
    		</style>
    		<?php
    	}
    }
  • Hashtag your Jetpack with Taxonomies

    Hashtag your Jetpack with Taxonomies

    So maybe you read my post about hash tagging Jetpack Publisher posts with a complex check of “Is this tag also a custom post type?” and you thought “Mika, that’s awesome. But I don’t have a database of shows!”

    To my reporter friend who lamented this to me, I have a solution.

    The Real Problem

    I think most of the problem with Jetpack’s custom Publicize message is that it’s (rightly) hidden by default. Most people don’t need it. But those that do want to see it so we remember “and do this…”

    Publicize Settings on your post sidebar

    And let’s be honest, this visibility issue is going to be worse when we move to Gutenberg.

    Again, I don’t think Jetpack is in the wrong here. The majority of users don’t need to be so aggro about their messages. There are a lot of WordPress sites that we would classify as ‘smaller’ sites. And those who aren’t tend to need very bespoke/custom solutions, which is the problem.

    My solution works for me because it’s easily automated and checkable. We’re very pedantic about tags (seriously, Tracy went through all our tags and cleaned them up), we have a great system to match tag to show, and, most significantly, we know that our social media engagement relies on not just content, but the appropriate hashtags. That is, the shows we’re talking about.

    This means there are two types of ways to do this:

    1) Make all your tags (and/or categories) into your hashtags
    2) Make a custom taxonomy for your hashtags

    Since using all the tags might be a bit much, I went with option 2.

    The Code

    <?php
    /*
     * Jetpack tweaks
     * @version 1.0
     * @package mu-plugins
     */
    
    class HalfElf_Jetpack {
    
    	public function __construct() {
    		add_action( 'publish_post', array( $this, 'custom_message_save' ) );
    		add_action( 'init', array( $this, 'register_taxonomy_hashtag' ) );
    	}
    
    	public function register_taxonomy_hashtag() {
    
    		//parameters for the new taxonomy
    		$arguments = array(
    			'label'                 => 'Hashtags',
    			'hierarchical'          => false,
    			'public'                => false,
    			'show_ui'               => true,
    			'update_count_callback' => '_update_post_term_count',
    			'rewrite'               => false,
    		);
    
    		register_taxonomy( 'flf_hashtags', 'post', $arguments );
    	}
    
    	public function publicize_hashtags() {
    		$post      = get_post();
    		$hash_tags = '';
    
    		// If the post isn't empty AND it's a post (not a page etc), let's go!
    		if ( ! empty( $post ) && 'post' === get_post_type( $post->ID ) ) {
    
    			update_post_meta( $post->ID, '_wpas_mess', 'test' );
    
    			// First let's add the hashtags
    			$post_tags = get_the_terms( $post->ID, 'flf_hashtags' );
    			if ( ! empty( $post_tags ) ) {
    				// Create list of tags with hashtags in front of them
    				foreach ( $post_tags as $tag ) {
    					// Change tag from this-name to thisname and slap a hashtag on it.
    					$tag_name   = str_replace( '-', '', $tag->slug );
    					$hash_tags .= ' #' . $tag_name;
    				}
    			}
    
    			// Next we add a category in specific situations.
    			$post_cats = get_the_category( $post->ID );
    			if ( ! empty( $post_cats ) ) {
    				// Create list of tags with hashtags in front of them
    				foreach ( $post_cats as $cat ) {
    					if ( 'MAINCAT' === $cat->slug ) {
    						// Change slug from this-name to thisname and slap a hashtag on it.
    						$cat_name   = str_replace( '-', '', $cat->slug );
    						$hash_tags .= ' #' . $cat_name;
    					}
    				}
    			}
    		}
    
    		// Loop back. If there are hashtags, we add them.
    		if ( '' !== $hash_tags ) {
    			// Create our custom message
    			$custom_message = 'New post! ' . get_the_title() . $hash_tags;
    			update_post_meta( $post->ID, '_wpas_mess', $custom_message );
    		}
    	}
    
    	// Save that message
    	public function custom_message_save() {
    		add_action( 'save_post', array( $this, 'publicize_hashtags' ) );
    	}
    
    }
    
    new HalfElf_Jetpack();
    

    A Little Explanation

    You may notice I added in a bit that looks for a specific category:

    if ( 'MAINCAT' === $cat->slug ) { ... }
    

    The reason here is that on the specific site I wrote this for, they have four (yes four!) categories:

    1) Announcements
    2) News
    3) Fandom (actually named FOR the fandom)
    4) Miscellaneous

    They wanted item to be a tag, so it would always #JanelleMonae (for example). For them I did a super basic ‘if the fandom, then the hashtag’ but a slightly more common situation would be someone having a category for ‘Fandoms’ and then subcategories our fandom. For that you’ll want something like this:

    foreach ( $post_cats as $cat ) {
    	$cat_mom = $cat->category_parent;
    	if( $cat_mom > 0 && 'fandoms' === $cat_mom->slug ) {
    		// Change slug from this-name to thisname and slap a hashtag on it.
    		$cat_name   = str_replace( '-', '', $cat->slug );
    		$hash_tags .= ' #' . $cat_name;
    	}
    }
    

    Enjoy your hash tagging!

  • Hashtag Your Jetpack with Custom Post Types

    Hashtag Your Jetpack with Custom Post Types

    The brunt of this code comes from Jeremy Herve, who was explaining to someone how to add Category hashtags prefixed to Jetpack Publicize tweets.

    That is, someone wanted to take a category for a post (say ‘How To’) and convert that into a hashtag (say #howto).

    I too wanted to do this, but like my weirdly related posts, I needed to do the following:

    1) Get the tags
    2) Check if the tag slug was the same as a post slug for a specific custom post type
    3) Output the hashtag as all one-word, no spaces, no hyphens

    Here’s The Code

    No more explaining, here’s the code.

    class Hashtag_Jetpack {
    
    	public function __construct() {
    		add_action( 'publish_post', array( $this, 'custom_message_save' ) );
    	}
    
    	public function publicize_hashtags() {
    		$post = get_post();
    
    		// If the post isn't empty AND it's a post (not a page etc), let's go!
    		if ( ! empty( $post ) && 'post' === get_post_type( $post->ID ) ) {
    			$post_tags = get_the_tags( $post->ID );
    			if ( ! empty( $post_tags ) ) {
    				// Create list of tags with hashtags in front of them
    				$hash_tags = '';
    				foreach ( $post_tags as $tag ) {
    					// Limit this to shows only.
    					$maybeshow = get_page_by_path( $tag->name, OBJECT, 'post_type_shows' );
    					if ( $maybeshow->post_name === $tag->slug ) {
    						// Change tag from this-name to thisname and slap a hashtag on it.
    						$tag_name   = str_replace( '-', '', $tag->slug );
    						$hash_tags .= ' #' . $tag_name;
    					}
    				}
    
    				// Create our custom message
    				$custom_message = 'New post! ' . get_the_title() . $hash_tags;
    				update_post_meta( $post->ID, '_wpas_mess', $custom_message );
    			}
    		}
    	}
    
    	// Save that message
    	public function custom_message_save() {
    		add_action( 'save_post', array( $this, 'publicize_hashtags' ) );
    	}
    
    }
    
    new Hashtag_Jetpack();
    

    The only ‘catch’ you may stumble on is that I’m checking against the post type of post_type_shows – just change that as you need to.

    Voila! Instant hashtags.

    Oh and if you’re wondering why I didn’t put in a check for “Is Jetpack active…” the reason is that this is adding a post meta, and doesn’t actually depend on Jetpack being active at all. Will it ‘clutter up’ your database if Jetpack isn’t active? Yes. But it won’t break your site so it’s safe enough for me.

  • Light Fingered Fish

    Light Fingered Fish

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

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

    The Code

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

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

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

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

    Buuuuut what if you wanted to do it by slug?

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

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

        foreach ( $redirected_urls as $source => $destination ) {
            if ( strpos( $source, $slug ) !== false
                return $destination;
            }
        }
    
  • Customizing Jetpack Feedback

    Customizing Jetpack Feedback

    Fair bit of warning, this is a big code heavy post. I use Jetpack to handle a variety of things on my sites. Contact forms, stats, embeds, monitoring, backups, a bit of security (brute force prevention), and in some cases Photon and galleries. Oh and I especially use it for the tweeting and facebookieing of posts. Love it or hate it, it has it's uses and as a user, I find it easier to work with than its alternatives. However! Jetpack is not perfect. And my current drama llama was that I wanted to do two things:
    1. Show on my dashboard how many messages I had
    2. Mark a feedback message as 'answered' without deleting
    The second item was very important as on a shared-management type site, it's hard to know who did what and did everyone handle an email or what have you? Really a better tool would be something that you could reply to from within the admin dashboard, versus emailing all over, but that's another day. Instead, I decided to tackle a custom post status.

    This is NOT Fully Supported

    This is my caveat. My big warning. WordPress doesn't yet fully support Custom Post Status. That is, yes you can totally register them, but there's no easy way to put in the interface, as the trac ticket has been around since 2010 (you read that right) and it's still not done. All that said, if you're willing to wrangle a bit of Javascript and sanitize your outputs properly, you can do this.

    Big Block of Code

    <?php
    /*
    Description: Jetpack Customizations
    Version: 1.0
    */
    
    if ( ! defined('WPINC' ) ) die;
    
    /**
     * Customize_Jetpack_Feedback class.
     * Functions used by Jetpack to cutomize Feedback
     */
    class Customize_Jetpack_Feedback {
    
    	/**
    	 * Constructor
    	 * @since 1.0
    	 */
    	public function __construct() {
    		add_action( 'dashboard_glance_items', array( $this, 'dashboard_glance' ) );
    		add_action( 'admin_head', array( $this, 'dashboard_glance_css' ) );
    		
    		add_action( 'init', array( $this, 'custom_post_statuses' ), 0 );
    		add_filter( 'post_row_actions', array( $this, 'add_posts_rows' ), 10, 2);
    		add_action( 'plugins_loaded', array( $this, 'mark_as_answered' ) );
    		add_filter( 'display_post_states', array( $this, 'display_post_states' ) );
    		add_action( 'admin_footer-post.php', array( $this, 'add_archived_to_post_status_list' ) );
    		add_action( 'admin_footer-edit.php', array( $this, 'add_archived_to_bulk_edit' ) );
    	}
    
    	/**
    	 * Add custom post status for Answered
    	 * 
    	 * @access public
    	 * @return void
    	 * @since 1.0
    	 */
    	public function custom_post_statuses() {
    		register_post_status( 'answered', array(
    			'label'                     => 'Answered',
    			'public'                    => false,
    			'exclude_from_search'       => true,
    			'show_in_admin_all_list'    => true,
    			'show_in_admin_status_list' => true,
    			'label_count'               => _n_noop( 'Answered <span class="count">(%s)</span>', 'Answered <span class="count">(%s)</span>' ),
    		) );
    	}
    
    	/**
    	 * Add URL for replying to feedback.
    	 * 
    	 * @access public
    	 * @param mixed $actions
    	 * @param mixed $post
    	 * @return void
    	 * @since 1.0
    	 */
    	public function add_posts_rows( $actions, $post ) {
    		// Only for Feedback
    		if ( $post->post_type == 'feedback' ) {
    			$url = add_query_arg( 'answered_post_status-post_id', $post->ID );
    			$url = add_query_arg( 'answered_post_status-nonce', wp_create_nonce( 'answered_post_status-post_id' . $post->ID ), $url );
    	
    			// Edit URLs based on status
    			if ( $post->post_status !== 'answered' ) {
    				$url = add_query_arg( 'answered_post_status-status', 'answered', $url );
    				$actions['answered_link']  = '<a href="' . $url . '" title="Mark This Post as Answered">Answered</a>';
    			} elseif ( $post->post_status == 'answered' ){
    				$url = add_query_arg( 'answered_post_status-status', 'publish', $url );
    				$actions['answered']  = '<a class="untrash" href="' . $url . '" title="Mark This Post as Unanswered">Unanswered</a>';
    				unset( $actions['edit'] );
    				unset( $actions['trash'] );
    			}
    		}
    		return $actions;
    	}
    
    	/**
    	 * Add Answered to post statues
    	 * 
    	 * @access public
    	 * @param mixed $states
    	 * @return void
    	 * @since 1.0
    	 */
    	function display_post_states( $states ) {
    		global $post;
    
    		if ( $post->post_type == 'feedback' ) {
    			$arg = get_query_var( 'post_status' );
    			if( $arg != 'answered' ){
    				if( $post->post_status == 'answered' ){
    					return array( 'Answered' );
    				}
    			}
    		}
    
    		return $states;
    	}
    
    	/**
    	 * Process marking as answered
    	 * 
    	 * @access public
    	 * @return void
    	 * @since 1.0
    	 */
    	public function mark_as_answered() {
    
    		// If contact forms aren't active, we'll just pass
    		if ( class_exists( 'Jetpack' ) && Jetpack::is_module_active( 'contact-form' ) ) {
    
    			// Check Nonce
    			if ( isset( $_GET['answered_post_status-nonce'] ) && wp_verify_nonce( $_GET['answered_post_status-nonce'], 'answered_post_status-post_id' . $_GET['answered_post_status-post_id'] ) ) { 
    				// Check Current user Can and then process
    				if( current_user_can('publish_posts') && isset( $_GET['answered_post_status-status'] ) ) {
    					$GLOBALS[ 'wp_rewrite' ] = new wp_rewrite;
    		
    					$status  = sanitized_text_field( $_GET['answered_post_status-status'] );
    					$post_id = (int) $_GET['answered_post_status-post_id'];
    		
    					// If it's not a valid status, we have a problem
    					if ( !in_array( $status, array( 'answered', 'publish' ) ) ) die( 'ERROR!!!' );
    		
    					$answered = array( 'ID' => $post_id, 'post_status' => $status );
    					wp_update_post( $answered );
    				}
    			}
    
    		}
    	}
    
    
    	/**
    	 * add_archived_to_post_status_list function.
    	 * 
    	 * @access public
    	 * @return void
    	 * @since 1.0
    	 */
    	function add_archived_to_post_status_list(){
    		global $post;
    		$complete = $label = '';
    
    		// Bail if not feedback
    		if ( $post->post_type !== 'feedback' ) return;
    
    		if( $post->post_status == 'answered' ) {
    			echo '
    				<script>
    					jQuery(document).ready(function($){
    						$("#post-status-display" ).text("Answered");
    						$("select#post_status").append("<option value=\"answered\" selected=\"selected\">Answered</option>");
    						$(".misc-pub-post-status label").append("<span id=\"post-status-display\">Answered</span>");
    					});
    				</script>
    			';
    		} elseif ( $post->post_status == 'publish' ){
    			echo '
    				<script>
    					jQuery(document).ready(function($){
    						$("select#post_status").append("<option value=\"answered\" >Answered</option>");
    					});
    				</script>
    			';
    		}
    	} 
    
    	public function add_archived_to_bulk_edit() {
    		global $post;
    		if ( $post->post_type !== 'feedback' ) return;	
    		?>
    			<script>
    			jQuery(document).ready(function($){
    				$(".inline-edit-status select ").append("<option value=\"answered\">Answered</option>");
    				$(".bulkactions select ").append("<option value=\"answered\">Mark As Answered</option>");
    			});
    			</script>
    		<?php
    	}
    
    	/*
    	 * Show Feedback in "Right Now"
    	 *
    	 * @since 1.0
    	 */
    	public function dashboard_glance() {
    		if ( class_exists( 'Jetpack' ) && Jetpack::is_module_active( 'contact-form' ) ) {
    			foreach ( array( 'feedback' ) as $post_type ) {
    				$num_posts = wp_count_posts( $post_type );
    				$count_posts = ( isset( $num_posts->publish ) )? $num_posts->publish : '0';
    				if ( $count_posts !== '0' ) {
    					if ( 'feedback' == $post_type ) {
    						$text = _n( '%s Message', '%s Messages', $count_posts );
    					}
    					$text = sprintf( $text, number_format_i18n( $count_posts ) );
    					printf( '<li class="%1$s-count"><a href="edit.php?post_type=%1$s">%2$s</a></li>', $post_type, $text );
    				}
    			}
    		}
    	}
    
    	/*
    	 * Custom Icon for Feedback in "Right Now"
    	 *
    	 * @since 1.0
    	 */
    	public function dashboard_glance_css() {
    	if ( class_exists( 'Jetpack' ) && Jetpack::is_module_active( 'contact-form' ) ) {
    		?>
    		<style type='text/css'>
    			#adminmenu #menu-posts-feedback div.wp-menu-image:before, #dashboard_right_now li.feedback-count a:before {
    				content: '\f466';
    				margin-left: -1px;
    			}
    		</style>
    		<?php
    		}
    	}
    
    }
    
    new Customize_Jetpack_Feedback();
    
    What this does is create a new status for your feedback of “Answered”. Ta Dah!