pfefferle/wordpress-semantic-linkbacks

View on GitHub
includes/class-linkbacks-handler.php

Summary

Maintainability
D
2 days
Test Coverage
<?php

// Mentions with content less than this length will be rendered inline.
define( 'MAX_INLINE_MENTION_LENGTH', 300 );

/**
 * Semantic linkbacks class
 *
 * @author Matthias Pfefferle
 */
class Linkbacks_Handler {
    /**
     * Initialize the plugin, registering WordPress hooks.
     */
    public static function init() {

        /* Add meta boxes on the 'add_meta_boxes' hook. */
        add_action( 'add_meta_boxes', array( 'Linkbacks_Handler', 'add_meta_boxes' ) );

        // enhance linkbacks
        add_filter( 'preprocess_comment', array( 'Linkbacks_Handler', 'enhance' ), 0, 1 );
        add_filter( 'wp_update_comment_data', array( 'Linkbacks_Handler', 'enhance' ), 11, 3 );

        // Updates Comment Meta if set in commentdata
        add_action( 'edit_comment', array( 'Linkbacks_Handler', 'update_meta' ), 10, 2 );
        add_action( 'edit_comment', array( 'Linkbacks_Handler', 'save_comment_meta' ) );

        // To extend or to override the default behavior, just use the `comment_text` filter with a lower
        // priority (so that it's called after this one) or remove the filters completely in
        // your code: `remove_filter('comment_text', array('Linkbacks_Handler', 'comment_text_add_cite'), 11);`
        if ( ! self::render_comments() ) {
            add_filter( 'get_comment_text', array( 'Linkbacks_Handler', 'comment_text_add_cite' ), 11, 3 );
        }
        add_filter( 'get_comment_text', array( 'Linkbacks_Handler', 'comment_text_excerpt' ), 12, 3 );
        add_filter( 'comment_excerpt', array( 'Linkbacks_Handler', 'comment_text_excerpt' ), 5, 2 );

        add_filter( 'get_comment_link', array( 'Linkbacks_Handler', 'get_comment_link' ), 99, 3 );
        add_filter( 'get_comment_author_url', array( 'Linkbacks_Handler', 'get_comment_author_url' ), 99, 3 );
        add_filter( 'comment_class', array( 'Linkbacks_Handler', 'comment_class' ), 10, 4 );
        add_filter( 'wp_list_comments_args', array( 'Linkbacks_Handler', 'filter_comment_args' ) );
        add_action( 'comment_form_before', array( 'Linkbacks_Handler', 'show_mentions' ) );
        add_action( 'comment_form_comments_closed', array( 'Linkbacks_Handler', 'show_mentions' ) );

        // Domain Approval Check
        add_filter( 'semantic_linkbacks_commentdata', array( 'Linkbacks_Handler', 'domain_approval_check' ), 99, 1 );

        // Register Meta Keys
        self::register_meta();
    }

    /**
     * Create a  meta boxes to be displayed on the comment editor screen.
     */
    public static function add_meta_boxes() {
        add_meta_box(
            'semantic-linkbacks-meta',
            esc_html__( 'Semantic Linkbacks Data', 'semantic-linkbacks' ),
            array( 'Linkbacks_Handler', 'comment_metabox' ),
            'comment',
            'normal',
            'default'
        );
    }

    /**
     * Load template for comment metabox.
     */
    public static function comment_metabox() {
        load_template( dirname( __FILE__ ) . '/../templates/linkbacks-edit-comment-form.php' );
    }

    /**
     * Saves Comment Meta Options
     *
     * @param int $comment_id
     *
     */
    public static function save_comment_meta( $comment_id ) {
        // If this is an autosave, our form has not been submitted, so we don't want to do anything.
        if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
            return;
        }
        // Check the user's permissions.
        if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
            return;
        }
        if ( ! empty( $_POST['semantic_linkbacks_type'] ) ) {
            update_comment_meta( $comment_id, 'semantic_linkbacks_type', $_POST['semantic_linkbacks_type'] );
        }
        if ( ! empty( $_POST['avatar'] ) ) {
            update_comment_meta( $comment_id, 'avatar', $_POST['avatar'] );
        } else {
            delete_comment_meta( $comment_id, 'avatar' );
        }
    }

    /**
     * Filter the comments and ignore every comment other than 'comment' and 'mention'
     *
     * @param array $args an array of arguments for displaying comments
     *
     * @return array the filtered array
     */
    public static function filter_comment_args( $args ) {
        $args['walker'] = new Semantic_Linkbacks_Walker_Comment();
        return $args;
    }

    /**
     * Filter whether to override comment presentation.
     * To use the default html5_comment set this filter to false
     *
     * @return boolean
     */
    public static function render_comments() {
        return apply_filters( 'semantic_linkbacks_render_comments', ! current_theme_supports( 'microformats2' ) );
    }

    /**
     *
     *
     */
    public static function show_mentions() {
        // If this filter is set to false then hide the template and hide the option. This should be used by themes
        if ( apply_filters( 'semantic_linkbacks_facepile', true ) ) {
            load_template( plugin_dir_path( dirname( __FILE__ ) ) . 'templates/linkbacks.php' );
        }
    }

    /**
     * This is more to lay out the data structure than anything else.
     */
    public static function register_meta() {
        $args = array(
            'sanitize_callback' => 'esc_url_raw',
            'type'              => 'string',
            'description'       => 'Author URL',
            'single'            => true,
            'show_in_rest'      => true,
        );
        register_meta( 'comment', 'semantic_linkbacks_author_url', $args );

        $args = array(
            'sanitize_callback' => 'esc_url_raw',
            'type'              => 'string',
            'description'       => 'Avatar URL',
            'single'            => true,
            'show_in_rest'      => true,
        );
        register_meta( 'comment', 'avatar', $args );

        $args = array(
            'sanitize_callback' => 'esc_url_raw',
            'type'              => 'string',
            'description'       => 'Canonical URL',
            'single'            => true,
            'show_in_rest'      => true,
        );
        register_meta( 'comment', 'semantic_linkbacks_canonical', $args );

        $args = array(
            'type'         => 'string',
            'description'  => 'Linkbacks Type',
            'single'       => true,
            'show_in_rest' => true,
        );
        register_meta( 'comment', 'semantic_linkbacks_type', $args );
    }

    /**
     * Update an Enhanced Comment
     */
    public static function enhance( $commentdata, $comment = array(), $commentarr = array() ) {
        // check if comment is a linkback
        if ( ! in_array( $commentdata['comment_type'], apply_filters( 'semantic_linkbacks_enhance_comment_types', array( 'webmention', 'pingback', 'trackback' ) ), true ) ) {
            return $commentdata;
        }

        // only run the enhancer if `remote_source_original` is set
        if ( empty( $commentdata['remote_source_original'] ) ) {
            return $commentdata;
        }

        // initialize comment_meta array
        if ( ! array_key_exists( 'comment_meta', $commentdata ) ) {
            $commentdata['comment_meta'] = array();
        }

        //  If target is not set then set based on permalink
        if ( ! isset( $commentdata['target'] ) ) {
            $commentdata['target'] = get_permalink( $commentdata['comment_post_ID'] );
        }
        // add replytocom if present
        if ( isset( $commentdata['comment_parent'] ) && ! empty( $commentdata['comment_parent'] ) ) {
            $commentdata['target'] = add_query_arg( array( 'replytocom' => $commentdata['comment_parent'] ), $commentdata['target'] );
        }

        // add source url as comment-meta
        $commentdata['comment_meta']['semantic_linkbacks_source'] = esc_url_raw( $commentdata['comment_author_url'] );

        // adds a hook to enable some other semantic handlers for example schema.org
        $commentdata = apply_filters( 'semantic_linkbacks_commentdata', $commentdata );

        // remove "webmention" comment-type if $type is "reply"
        if ( isset( $commentdata['comment_meta']['semantic_linkbacks_type'] ) ) {
            if ( in_array( $commentdata['comment_meta']['semantic_linkbacks_type'], apply_filters( 'semantic_linkbacks_comment_types', array( 'reply' ) ), true ) ) {
                // https://core.trac.wordpress.org/ticket/49236 - As of WP5.5, comment will be the default comment type, not empty.
                $commentdata['comment_type'] = version_compare( get_bloginfo( 'version' ), '5.5', '>=' ) ? 'comment' : '';
            }
        }

        return wp_unslash( $commentdata );
    }

    /**
     * Use the whitelist check function to approve a comment if the source domain is on the whitelist.
     *
     * @param array $commentdata
     * @return array $commentdata
     */
    public static function domain_approval_check( $commentdata ) {
        if ( ! $commentdata || is_wp_error( $commentdata ) || ! function_exists( 'is_webmention_source_whitelisted' ) || ! isset( $commentdata['semantic_linkbacks_canonical'] ) ) {
            return $commentdata;
        }
        // Check if the canonical URL is on the whitelist
        if ( is_webmention_source_whitelisted( $commentdata['semantic_linkbacks_canonical'] ) ) {
            $commentdata['comment_approved'] = 1;
        }

        return $commentdata;
    }

    /**
     * Retrieve Remote Source
     *
     * @param string $url URL to Retrieve
     * @return remote source
     */
    public static function retrieve( $url ) {
        global $wp_version;
        $user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) );
        $args       = array(
            'timeout'             => 100,
            'limit_response_size' => 1048576,
            'redirection'         => 20,
            'user-agent'          => "$user_agent; verifying linkback",
        );
        return wp_safe_remote_get( $url, $args );
    }

    /**
     * Save Meta - to Match the core functionality in wp_insert_comment.
     * To be Removed if This Functionality Hits Core.
     *
     * @param array $commentdata The new comment data
     * @param array $comment The old comment data
     */
    public static function update_meta( $comment_id, $commentdata ) {
        // If metadata is provided, store it.
        if ( isset( $commentdata['comment_meta'] ) && is_array( $commentdata['comment_meta'] ) ) {
            foreach ( $commentdata['comment_meta'] as $meta_key => $meta_value ) {
                update_comment_meta( $comment_id, $meta_key, $meta_value, true );
            }
        }
    }

    /**
     * Returns an array of comment type excerpts to their translated and pretty display versions
     *
     * @return array The array of translated post format excerpts.
     */
    public static function get_comment_type_excerpts() {
        $strings = array(
            // translators: Name verb on domain
            'mention'         => __( '%1$s mentioned %2$s on <a href="%3$s">%4$s</a>.', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'reply'           => __( '%1$s replied to %2$s on <a href="%3$s">%4$s</a>.', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'repost'          => __( '%1$s reposted %2$s on <a href="%3$s">%4$s</a>.', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'like'            => __( '%1$s liked %2$s on <a href="%3$s">%4$s</a>.', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'favorite'        => __( '%1$s favorited %2$s on <a href="%3$s">%4$s</a>.', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'tag'             => __( '%1$s tagged %2$s on <a href="%3$s">%4$s</a>.', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'bookmark'        => __( '%1$s bookmarked %2$s on <a href="%3$s">%4$s</a>.', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'rsvp:yes'        => __( '%1$s is <strong>attending</strong>.', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'rsvp:no'         => __( '%1$s is <strong>not attending</strong>.', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'rsvp:maybe'      => __( 'Maybe %1$s will be <strong>attending</strong>.', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'rsvp:interested' => __( '%1$s is <strong>interested</strong> in this event.', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'invited'         => __( '%1$s is <strong>invited</strong>.', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'listen'          => __( '%1$s <strong>listened</strong> to %2$s (via <a href="%3$s">%4$s</a>).', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'watch'           => __( '%1$s <strong>watched</strong> %2$s (via <a href="%3$s">%4$s</a>).', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'read'            => __( '%1$s <strong>read</strong> %2$s (via <a href="%3$s">%4$s</a>).', 'semantic-linkbacks' ),
            // translators: Name verb on domain
            'follow'          => __( '%1$s <strong>followed</strong> %2$s (via <a href="%3$s">%4$s</a>).', 'semantic-linkbacks' ),

        );

        return $strings;
    }

    /**
    * Returns an array of comment type slugs to their translated and pretty display versions
    *
    * @param $type Return only one type. All if null
    * @return array The array of translated comment type names.
    */
    public static function get_comment_type_strings( $type = null ) {
        $strings = array(
            // Special case. any value that evals to false will be considered standard
            'mention'         => __( 'Mention', 'semantic-linkbacks' ),

            'reply'           => __( 'Reply', 'semantic-linkbacks' ),
            'repost'          => __( 'Repost', 'semantic-linkbacks' ),
            'like'            => __( 'Like', 'semantic-linkbacks' ),
            'favorite'        => __( 'Favorite', 'semantic-linkbacks' ),
            'tag'             => __( 'Tag', 'semantic-linkbacks' ),
            'bookmark'        => __( 'Bookmark', 'semantic-linkbacks' ),
            'rsvp:yes'        => __( 'RSVP Yes', 'semantic-linkbacks' ),
            'rsvp:no'         => __( 'RSVP No', 'semantic-linkbacks' ),
            'rsvp:maybe'      => __( 'RSVP Maybe', 'semantic-linkbacks' ),
            'rsvp:interested' => __( 'RSVP Interested', 'semantic-linkbacks' ),
            'invited'         => __( 'Invited', 'semantic-linkbacks' ),
            'listen'          => __( 'Listen', 'semantic-linkbacks' ),
            'watch'           => __( 'Watch', 'semantic-linkbacks' ),
            'read'            => __( 'Read', 'semantic-linkbacks' ),
            'follow'          => __( 'Follow', 'semantic-linkbacks' ),
            'reacji'          => __( 'Reacji', 'semantic-linkbacks' ),
        );
        if ( ! $type ) {
            return $strings;
        }
        return isset( $strings[ $type ] ) ? $strings[ $type ] : $strings['mention'];
    }

    public static function comment_type_select( $type, $echo = false ) {
        $choices = self::get_comment_type_strings();
        $return  = '';
        foreach ( $choices as $value => $text ) {
            $return .= sprintf( '<option value=%1s %2s>%3s</option>', $value, selected( $type, $value, false ), $text );
        }
        if ( ! $echo ) {
            return $return;
        }
        echo $return;
    }

    /**
    * Returns an array of post types (with articles)
    *
    * @return array The array of translated comment type names.
    */
    public static function get_post_type_strings() {
        $strings = array(
            // Generic Post Types
            'post'     => __( 'this Post', 'semantic-linkbacks' ),
            'page'     => __( 'this Page', 'semantic-linkbacks' ),
            // Post Format Types
            'standard' => __( 'this Article', 'semantic-linkbacks' ),
            'aside'    => __( 'this Aside', 'semantic-linkbacks' ),
            'chat'     => __( 'this Chat', 'semantic-linkbacks' ),
            'gallery'  => __( 'this Gallery', 'semantic-linkbacks' ),
            'link'     => __( 'this Link', 'semantic-linkbacks' ),
            'image'    => __( 'this Image', 'semantic-linkbacks' ),
            'quote'    => __( 'this Quote', 'semantic-linkbacks' ),
            'status'   => __( 'this Status', 'semantic-linkbacks' ),
            'video'    => __( 'this Video', 'semantic-linkbacks' ),
            'audio'    => __( 'this Audio', 'semantic-linkbacks' ),
        );

        return $strings;
    }

    /**
     * Return correct URL
     *
     * @param WP_Comment|int $comment the comment object or comment ID
     *
     * @return string the URL
     */
    public static function get_url( $comment ) {
        if ( is_numeric( $comment ) ) {
            $comment = get_comment( $comment );
        }
        // get canonical url...
        $semantic_linkbacks_canonical = get_comment_meta( $comment->comment_ID, 'semantic_linkbacks_canonical', true );
        // ...or fall back to source
        if ( ! $semantic_linkbacks_canonical ) {
            $semantic_linkbacks_canonical = get_comment_meta( $comment->comment_ID, 'semantic_linkbacks_source', true );
        }
        // ...or author url
        if ( ! $semantic_linkbacks_canonical ) {
            $semantic_linkbacks_canonical = $comment->comment_author_url;
        }

        return $semantic_linkbacks_canonical;
    }

    /**
     * Return canonical URL
     *
     * @param int|WP_Comment $comment Comment
     *
     * @return string the URL
     */
    public static function get_canonical_url( $comment ) {
        if ( is_numeric( $comment ) ) {
            $comment = get_comment( $comment );
        }
        // get canonical url...
        return get_comment_meta( $comment->comment_ID, 'semantic_linkbacks_canonical', true );
    }

    /**
     * Return type
     *
     * @param int|WP_Comment $comment Comment
     *
     * @return string the type
     */
    public static function get_type( $comment ) {
        if ( $comment instanceof WP_Comment ) {
            $comment = $comment->comment_ID;
        }
        // get type...
        return get_comment_meta( $comment, 'semantic_linkbacks_type', true );
    }

    /**
     * Return property
     *
     * @param int|WP_Comment $comment Comment
     * @param string $key Key
     *
     * @return boolean|string|array Property or False if Property Does Not Exist
     */
    public static function get_prop( $comment, $key ) {
        if ( $comment instanceof WP_Comment ) {
            $comment = $comment->comment_ID;
        }
        $prop = get_comment_meta( $comment, $key );
        if ( ! $prop || is_string( $prop ) ) {
            return $prop;
        }
        if ( 1 === count( $prop ) ) {
            return array_shift( $prop );
        }
        return $prop;
    }


    /**
     * Return author URL
     *
     * @param int|WP_Comment $comment Comment
     *
     * @return string the URL
     */
    public static function get_author_url( $comment ) {
        if ( $comment instanceof WP_Comment ) {
            $comment = $comment->comment_ID;
        }
        // get author URL...
        return get_comment_meta( $comment, 'semantic_linkbacks_author_url', true );
    }

    /**
     * Add cite to "reply"s
     *
     * thanks to @snarfed for the idea
     *
     * @param string $text the comment text
     * @param WP_Comment $comment the comment object
     * @param array $args a list of arguments
     *
     * @return string the filtered comment text
     */
    public static function comment_text_add_cite( $text, $comment = null, $args = array() ) {
        if ( ! $comment ) {
            return $text;
        }

        $semantic_linkbacks_type = self::get_type( $comment );

        // only change text for "real" comments (replys)
        if ( ! $semantic_linkbacks_type ||
            '' !== $comment->comment_type ||
            'reply' !== $semantic_linkbacks_type ) {
            return $text;
        }

        $url  = self::get_url( $comment );
        $host = wp_parse_url( $url, PHP_URL_HOST );

        // strip leading www, if any
        $host = preg_replace( '/^www\./', '', $host );
        // note that WordPress's sanitization strips the class="u-url". sigh. :/ also,
        // <cite> is one of the few elements that make it through the sanitization and
        // is still uncommon enough that we can use it for styling.
        $cite = apply_filters( 'semantic_linkbacks_cite', '<p><cite><a class="u-url" href="%1s">via %2s</a></cite></p>' );
        return $text . sprintf( $cite, $url, $host );
    }

    /**
     * Returns a post type that is used to generate text. This can be from post formats/kinds/post type/etc
     *
     * @param int $post_id Post ID
     *
     * @return string the post type
     */
    public static function get_post_type( $post_id ) {
        $post_typestrings = self::get_post_type_strings();
        $post_type        = $post_typestrings['post'];

        // If this is the page homepages are redirected to then use the site name
        if ( get_option( 'webmention_home_mentions', 0 ) === $post_id ) {
            $post_type = get_bloginfo( 'name' );
        } elseif ( 'page' === get_post_type( $post_id ) ) {
            $post_type = $post_typestrings['page'];
        } elseif ( current_theme_supports( 'post-formats' ) ) {
            $post_format = get_post_format( $post_id );

            // add "standard" as default for post format enabled types
            if ( ! $post_format || ! in_array( $post_format, array_keys( $post_typestrings ), true ) ) {
                $post_format = 'standard';
            }

            $post_type = $post_typestrings[ $post_format ];
        }

        return apply_filters( 'semantic_linkbacks_post_type', $post_type, $post_id );
    }



    /**
     * Generate excerpt for all types except "reply"
     *
     * @param string $text the comment text
     * @param WP_Comment $comment the comment object
     * @param array $args a list of arguments
     *
     * @return string the filtered comment text
     */
    public static function comment_text_excerpt( $text, $comment = null, $args = array() ) {
        if ( ! is_object( $comment ) ) {
            $comment = get_comment( $comment );
        }

        if ( ! $comment ) {
            return $text;
        }

        $semantic_linkbacks_type = self::get_type( $comment );

        // only change text for pingbacks/trackbacks/webmentions
        if ( '' === $comment->comment_type ||
            ! $semantic_linkbacks_type ||
            'reply' === $semantic_linkbacks_type ) {
            return $text;
        }

        // check semantic linkback type
        if ( ! in_array( $semantic_linkbacks_type, array_keys( self::get_comment_type_strings() ), true ) ) {
            $semantic_linkbacks_type = 'mention';
        }

        $post_type = self::get_post_type( $comment->comment_post_ID );

        // get all the excerpts
        $comment_type_excerpts = self::get_comment_type_excerpts();

        $url = self::get_url( $comment );

        // parse host
        $host = wp_parse_url( $url, PHP_URL_HOST );
        // strip leading www, if any
        $host = preg_replace( '/^www\./', '', $host );

        // generate output. use full content if it's small enough, otherwise use excerpt.
        $text_len = mb_strlen( wp_strip_all_tags( html_entity_decode( $text, ENT_QUOTES ) ) );
        if ( ! ( 'mention' === $semantic_linkbacks_type && $text_len <= MAX_INLINE_MENTION_LENGTH ) ) {
            $text = sprintf( $comment_type_excerpts[ $semantic_linkbacks_type ], get_comment_author_link( $comment->comment_ID ), $post_type, $url, $host );
        }
        return apply_filters( 'semantic_linkbacks_excerpt', $text );
    }

    /**
     * Replace comment url with canonical url
     *
     * @param string     $link the link url
     * @param WP_Comment $comment the comment object
     * @param array      $args a list of arguments to generate the final link tag
     *
     * @return string the linkback source or the original comment link
     */
    public static function get_comment_link( $link, $comment, $args ) {
        $semantic_linkbacks_canonical = self::get_canonical_url( $comment );

        if ( is_singular() && $semantic_linkbacks_canonical ) {
            return $semantic_linkbacks_canonical;
        }

        return $link;
    }

    /**
     * Replace comment url with author url
     *
     * @param string     $url        The comment author's URL.
     * @param int        $comment_ID The comment ID.
     * @param WP_Comment $comment    The comment object.
     *
     * @return string the replaced/parsed author url or the original comment link
     */
    public static function get_comment_author_url( $url, $id, $comment ) {
        $author_url = self::get_author_url( $comment );

        if ( $author_url ) {
            return $author_url;
        }

        return $url;
    }

    /**
     * Add comment classes from `semantic_linkbacks_type`s
     *
     * @return array the extended comment classes as array
     */
    public static function comment_class( $classes, $class, $comment_id, $post_id ) {
        // get comment
        $comment = get_comment( $comment_id );
        // "comment type to class" mapper
        $class_mapping = array(
            'mention'         => array( 'u-mention' ),

            'reply'           => array( 'u-comment' ),
            'repost'          => array( 'u-repost' ),
            'like'            => array( 'u-like' ),
            'favorite'        => array( 'u-favorite' ),
            'tag'             => array( 'u-tag' ),
            'bookmark'        => array( 'u-bookmark' ),
            'rsvp:yes'        => array( 'u-rsvp' ),
            'rsvp:no'         => array( 'u-rsvp' ),
            'rsvp:maybe'      => array( 'u-rsvp' ),
            'rsvp:interested' => array( 'u-rsvp' ),
            'invited'         => array( 'u-invitee' ),
            'listen'          => array( 'u-listen' ),
            'read'            => array( 'u-read' ),
            'watch'           => array( 'u-watch' ),
            'follow'          => array( 'u-follow' ),
        );

        $semantic_linkbacks_type = self::get_type( $comment );

        // check the comment type
        if ( $semantic_linkbacks_type && isset( $class_mapping[ $semantic_linkbacks_type ] ) ) {
            $classes = array_merge( $classes, $class_mapping[ $semantic_linkbacks_type ] );
            $classes = array_unique( $classes );
        }
        $classes[] = 'h-cite';
        return $classes;
    }
}