40, 'width' => 40, 'flex-height' => true, 'flex-width' => true, ) ); add_theme_support( 'custom-header', array( 'default-text-color' => '000000', 'width' => 1500, 'height' => 500, 'flex-height' => true, 'flex-width' => true, ) ); add_theme_support( 'custom-background', array( 'default-color' => 'ffffff', ) ); add_theme_support( 'html5', array( 'comment-list', 'comment-form', 'search-form', 'gallery', 'caption', 'style', 'script', ) ); register_nav_menus( array( 'primary' => esc_html__( 'Primary Menu', 'blurt' ), 'footer' => esc_html__( 'Footer Menu', 'blurt' ), ) ); add_image_size( 'blurt-gallery-thumb', 300, 300, true ); add_image_size( 'blurt-gallery-large', 600, 600, false ); } add_action( 'after_setup_theme', 'blurt_setup' ); /** * Set the content width in pixels. */ function blurt_content_width() { $GLOBALS['content_width'] = 780; } add_action( 'after_setup_theme', 'blurt_content_width', 0 ); /** * Enforce strict reverse-chronological feed order. * * WordPress puts sticky posts at the top of the home query by default, * which breaks the "no algorithmic timeline" rule. This ensures the * main feed, author archives, search, and tag pages all sort by date * descending with no sticky-post reordering. * * @param WP_Query $query The main query. */ function blurt_enforce_reverse_chronological( $query ) { if ( is_admin() || ! $query->is_main_query() ) { return; } $query->set( 'orderby', 'date' ); $query->set( 'order', 'DESC' ); $query->set( 'ignore_sticky_posts', 1 ); // Exclude reposts from search results. if ( $query->is_search() ) { $meta_query = $query->get( 'meta_query' ); if ( ! is_array( $meta_query ) ) { $meta_query = array(); } $meta_query[] = array( 'key' => '_blurt_repost_of', 'compare' => 'NOT EXISTS', ); $query->set( 'meta_query', $meta_query ); } } add_action( 'pre_get_posts', 'blurt_enforce_reverse_chronological' ); /** * Register the 'tab' query var for profile page tab switching. * * @param array $vars Allowed query variables. * @return array Modified query variables. */ function blurt_query_vars( $vars ) { $vars[] = 'tab'; return $vars; } add_filter( 'query_vars', 'blurt_query_vars' ); /** * Auto-create the "Tools" page on theme activation so that * page-tools.php is used via the WordPress template hierarchy. */ function blurt_create_tools_page() { $existing = get_page_by_path( 'tools', OBJECT, 'page' ); if ( ! $existing ) { // Also check trashed/drafted pages. $existing = get_posts( array( 'name' => 'tools', 'post_type' => 'page', 'post_status' => array( 'publish', 'draft', 'trash', 'private', 'pending' ), 'numberposts' => 1, ) ); } if ( $existing ) { return; } wp_insert_post( array( 'post_title' => __( 'Tools', 'blurt' ), 'post_name' => 'tools', 'post_type' => 'page', 'post_status' => 'publish', 'post_content' => '', 'post_author' => get_current_user_id() ? get_current_user_id() : 1, ) ); } add_action( 'after_switch_theme', 'blurt_create_tools_page' ); /** * Check if the current page is the Tools page. * * @return bool True if viewing the tools page. */ function blurt_is_tools_page() { return is_page( 'tools' ); } /** * Include pending comments in comment queries for admins. * * Filters both the main comment query and the top-level count query * used by comments_template() so that unapproved comments are fetched. * * @param array $args Comment query arguments. * @return array Modified arguments. */ function blurt_include_pending_comments( $args ) { if ( current_user_can( 'moderate_comments' ) ) { $args['status'] = 'all'; } return $args; } add_filter( 'comments_template_query_args', 'blurt_include_pending_comments' ); add_filter( 'comments_template_top_level_query_args', 'blurt_include_pending_comments' ); /** * Also filter the pre_get_comments query to include pending for admins. * * This catches comment queries that bypass comments_template filters, * such as direct WP_Comment_Query calls on single post views. * * @param WP_Comment_Query $comment_query The comment query instance. */ function blurt_pre_get_comments_pending( $comment_query ) { if ( is_admin() ) { return; } if ( current_user_can( 'moderate_comments' ) && is_singular() ) { $comment_query->query_vars['status'] = 'all'; } } add_action( 'pre_get_comments', 'blurt_pre_get_comments_pending' ); /** * AJAX: Moderate a comment (approve, spam, or delete). */ function blurt_moderate_comment() { check_ajax_referer( 'blurt_nonce', 'nonce' ); if ( ! is_user_logged_in() || ! current_user_can( 'moderate_comments' ) ) { wp_send_json_error( 'Insufficient permissions.' ); } $comment_id = isset( $_POST['comment_id'] ) ? absint( $_POST['comment_id'] ) : 0; $action = isset( $_POST['mod_action'] ) ? sanitize_key( $_POST['mod_action'] ) : ''; if ( ! $comment_id || ! get_comment( $comment_id ) ) { wp_send_json_error( 'Invalid comment.' ); } if ( ! in_array( $action, array( 'approve', 'spam', 'delete' ), true ) ) { wp_send_json_error( 'Invalid action.' ); } if ( 'approve' === $action ) { wp_set_comment_status( $comment_id, 'approve' ); } elseif ( 'spam' === $action ) { wp_spam_comment( $comment_id ); } elseif ( 'delete' === $action ) { wp_trash_comment( $comment_id ); } wp_send_json_success( array( 'action' => $action, 'comment_id' => $comment_id ) ); } add_action( 'wp_ajax_blurt_moderate_comment', 'blurt_moderate_comment' ); /** * Disable Jetpack Post Flair (sharing buttons, likes, ratings) entirely. * * Blurt has its own interaction stubs in the post card footer, so the * default wp.com post-flair bar is redundant and visually out of place. */ add_filter( 'post_flair_disable', '__return_true' ); /** * Check whether the current site is on a free (unpaid) plan. * * Used to gate the promotional card so it only appears on free sites, * matching the same logic as the WordPress.com marketing bar. * * @return bool True if the site is free (no paid plan). */ function blurt_is_free_site() { return ! (bool) get_blog_option( get_current_blog_id(), 'bundle_upgrade' ); } /** * Disable Jetpack Carousel. * * Blurt has its own image lightbox. The Carousel overlay conflicts * with it and its close button is covered by the marketing bar. */ add_filter( 'jp_carousel_maybe_disable', '__return_true' ); /** * Disable WordPress emoji detection script. * * Modern browsers render emoji natively. The wp-emoji-release.min.js * script and related inline styles are unnecessary overhead. */ function blurt_disable_emojis() { remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); remove_action( 'wp_print_styles', 'print_emoji_styles' ); remove_filter( 'the_content_feed', 'wp_staticize_emoji' ); remove_filter( 'comment_text_rss', 'wp_staticize_emoji' ); remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' ); } add_action( 'init', 'blurt_disable_emojis' ); /** * Disable the Verbum comment editor so Blurt uses its own simple textarea form. * * Verbum (loaded by jetpack-mu-wpcom) aggressively replaces the comment form * with a rich editor. We undo those hooks and dequeue its assets so the theme's * own compose-style comment form in comments.php is used instead. */ function blurt_disable_verbum() { // Bail early if Verbum class doesn't exist (e.g. non-wpcom installs). if ( ! class_exists( 'Automattic\Jetpack\Jetpack_Mu_Wpcom\Verbum_Comments' ) && ! class_exists( 'Verbum_Comments' ) ) { return; } // Remove Verbum's comment_form_field_comment filter that returns false. remove_filter( 'comment_form_field_comment', '__return_false', 11 ); // Remove Verbum's logged-in-as blanking. remove_filter( 'comment_form_logged_in', '__return_empty_string' ); // Dequeue Verbum scripts and styles so the editor wrapper never loads. add_action( 'wp_enqueue_scripts', function () { wp_dequeue_script( 'verbum-settings' ); wp_dequeue_script( 'verbum' ); wp_dequeue_style( 'verbum' ); wp_dequeue_script( 'verbum-editor' ); wp_dequeue_style( 'verbum-editor' ); }, 999 ); // Remove Verbum's render element that injects the editor wrapper div. global $wp_filter; foreach ( array( 'comment_form_submit_field', 'comment_form_must_log_in_after' ) as $hook ) { if ( isset( $wp_filter[ $hook ] ) ) { foreach ( $wp_filter[ $hook ]->callbacks as $priority => $callbacks ) { foreach ( $callbacks as $key => $callback ) { if ( is_array( $callback['function'] ) && is_object( $callback['function'][0] ) && 'verbum_render_element' === $callback['function'][1] ) { remove_filter( $hook, $callback['function'], $priority ); } } } } } // Remove Verbum's comment_form_defaults override so our args are respected. if ( isset( $wp_filter['comment_form_defaults'] ) ) { foreach ( $wp_filter['comment_form_defaults']->callbacks as $priority => $callbacks ) { foreach ( $callbacks as $key => $callback ) { if ( is_array( $callback['function'] ) && is_object( $callback['function'][0] ) && 'comment_form_defaults' === $callback['function'][1] ) { remove_filter( 'comment_form_defaults', $callback['function'], $priority ); } } } } } add_action( 'wp', 'blurt_disable_verbum' ); /** * Auto-assign hashtags in post content as WordPress tags. * * When a post is published, any #word patterns in the content are * extracted and assigned as post_tag terms. This ensures the tag * archive pages exist and blurt_linkify_hashtags() can generate * valid URLs. * * @param int $post_id Post ID. * @param WP_Post $post Post object. * @param bool $update Whether this is an update. */ function blurt_auto_assign_hashtags( $post_id, $post, $update ) { if ( 'post' !== $post->post_type || 'publish' !== $post->post_status ) { return; } if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } // Skip reposts — their content belongs to the original post. if ( get_post_meta( $post_id, '_blurt_repost_of', true ) ) { return; } $content = wp_strip_all_tags( $post->post_content ); if ( preg_match_all( '/(?<=\s|^)#([a-zA-Z0-9_\-]+)/u', $content, $matches ) ) { $tags = array_unique( array_map( 'sanitize_title', $matches[1] ) ); $tags = array_filter( $tags ); if ( $tags ) { wp_set_post_tags( $post_id, $tags, true ); } } } add_action( 'save_post', 'blurt_auto_assign_hashtags', 10, 3 ); /** * Make URLs in post and comment content clickable. * * WordPress core's make_clickable() converts bare URLs and email addresses * into anchor tags. Hooked before hashtag linkification so that URL-detection * runs on plain text, and hashtag links are added afterward. */ add_filter( 'the_content', 'make_clickable', 9 ); add_filter( 'comment_text', 'make_clickable', 9 ); /** * Convert #hashtags in post content to clickable tag archive links. * * Matches #word patterns that are not already inside an HTML tag or anchor, * and replaces them with a link to the corresponding WordPress tag page. * * All hashtags in the content are extracted first and batch-fetched in a * single get_terms() query, avoiding one DB hit per hashtag. A static * cache retains results across multiple calls within the same request. * * @param string $content The post content. * @return string Content with hashtags linked. */ function blurt_linkify_hashtags( $content ) { // Static cache of slug => WP_Term (or false) persists across calls. static $term_cache = array(); $pattern = '/(?<=\s|^|>)#([a-zA-Z0-9_\-]+)(?=[\s.,;:!?\'")<\]|$])/u'; // Extract all hashtag slugs from this content. if ( ! preg_match_all( $pattern, $content, $all_matches ) ) { return $content; } $slugs_to_fetch = array(); foreach ( $all_matches[1] as $raw_tag ) { $slug = sanitize_title( $raw_tag ); if ( $slug && ! isset( $term_cache[ $slug ] ) ) { $slugs_to_fetch[] = $slug; } } // Batch-fetch any uncached slugs in a single query. if ( $slugs_to_fetch ) { $terms = get_terms( array( 'taxonomy' => 'post_tag', 'slug' => array_unique( $slugs_to_fetch ), 'hide_empty' => false, ) ); // Index fetched terms by slug. $found = array(); if ( ! is_wp_error( $terms ) ) { foreach ( $terms as $term ) { $found[ $term->slug ] = $term; } } // Populate cache: found terms get the object, misses get false. foreach ( array_unique( $slugs_to_fetch ) as $slug ) { $term_cache[ $slug ] = isset( $found[ $slug ] ) ? $found[ $slug ] : false; } } // Fallback URL pieces (cached across calls via static). static $tag_base_url = null; if ( null === $tag_base_url ) { $tag_base = get_option( 'tag_base' ); $tag_base_url = home_url( '/' . ( $tag_base ? $tag_base : 'tag' ) . '/' ); } return preg_replace_callback( $pattern, function ( $matches ) use ( &$term_cache, $tag_base_url ) { $tag_slug = sanitize_title( $matches[1] ); $tag = isset( $term_cache[ $tag_slug ] ) ? $term_cache[ $tag_slug ] : false; if ( $tag ) { $url = get_tag_link( $tag->term_id ); } else { $url = $tag_base_url . rawurlencode( $tag_slug ) . '/'; } return sprintf( '#%s', esc_url( $url ), esc_html( $matches[1] ) ); }, $content ); } add_filter( 'the_content', 'blurt_linkify_hashtags', 20 ); // Priority 31 so it runs after wpautop (30) — the
tags ensure // the regex lookahead matches hashtags at the end of a comment. add_filter( 'comment_text', 'blurt_linkify_hashtags', 31 ); /** * Strip Gutenberg inline styles and color classes from post content. * * The block editor injects inline style attributes (e.g. * style="color:var(--wp--preset--color--contrast)") and utility classes * (e.g. has-text-color, has-contrast-color) on paragraph and wrapper * elements. These inline styles have higher specificity than any CSS * selector, so they override the theme's own color rules. * * Since Blurt controls all its own typography and color via style.css, * we strip these from the rendered content so the theme styles apply * cleanly. * * @param string $content The post content HTML. * @return string Cleaned content. */ function blurt_strip_block_inline_styles( $content ) { // Remove inline style attributes entirely. $content = preg_replace( '/\s+style="[^"]*"/i', '', $content ); // Remove Gutenberg color/layout utility classes. $content = preg_replace_callback( '/class="([^"]*)"/i', function ( $matches ) { $classes = preg_replace( '/\b(has-[\w-]+-color|has-text-color|has-background|has-link-color|has-[\w-]+-background-color|wp-elements-[\w-]+)\b/', '', $matches[1] ); $classes = preg_replace( '/\s{2,}/', ' ', trim( $classes ) ); return 'class="' . $classes . '"'; }, $content ); return $content; } add_filter( 'the_content', 'blurt_strip_block_inline_styles', 999 ); add_filter( 'comment_text', 'blurt_strip_block_inline_styles', 999 ); /** * Strip [gallery] shortcodes from post content. * * Blurt displays post images via blurt_get_post_images() in the * template gallery grid. Without this filter the gallery shortcode * is also rendered inline by the_content (and enhanced by Jetpack * Carousel), causing duplicate image sets. * * @param string $content The post content. * @return string Content without gallery shortcodes. */ function blurt_strip_gallery_shortcode( $content ) { return preg_replace( '/\[gallery[^\]]*\]/', '', $content ); } add_filter( 'the_content', 'blurt_strip_gallery_shortcode', 5 ); /** * Enqueue scripts and styles. */ function blurt_scripts() { wp_enqueue_style( 'blurt-style', get_stylesheet_uri(), array(), wp_get_theme()->get( 'Version' ) ); wp_enqueue_script( 'blurt-script', get_template_directory_uri() . '/js/blurt.js', array(), wp_get_theme()->get( 'Version' ), array( 'strategy' => 'defer' ) ); // Determine end-of-timeline message based on page context. $end_message = __( 'You\u2019ve reached the beginning of the timeline.', 'blurt' ); if ( is_tag() ) { $end_message = sprintf( /* translators: %s: tag name */ __( 'No more posts tagged #%s', 'blurt' ), single_tag_title( '', false ) ); } elseif ( is_search() ) { $end_message = __( 'No more results found.', 'blurt' ); } elseif ( is_author() ) { $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'posts'; $author_name = get_queried_object() ? get_queried_object()->display_name : ''; switch ( $tab ) { case 'reposts': $end_message = sprintf( __( 'No more reposts from %s.', 'blurt' ), $author_name ); break; case 'comments': $end_message = sprintf( __( 'No more comments from %s.', 'blurt' ), $author_name ); break; case 'media': $end_message = sprintf( __( 'No more media posts from %s.', 'blurt' ), $author_name ); break; case 'likes': $end_message = sprintf( __( 'No more liked posts from %s.', 'blurt' ), $author_name ); break; default: $end_message = sprintf( __( 'No more posts from %s.', 'blurt' ), $author_name ); break; } } elseif ( is_category() || is_archive() ) { $end_message = __( 'No more posts in this archive.', 'blurt' ); } wp_localize_script( 'blurt-script', 'blurtData', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'blurt_nonce' ), 'poll_interval' => 13000, 'user_logged_in' => is_user_logged_in(), 'current_user_id' => get_current_user_id(), 'can_upload' => is_user_logged_in() && current_user_can( 'upload_files' ), 'max_post_images' => 4, 'max_comment_images' => 1, 'end_message' => $end_message, 'i18n' => array( 'repost' => __( 'Repost', 'blurt' ), 'undo_repost' => __( 'Undo repost', 'blurt' ), 'quote_repost' => __( 'Quote repost', 'blurt' ), /* translators: shown while an image is being uploaded */ 'uploading' => __( 'Uploading…', 'blurt' ), 'upload_error' => __( 'Upload failed. Please try again.', 'blurt' ), 'remove_image' => __( 'Remove image', 'blurt' ), ), ) ); // Enqueue import scripts on the tools page. if ( blurt_is_tools_page() && current_user_can( 'manage_options' ) ) { wp_enqueue_script( 'jszip', get_template_directory_uri() . '/js/jszip.min.js', array(), '3.10.1', true ); wp_enqueue_script( 'fflate', get_template_directory_uri() . '/js/fflate.min.js', array(), '0.8.2', true ); wp_enqueue_script( 'blurt-import', get_template_directory_uri() . '/js/import.js', array( 'jszip', 'fflate' ), wp_get_theme()->get( 'Version' ), true ); } } add_action( 'wp_enqueue_scripts', 'blurt_scripts' ); /** * Dequeue block editor and global styles that this classic theme does not need. * * WordPress core enqueues wp-block-library, global-styles, and classic-theme-styles * on the frontend even for classic themes. These inject color declarations on p, a, * and other elements that compete with theme styles. Since Blurt never uses block * markup, these stylesheets are unnecessary and cause text to appear faint. */ /** * Remove the default WordPress.com comment likes button. * * Blurt has its own heart/like button on comments, so the built-in * "comment-likes comment-not-liked" widget is redundant. */ function blurt_disable_comment_likes() { remove_filter( 'comment_text', 'comment_like_button', 12 ); } add_action( 'wp', 'blurt_disable_comment_likes' ); /** * Disable the WordPress.com actionbar. * * Blurt has its own compose, like, and follow UI so the floating * actionbar is redundant. Remove both the asset enqueue and the * footer HTML injection. */ function blurt_disable_actionbar() { remove_action( 'wp_enqueue_scripts', 'wpcom_actionbar_enqueue_scripts', 101 ); remove_action( 'wp_footer', 'wpcom_actionbar_footer' ); } add_action( 'init', 'blurt_disable_actionbar' ); function blurt_dequeue_block_styles() { wp_dequeue_style( 'wp-block-library' ); wp_dequeue_style( 'wp-block-library-theme' ); wp_dequeue_style( 'global-styles' ); wp_dequeue_style( 'classic-theme-styles' ); wp_dequeue_style( 'free-site-marketing-bar' ); wp_dequeue_style( 'wpcom-core-compat-playlist-styles' ); wp_dequeue_style( 'wpcom-bbpress2-staff-css' ); wp_dequeue_style( 'reblogging' ); wp_dequeue_style( 'geo-location-flair' ); wp_dequeue_style( 'h4-global' ); } add_action( 'wp_enqueue_scripts', 'blurt_dequeue_block_styles', 100 ); /** * Register widget areas. */ function blurt_widgets_init() { register_sidebar( array( 'name' => esc_html__( 'Right Sidebar', 'blurt' ), 'id' => 'sidebar-right', 'description' => esc_html__( 'Add widgets here for the right sidebar.', 'blurt' ), 'before_widget' => '', 'before_title' => '
%s%s', wp_kses_post( $original_content ), esc_html( $original_author ) ); $new_post_id = wp_insert_post( array( 'post_title' => wp_trim_words( wp_strip_all_tags( $original_content ), 10, '...' ), 'post_content' => $repost_content, 'post_status' => 'publish', 'post_author' => get_current_user_id(), ) ); if ( is_wp_error( $new_post_id ) ) { wp_send_json_error( 'Failed to create repost.' ); } update_post_meta( $new_post_id, '_blurt_repost_of', $post_id ); } $new_data = blurt_get_repost_data( $post_id ); wp_send_json_success( array( 'reposted' => $new_data['reposted'], 'count' => $new_data['count'], ) ); } add_action( 'wp_ajax_blurt_toggle_repost', 'blurt_toggle_repost' ); /** * Enforce 500 character limit on post content. * * @param array $data An array of slashed, sanitized post data. * @param array $postarr An array of sanitized post data. * @return array Modified post data. */ function blurt_enforce_character_limit( $data, $postarr ) { if ( 'post' !== $data['post_type'] ) { return $data; } // Reposts contain the original post's content and should not be truncated. if ( ! empty( $postarr['meta_input']['_blurt_repost_of'] ) || ( ! empty( $postarr['ID'] ) && get_post_meta( $postarr['ID'], '_blurt_repost_of', true ) ) ) { return $data; } $content = wp_strip_all_tags( $data['post_content'] ); $char_count = mb_strlen( trim( blurt_strip_urls_for_count( $content ) ) ); if ( $char_count > 500 ) { // Only truncate the non-URL text portion; this is a safety net. $data['post_content'] = mb_substr( $data['post_content'], 0, 1000 ); } return $data; } add_filter( 'wp_insert_post_data', 'blurt_enforce_character_limit', 10, 2 ); /** * Pre-update check for character limit. * * @param int $post_id Post ID. */ function blurt_pre_update_character_check( $post_id ) { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } if ( 'post' !== get_post_type( $post_id ) ) { return; } $post = get_post( $post_id ); $content = wp_strip_all_tags( $post->post_content ); if ( mb_strlen( trim( blurt_strip_urls_for_count( $content ) ) ) > 500 ) { wp_update_post( array( 'ID' => $post_id, 'post_content' => mb_substr( $post->post_content, 0, 1000 ), ) ); } } add_action( 'pre_post_update', 'blurt_pre_update_character_check' ); /** * Customizer additions. * * @param WP_Customize_Manager $wp_customize Theme Customizer object. */ function blurt_customize_register( $wp_customize ) { $wp_customize->add_section( 'blurt_options', array( 'title' => esc_html__( 'Blurt Options', 'blurt' ), 'priority' => 30, ) ); $wp_customize->add_setting( 'blurt_accent_color', array( 'default' => '#6366F1', 'sanitize_callback' => 'sanitize_hex_color', 'transport' => 'postMessage', ) ); $wp_customize->add_control( new WP_Customize_Color_Control( $wp_customize, 'blurt_accent_color', array( 'label' => esc_html__( 'Accent Color', 'blurt' ), 'section' => 'blurt_options', ) ) ); } add_action( 'customize_register', 'blurt_customize_register' ); /** * Output customizer CSS. */ function blurt_customizer_css() { $accent_color = get_theme_mod( 'blurt_accent_color', '#6366F1' ); ?> post_content ); return mb_strlen( trim( blurt_strip_urls_for_count( $text ) ) ); } /** * Get user statistics. * * @param int $user_id User ID. * @return array Array with post_count, follower_count, following_count. */ function blurt_get_user_stats( $user_id ) { $post_count = count_user_posts( $user_id, 'post', true ); $follower_count = 0; if ( function_exists( 'wpcom_subs_total_for_blog' ) ) { $follower_count = (int) wpcom_subs_total_for_blog(); } $following_count = 0; if ( function_exists( 'wpcom_subs_get_blogs_ids' ) ) { $following_count = count( wpcom_subs_get_blogs_ids( array( 'user_id' => $user_id ) ) ); } return array( 'post_count' => $post_count, 'follower_count' => $follower_count, 'following_count' => $following_count, ); } /** * Get avatar URL for a user. * * @param int $user_id User ID. * @param int $size Avatar size in pixels. * @return string Avatar URL. */ /** * Apply WordPress.com Site Accelerator (Photon) optimization to an image URL. * * Adds width, quality, and strip parameters for optimal delivery. * * @param string $url The image URL. * @param int $width Desired width in pixels. 0 for no resize. * @param int $height Desired height in pixels. 0 for no resize. * @param array $args Additional Photon args (quality, fit, resize, etc.). * @return string Optimized image URL. */ function blurt_optimize_image_url( $url, $width = 0, $height = 0, $args = array() ) { if ( ! $url ) { return $url; } // Append Photon parameters directly to the site-origin URL. // WordPress.com supports these parameters on its own domain. $params = array( 'strip' => 'all', 'quality' => 80, ); if ( $width && $height ) { $params['fit'] = $width . ',' . $height; } elseif ( $width ) { $params['w'] = $width; } elseif ( $height ) { $params['h'] = $height; } $params = array_merge( $params, $args ); return add_query_arg( $params, $url ); } function blurt_get_avatar_url( $user_id, $size = 48 ) { // Request 2x the display size for crisp rendering on high-DPI screens. $retina_size = $size * 2; $url = get_avatar_url( $user_id, array( 'size' => $retina_size ) ); if ( ! $url ) { return ''; } return blurt_optimize_image_url( $url, $retina_size, $retina_size, array( 'fit' => $retina_size . ',' . $retina_size ) ); } /** * Format post time as full date and time string. * * @param WP_Post|int $post Post object or ID. * @return string Formatted date and time. */ function blurt_time_format( $post ) { $post = get_post( $post ); if ( ! $post ) { return ''; } return get_the_date( '', $post ) . ' ' . get_the_time( '', $post ); } /** * Return an