Create WordPress theme from scratch – Part 9 – Comments Template

Create WordPress Theme From Scratch

Welcome to Part 9 of the series Create WordPress theme from scratch. In this tutorial, we will create a custom comments template file.

If you missed the Part 8 of this series, then check it out here.

Comments Template

WordPress offers a  fine grain of control over the comments template.Let’s start with the basics first. Create a new file called comments.php in your theme directory. Paste the following lines of code in it.

<?php
if ( post_password_required() ) {
    return;
}
?>

<div id="comments" class="comments-area">

    <?php if ( have_comments() ) : ?>
        
        <?php
        $comments_number = get_comments_number();
        $comments_title = sprintf(
            _nx(
                '%1$s comment',
                '%1$s comments',
                $comments_number,
                'comments title',
                'mytheme'
            ),
            number_format_i18n( $comments_number )
        );
        printf( '<h2 class="comments-title">%1$s</h2>', $comments_title );
        ?>

        <?php the_comments_navigation(); ?>

        <ul class="comment-list">
            <?php wp_list_comments(); ?>
        </ul><!-- .comment-list -->

        <?php the_comments_navigation(); ?>

    <?php endif; ?>

    <?php if ( ! comments_open() && get_comments_number() && post_type_supports( get_post_type(), 'comments' ) ) : ?>
        <p class="no-comments"><?php _e( 'Comments are closed.', 'mytheme' ); ?></p>
    <?php endif; ?>

    <?php comment_form(); ?>

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

The above code will display comments with navigation when needed and a comments form.

We start with a simple conditional check. We don’t want to do anything with this template file if the post needs password authentication.

if ( post_password_required() ) {
    return;
}

post_password_required( $post )

This will return true when the current post is password protected and  a valid password has not been provided. Else it will return false.

Next, we are checking to see if we have comments.

<?php if ( have_comments() ) : ?>

have_comments()

Conditional check that returns true when comments are available. Be careful with this function as it only works after the comments template has been called!

The following code displays the number of comments available.

<?php
$comments_number = get_comments_number();
$comments_title = sprintf(
    _nx(
        '%1$s comment',
        '%1$s comments',
        $comments_number,
        'comments title',
        'mytheme'
    ),
    number_format_i18n( $comments_number )
);
printf( '<h2 class="comments-title">%1$s</h2>', $comments_title );
?>

get_comments_number()

Returns the total number of comments for a post including Trackbacks and Pingbacks.

_nx( $single, $plural, $number, $context, $domain )

This is a context based translator function for a single or plural number. Here are the available options:

  • $single – Text to use when the number is 1.
  • $plural – Text to use when the number is plural (actually other than 1).
  • $number – The number to check when using single or plural text.
  • $context – Comment to help out the friendly translator.
  • $domain – Text translation domain.

Basically, we are displaying a nicely formatted title with the number of comments making sure that it is translation ready.

Next, we are displaying the comments with navigation on top and bottom.

<?php the_comments_navigation(); ?>

<ul class="comment-list">
    <?php wp_list_comments(); ?>
</ul><!-- .comment-list -->

<?php the_comments_navigation(); ?>

the_comments_navigation( $args )

Displays navigation to older and newer comments when applicable. It supports the following arguments

  • prev_text – Anchor text of the previous comments link.
  • next_text – Anchor text of the next comments link
  • screen_reader_text – Text meant for screen readers.

wp_list_comments( $args, $comments )

Displays the comments for a post based on a whole lot of conditions some of which are set in the administration area. Arguments are supplied using an associative array.

For a complete list of supported arguments, check the codex documentation. You will need to get familiar with this function and its arguments before customizing the comments display.

To make interacting with threaded comments easier, we need to add the comments Javascript that WordPress offers. In the theme functions file, modify the mytheme_scripts function and add the following lines of code

if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
    wp_enqueue_script( 'comment-reply' );
}

We are checking if we are on a singular page, whether comments are open and the support for threaded comments is enabled. Here is the complete code of mytheme_scripts function.

function mytheme_scripts() {
    wp_enqueue_style( 'mytheme-bootstrap', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css' );
    wp_enqueue_style( 'mytheme-google-fonts', 'https://fonts.googleapis.com/css?family=Slabo+13px' );
    wp_enqueue_style( 'mytheme-style', get_stylesheet_uri() );
    if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
        wp_enqueue_script( 'comment-reply' );
    }
}

Customizing the comment HTML

By using the callback argument of wp_list_comments, we can supply a custom function to render the comment output HTML. When using this feature, all internal WordPress functionality is bypassed. Let’s use it to customize our comments output.

Here is the code of our custom comments callback function.

function mytheme_comment( $comment, $args, $depth ) {
    $tag = $args['style'] == 'div' ? 'div' : 'li';
    $add_below = 'comment-entry';
    ?>
    <<?php echo $tag ?> <?php comment_class( empty( $args['has_children'] ) ? '' : 'parent' ) ?> id="comment-<?php comment_ID() ?>">

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

        <footer class="comment-footer">
            <span class="comment-author vcard">
                <?php if ( $args['avatar_size'] != 0 ) echo get_avatar( $comment, $args['avatar_size'] ); ?>
                <cite class="fn"><?php echo get_comment_author_link(); ?></cite>
            </span>

            <?php if ( '0' == $comment->comment_approved ) : ?>
                <span class="comment-awaiting-moderation"><?php _e( 'Your comment is awaiting moderation.', 'mytheme' ); ?></span>
            <?php endif; ?>

            <span class="comment-meta">
                <time class="comment-time" datetime="<?php echo esc_attr( get_comment_time( 'c' ) ); ?>">
                    <?php printf( __( '%1$s at %2$s', 'mytheme' ), get_comment_date(), get_comment_time() ); ?>
                </time>
                <?php edit_comment_link( __( 'Edit', 'mytheme' ) ); ?>
            </span>
        </footer>

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

        <div class="reply">
            <?php comment_reply_link( array_merge( $args, array(
                'add_below' => $add_below,
                'depth' => $depth,
                'max_depth' => $args['max_depth'] )
            ) );
            ?>
        </div>

    </article>
    <?php
}

The callback function starts with

$tag = $args['style'] == 'div' ? 'div' : 'li';

This is setting the opening tag element based on what was specified in the style argument. If you look at the complete code, you will notice that we do not provide the closing tag. It’s because WordPress will close the tag automatically.

Next, we are setting a variable called $add_below that will be used by the WordPress comments Javascript to move the comment form around.

$add_below = 'comment-entry';

This variable combined with the comment id ({$add_below}-{$id}) must match the id attribute of the comment container element.  For example, comment-16.

After that, we start the actual comment rendering

<<?php echo $tag ?> <?php comment_class( empty( $args['has_children'] ) ? '' : 'parent' ) ?> id="comment-<?php comment_ID(); ?>">

We are using the comment_class to output the class names. This outputs several class names which provide easy styling for theme developers.

Next, we have the actual comment with an id matching our $add_below argument.

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

Inside the comment, we are first displaying the comment author. We are grabbing the avatar based on the avatar_size argument provided.

<span class="comment-author vcard">
    <?php if ( $args['avatar_size'] != 0 ) echo get_avatar( $comment, $args['avatar_size'] ); ?>
    <cite class="fn"><?php echo get_comment_author_link(); ?></cite>
</span>

If the comment is yet to be approved, then we are showing a notification

<?php if ( '0' == $comment->comment_approved ) : ?>
    <span class="comment-awaiting-moderation"><?php _e( 'Your comment is awaiting moderation.', 'mytheme' ); ?></span>
<?php endif; ?>

In the meta section, we are displaying the time when the comment was posted and an edit link.

get_comment_time

Returns the time of the current comment.

get_comment_date

Returns the date of the current comment.

edit_comment_link

Displays a link to edit the comment when a user is logged in and has the rights to edit the comment.

<span class="comment-meta">
    <time class="comment-time" datetime="<?php echo esc_attr( get_comment_time( 'c' ) ); ?>">
        <?php printf( __( '%1$s at %2$s', 'mytheme' ), get_comment_date(), get_comment_time() ); ?>
    </time>
    <?php edit_comment_link( __( 'Edit', 'mytheme' ) ); ?>
</span>

The actual comment content follows next

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

comment_text

Displays the comment text.

Finally, we have the reply link.

<div class="reply">
    <?php comment_reply_link( array_merge( $args, array(
        'add_below' => $add_below,
        'depth' => $depth,
        'max_depth' => $args['max_depth'] )
    ) );
    ?>
</div>

comment_reply_link

Displays reply link to a comment. We provide $add_below as an argument or else the WordPress comments Javascript won’t work. This completes our custom comments rendering.

Back to our comments.php template, after showing the comments, we are rendering the comment form.

<?php comment_form(); ?>

comment_form( $args, $post_id )

This tag outputs a complete comment form. It accepts the following arguments

  • $args – an associative array that can be used to set different strings and fields.
  • $post_id – The form will be generated for the post ID specified by this argument.

For a complete list of supported options, check the codex documentation.

By default, WordPress outputs the comment form fields wrapped in p elements. Let’s customize our comments form. We will create our form with a simpler markup. Here is the completed code.

$comment_field = sprintf( '<textarea class="form-control" id="comment" name="comment" placeholder="%1$s"></textarea>', __( 'Your comment here', 'mytheme' ) );

$fields =  array(
    'author' => sprintf(
        '<input class="form-control" id="author" name="author" type="text" value="%1$s" placeholder="%2$s" %3$s />',
        sprintf( __( '%s', 'mytheme' ), esc_attr( $commenter['comment_author'] ) ),
        __( 'Your Name', 'mytheme' ),
        ( $req ? 'required' : '' )
    ),

    'email' => sprintf(
        '<input class="form-control" id="email" name="email" type="email" value="%1$s" placeholder="%2$s" %3$s />',
        sprintf( __( '%s', 'mytheme' ), esc_attr( $commenter['comment_author_email'] ) ),
        __( 'Your Email', 'mytheme' ),
        ( $req ? 'required' : '' )
    ),

    'url' => sprintf(
        '<input class="form-control" id="url" name="url" type="text" value="%1$s" placeholder="%2$s" />',
        sprintf( __( '%s', 'mytheme' ), esc_attr( $commenter['comment_author_url'] ) ),
        __( 'Your Web Site', 'mytheme' )
    ),
);

$comment_form_args = array(
    'comment_field'        => $comment_field,
    'fields'               => $fields,
    'class_submit'         => 'btn',
);

The first variable $comment_field contains the text that will render the textarea of the real comment. This will simply render a textarea with a placeholder.

$comment_field = sprintf( '<textarea class="form-control" id="comment" name="comment" placeholder="%1$s"></textarea>', __( 'Your comment here', 'mytheme' ) );

After that, we set the $fields variable which is an array containing rendering strings for name, email and URL fields.

$fields =  array(
    'author' => sprintf(
        '<input class="form-control" id="author" name="author" type="text" value="%1$s" placeholder="%2$s" %3$s />',
        sprintf( __( '%s', 'mytheme' ), esc_attr( $commenter['comment_author'] ) ),
        __( 'Your Name', 'mytheme' ),
        ( $req ? 'required' : '' )
    ),

    'email' => sprintf(
        '<input class="form-control" id="email" name="email" type="email" value="%1$s" placeholder="%2$s" %3$s />',
        sprintf( __( '%s', 'mytheme' ), esc_attr( $commenter['comment_author_email'] ) ),
        __( 'Your Email', 'mytheme' ),
        ( $req ? 'required' : '' )
    ),

    'url' => sprintf(
        '<input class="form-control" id="url" name="url" type="text" value="%1$s" placeholder="%2$s" />',
        sprintf( __( '%s', 'mytheme' ), esc_attr( $commenter['comment_author_url'] ) ),
        __( 'Your Web Site', 'mytheme' )
    ),
);

We are using two variables provided by WordPress – $commenter and $req. The $commenter variable will contain current commenter’s name, email, and URL. The $req field indicates whether the name and email fields are required or not.

The $commenter and $req fields won’t be available if accessed from a callback function. In such cases, we will have to set them manually

$commenter = wp_get_current_commenter();
$req = get_option( 'require_name_email' );
$aria_req = ( $req ? " aria-required='true'" : '' );

The third variable will be helpful in case you are providing support for assistive technologies in your theme.

The $comment_form_args is a variable we are preparing to assemble all the arguments. We are also setting the class for the submit button using this variable.

<?php comment_form( $comment_form_args ); ?>

Here is the completed comment form code

<?php
if ( post_password_required() ) {
    return;
}

$comment_field = sprintf( '<textarea class="form-control" id="comment" name="comment" placeholder="%1$s"></textarea>', __( 'Your comment here', 'mytheme' ) );

$fields =  array(
    'author' => sprintf(
        '<input class="form-control" id="author" name="author" type="text" value="%1$s" placeholder="%2$s" %3$s />',
        sprintf( __( '%s', 'mytheme' ), esc_attr( $commenter['comment_author'] ) ),
        __( 'Your Name', 'mytheme' ),
        ( $req ? 'required' : '' )
    ),

    'email' => sprintf(
        '<input class="form-control" id="email" name="email" type="email" value="%1$s" placeholder="%2$s" %3$s />',
        sprintf( __( '%s', 'mytheme' ), esc_attr( $commenter['comment_author_email'] ) ),
        __( 'Your Email', 'mytheme' ),
        ( $req ? 'required' : '' )
    ),

    'url' => sprintf(
        '<input class="form-control" id="url" name="url" type="text" value="%1$s" placeholder="%2$s" />',
        sprintf( __( '%s', 'mytheme' ), esc_attr( $commenter['comment_author_url'] ) ),
        __( 'Your Web Site', 'mytheme' )
    ),
);

$comment_form_args = array(
    'comment_field'        => $comment_field,
    'fields'               => $fields,
    'class_submit'         => 'btn',
);

function mytheme_comment( $comment, $args, $depth ) {
    $tag = $args['style'] == 'div' ? 'div' : 'li';
    $add_below = 'comment-entry';
    ?>
    <<?php echo $tag ?> <?php comment_class( empty( $args['has_children'] ) ? '' : 'parent' ) ?> id="comment-<?php comment_ID(); ?>">

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

        <footer class="comment-footer">
            <span class="comment-author vcard">
                <?php if ( $args['avatar_size'] != 0 ) echo get_avatar( $comment, $args['avatar_size'] ); ?>
                <cite class="fn"><?php echo get_comment_author_link(); ?></cite>
            </span>

            <?php if ( 0 == $comment->comment_approved ) : ?>
                <span class="comment-awaiting-moderation"><?php _e( 'Your comment is awaiting moderation.', 'mytheme' ); ?></span>
            <?php endif; ?>

            <span class="comment-meta">
                <time class="comment-time" datetime="<?php echo esc_attr( get_comment_time( 'c' ) ) ?>">
                    <?php printf( __( '%1$s at %2$s', 'mytheme' ), get_comment_date(), get_comment_time() ) ?>
                </time>
                <?php edit_comment_link( __( 'Edit', 'mytheme' ) ); ?>
            </span>
        </footer>

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

        <div class="reply">
            <?php comment_reply_link( array_merge( $args, array(
                'add_below' => $add_below,
                'depth' => $depth,
                'max_depth' => $args['max_depth'] )
            ) );
            ?>
        </div>

    </article>
    <?php
}

?>

<div id="comments" class="comments-area">

    <?php if ( have_comments() ) : ?>

        <?php
        $comments_number = get_comments_number();
        $comments_title = sprintf(
            _nx(
                '%1$s comment',
                '%1$s comments',
                $comments_number,
                'comments title',
                'mytheme'
            ),
            number_format_i18n( $comments_number )
        );
        printf( '<h2 class="comments-title">%1$s</h2>', $comments_title );
        ?>

        <?php the_comments_navigation(); ?>

        <ul class="comment-list">
            <?php wp_list_comments( array( 'callback' => 'mytheme_comment' ) ); ?>
        </ul><!-- .comment-list -->

        <?php the_comments_navigation(); ?>

    <?php endif; ?>

    <?php if ( ! comments_open() && get_comments_number() && post_type_supports( get_post_type(), 'comments' ) ) : ?>
        <p class="no-comments"><?php _e( 'Comments are closed.', 'mytheme' ); ?></p>
    <?php endif; ?>

    <?php comment_form( $comment_form_args ); ?>

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

Conclusion

This concludes the ninth part of the series Create WordPress theme from scratch. We have completed building a fully functional WordPress theme. In the next part of the series, we will add custom header and background image to our completed theme.

Time Quotes

Next Article

24 Time Quotes to Inspire You to Do Something important Today.