darrinb http://darrinb.com Professional WordPress development since 2006 Mon, 25 Jan 2016 21:33:04 +0000 en-US hourly 1 Bootstrap Comments for WordPress http://darrinb.com/wp-bootstrap-comments/ http://darrinb.com/wp-bootstrap-comments/#respond Sun, 17 Jan 2016 16:44:04 +0000 http://darrinb.com/?p=1240 WP Bootstrap Comments for WordPress provides a comment walker class for building Bootstrap-compatible nested comment threads for WordPress posts. It merges the required markup and classes needed by Bootstrap with the classes and markup typically used by WordPress in the native comment walker class.

The post Bootstrap Comments for WordPress appeared first on darrinb.

]]>
WP Bootstrap Comments for WordPress provides a comment walker class for building Bootstrap-compatible nested comment threads for WordPress posts. It merges the required markup and classes needed by Bootstrap with the classes and markup typically used by WordPress in the native comment walker class.

When I decided to redesign this site earlier this year, I decided to go with Bootstrap as my front end framework. I find it easy to use, well-documented, and flexible enough for my needs. Being a “from scratch” kind of guy, I always build my themes from from the ground up–utilizing various frameworks and libraries–but always writing the markup myself. Given that I decided to use Bootstrap to handle the site’s responsive requirements, I set about integrating Bootstrap classes into native WordPress markup.

When it came time to build out the comments section for my posts, I admit I was stumped at first. Bootstrap has a Media component that handles styling for building various media lists (like comments), but it requires different markup than what WP does out-of-the-box. While Bootstrap structurally nests comments (where one comment is a child of another), WordPress only visually nests comments. While the Walker_Comment class may build individual comments, it doesn’t build a list of nested comments.

This is the markup the Walker_Comment class creates:

<div id="comment-19" class="comment odd alt thread-odd thread-alt depth-1 parent">
    <article id="div-comment-19" class="comment-body">
        <footer class="comment-meta">
            <div class="comment-author vcard">
                [avatar] 
                [author name]                
            </div>    
            <div class="comment-metadata">
                [comment timestamp]
                [edit link]                
            </div>    
        </footer>    
        <div class="comment-content">
            [comment content]
        </div>    
        <div class="reply">
           [reply link]
        </div>            
    </article>    
    <div id="comment-219" class="comment byuser comment-author-darrinb bypostauthor even depth-2">
        <article id="div-comment-219" class="comment-body">
            <footer class="comment-meta">
                <div class="comment-author vcard">
                    [avatar]
                    [author name]                
                </div>    
                <div class="comment-metadata">
                    [comment timestamp]
                    [edit link]                
                </div>    
            </footer>    
            <div class="comment-content">
                [comment content]
            </div>    
            <div class="reply">
                [reply link]
            </div>    
        </article>    
    </div>    
</div>

Note that each comment article tag is wrapped in a div tag and the child div tags are within the parent div tag, they’re not within the parent article tag. Bootstrap needs the child comments to be nested within the parent comment itself, not the comment’s outer wrapper tag. E.g., within the parent article tag, not the parent div tag.

This is the markup Bootstrap needs*:

<div class="media"> <!-- parent comment wrapper -->
    <div class="media-left">
        [avatar]
    </div>
    <div class="media-body"> <!-- parent comment content -->
        <h4 class="media-heading">[author]</h4>
        [comment content]
        <div class="media"> <!-- child comment wrapper -->
            <div class="media-left">
                [avatar]
            </div>
            <div class="media-body"> <!-- child comment content -->
                <h4 class="media-heading">[author]</h4>
                [comment content]
            </div>
        </div>
    </div>
</div>

Note how the child comments sits within the parent media-body tag. I needed to replicate this behavior in a WordPress comment list so I could utilize Bootstrap’s built-in classes. This proved more challenging than I originally thought given that the default walker outputs each comment in its entirety, and I needed a way to essentially inject a child comment within a parent comment. A quick Google search turned up walkers that build out Bootstrap-compatible comments in various forms, but none (that I could find) accounted for nested comments, so I decided to roll my own.

Enter the WP Bootstrap Comments plugin.

Technical Overview

(Click here to skip this section and go straight to the install steps.)

The WP_Bootstrap_Comments_Walker class was modeled closely off the core Walker_Comment class. It bridges native WordPress markup with Bootstrap styles allowing the use of the Bootstrap v3 framework.

WP_Bootstrap_Comments_Walker::start_el()

Method: Starts the comment output. Will check if the comment has children or is a stand-alone comment.

This is the method that starts building the comment. It determines what type of comment is being requested (pingback, trackback, html5, or the default xhtml) and calls the corresponding builder method. If it determines the comment being built has children, it will call the parent method: start_parent_html5_comment().

/**
 * Start the element output.
 *
 * This opens the comment.  Will check if the comment has children or is a stand-alone comment.
 *
 * @access public
 * @since 0.1.0
 *
 * @see Walker::start_el()
 * @see wp_list_comments()
 *
 * @global int        $comment_depth
 * @global WP_Comment $comment
 *
 * @param string $output  Passed by reference. Used to append additional content.
 * @param object $comment Comment data object.
 * @param int    $depth   Depth of comment in reference to parents.
 * @param array  $args    An array of arguments.
 */
public function start_el( &$output, $comment, $depth = 0, $args = array(), $id = 0 )
{
    $depth++;
    $GLOBALS['comment_depth'] = $depth;
    $GLOBALS['comment'] = $comment;
    if ( !empty( $args['callback'] ) ) {
        ob_start();
        call_user_func( $args['callback'], $comment, $args, $depth );
        $output .= ob_get_clean();
        return;
    }
    if ( ( 'pingback' == $comment->comment_type || 'trackback' == $comment->comment_type ) && $args['short_ping'] ) {
        ob_start();
        $this->ping( $comment, $depth, $args );
        $output .= ob_get_clean();
    } elseif ( 'html5' === $args['format'] ) {
        ob_start();
        if ( !empty( $args['has_children'] ) ) {
            $this->start_parent_html5_comment( $comment, $depth, $args );
        } else {
            $this->html5_comment( $comment, $depth, $args );
        }
        $output .= ob_get_clean();
    } else {
        ob_start();
        $this->comment( $comment, $depth, $args );
        $output .= ob_get_clean();
    }
}

WP_Bootstrap_Comments_Walker::end_el()

Method: Ends the comment output, if needed. Will check if the comment has children or is a stand-alone comment.

Similar to start_el(), this method determines if the comment has children or not. If it does, it calls the end_parent_html5_comment() method. If it doesn’t have children, it will simply output the required closing tag.

/**
 * Ends the element output, if needed.
 *
 * This ends the comment.  Will check if the comment has children or is a stand-alone comment.
 *
 * @access public
 * @since 0.1.0
 *
 * @see Walker::end_el()
 * @see wp_list_comments()
 *
 * @param string     $output  Passed by reference. Used to append additional content.
 * @param WP_Comment $comment The comment object. Default current comment.
 * @param int        $depth   Depth of comment.
 * @param array      $args    An array of arguments.
 */
public function end_el( &$output, $comment, $depth = 0, $args = array() )
{
    if ( !empty( $args['end-callback'] ) ) {
        ob_start();
        call_user_func( $args['end-callback'], $comment, $args, $depth );
        $output .= ob_get_clean();
        return;
    }
    if ( !empty( $args['has_children'] ) && 'html5' === $args['format']) {
        ob_start();
        $this->end_parent_html5_comment( $comment, $depth, $args );
        $output .= ob_get_clean();
    } else {
        if ( 'div' == $args['style'] ) {
            $output .= "</div><!-- #comment-## -->\n";
        } else {
            $output .= "</li><!-- #comment-## -->\n";
        }
    }
}

WP_Bootstrap_Comments_Walker::start_parent_html5_comment()

Method: Outputs the beginning of an html5 parent comment.

Since Bootstrap requires child comments to sit within their parent comment, this method writes the opening of an html5 comment and excludes the closing tags so children comments can be nested inside. It does this by calling the html5_comment() method with the $is_parent flag set to true.

/**
 * Output the beginning of a parent comment in the HTML5 format.
 *
 * Bootstrap media element requires child comments to be nested within the parent media-body.
 * The original comment walker writes the entire comment at once, this method writes the opening
 * of a parent comment so children comments can be nested within.
 *
 * @access protected
 * @since 0.1.0
 *
 * @see http://getbootstrap.com/components/#media
 * @see wp_list_comments()
 *
 * @param object $comment Comment to display.
 * @param int    $depth   Depth of comment.
 * @param array  $args    An array of arguments.
 */
protected function start_parent_html5_comment( $comment, $depth, $args )
{
    $this->html5_comment( $comment, $depth, $args, $is_parent = true );
}

WP_Bootstrap_Comments_Walker::html5_comment()

Method: Builds the comment markup.

Here we get into the meat of the plugin. This method outputs the majority of the markup needed to display an html5 comment.

/**
 * Output a comment in the HTML5 format.
 *
 * @access protected
 * @since 0.1.0
 *
 * @see wp_list_comments()
 *
 * @param object  $comment   Comment to display.
 * @param int     $depth     Depth of comment.
 * @param array   $args      An array of arguments.
 * @param boolean $is_parent Flag indicating whether or not this is a parent comment
 */
protected function html5_comment( $comment, $depth, $args, $is_parent = false )
{
    $tag = ( 'div' === $args['style'] ) ? 'div' : 'li';
    $type = get_comment_type();
    $comment_classes = array();
    $comment_classes[] = 'media';
    // if it's a parent
    if ( $this->has_children ) {
        $comment_classes[] = 'parent';
        $comment_classes[] = 'has-children';
    }
    // if it's a child
    if ( $comment->comment_parent > 0 ) {
        $comment_classes[] = 'child';
        $comment_classes[] = 'has-parent';
        $comment_classes[] = 'parent-' . $comment->comment_parent;
    }
    $comment_classes = apply_filters( 'wp_bootstrap_comment_class', $comment_classes, $comment, $depth, $args );
    $class_str = implode(' ', $comment_classes);
?>
    <<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( $class_str, $comment ); ?>>

        <article id="div-comment-<?php comment_ID(); ?>" class="comment-body">

            <?php if ( 0 != $args['avatar_size'] && 'pingback' !== $type && 'trackback' !== $type ) { ?>
                <div class="media-left">
                    <?php echo $this->get_comment_author_avatar( $comment, $args ); ?>
                </div>
            <?php }; ?>

            <div class="media-body">

                <footer class="comment-meta">
                    <div class="comment-author vcard">
                        <?php printf( __( '%s <span class="says sr-only">says:</span>' ), sprintf( '<b class="media-heading fn">%s</b>', get_comment_author_link( $comment ) ) ); ?>
                    </div><!-- /.comment-author -->

                    <div class="comment-metadata">
                        <a href="<?php echo esc_url( get_comment_link( $comment, $args ) ); ?>">
                            <time datetime="<?php comment_time( 'c' ); ?>">
                                <?php
                                /* translators: 1: comment date, 2: comment time */
                                printf( __( '%1$s at %2$s' ), get_comment_date( '', $comment ), get_comment_time() );
                                ?>
                            </time>
                        </a>
                        <?php edit_comment_link( __( 'Edit' ), '<span class="edit-link">', '</span>' ); ?>
                    </div><!-- /.comment-metadata -->

                    <?php if ( '0' == $comment->comment_approved ) : ?>
                        <p class="comment-awaiting-moderation"><?php _e( 'Your comment is awaiting moderation.' ); ?></p>
                    <?php endif; ?>
                </footer><!-- /.comment-meta -->

                <div class="comment-content">
                    <?php comment_text(); ?>
                </div><!-- /.comment-content -->

                <?php $this->comment_reply_link( $comment, $depth, $args, $add_below = 'reply-comment' ); ?>

                <?php if ( $is_parent ) { ?>
                    <div class="child-comments">
                <?php } else { ?>
                        </div><!-- /.media-body -->
                    </article><!-- /.comment-body -->
                <?php } ?>

<?php
}

It begins by determining which wrapper element to generate; either a div or an li tag.

<<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( $class_str, $comment ); ?>>

Following this, it determines which classes to apply to this wrapper element by checking the $this->has_children $comment->comment_parent properties. These classes are then filterable by applying the wp_bootstrap_comment_class filter to the $comment_classes array prior to generating the classes string.

$comment_classes = apply_filters( 'wp_bootstrap_comment_class', $comment_classes, $comment, $depth, $args );

Digging further, the method checks if the avatar_size argument was passed in, and if it has (and this comment is not a ping/trackback), builds out the avatar section.

<?php if ( 0 != $args['avatar_size'] && 'pingback' !== $type && 'trackback' !== $type ) { ?>
    <div class="media-left">
        <?php echo $this->get_comment_author_avatar( $comment, $args ); ?>
    </div>
<?php }; ?>

The next section builds out the content section of the comment. This is where the author name, time stamp, and actual comment are displayed.

<div class="media-body">

    <footer class="comment-meta">
        <div class="comment-author vcard">
            <?php printf( __( '%s <span class="says sr-only">says:</span>' ), sprintf( '<b class="media-heading fn">%s</b>', get_comment_author_link( $comment ) ) ); ?>
        </div><!-- /.comment-author -->

        <div class="comment-metadata">
            <a href="<?php echo esc_url( get_comment_link( $comment, $args ) ); ?>">
                <time datetime="<?php comment_time( 'c' ); ?>">
                    <?php
                    /* translators: 1: comment date, 2: comment time */
                    printf( __( '%1$s at %2$s' ), get_comment_date( '', $comment ), get_comment_time() );
                    ?>
                </time>
            </a>
            <?php edit_comment_link( __( 'Edit' ), '<span class="edit-link">', '</span>' ); ?>
        </div><!-- /.comment-metadata -->

        <?php if ( '0' == $comment->comment_approved ) : ?>
            <p class="comment-awaiting-moderation"><?php _e( 'Your comment is awaiting moderation.' ); ?></p>
        <?php endif; ?>
    </footer><!-- /.comment-meta -->

    <div class="comment-content">
        <?php comment_text(); ?>
    </div><!-- /.comment-content -->

    <?php $this->comment_reply_link( $comment, $depth, $args, $add_below = 'reply-comment' ); ?>

It’s all pretty standard up to this point; mostly just a re-write of the original Walker_Comment class and adding in Bootstrap classes where needed. The section after the reply link ($this->comment_reply_link()) is where the Bootstrap nesting begins. Here we check if the comment has children by checking if the $is_parent flag is set to true. Remember, this was set by the start_parent_html5_comment() method. If it is, we open the child comments div. If not, we close out the comment.

<?php if ( $is_parent ) { ?>
    <div class="child-comments">
<?php } else { ?>
        </div><!-- /.media-body -->
    </article><!-- /.comment-body -->
<?php } ?>

WP_Bootstrap_Comments_Walker::end_parent_html5_comment()

Method: Outputs the end of an html5 parent comment.

Here we close out the parent html5 comment. This outputs the closing child comments section, the parent media body section, the parent article tag, and the parent wrapper element.

This method is called by WP_Bootstrap_Comments_Walker::end_el().

/**
 * Output the end of a parent comment in the HTML5 format.
 *
 * Bootstrap media element requires child comments to be nested within the parent media-body.
 * The original comment walker writes the entire comment at once, this method writes the end
 * of a parent comment so child comments can be nested within.
 *
 * @see http://getbootstrap.com/components/#media
 *
 * @access protected
 * @since 0.1.0
 *
 * @see wp_list_comments()
 *
 * @param object $comment Comment to display.
 * @param int    $depth   Depth of comment.
 * @param array  $args    An array of arguments.
 */
protected function end_parent_html5_comment( $comment, $depth, $args )
{
    $tag = ( 'div' === $args['style'] ) ? 'div' : 'li';
?>
                </div><!-- /.child-comments -->
            </div><!-- /.media-body -->
        </article><!-- /.comment-body -->
    </<?php echo $tag; ?>><!-- /.parent -->

<?php
}

WP_Bootstrap_Comments_Walker::ping()

Method: Outputs a pingback comment.

Pingbacks and trackbacks are not forgotten! This method builds out a pingback comment with Bootstrap classes applied.

Note: This will only be used if the short_ping argument is passed to the wp_list_comments() function in your comments.php template. If the short_ping argument is not passed, a full comment will be built using either the WP_Bootstrap_Comments_Walker::html5_comment() method or the Walker_Comment::comment() method.

/**
 * Output a pingback comment
 *
 * @access protected
 * @since 0.1.0
 *
 * @see wp_list_comments()
 *
 * @param WP_Comment $comment The comment object.
 * @param int        $depth   Depth of comment.
 * @param array      $args    An array of arguments.
 */
protected function ping( $comment, $depth, $args ) {
    $tag = ( 'div' == $args['style'] ) ? 'div' : 'li';
    $comment_classes = array();
    $comment_classes[] = 'media';
    $comment_classes = apply_filters( 'wp_bootstrap_comment_class', $comment_classes, $comment, $depth, $args );
    $class_str = implode(' ', $comment_classes);
?>
    <<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( $class_str, $comment ); ?>>
        <div class="comment-body">
            <div class="media-body">
                <?php _e( 'Pingback:' ); ?> <?php comment_author_link( $comment ); ?> <?php edit_comment_link( __( 'Edit' ), '<span class="edit-link">', '</span>' ); ?>
            </div><!-- /.media-body -->
        </div><!-- /.comment-body -->
<?php
}

WP_Bootstrap_Comments_Walker::get_comment_author_avatar()

Method: Generate avatar markup

This method builds the markup for displaying the avatar. If an author url can be retrieved, the avatar is wrapped in a link to the comment author’s url. If no url is retrieved only the avatar is displayed.

/**
 * Generate avatar markup
 *
 * @access protected
 * @since 0.1.0
 *
 * @param object $comment Comment to display.
 * @param array  $args    An array of arguments.
 */
protected function get_comment_author_avatar( $comment, $args )
{
    $avatar_string = get_avatar( $comment, $args['avatar_size'] );
    $comment_author_url = get_comment_author_url( $comment );
    if ( '' !== $comment_author_url ) {
        $avatar_string = sprintf(
            '<a href="%1$s" class="author-link url" rel="external nofollow">%2$s</a>',
            esc_url($comment_author_url),
            $avatar_string
        );
    };
    return $avatar_string;
}

Method: Generate markup for the comment reply link

This method generates the markup to display the comment reply link.

Note: this differs from the standard comment reply link in that the link will append the comment form after the actual reply link, not the comment itself. The default behavior of placing the form after the comment wrapper div results in a poor user experience on comments that have a lot of replies; it pushes the form too far down to see the actual comment you’re replying to. This method places the form directly below the reply link, where you can still see the comment you’re replying to.

/**
 * Displays the HTML content for reply to comment link.
 *
 * @access protected
 * @since 0.1.0
 *
 * @param object $comment   Comment being replied to. Default current comment.
 * @param int    $depth     Depth of comment.
 * @param array  $args      An array of arguments for the Walker Object
 * @param string $add_below The id of the element where the comment form will be placed
 */
protected function comment_reply_link( $comment, $depth, $args, $add_below = 'div-comment' )
{
    $type = get_comment_type();
    if ( 'pingback' === $type || 'trackback' === $type ) {
        return;
    }
    comment_reply_link( array_merge( $args, array(
        'add_below' => $add_below,
        'depth'     => $depth,
        'max_depth' => $args['max_depth'],
        'before'    => '<div id="reply-comment-'.$comment->comment_ID.'" class="reply">',
        'after'     => '</div>'
    ) ) );
}

The Resulting Markup

Once activated the resulting markup will look similar to this:

<div id="comment-01" class="comment odd alt thread-odd thread-alt depth-1 media parent has-children">

    <article id="div-comment-01" class="comment-body">

        <div class="media-left">
            <a href="http://author-url.com" class="author-link url" rel="external nofollow">
                <img alt='' src='http://gravatar-url.com' srcset='http://gravatar-url.com?s=72&amp;d=mm&amp;r=g 2x' class='avatar media-object img-circle avatar-36 photo' height='36' width='36' />
            </a>
        </div>

        <div class="media-body">

            <footer class="comment-meta">
                <div class="comment-author vcard">
                    <b class="media-heading fn"><a href='http://author-url.com' rel='external nofollow' class='url'>Author</a></b> <span class="says sr-only">says:</span>
                </div>
                <div class="comment-metadata">
                    <a href="http://post-url.com/#comment-01"> <time datetime="0000-00-00T00:00:00+00:00"> Month 01, YEAR at H:mm pm </time> </a>
                </div>
            </footer>

            <div class="comment-content">
                <p>Comment text.</p>
            </div>

            <div id="reply-comment-01" class="reply">
                <a rel='nofollow' class='comment-reply-link' href='http://post-url.com/?replytocom=19#respond' onclick='return addComment.moveForm( "reply-comment-01", "19", "respond", "543" )' aria-label='Reply to Author'>Reply</a>
            </div>

            <div class="child-comments">

                <div id="comment-02" class="comment byuser comment-author-postauthor bypostauthor even depth-2 media child has-parent parent-01">
                    <article id="div-comment-02" class="comment-body">
                        <div class="media-left">
                            <img alt='' src='http://gravatar-url.com' srcset='http://gravatar-url.com?s=72&amp;d=mm&amp;r=g 2x' class='avatar media-object img-circle avatar-36 photo' height='36' width='36' />
                        </div>
                        <div class="media-body">
                            <footer class="comment-meta">
                                <div class="comment-author vcard">
                                    <b class="media-heading fn">Author</b> <span class="says sr-only">says:</span>
                                </div>
                                <div class="comment-metadata">
                                    <a href="http://post-url.com/#comment-02"> <time datetime="0000-00-00T00:00:00+00:00"> Month 01, YEAR at H:mm pm </time> </a>
                                </div>
                            </footer>
                            <div class="comment-content">
                                <p>Comment text.</p>
                            </div>
                        </div>
                    </article>
                </div>

                <div id="comment-03" class="comment odd alt depth-2 media child has-parent parent-01">
                    <article id="div-comment-03" class="comment-body">
                        <div class="media-left">
                            <a href="http://author-url.com" class="author-link url" rel="external nofollow">
                                <img alt='' src='http://gravatar-url.com' srcset='http://gravatar-url.com?s=72&amp;d=mm&amp;r=g 2x' class='avatar media-object img-circle avatar-36 photo' height='36' width='36' />
                            </a>
                        </div>
                        <div class="media-body">
                            <footer class="comment-meta">
                                <div class="comment-author vcard">
                                    <b class="media-heading fn"><a href='http://author-url.com' rel='external nofollow' class='url'>Author</a></b> <span class="says sr-only">says:</span>
                                </div>
                                <div class="comment-metadata">
                                    <a href="http://post-url.com/#comment-03"> <time datetime="0000-00-00T00:00:00+00:00"> Month 01, YEAR at H:mm pm </time> </a>
                                </div>
                            </footer>
                            <div class="comment-content">
                                <p>Comment text.</p>
                            </div>
                        </div>
                    </article>
                </div>

            </div><!-- /.child-comments -->

        </div><!-- /.media-body -->

    </article><!-- /.comment-body -->

</div><!-- /.parent -->

As you can see, the resulting markup follows the Bootstrap markup convention of giving each comment wrapper a class of media, a media-left div for avatars, and a media-body div for the comment content. It also adds the media-heading class on the b tag within the comment-author div. And all child comments are nested within the media-body div of their respective parent comments. This markup allows you to use native Bootstrap v3 styling while still maintaining (mostly) native WordPress comment structure.

Installation

The plugin is awaiting review by the plugin team at the WordPress.org repo, but in the meantime, you can download and install off of GitHub. Just follow the instructions below.

From GitHub:

  1. Download the latest stable version.
  2. Extract the zip folder to your plugins directory.
  3. Activate in the “Plugins” area of your admin by clicking the “Activate” link.
  4. No further setup or configuration is necessary.

Usage

Using it out of the box is really straight-forward. Just add a call to the class in the walker argument of wp_list_comments() in your comments.php template. Like so:

wp_list_comments( array(
    'style'       => 'div',
    'short_ping'  => true,
    'avatar_size' => 42,
    'walker' => new WP_Bootstrap_Comments_Walker(),
) );

If you’re using the ol or ul style arguments you can use Boostrap’s native media styles by adding the media-list class to your list element like so:

<ul class="media-list">
    <?php
        wp_list_comments( array(
            'style'       => 'ul',
            'short_ping'  => true,
            'avatar_size' => 42,
            'walker' => new WP_Bootstrap_Comments_Walker(),
        ) );
    ?>
</ul><!-- /.media-list -->

Notes & Resources

You can check out the full project on GitHub here: WP Bootstrap Comments

The following are various functions, methods, documents mentioned throughout this post. They’re compiled here for easy reference.

  • * Bootstrap does not specifically need an h4 tag here, just the media-heading class.

The post Bootstrap Comments for WordPress appeared first on darrinb.

]]>
http://darrinb.com/wp-bootstrap-comments/feed/ 0
The WordPress MU-Plugins Directory: The Site Configuration File http://darrinb.com/the-wordpress-mu-plugins-directory-the-site-configuration-file/ http://darrinb.com/the-wordpress-mu-plugins-directory-the-site-configuration-file/#respond Sat, 19 Dec 2015 13:40:47 +0000 http://darrinb.com/?p=964 In the continuing series on the MU Plugins directory, we’re going to focus on the siteconfig.php file. In the sites…

The post The WordPress MU-Plugins Directory: The Site Configuration File appeared first on darrinb.

]]>
In the continuing series on the MU Plugins directory, we’re going to focus on the siteconfig.php file. In the sites I develop, I use the siteconfig.php file as sort of a master theme “functions.php” file. Similar to a theme’s functions.php file, we can use a siteconfig.php file to house functions needed by the site in order to operate properly. The difference between the two is the siteconfig.php file–by being loaded from the mu-plugins directory–will run all its functions regardless of what theme is installed on the site, freeing you up to switch themes without losing certain functionality.

I use the siteconfig.php file to load custom post-types, metaboxes, taxonomies, and widgets that I use. I also use it to load certain functions that I like to have available regardless of the theme installed. Items such as these are needed regardless of the theme installed because they’re used to manage the content on your site, whereas a theme is used to display the content on your site.

For the purposes of this article though, let’s concentrate on the functions that I load.

Site Setup

This function is taken pretty much straight from the default theme and adds support for post thumbnails and automatic feed links. It also adds an excerpt metabox for page post-types. It’s called on the “after_setup_theme” action hook.

View this code snippet on GitHub.

Custom Body Classes

This function adds custom body classes to the <body> element. There’s a separate article outlining it here: Customizing the WordPress body_class()function.

Custom Post Classes

This function adds custom post classes to the your post element by hooking into the WordPress post_class filter. It’s helpful by allowing you to style posts based on unique classes.

View this code snippet on GitHub.

Email Filters

I use these two functions to filter the from email address, and the from name for emails sent from the site.

View this code snippet on GitHub.

Include All Post Types in post_tag Taxonomy Queries

I use this function on some of my corporate client sites. What this does is tells WordPress to search on all post types when doing a post_tag taxonomy query. Without this, WP will only look for post tags (“tags”) on the “post” post-type (as opposed to including custom post types). It still checks to see if a specific post-type is being requested though. Note: you have to enable the post_tag taxonomy on other post types in order for this to work.

View this code snippet on GitHub.

Include All Post Types in category Taxonomy Queries

Similar to the post_tag query, WordPress only searches on “post” post-types when doing a category (“Category”) taxonomy term search. This function tells WordPress to include all post-types in its search. Same as the post_tag fix above, you must enable the category taxonomy on other post-types in order for this to work.

View this code snippet on GitHub.

Speaking of registering the post_tag and category taxonomies…

Adding post_tag Taxonomy Support for all Post-types

Include this to have the ability to add “tags” to any content type registered on your site. (Notice the priority call of “99”, it means it will fire after your custom post-types have been registered.)

View this code snippet on GitHub.

Adding category Taxonomy Support for all Post-types

Include this to have the ability to add a Category to any content type registered on your site. (Notice the priority call of “99”, it means it will fire after your custom post-types have been registered.)

View this code snippet on GitHub.

And there you have it, some of the standard functions I include in my siteconfig.php file in my mu-plugins directory. There are a few more I include, highly-customized ones for various client installs, but these are the standard ones I include in all my WordPress installs. The only items not mentioned are the functions that load and register site-wide styles and javascripts. (That’s the next article.) One thing to note, since all of these functions are called on various WordPress action hooks/filters, they could (and should) be in a Class to prevent possible function conflicts. I’ll cover that in a later post.

The post The WordPress MU-Plugins Directory: The Site Configuration File appeared first on darrinb.

]]>
http://darrinb.com/the-wordpress-mu-plugins-directory-the-site-configuration-file/feed/ 0
The WordPress MU-Plugins Directory: An Overview http://darrinb.com/the-wordpress-mu-plugins-directory-an-overview/ http://darrinb.com/the-wordpress-mu-plugins-directory-an-overview/#respond Thu, 17 Dec 2015 01:21:18 +0000 http://dbdb.localhost/?p=99 I’ve found using this directory an indispensable way for organizing my installs, maintaining separation of theme (display) functionality from site functionality, and adding more one level of insurance a less-technical user can’t accidentally break or disrupt the operation of the site.

The post The WordPress MU-Plugins Directory: An Overview appeared first on darrinb.

]]>
When I first started using the mu-plugins directory on my WordPress projects, I mainly used it to house my must-use plugins that the site required in order to function properly. I did this mainly to ensure my clients wouldn’t accidentally deactivate or (worse) delete a plugin and inadvertently break their site. So in went various meta boxes, custom post types, and custom taxonomies.

In talking with other developers I was asked, why not just stick those in the theme’s functions.php file? The short answer, the sites I work on tend to be internal sites for large corporations who frequently change their theme up. Housing site-dependent files in the functions.php file is just asking for trouble. A theme’s functions.php file should only house functions that are specific to that theme. I’m a big believer in function separation/organization. Yes, you can house functionality in a theme, but I personally think any functionality that does not directly relate to the appearance of the site’s content should be deployed by a plugin.

There is a common misconception among WordPress devs and users that too many plugins will slow a site down. Only poorly-developed plugins will slow your site down. Plugin development tends to take a backseat to Theme development in the WordPress community. Maybe it’s because it seems more daunting since it’s a bit more technical than themes in that it’s more php and action/filter hooks rather than HTML and CSS. Or maybe it’s because Themes are much more tangible; you can visually see your changes to your Themes, whereas adding an id attribute to the body element is hidden under the hood.

Tangent. Back on track.

So a good way to extract functionality (that your site needs in order to work properly) out of your theme, but still ensure its use, is the mu-plugins directory. If you haven’t heard of it before, not to worry, a lot of people in the WP community haven’t. It’s not even created in a standard install of WordPress, you have to manually add it in your wp-content directory. It goes in the same level as your plugins and themes directories, like so:

/wp-content/mu-plugins
/wp-content/plugins
/wp-content/themes

Some of the cool benefits of this directory are:

  • You don’t need to activate them, they run automatically.
  • Your other plugins can use hooks in your mu plugins since they load before normal plugins.
  • Your admin users and clients can’t accidentally turn off functionality that’s needed to run your site.

Some downsides (though I don’t really consider these downsides):

  • They can’t be turned off on the plugin screen in the back end. (Though they are listed there.)
  • They don’t show update notifications.
  • They don’t trigger activation hooks (since they aren’t activated or deactivated).
  • WordPress doesn’t check nested directories within the MU directory.

I won’t go into too detailed of an overview in this post, but you can find a detailed description in the Codex here: Must Use Plugins. In it’s most basic use, you can upload single-level plugins (i.e., one file) to this directory, and WordPress will automatically enable it during execution. In fact, WordPress will automatically execute any PHP code it finds in this directory, so be careful what you upload.

In addition to using this directory to house my files for custom post types, taxonomies, and metaboxes, I also use it to house libraries of individual functions that the site would/could use regardless of which theme is installed. (The body element id function listed above being one of them). I do this by using a master site file I call “siteconfig.php”. I’ll cover that in another post, but you can think of it similar to a theme’s functions.php file.

In some upcoming posts I’ll cover the following topics:

  • Loading a siteconfig.php file.
  • Loading nested plugins (to keep this directory organized).
  • Loading site-wide styles and scripts.

Stay tuned!

The post The WordPress MU-Plugins Directory: An Overview appeared first on darrinb.

]]>
http://darrinb.com/the-wordpress-mu-plugins-directory-an-overview/feed/ 0
The WordPress MU-Plugins Directory: An Overview http://darrinb.com/the-wordpress-mu-plugins-directory-an-overview-2/ http://darrinb.com/the-wordpress-mu-plugins-directory-an-overview-2/#respond Sun, 24 Feb 2013 23:45:20 +0000 http://darrinb.com/?p=928 When I first started using the mu-plugins directory on my WordPress projects, I mainly used it to house my must-use…

The post The WordPress MU-Plugins Directory: An Overview appeared first on darrinb.

]]>
When I first started using the mu-plugins directory on my WordPress projects, I mainly used it to house my must-use plugins that the site required in order to function properly. I did this mainly to ensure my clients wouldn’t accidentally deactivate or (worse) delete a plugin and inadvertently break their site. So in went various meta boxes, custom post types, and custom taxonomies.

In talking with other developers I was asked, why not just stick those in the theme’s functions.php file? The short answer, the sites I work on tend to be internal sites for large corporations who frequently change their theme up. Housing site-dependent files in the functions.php file is just asking for trouble. A theme’s functions.php file should only house functions that are specific to that theme. I’m a big believer in function separation/organization. Yes, you can house functionality in a theme, but I personally think any functionality that does not directly relate to the appearance of the site’s content should be deployed by a plugin.

There is a common misconception among WordPress devs and users that too many plugins will slow a site down. Only poorly-developed plugins will slow your site down. Plugin development tends to take a backseat to Theme development in the WordPress community. Maybe it’s because it seems more daunting since it’s a bit more technical than themes in that it’s more php and action/filter hooks rather than HTML and CSS. Or maybe it’s because Themes are much more tangible; you can visually see your changes to your Themes, whereas adding an id attribute to the body element is hidden under the hood.

Tangent. Back on track.

So a good way to extract functionality (that your site needs in order to work properly) out of your theme, but still ensure its use, is the mu-plugins directory. If you haven’t heard of it before, not to worry, a lot of people in the WP community haven’t. It’s not even created in a standard install of WordPress, you have to manually add it in your wp-content directory. It goes in the same level as your plugins and themes directories, like so:

/wp-content/mu-plugins
/wp-content/plugins
/wp-content/themes

Some of the cool benefits of this directory are:

  • You don’t need to activate them, they run automatically.
  • Your other plugins can use hooks in your mu plugins since they load before normal plugins.
  • Your admin users and clients can’t accidentally turn off functionality that’s needed to run your site.

Some downsides (though I don’t really consider these downsides):

  • They can’t be turned off on the plugin screen in the back end. (Though they are listed there.)
  • They don’t show update notifications.
  • They don’t trigger activation hooks (since they aren’t activated or deactivated).
  • WordPress doesn’t check nested directories within the MU directory.

I won’t go into too detailed of an overview in this post, but you can find a detailed description in the Codex here: Must Use Plugins. In it’s most basic use, you can upload single-level plugins (i.e., one file) to this directory, and WordPress will automatically enable it during execution. In fact, WordPress will automatically execute any PHP code it finds in this directory, so be careful what you upload.

In addition to using this directory to house my files for custom post types, taxonomies, and metaboxes, I also use it to house libraries of individual functions that the site would/could use regardless of which theme is installed. (The body element id function listed above being one of them). I do this by using a master site file I call “siteconfig.php”. I’ll cover that in another post, but you can think of it similar to a theme’s functions.php file.

I’ve found using this directory an indispensable way for organizing my installs, maintaining separation of theme (display) functionality from site functionality, and adding more one level of insurance a less-technical user can’t accidentally break or disrupt the operation of the site.

In some upcoming posts I’ll cover the following topics:

  • Loading a siteconfig.php file.
  • Loading nested plugins (to keep this directory organized).
  • Loading site-wide styles and scripts.

Stay tuned!

The post The WordPress MU-Plugins Directory: An Overview appeared first on darrinb.

]]>
http://darrinb.com/the-wordpress-mu-plugins-directory-an-overview-2/feed/ 0
Git Commands http://darrinb.com/git-commands/ http://darrinb.com/git-commands/#respond Tue, 15 Jan 2013 19:13:24 +0000 http://darrinb.com/?p=930 Just a running note of git commands for future reference.

The post Git Commands appeared first on darrinb.

]]>
Update the origin url for your current git project:
$ git config remote.origin.url
Show current project config:
$ git config -l

will return something similar to:

user.name=[user name]
user.email=[user email]
credential.helper=osxkeychain
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
core.precomposeunicode=false
remote.origin.url=[remote url, either https:// or ssh]
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master
Checkout a remote branch:
$ git checkout -b [local directory] origin/[name of remote branch]
Temporarily stop tracking changes:
$ git update-index --assume-unchanged [path-to-file]
Re-start tracking changes:
$ git update-index --no-assume-unchanged [path-to-file]
To untrack a file that has already been add/initialized. I.e., to stop tracking the file, but not delete it:
$ git rm --cached [path-to-file/filename]
Delete a local branch:
$ git branch -D [name of branch]
Delete remote branch
$ git push origin --delete [name of branch] 
$ git push origin :[name of branch]
Remove a Directory (and its files) once it’s been added
git rm -r --cached bin/
(useful when forgetting to add wp-content/uploads to .gitignore

The post Git Commands appeared first on darrinb.

]]>
http://darrinb.com/git-commands/feed/ 0
Including a Template File from Your WordPress Plugin http://darrinb.com/including-a-template-file-from-your-wordpress-plugin/ http://darrinb.com/including-a-template-file-from-your-wordpress-plugin/#respond Wed, 06 Jun 2012 18:31:27 +0000 http://darrinb.com/?p=918 Just some sample code for future reference.

The post Including a Template File from Your WordPress Plugin appeared first on darrinb.

]]>
add_filter('template_include', 'load_plugin_template'); function load_plugin_template($template){ global $wp_query; $id = (int) $wp_query->get_queried_object_id(); $new_template = get_post_meta($id, '_fvt_template', true); if( 'y' == $new_template ) $template = dirname(__FILE__) . '/template.php'; return $template; }

The post Including a Template File from Your WordPress Plugin appeared first on darrinb.

]]>
http://darrinb.com/including-a-template-file-from-your-wordpress-plugin/feed/ 0
WP True Typed — a WordPress Plugin to Fight Comment Spam http://darrinb.com/wp-true-typed/ http://darrinb.com/wp-true-typed/#comments Sat, 03 Jul 2010 20:43:27 +0000 http://darrinb.com/?p=756 This is the official plugin page for WP True Typed, an anti-spam plugin to protect your site from comment spam.

The post WP True Typed — a WordPress Plugin to Fight Comment Spam appeared first on darrinb.

]]>
As any author/site admin knows, fighting comment spam is one of the biggest responsibilities of maintaining a website. You want interactivity with your reader base and site visitors, but comment spam wastes your valuable time and resources. There are plenty of front-line and “back of the house” tools available though, to help combat comment spam.

Front-line tools are those that are placed directly in the comment form and require the comment author to answer some sort of challenge. Captcha-based challenges use images–of either words or objects–and require the comment author to type in what they see, while math-based challenges require the comment author to solve an equation. Back of the house tools work silently behind the scenes and either employ the use of javascripts, cookies, css, etc., or web-based services such as Akismet.

WordPress has quite a few internal spam checks, from comment moderation to blacklists to link checks. Akismet is installed (though not activated) by default in all new installations as well. The Codex has an article outlining these internal spam checks and is a must-read. You can check it out here: Combating Comment Spam.

But like the article points out:

There is no “one size fits all” method that will protect your comments; spammers use many tactics. Consider using multiple defenses. Remember spammers change the way they attack so you must keep your choices updated.

The challenge for site admins lies in preventing comment spam without inconveniencing legitimate commenters. And for some reason, many people consider answering a challenge question of any sort–captcha-based or otherwise–an inconvenience. Granted, a poorly-conceived challenge can be an inconvenience, but a well developed one shouldn’t be. It just becomes another field to fill in on the form. After all, you’re already required to enter name, email, comment text, etc.

The challenge for anti-spam developers lies in creating a tool that is challenging enough to catch spam, but one that is still accessible, easy to understand, and doesn’t require too much from the commenter. That was the thought behind developing WP True Typed.

WP True Typed dynamically creates an anti-spam challenge question based on the post or page the
comment form is on, and prevents the comment from being processed if answered incorrectly. It works as
a first line of defense in battling comment spam.

Great, but what does it actually do?

WP True Typed scans the page the comment form is on, grabs the title of that page, and asks the commenter to enter information about the title. Specifically, the first word of the title.

For the developers and tech geeks among you, keep reading to see how it works.

If you just want to install the plugin and move on, click here to skip this section.

The Code Explained

We’re going to break the plugin down into its various functions and examine how each works. WP True Typed uses a lot of WordPress’ internal comment functions and author-validation functions. We build upon these and hook onto various action/filter hooks throughout the commenting process; starting with the comment author.

Do You Validate?

The first function of WP True Typed is to check whether or not the commenter is a registered user and signed in, or if he’s already had a comment approved. If so, the validation process isn’t even loaded, and the commenter will be able to avoid dealing with validation at all. This doesn’t affect the site’s discussion settings. If you still have “An administrator must always approve the comment” set in your Dashboard, each comment will still have to be approved. All this setting does is determine whether or not the commenter has to do the validation.

function ttype_author_can_comment() {
    if ( is_user_logged_in() ) {
        return true;    
    } else {
        global $wpdb;
        $author = '';
        $email = '';
        $author .= ( isset($_COOKIE['comment_author_'.COOKIEHASH]) ) ? stripslashes($_COOKIE['comment_author_' . COOKIEHASH]) : '';
        $email .= ( isset($_COOKIE['comment_author_email_'.COOKIEHASH]) ) ? stripslashes($_COOKIE['comment_author_email_' . COOKIEHASH]) : '';
        if ( '' != $author && '' != $email ) {
            $ok_to_comment = $wpdb->get_var("SELECT comment_approved FROM $wpdb->comments WHERE comment_author = '$author' AND comment_author_email = '$email' and comment_approved = '1' LIMIT 1");
            if ( (1 == $ok_to_comment) ) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
}

Create the Gate

The next function creates the actual validation field for your comment form. Since the default comment form changed in WordPress 3.0, this function first checks what version you’re running and outputs the validation field accordingly. Version 3.0 saw numerous changes to the form, one of which was adding a class attribute on each enclosing paragraph. WP True Typed follows the naming convention and markup structure of the default form and adds a class of “comment-form-validation” to the paragraph enclosing the validation input field and label. This class is also used to apply some styling via the plugin’s included style sheet; style_truetyped.css.

function ttype_add_validation_field() {
    $wp_version = absint(get_bloginfo('version'));
    if ( $wp_version < 3 ) { ?>
        <p class="comment-form-validation">
            <label for="validate"><small>Validation (Enter <b>first word</b> of <b> post title</b>, no characters or numbers.)(required)</small></label>
            <input id="validate" type="text" size="22" value="" name="validate" aria-required="true" tabindex="6" />
        </p>
    <?php } else { ?>
        <p class="comment-form-validation">
            <label for="validate">Validation (Enter the <b>first word</b> of the <b> post title</b>, no characters or numbers.)</label><span class="required">*</span>
            <input id="validate" type="text" size="30" value="" name="validate" aria-required="true" />
        </p>
    <?php };
};

Here’s what it looks like in the WP 3.0 default installation:

And in versions prior to WP 3.0 (again, default installation):

“C” is For “Cookie” As In, Comment Cookie

With the next function we’re modifying the comment form textarea. This will only work with WP 3.0 and higher since it uses a filter hook introduced in this version; (comment_form_field_comment). What we’re doing is storing the value of the comment text in a cookie and then displaying it in the textarea if the commenter doesn’t pass validation. This is one way WP True Typed addresses the whole “inconvenience” argument; if a legitimate commenter accidentally enters the wrong validation, this plugin remembers his comment and re-displays it when he’s redirected back to the form.

function ttype_comment_comment_field() {
    $comment_field = '<p class="comment-form-comment"><label for="comment">' . _x( 'Comment', 'noun' ) . '</label><textarea id="comment" name="comment" cols="45" rows="8" aria-required="true">'. esc_attr($_COOKIE['comment_author_comment_' . COOKIEHASH]) .'</textarea></p>';
    return $comment_field;
}

You can replicate this same functionality in older versions of WordPress by replacing this section in the default form:

<p><textarea name="comment" id="comment" cols="58" rows="10" tabindex="4"></textarea></p>

with the following:

<p><textarea name="comment" id="comment" cols="58" rows="10" tabindex="4"><?php echo stripslashes($_COOKIE['comment_author_comment_' . COOKIEHASH]);  ?></textarea></p>

Failure to Launch

The next function generates the actual error message displayed if the commenter fails the validation. This error message will be inserted into the comment form above the validation field when the commenter is redirected.

function ttype_comment_err_message() {
    echo '<p id="comment-form-error-msg" class="comment-form-error">Error: the validation you entered is incorrect.</p>';
};

Here’s what it looks like in the WP 3.0 default installation:

And in versions prior to WP 3.0 (again, default installation):

Dress it Up or Down

The next function calls the plugin’s CSS style sheet. The default styles make sure the paragraph enclosing the validation field clears any floats, and style the error message with a light red background and a darker border. The included style sheet is lightweight (1kb) and is only included in pages or posts that have comments open. By including a style sheet vs. writing directly to the <head> section of the site, you can style these elements directly in the style sheet vs. having to dig into the plugin source code.

function ttype_css() {
    $css_file = WP_PLUGIN_URL . '/'. basename(dirname(__FILE__)) . '/style_truetyped.css';
    echo '<link rel="stylesheet" href="'.$css_file.'" type="text/css" media="screen" />'."n";
}

The Gatekeeper

Since we’ve already built the gate, we now have to have something watch it. This is what the next function does; the actual data generation and validation. It accomplishes the following:

  1. Checks if the commenter is pre-approved (via the ttype_author_can_comment() function). If so, we skip the validation.
  2. Unsets the comment text cookie set by the plugin. This is in case the commenter changed his comment before re-submitting the form.
  3. Retrieves the post title, strips all special characters and numbers, and returns the first word.
  4. Retrieves the word entered by the commenter in the validation field of the comment form.Compares the values from steps 3 and 4. If they don’t match:
    1. Generate cookies for commenter’s name, email, url, and comment text.
    2. Check for a comment parent (if commenter clicked on reply link in nested comments).
    3. Add query variables for the error message and comment parent (if applicable).
    4. Redirect the commenter back to the comment form.
function ttype_validate() {

    if( ttype_author_can_comment() )
    return;
        
    if ( isset($_COOKIE['comment_author_comment_'.COOKIEHASH]) ) {
        setcookie('comment_author_comment_' . COOKIEHASH, '', time() - 60, COOKIEPATH, COOKIE_DOMAIN);
    };
    
    $comment_post_ID = isset( $_POST['comment_post_ID'] ) ? (int) $_POST['comment_post_ID'] : 0;
        
    $post_title = get_the_title($comment_post_ID);
    $post_title = strip_tags($post_title);
    $post_title = preg_replace('#[^A-Za-zss+]#','',$post_title);
    $post_title = explode(' ', $post_title);
    foreach ($post_title as $key => $value) {
      if ( is_null($value) || '' == $value ) {
        unset($post_title[$key]);
      }
    } 
    $post_title = array_values($post_title);
    $post_title_first_word = $post_title[0];
    $post_title_first_word = preg_replace('#[^A-Za-z]#', '', $post_title_first_word);
    $post_title_first_word = strtolower($post_title_first_word);
    $post_title_first_word = md5($post_title_first_word);                    

    $comment_validation = strip_tags($_POST['validate']);
    $comment_validation = trim($comment_validation);
    $comment_validation = preg_replace('#[^A-Za-z]#', '', $comment_validation);
    $comment_validation = ( '' != $comment_validation ) ? $comment_validation: 'validation-fail';   
    $comment_validation = strtolower($comment_validation);                                       
    $comment_validation = md5($comment_validation);                                                
    
    if ( $comment_validation != $post_title_first_word ) {
        
        $comment_cookie_lifetime = apply_filters('comment_cookie_lifetime', 30000000);
        setcookie('comment_author_' . COOKIEHASH, $_POST['author'], time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN);
        setcookie('comment_author_email_' . COOKIEHASH, $_POST['email'], time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN);
        setcookie('comment_author_url_' . COOKIEHASH, esc_url($_POST['url']), time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN);
        setcookie('comment_author_comment_' . COOKIEHASH, stripslashes(htmlentities($_POST['comment'], ENT_QUOTES, 'utf-8' )), time() + 60, COOKIEPATH, COOKIE_DOMAIN);
      
        $comment_parent = isset($_POST['comment_parent']) ? absint($_POST['comment_parent']) : 0;

        $ref = remove_query_arg( array('m', 'replytocom'), wp_get_referer() );
        $ref = $ref.'#comment-form-error-msg';
        $ref = ( $comment_parent > 0 ) ? add_query_arg (array('replytocom' => $comment_parent , 'm' => 'comm-err'), $ref) : add_query_arg ('m', 'comm-err', $ref) ;
        wp_redirect($ref);
        exit;
    }
};
add_action('pre_comment_on_post', 'ttype_validate');

As previously mentioned, this function does the bulk of the processing for the plugin. We hook it onto the pre_comment_on_post action hook so that it executes before WordPress does anything with the comment. Current default WordPress action is to only redirect the commenter back to the comment area if he passes validation and WordPress runs all its checks. If anything is missing, you get that dreaded error screen:

This abrupt stop is a huge inconvenience and disrupts your commenters’ experience. There’s no indication of what to do next; no link back to the post, no link to your home page, etc. Your user is just stopped. It’s the equivalent of running into a brick wall.

WP True Typed doesn’t address this default WordPress action (although it probably will in a future update), but by hooking the plugin onto the pre_comment_on_post action hook , it allows us to redirect the commenter back to the post if he fails the validation. And since it saves all the information he entered in the same cookies WordPress sets, we save the commenter any headaches.

If the commenter passes the validation test, the comment is passed along to the default WordPress comment processing.

Making it all Work

The last function of the plugin brings everything together and does the actual execution of the various functions that make up the plugin:

function ttype_init() {
    global $post;

    if( !comments_open($post->ID) || ttype_author_can_comment() )
    return;

    ttype_css();

    $fail = '';
    $fail .= $_GET['m'];
    
    $wp_version = absint(get_bloginfo('version'));
    if ( $wp_version < 3 ) {
        if( 'comm-err' == $fail ) { add_action('comment_form', 'ttype_comment_err_message', 1); }
        add_action('comment_form', 'ttype_add_validation_field', 1);
    } else {
        if( 'comm-err' == $fail ) { add_action('comment_form_before_fields', 'ttype_comment_err_message', 1); }
        add_action('comment_form_after_fields', 'ttype_add_validation_field', 2);
        add_filter('comment_form_field_comment', 'ttype_comment_comment_field', 1);
    }
}
add_action('wp_head','ttype_init');

The first thing we’re doing is checking to see if comments are closed or if the commenter is a pre-approved commenter. If so, the plugin will stop loading.

global $post;

if( !comments_open($post->ID) || ttype_author_can_comment() )
return;

Next we’re loading the style sheet:

ttype_css();

Next we’re checking for an error message:

$fail = '';
$fail .= $_GET['m'];

Next we’re checking what version of WordPress the site is using, and hooking onto the appropriate action/filter hook. In versions lower than WP 3.0 we’re hooking on the comment_form action hook, in all other versions we’re hooking on to various action/filter hooks provided by the new comment_form(); function provided in WordPress 3.0.

$wp_version = absint(get_bloginfo('version'));
if ( $wp_version < 3 ) {
    if( 'comm-err' == $fail ) { add_action('comment_form', 'ttype_comment_err_message', 1); }
    add_action('comment_form', 'ttype_add_validation_field', 1);
} else {
    if( 'comm-err' == $fail ) { add_action('comment_form_before_fields', 'ttype_comment_err_message', 1); }
    add_action('comment_form_after_fields', 'ttype_add_validation_field', 2);
    add_filter('comment_form_field_comment', 'ttype_comment_comment_field', 1);
}

And finally, we hook this function to the wp_head action hook:

add_action('wp_head','ttype_init');

Installing the Script

  1. Download the WP True Typed plugin from the WordPress Plugin Repository.
  2. Unzip the folder and upload to your plugins directory (..wp-content/plugins)
  3. Activate in the Plugins section of your Admin Dashboard

Using the Script

Using the script is as easy as following the installation steps outlined above. Once done, WP True Typed will just work. In its default styling, it’s set to fit in with WordPress’ default comment form (any version–see Notes & Resources). If you’d like to style it to fit your current comment form, there’s an included style sheet called “style_truetyped.css”. The paragraph enclosing the validation input field and label has a class of “comment-form-validation” applied, and the error message paragraph has an id of “comment-form-error-msg”.

As you can see from the comment form on this site, it’s easy to customize the form and still work the validation field in.

Notes & Resources

WP True Typed is intended to work with the default comment forms included in all versions of WordPress. If you use a custom comment form and have a version of WordPress older than 3.0, make sure your comment form has the following line (usually located just above the </form> element):

<?php do_action('comment_form', $post->ID); ?>

(My personal preference is to move the comment_form action hook above the submit button, just below the textarea. This way, the validation field will show before the submit button, not after.)

For 3.0 and higher, the plugin uses the following hooks:

  • comment_form_before_fields
  • comment_form_after_fields
  • comment_form_field_comment

So if you’re using a custom comment form, make sure you’re including these hooks.

Features

  • Checks for registered users or pre-approved comment authors
  • No cookies needed
  • No javascript needed
  • No images needed
  • Dynamic, changes based on post or page
  • Section 508 and WAI accessible
  • Valid HTML
  • Works with any comment form
  • Included CSS style sheet for customization

The post WP True Typed — a WordPress Plugin to Fight Comment Spam appeared first on darrinb.

]]>
http://darrinb.com/wp-true-typed/feed/ 7
Plugin Released: WP Post Encode http://darrinb.com/plugin-released-wp-post-encode/ http://darrinb.com/plugin-released-wp-post-encode/#respond Sat, 03 Jul 2010 19:12:49 +0000 http://darrinb.com/?p=824 WP Post Encode–a WordPress plugin geared toward authors who write a lot about code in their posts–was recently released and…

The post Plugin Released: WP Post Encode appeared first on darrinb.

]]>
WP Post Encode–a WordPress plugin geared toward authors who write a lot about code in their posts–was recently released and is now available in the WordPress Plugin Repository.

The aim of the plugin is to allow authors to inlcude raw code such as HTML, PHP, JavaScript, etc., in their posts by providing them with an encode filter that will encode any code/markup language they wrap in special quicktags.

To learn more about the plugin, click here: WP Post Encode.

The post Plugin Released: WP Post Encode appeared first on darrinb.

]]>
http://darrinb.com/plugin-released-wp-post-encode/feed/ 0
WP Post Encode — a WordPress Plugin for Including Raw Code in Posts http://darrinb.com/wp-post-encode/ http://darrinb.com/wp-post-encode/#comments Fri, 25 Jun 2010 15:37:21 +0000 http://darrinb.com/?p=706 This is the official plugin page for WP Post Encode, a WordPress plugin for including raw code in your posts.

The post WP Post Encode — a WordPress Plugin for Including Raw Code in Posts appeared first on darrinb.

]]>
Note: This is an updated continuation of the article: “Adding and Filtering Raw HTML in WordPress Posts”. It’s recommended you read that article before this to get a background on this plugin.

That said, let’s dive into this plugin! WP Post Encode is a plugin written with developers in mind, or anyone who includes a lot of code in their articles. As anyone who includes scripts in their tutorials, posts, or articles knows, manually encoding any included code–whether it’s HTML, PHP, JavaScript, or any others–is a huge pain and colossal time killer. That’s where WP Post Encode comes in; it allows authors to simply write out any code script he/she wants without worrying about encoding it. All you have to two is wrap it custom quicktags, which are created automatically on plugin installation.

For the geeks, err, developers out there, continue reading for an in-depth look at the functions that make up the plugin.

If you’re just looking to install and use the plugin, click here to skip to the bottom of this article.

The Code

function dbdb_post_encode_activate() {
    if ( '' == get_option( 'dbdb_post_encode') ) {
        $name = 'dbdb_post_encode';
        $value = array();
        $value['buttons'][0]['text']  = 'encode';
        $value['buttons'][0]['start'] = '<!--encode-->';
        $value['buttons'][0]['end']   = '<!--/encode-->';
        $autoload = 'yes';
        add_option($name, $value, $autoload);
        }
}

function dbdb_post_encode_deactivate() {
    delete_option( 'dbdb_post_encode' );
}

if ( strpos( $_SERVER['REQUEST_URI'], 'post.php' ) || strpos( $_SERVER['REQUEST_URI'], 'post-new.php' ) || strpos( $_SERVER['REQUEST_URI'], 'page-new.php' ) || strpos( $_SERVER['REQUEST_URI'], 'page.php' ) ) {

    add_action( 'admin_footer', 'dbdb_post_encode_buttons' );
    
    function dbdb_post_encode_buttons() {
    
        $o = get_option( 'dbdb_post_encode' );
        
        if ( count( $o['buttons'] ) > 0 ) {
            echo <<<EOT
            <script type="text/javascript">
                <!--
                    if (dbencToolbar = document.getElementById("ed_toolbar")) {
                        var dbencNr, dbencBut, dbencStart, dbencEnd;
EOT;
                        for ($i = 0; $i < count($o['buttons']); $i++) {
                            $b = $o['buttons'][$i];
                            $txt = html_entity_decode(stripslashes($b['txt']), ENT_COMPAT, get_option('blog_charset'));
                            $text = stripslashes($b['text']);
                            $b['text'] = stripslashes($b['text']);
                            $start = preg_replace('![nr]+!', "\n", $b['start']);
                            $start = str_replace("'", "'", $start);
                            $end = preg_replace('![nr]+!', "\n", $b['end']);
                            $end = str_replace("'", "'", $end);
                            echo <<<EOT
                            dbencStart = '{$start}';
                            dbencEnd = '{$end}';
                            dbencNr = edButtons.length;
                            edButtons[dbencNr] = new edButton('ed_dbenc{$i}','{$b['txt']}',dbencStart, dbencEnd,'');
                            var dbencBut = dbencToolbar.lastChild;
                            while (dbencBut.nodeType != 1) {
                                dbencBut = dbencBut.previousSibling;
                            }
                            dbencBut = dbencBut.cloneNode(true);
                            dbencToolbar.appendChild(dbencBut);
                            dbencBut.value = '{$b['text']}';
                            dbencBut.title = dbencNr;
                            dbencBut.onclick = function () {edInsertTag(edCanvas, parseInt(this.title));}
                            dbencBut.id = "ed_dbenc{$i}";
EOT;
                        }
                    echo <<<EOT
                    }
                //-->
            </script>
EOT;
        }
    }
}

if ( function_exists( 'register_activation_hook' ) ) {
    register_activation_hook( __FILE__, 'dbdb_post_encode_activate' );
}
if ( function_exists('register_deactivation_hook') ) {
    register_deactivation_hook(__FILE__, 'dbdb_post_encode_deactivate');
}

function dbdb_quick_encode($content_text) {
    $charset = get_bloginfo('charset');
    $replaced_text = preg_replace('#(<!--encode-->)(.*?)(<!--/encode-->)#isme', "'$1'.str_replace(array('<!--nextpage-->', '<!--more-->'), array('&lt;!--nextpage--&gt;', '&lt;!--more--&gt;'), '$2').'$3'", $content_text);
    foreach($replaced_text as $k => $v ) {
        $encoded_text[$k] = str_replace(array('"'),array('"'), $v);
    }
    return $encoded_text;
};
add_filter('wp_insert_post_data','dbdb_quick_encode', 1, 1);

function dbdb_post_encode($text) {
    $text = str_replace(array("rn", "r"), "n", $text);
    $text = preg_replace_callback("#(<!--encode-->)(.*?)(<!--/encode-->)#is", 'dbdb_code_encode', $text);
    return $text;
};
add_filter('the_content','dbdb_post_encode', 1, 1);

function dbdb_code_encode( $matches ) {
    $charset = get_bloginfo('charset');
    $text = trim($matches[2]);
    $text = str_replace(array('&lt;!--nextpage--&gt;', '&lt;!--more--&gt;'), array('<!--nextpage-->', '<!--more-->'), $text);
    $text = htmlspecialchars($text, ENT_QUOTES, $charset);
    $text = str_replace('[','[', $text);
    $text = str_replace(array("rn", "r"), "n", $text);
    $text = preg_replace("#nnn+#", "nn", $text);
    return $text;
};

The Code Explained

The first function defines the action the plugin will take when the plugin is activated . It checks to see if the plugin’s option has been added to the wp_options table, and if not, add it. It defines the name of the option (“dbdb_post_encode”), creates an array named “buttons” which contain the values for the quicktag buttons created by the plugin’s javascript script, and tells WordPress to load the plugin automatically on page-load.

function dbdb_post_encode_activate() {
    if ( '' == get_option( 'dbdb_post_encode') ) {
        $name = 'dbdb_post_encode';
        $value = array();
        $value['buttons'][0]['text']  = 'encode';
        $value['buttons'][0]['start'] = '<!--encode-->';
        $value['buttons'][0]['end']   = '<!--/encode-->';
        $autoload = 'yes';
        add_option($name, $value, $autoload);
        }
}

The second function of the plugin defines the action the plugin should take if it’s deactivated. In this case, it deletes the option from the wp_options table.

function dbdb_post_encode_deactivate() {
    delete_option( 'dbdb_post_encode' );
}

The next section of the plugin is the javascript that displays the actual buttons on the HTML editor toolbar. The plugin will only load this script on Post/Page add/edit screens:

if ( strpos( $_SERVER['REQUEST_URI'], 'post.php' ) || strpos( $_SERVER['REQUEST_URI'], 'post-new.php' ) || strpos( $_SERVER['REQUEST_URI'], 'page-new.php' ) || strpos( $_SERVER['REQUEST_URI'], 'page.php' ) ) {

    add_action( 'admin_footer', 'dbdb_post_encode_buttons' );
    
    function dbdb_post_encode_buttons() {
    
        $o = get_option( 'dbdb_post_encode' );
        
        if ( count( $o['buttons'] ) > 0 ) {
            echo <<<EOT
            <script type="text/javascript">
                <!--
                    if (dbencToolbar = document.getElementById("ed_toolbar")) {
                        var dbencNr, dbencBut, dbencStart, dbencEnd;
EOT;
                        for ($i = 0; $i < count($o['buttons']); $i++) {
                            $b = $o['buttons'][$i];
                            $txt = html_entity_decode(stripslashes($b['txt']), ENT_COMPAT, get_option('blog_charset'));
                            $text = stripslashes($b['text']);
                            $b['text'] = stripslashes($b['text']);
                            $start = preg_replace('![nr]+!', "\n", $b['start']);
                            $start = str_replace("'", "'", $start);
                            $end = preg_replace('![nr]+!', "\n", $b['end']);
                            $end = str_replace("'", "'", $end);
                            echo <<<EOT
                            dbencStart = '{$start}';
                            dbencEnd = '{$end}';
                            dbencNr = edButtons.length;
                            edButtons[dbencNr] = new edButton('ed_dbenc{$i}','{$b['txt']}',dbencStart, dbencEnd,'');
                            var dbencBut = dbencToolbar.lastChild;
                            while (dbencBut.nodeType != 1) {
                                dbencBut = dbencBut.previousSibling;
                            }
                            dbencBut = dbencBut.cloneNode(true);
                            dbencToolbar.appendChild(dbencBut);
                            dbencBut.value = '{$b['text']}';
                            dbencBut.title = dbencNr;
                            dbencBut.onclick = function () {edInsertTag(edCanvas, parseInt(this.title));}
                            dbencBut.id = "ed_dbenc{$i}";
EOT;
                        }
                    echo <<<EOT
                    }
                //-->
            </script>
EOT;
        }
    }
}

The next two functions in the plugin register the functions that need to run when the plugin is activated or deactivated. In this case, we’re calling the first two functions we defined; “dbdb_post_encode_activate()” and “dbdb_post_encode_deactivate()”.

if ( function_exists( 'register_activation_hook' ) ) {
    register_activation_hook( __FILE__, 'dbdb_post_encode_activate' );
}

if ( function_exists('register_deactivation_hook') ) {
    register_deactivation_hook(__FILE__, 'dbdb_post_encode_deactivate');
}

The next function encodes two default WordPress quicktags, <!--nextpage--> and <!--more--> if you wrap them in the plugin’s custom <!--encode--> quicktags. Why do we have to do this? Because WordPress processes the core quicktags before anything else when displaying content on the screen. So if you want to show these two quicktags in sample markup in a post, they need to be encoded or the post will display weird.

Since we’re hooking this function to the “wp_insert_post_data” filter hook, it will encode the WP default quicktags as the post is added to the posts table. (If you hit “save draft” as you’re writing a post, you’ll see these two quicktags encoded when your page refreshes.)

function dbdb_quick_encode($content_text) {
    $charset = get_bloginfo('charset');
    $replaced_text = preg_replace('#(<!--encode-->)(.*?)(<!--/encode-->)#isme', "'$1'.str_replace(array('<!--nextpage-->', '<!--more-->'), array('&lt;!--nextpage--&gt;', '&lt;!--more--&gt;'), '$2').'$3'", $content_text);
    foreach($replaced_text as $k => $v ) {
        $encoded_text[$k] = str_replace(array('"'),array('"'), $v);
    }
    return $encoded_text;
};
add_filter('wp_insert_post_data','dbdb_quick_encode', 1, 1);

The next function, “dbdb_post_encode()”, gets attached to the “the_content” filter hook (which is fired before a post is displayed on-screen) and looks for any content between the plugin’s custom <!--encode--> quicktags. When it finds a match, it calls the “dbdb_code_encode()” function, which does the actual encoding.

function dbdb_post_encode($text) {
    $text = str_replace(array("rn", "r"), "n", $text);
    $text = preg_replace_callback("#(<!--encode-->)(.*?)(<!--/encode-->)#is", 'dbdb_code_encode', $text);
    return $text;
};
add_filter('the_content','dbdb_post_encode', 1, 1);

The “dbdb_code_encode()” function is based off a function I found while reading through the bbPress source code. It takes the match provided it by “dbdb_post_encode()” and performs the following actions:

  1. Gets the blog’s character set (for encoding)
  2. Trims any whitespace from the matched content
  3. Un-encodes the quicktags previously encoded
  4. Encodes the matched content using PHP’s htmlspecialchars function (this will double-encode anything HTML entity found, so “&amp;” will become “&amp;amp;”)
  5. Encodes any shortcodes being shown as sample markup (discovered this while re-writing the WP Columnize plugin)
  6. Cleans up any multiple lines (used for large blocks of script)
function dbdb_code_encode( $matches ) {
    $charset = get_bloginfo('charset');
    $text = trim($matches[2]);
    $text = str_replace(array('&lt;!--nextpage--&gt;', '&lt;!--more--&gt;'), array('<!--nextpage-->', '<!--more-->'), $text);
    $text = htmlspecialchars($text, ENT_QUOTES, $charset);
    $text = str_replace('[','[', $text);
    $text = str_replace(array("rn", "r"), "n", $text);
    $text = preg_replace("#nnn+#", "nn", $text);
    return $text;
};

Installing the Script

Manually:

  1. Download the “wp-post-encode.zip” folder from the WordPress plugin repository.
  2. Unzip the folder and upload to your plugins directory (..wp-content/plugins)
  3. Activate in the Plugins section of your Admin Dashboard

Automatically:

  1. Download the “wp-post-encode.zip” folder from the WordPress plugin repository.
  2. Naviagte to the Plugins section of your Admin Dashboard (Plugins→Add New→Upload).
  3. Click the “browse” button, select the zip folder from your computer, and then click the “install” button.
  4. Follow the install prompts after the folder has been uploaded.

Using the Script

Once you’ve installed the script, using it is as easy as typing your post as normal and wrapping your markup in <!--encode--> quicktags. I find it easiest to write out the code first, then highlight anything I want to encode, then click the “encode” button in the HTML editor.

To encode an in‐line piece of code, wrap it like so:

<code><!--encode-->$variable = 'value';<!--/encode--></code>

To display a block of text do it like so:

<pre>
<!--encode-->
<html>
    <head>
        <title>Lorem ipsum dolor sit amet</title>
    </head>
<body>
<!--/encode-->
</pre>

Notes & Resources

Some plugins that offer similar functionality:

The post WP Post Encode — a WordPress Plugin for Including Raw Code in Posts appeared first on darrinb.

]]>
http://darrinb.com/wp-post-encode/feed/ 9
Adding and Filtering Raw HTML in WordPress Posts http://darrinb.com/adding-and-filtering-raw-html-in-wordpress-posts/ http://darrinb.com/adding-and-filtering-raw-html-in-wordpress-posts/#comments Mon, 14 Jun 2010 13:09:59 +0000 http://darrinb.com/?p=588 How to avoid manually encoding code in WordPress posts.

The post Adding and Filtering Raw HTML in WordPress Posts appeared first on darrinb.

]]>
UPDATE (06/28/2010):
This has now been released as a plugin and is now available in the WordPress Plugin Repository. Click here for the official plugin page.

I write a lot of tutorials on this site that cover topics ranging from WordPress and PHP to XHTML and CSS. In these articles I often give sample code to show an example of how to implement the script into your working site. Until this point I had been manually converting certain characters in the code examples to their character entities so they would display properly on screen. Primarily it’s been the left and right angle brackets (<, >) that have needed to be converted since these are the HTML delimiters and will trigger display issues.

A Brief Background

HTML is a structured markup language where the text is delimited by tags in angle brackets. These tags (among other things) instruct your browser how to structure/display the information between them. Common tags are the opening and closing paragraph tags (<p>, </p>); this informs your browser that the content in between these tags is a paragraph in the overall structure of the document (web page).

But what happens when you’re trying to show the actual tag to the viewer?

You can’t just type the tag out, your browser will think it’s actual markup code and not display it. What we need to do is instruct the browser to display the actual angle brackets. We do this by using the angle bracket’s character entity, the code that represents the actual character. This code can either be in the form of the entity name, or the entity number. An entity name is an easy‐to‐remember name to display an HTML character (ex: &quot; displays a straight quotation mark "). An entity number is the numeric code used to display an HTML character (ex: &#34; also displays a straight quotation mark ").

To display the left‐angle bracket we could use either &lt; or &#60;. I prefer to use entity numbers over names as browser support for numbers is generally stronger than names.

Adding HTML Code To Your WordPress Post

Now that we’ve covered how to display HTML code on screen, let’s discuss how we can implement this into a WordPress post. On the surface it’s easy enough—just convert left and right‐angle brackets to their character entities. This is fine if you only add a few lines of code, but it’s pretty time consuming if you have a lot of code to display. What I was looking for was a way to just type out a post as normal and add HTML without having to do anything special.

After reviewing a few plugins that attempt to handle this, I decided to write my own that would employ the use of quicktags. Essentially what the function does is look for these quicktags and encode everything between them. By attaching this function to a WordPress filter, the markup is encoded before the post is stored in the database.

WordPress Filters

From the Codex:

Filters are functions that WordPress passes data through, at certain points in execution, just before taking some action with the data (such as adding it to the database or sending it to the browser screen).

What we’re going to do is create 3 functions that filter the post content before WordPress stores it in the wp_posts table and before it’s displayed on the screeen.

The Code

function dbdb_quick_encode($content_text) {
    $charset = get_bloginfo('charset');
    $replaced_text = preg_replace('#(<!--encode-->)(.*?)(<!--/encode-->)#isme', "'$1'.str_replace(array('<!--nextpage-->', '<!--more-->'), array('&lt;!--nextpage--&gt;', '&lt;!--more--&gt;'), '$2').'$3'", $content_text);
    foreach($replaced_text as $k => $v ) {
        $encoded_text[$k] = str_replace(array('"'),array('"'), $v);
    }
    return $encoded_text;
};
add_filter('wp_insert_post_data','dbdb_quick_encode', 1, 1);

function dbdb_post_encode($text) {
    $text = str_replace(array("rn", "r"), "n", $text);
    $text = preg_replace_callback("#(<!--encode-->)(.*?)(<!--/encode-->)#is", 'dbdb_code_encode', $text);
    return $text;
};
add_filter('the_content','dbdb_post_encode', 1, 1);

function dbdb_code_encode( $matches ) {
    $charset = get_bloginfo('charset');
    $text = trim($matches[2]);
    $text = str_replace(array('&lt;!--nextpage--&gt;', '&lt;!--more--&gt;'), array('<!--nextpage-->', '<!--more-->'), $text);
    $text = htmlspecialchars($text, ENT_QUOTES, $charset);
    $text = str_replace('[','&#91;', $text);              
    $text = str_replace(array("rn", "r"), "n", $text);
    $text = preg_replace("#nnn+#", "nn", $text);     
    if ( "<!--encode-->" != $matches[1] ) {
        $text = $matches[1].$text.$matches[3];
    }
    return $text;
};

The Code Explained

Whoa, that’s a lot of code; let’s break it down. The first function:

function dbdb_quick_encode($content_text) {
    $charset = get_bloginfo('charset');
    $replaced_text = preg_replace('#(<!--encode-->)(.*?)(<!--/encode-->)#isme', "'$1'.str_replace(array('<!--nextpage-->', '<!--more-->'), array('&lt;!--nextpage--&gt;', '&lt;!--more--&gt;'), '$2').'$3'", $content_text);
    foreach($replaced_text as $k => $v ) {
        $encoded_text[$k] = str_replace(array('"'),array('"'), $v);
    }
    return $encoded_text;
};
add_filter('wp_insert_post_data','dbdb_quick_encode', 1, 1);

What this first function does is encode any of WordPress’ quicktags (<!--nextpage-->, <!--more--> ) if they’re wrapped in <!--encode--> tags before your post is saved to the wp_posts table. Why? Because WordPress processes these quicktags before it does anything else with your post content, so if you want to display these quicktags as example markup, they need to be encoded before they’re stored.

What it does is look for these quicktags between our new custom <!--encode--> quicktags and replaces (str_replace()) the angle brackets (<, >) with their character entities (&lt;, &gt;) so WordPress will not confuse them for actual quicktags. We add our encoding function to the wp_insert_post_data hook provided by WordPress; which is processed just prior to storing data in the wp_posts table.

The second function:

function dbdb_post_encode($text) {
    $text = str_replace(array("rn", "r"), "n", $text);
    $text = preg_replace_callback("#(<!--encode-->)(.*?)(<!--/encode-->)#is", 'dbdb_code_encode', $text);
    return $text;
};
add_filter('the_content','dbdb_post_encode', 1, 1);

The first thing this function does is replace any duplicate carriage returns and/or new lines with 1 new line. (This just neatens things up a bit.) The second thing this function does is look for content wrapped in our custom quicktags and processes it using the third function listed.

This function is hooked onto the the_content filter hook provided by WP. This hook filters content after it’s pulled from the wp_posts table, but before it’s displayed on the screen.

The third function:

function dbdb_code_encode( $matches ) {
    $charset = get_bloginfo('charset');
    $text = trim($matches[2]);
    $text = str_replace(array('&lt;!--nextpage--&gt;', '&lt;!--more--&gt;'), array('<!--nextpage-->', '<!--more-->'), $text);
    $text = htmlspecialchars($text, ENT_QUOTES, $charset);
    $text = str_replace('[','&#91;', $text);              
    $text = str_replace(array("rn", "r"), "n", $text);
    $text = preg_replace("#nnn+#", "nn", $text);     
    if ( "<!--encode-->" != $matches[1] ) {
        $text = $matches[1].$text.$matches[3];
    }
    return $text;
};

This function does the bulk of the encoding and is based largely off of the encoding function used by bbPress for comment encoding on WordPress.org’s support forums. (It pays to read through source code.) So let’s break it down:

The first thing the function does is get the character encoding used by your WP installation (charset):

$charset = get_bloginfo('charset');

Next it trims any whitespace found in between <!--encode-->, <!--/encode--> quicktags:

$text = trim($matches[2]);

Next we’re unencoding the WP quicktags we previously encoded: (Huh? You’ll see in a second.)

$text = str_replace(array('&lt;!--nextpage--&gt;', '&lt;!--more--&gt;'), array('<!--nextpage-->', '<!--more-->'), $text);

Next we’re using PHP’s built‐in htmlspecialchars function to encode HTML characters:

$text = htmlspecialchars($text, ENT_QUOTES, $charset);

This will automatically encode ampersands (&), double quotes ("), single quotes ('), left-angle brackets (<), and right-angle brackets (>). So, if we left the WP quicktags encoded, the function would double‐encode them. In fact, with this function you don’t need to encode any markup if you wrap it in the <!--encode--> quicktags. If you do, this function will double‐encode it; “&amp;” will become “&amp;amp;

Next, if we’re showing shortcodes as sample markup, we need to encode them as well: (Discovered this when I re‐wrote the WP Columnize plugin article.)

$text = str_replace('[','&#91;', $text);

Now we’re just cleaning up any preformatted text by tidying up blank lines:

$text = str_replace(array("rn", "r"), "n", $text);
$text = preg_replace("#nnn+#", "nn", $text);

Finally, we’re stripping out the custom quicktags but leaving whatever tags the sample markup might have been wrapped in (<pre> or <code>):

if ( "<!--encode-->" != $matches[1] ) {
    $text = $matches[1].$text.$matches[3];
}

Installing The Script

  1. Download the "wp_post_encode.txt" file.
  2. Copy/paste all the code inside the file to your functions.php file. Do not copy/paste the sample code in this article. All quicktags are escaped so they can be displayed for this article, copy/pasting will prevent the function from working properly for you.
  3. Save the changes to your functions.php file

Using The Script

Once you’ve installed the script, using it is as easy as typing your post as normal and wrapping your markup in <!--encode--> quicktags. To encode an in‐line piece of code, wrap it like so:

<code><!--encode-->$variable = 'value';<!--/encode--></code>

To display a block of text do it like so:

<pre>
<!--encode-->
<html>
    <head>
        <title>Lorem ipsum dolor sit amet</title>
    </head>
<body>
<!--/encode-->
</pre>

Notes and Resources

I plan to release this as a plugin soon with the capability of adding an encode button to the html editor to make it easier, but until then simply wrap any content you want encoded in the <!--encode-->, <!--/encode--> quicktags.

Some plugins that offer similar functionality:

UPDATE (06/28/2010):
This has now been released as a plugin and is now available in the WordPress Plugin Repository. Click here for the official plugin page.

The post Adding and Filtering Raw HTML in WordPress Posts appeared first on darrinb.

]]>
http://darrinb.com/adding-and-filtering-raw-html-in-wordpress-posts/feed/ 7