CBC 01: Nguồn gốc chim bồ câu và các giống chim bồ câu phổ biến

Nguồn gốc và lịch sử chim bồ câu

Chim bồ câu (danh pháp khoa học: Columba livia) là loài chim thuộc họ Bồ câu (Columbidae), có nguồn gốc từ khu vực Địa Trung Hải, Bắc Phi, và Tây Á. Lịch sử loài chim này gắn liền với con người từ hàng nghìn năm trước. Chim bồ câu được thuần hóa cách đây khoảng 5.000–10.000 năm, chủ yếu để lấy thịt, trứng, và sau này phục vụ mục đích truyền tin hoặc làm cảnh. Trong các nền văn hóa, chim bồ câu thường được xem là biểu tượng của hòa bình, tình yêu, và sự trung thành.

Chim bồ câu hoang dã thường sống ở các vách núi hoặc khu vực đồng bằng, nhưng qua quá trình thuần hóa, chúng đã thích nghi với môi trường sống của con người và phát triển thành nhiều giống khác nhau.


Các giống chim bồ câu trên thế giới

Trên thế giới hiện nay, có hàng trăm giống bồ câu được lai tạo để phục vụ các mục đích khác nhau như nuôi lấy thịt, làm cảnh, hoặc đua. Một số giống phổ biến bao gồm:

  1. Chim bồ câu đua (Racing Homer):
    • Đặc điểm: Dáng thon dài, cơ bắp khỏe mạnh, có khả năng định vị và bay xa.
    • Ứng dụng: Được sử dụng nhiều trong các cuộc thi đua bồ câu.
  2. Chim bồ câu kiểng (Fancy Pigeon):
    • Đặc điểm: Bộ lông đẹp mắt, màu sắc phong phú, dáng đứng kiêu hãnh.
    • Ứng dụng: Nuôi để làm cảnh và tham gia các cuộc thi sắc đẹp.
  3. Chim bồ câu thịt (King Pigeon):
    • Đặc điểm: To lớn, trọng lượng nặng (có thể lên đến 1kg), thân mình ngắn và chắc.
    • Ứng dụng: Chuyên nuôi để lấy thịt.
  4. Chim bồ câu đưa thư (Carrier Pigeon):
    • Đặc điểm: Bay rất nhanh và xa, có khả năng định vị mạnh mẽ.
    • Ứng dụng: Truyền tin trong chiến tranh hoặc các khu vực không có liên lạc hiện đại.
  5. Chim bồ câu hoang dã (Rock Dove):
    • Đặc điểm: Màu xám tro, lưng xanh nhạt, có vệt đen ở cánh.
    • Ứng dụng: Thường gặp ở các khu vực đô thị và vùng hoang dã.

Các giống chim bồ câu phổ biến ở Việt Nam

Tại Việt Nam, chim bồ câu được nuôi phổ biến cho các mục đích như thương phẩm (lấy thịt) và làm cảnh. Một số giống phổ biến bao gồm:

  1. Bồ câu ta:
    • Đặc điểm: Kích thước nhỏ, khả năng sinh sản nhanh, dễ nuôi.
    • Ứng dụng: Chủ yếu nuôi để lấy thịt.
  2. Bồ câu Pháp:
    • Đặc điểm: To hơn bồ câu ta, có thể đạt 0.5–0.7kg/con, tăng trưởng nhanh.
    • Ứng dụng: Được nuôi phổ biến trong các mô hình chăn nuôi công nghiệp.
  3. Bồ câu Gà (Giant Runt):
    • Đặc điểm: Lớn hơn bồ câu Pháp, trọng lượng trung bình từ 1–1.2kg, dáng hơi giống gà.
    • Ứng dụng: Nuôi lấy thịt và làm cảnh.
  4. Bồ câu kiểng Việt Nam:
    • Đặc điểm: Có màu lông phong phú, ngoại hình đẹp, dáng đứng uyển chuyển.
    • Ứng dụng: Được ưa chuộng trong cộng đồng chơi chim cảnh.
  5. Bồ câu đua (Racing Homer):
    • Đặc điểm: Thân hình thon gọn, cánh khỏe, bay xa tốt.
    • Ứng dụng: Tham gia các cuộc thi đua bồ câu.
  6. Bồ câu Sư tử (Jacobins):
    • Đặc điểm: Bộ lông quanh cổ dày như bờm sư tử, dáng kiêu sa.
    • Ứng dụng: Nuôi làm cảnh.

Đặc điểm chung của chim bồ câu

  • Hình dáng: Đầu nhỏ, cổ dài vừa phải, đôi mắt tròn, mỏ ngắn và nhọn.
  • Lông: Mượt, thường có màu xám, trắng, đen, hoặc pha trộn giữa các màu.
  • Khả năng bay: Đôi cánh dài và khỏe giúp chim bồ câu bay xa và cao.
  • Tập tính: Sống thành đàn, trung thành với tổ, khả năng định vị tốt.
  • Sinh sản: Mỗi lứa đẻ từ 1–2 trứng, thời gian ấp khoảng 17–19 ngày. Chim bồ câu non lớn nhanh và có thể tách mẹ sau 30–35 ngày.

Lưu ý khi nuôi chim bồ câu ở Việt Nam

  • Tùy vào mục đích nuôi (thương phẩm, cảnh, đua), bạn nên chọn giống phù hợp.
  • Bồ câu Pháp và bồ câu ta là hai giống dễ nuôi, phù hợp cho các mô hình chăn nuôi nhỏ lẻ và công nghiệp.
  • Nuôi bồ câu kiểng hoặc bồ câu đua cần đầu tư cao hơn nhưng mang lại giá trị kinh tế và tinh thần lớn.

AZDIGI Black Friday 2024: 7 sắc cầu vồng 7 ngày giảm sốc

Siêu ưu đãi lớn nhất trong năm duy nhất trong mùa ngày hội mua sắm Black Friday với ưu đãi lên tới 90%, và ra mắt tuỳ chọn AMD EPYC cho Premium Business Hosting và Turbo Business Hosting.

Đen thôi chưa đủ! Black Friday 7 màu cầu vồng siêu đỉnh tại AZDIGI. Nhập hội AZDIGI với mức giá chỉ thấy một lần trong năm.

Black Friday 2024 AZDIGI
Black Friday 2024 AZDIGI

8 ngày rực rỡ, giảm giá hết cỡ lên đến 90%

Hòa mình vào không khí lễ hội mua sắm với “7 sắc cầu vồng, 7 ngày giảm sốc”: Red Friday, Orange Saturday, Yellow Sunday, Green Monday, Blue Tuesday, Indigo Wednesday, Violet Thursday, mỗi ngày đều mang đến cơ hội săn deal hot với ưu đãi lên đến 80% cho các dịch vụ được yêu thích nhất. Bùng nổ với ngày Black Friday với siêu ưu đãi 90%.

    • Pro Platinum Hosting: Giảm giá 70% cho mã BF2024_REDFRIDAY.

    • Premium Business Hosting: Giảm giá 70% cho mã: BF2024_ORGSAT.
    • Turbo Business Hosting: Giảm giá 70% cho mã: BF2024_YELLOWSUN.
    • AMD Cloud Server: Giảm giá 70% cho mã: BF2024_GREENMON.
    • AZ Pro Hosting, SEO Hosting: Giảm giá 70% cho mã: BF2024_BLUETUES.
    • Pro SSD VPS: Giảm giá 70% cho mã: BF2024_INDIGOWED.
    • Pro Platinum Hosting, Email Hosting, X-Platinum VPS: Giảm giá 80% cho mã: BF2024_VIOLETTHU.
    • Pro Platinum Hosting, AZ Pro Hosting, Premium Business Hosting, Email Hosting, X-Platinum VPS, Platinum Cloud Server: Giảm giá 90% cho mã: BF2024_BLACKFRIDAY.

Lưu ý mã rất nhanh hết: Quý khách hãy lưu lại mã và khung thời gian, và thực hiện truy cập vào đúng trang dịch vụ của sản phẩm áp dụng giảm giá để đặt hàng trước thời gian bắt đầu và bấm nút áp dụng mã ngay thời điểm 10 giờ sáng mỗi ngày.

Bạn đang muốn đăng ký ngay bây giờ không phải đợi? Hãy xem các khuyến mãi đang áp dụng ngay lúc này.

Nuôi chim bồ câu kết hợp nuôi thỏ: Mô hình chăn nuôi hiệu quả và bền vững

Trong bối cảnh nông nghiệp ngày càng hướng đến sự đa dạng và tối ưu hóa lợi nhuận, mô hình kết hợp nuôi chim bồ câu và thỏ đang được nhiều người lựa chọn. Không chỉ tận dụng hiệu quả diện tích chuồng trại và nguồn thức ăn, cách nuôi này còn giúp giảm thiểu chi phí đầu tư, tăng thu nhập, và tận dụng triệt để các phụ phẩm chăn nuôi.

Bài viết này sẽ giới thiệu về mô hình nuôi chim bồ câu, kết hợp với nuôi thỏ và gà thả vườn. Nếu bạn đang tìm kiếm một giải pháp chăn nuôi thông minh và thân thiện với môi trường, đây chắc chắn là lựa chọn đáng để thử!

Báo Giá Giống Chim Bồ Câu Tháng 11/2024 Tại Đắk Lắk

Trong tháng 11 năm 2024, tại khu vực Đắk Lắk, giá giống chim bồ câu đang có những biến động phù hợp với nhu cầu thị trường. Dưới đây là chi tiết giá cả của hai giống chim phổ biến:

  1. Chim Bồ Câu Pháp
    • Cặp chim non từ 2 đến 3 tháng tuổi: 350,000 VND
    • Cặp chim đã đến tuổi sinh sản: 500,000 VND – 600,000 VND

    Giống chim bồ câu Pháp nổi tiếng với khả năng sinh trưởng tốt, phù hợp với các mô hình chăn nuôi trong khu vực. Giá thành của giống này tương đối hợp lý, đặc biệt thích hợp cho những hộ gia đình hoặc trang trại muốn bắt đầu nuôi chim với chi phí thấp.

  2. Chim Titan Thái (Siêu Thịt)
    • Cặp chim non từ 2 đến 3 tháng tuổi: 400,000 VND

    Chim Titan Thái là một giống chim chuyên thịt, có tốc độ tăng trưởng nhanh và cho khối lượng thịt cao hơn so với bồ câu Pháp, vì vậy giá của giống chim này cũng cao hơn một chút. Với 400,000 VND cho mỗi cặp chim từ 2 đến 3 tháng tuổi, Titan Thái là lựa chọn lý tưởng cho những trang trại hướng đến mục tiêu sản xuất thịt bồ câu thương phẩm chất lượng cao.

Nhận Xét:

  • Chim Bồ Câu Pháp có lợi thế giá cả phải chăng, dễ nuôi, thích hợp với mô hình chăn nuôi quy mô vừa và nhỏ, phù hợp với điều kiện nuôi thả hỗn hợp và các gia đình muốn tăng thu nhập từ chăn nuôi.
  • Chim Titan Thái lại phù hợp cho các trang trại chuyên sản xuất bồ câu thương phẩm lớn, nhờ vào năng suất thịt cao hơn và chất lượng thịt tốt, dù giá cao hơn so với bồ câu Pháp.

Các hộ chăn nuôi tại Đắk Lắk có thể lựa chọn giống phù hợp với mục đích chăn nuôi của mình, từ đó đạt được hiệu quả kinh tế cao nhất.

1 Kg cá trê giống được bao nhiêu con

Số lượng cá trê giống trong 1 kg sẽ phụ thuộc vào kích thước của từng con cá. Thông thường:

  • Cá trê giống cỡ nhỏ (3-5 cm): Khoảng 200-300 con/kg.
  • Cá trê giống cỡ trung bình (5-7 cm): Khoảng 150-200 con/kg.
  • Cá trê giống cỡ lớn hơn (7-10 cm): Khoảng 80-100 con/kg.

Kích thước và số lượng cá trê giống có thể khác nhau tùy vào nguồn cung và cách nuôi dưỡng. Bạn nên hỏi trực tiếp nhà cung cấp cá giống để có con số chính xác nhất cho lô cá bạn định mua.

Đi mua Chim Bồ Câu tại thị trấn Krông Kmar tháng 11 năm 2024

Ghé lại trại chim bồ câu Quế Hoa tại thị trấn Krông Kmar sau 5 năm. Lần đầu mình đi xuống đây mua chim bồ câu giống là ngày 06/10/2019, lần này quay trở lại vào ngày 08/11/2024 thì trại đã không còn nuôi chim bồ câu nữa. Sẵn tiện xuống đây rồi thì chạy qua Hòa Thành tìm mua chim bồ câu giống luôn, tình cờ gặp được nhà cô chú nuôi chim bồ câu thả rông nhìn kích thước chim cũng lớn nên vào hỏi thăm thử.

Đếm số lượng glyphs của font bằng FontLib

Đoạn code ví dụ cách đếm glyphs của font bằng cách sử dụng tiện ích FontLib.

$font_file = get_post_meta( $post_id, 'font_file', true );

if ( $this->is_media_file_exists( $font_file ) ) {
    $font = FontLib\Font::load( get_attached_file( $font_file ) );
    $font->parse();

    $table  = $font->getTableObject( 'maxp' );
    $glyphs = $table->data['numGlyphs'] ?? '';
}

Đầu vào là chúng ta có meta key của bài viết là ‘font_file‘ và $post_id là id của bài viết font cần lấy thông tin.

Cho phép tải lên các file có đuôi khác nhau trên WordPress

Mặc định WordPress sẽ chỉ cho phép tải lên các đuôi tập tin cho sẵn. Nếu bạn muốn bổ sung thêm các đuôi mở rộng khác thì có thể áp dụng bộ lọc upload_mimes.

function hocwp_font_demo_upload_mimes( $mime_types ) {
    $mime_types['svg']   = 'image/svg+xml';
    $mime_types['ttf']   = 'application/x-font-ttf';
    $mime_types['otf']   = 'application/x-font-opentype';
    $mime_types['woff']  = 'application/font-woff';
    $mime_types['woff2'] = 'application/font-woff2';
    $mime_types['eot']   = 'application/vnd.ms-fontobject';
    $mime_types['sfnt']  = 'application/font-sfnt';

    return $mime_types;
}

add_filter( 'upload_mimes', 'hocwp_font_demo_upload_mimes' );

 

Hàm import cơ sở dữ liệu vào MySQL

Nếu bạn biết thông tin kết nối đến cơ sở dữ liệu nhưng không có cách nào để import database thì bạn có thể sử dụng hàm PHP để nhập cơ sở dữ liệu vào MySQL Server.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>MySQL Tools by Lai Dinh Cuong</title>
</head>
<body>
<?php
/**
 * Imports a large .sql file into a MySQL database.
 *
 * @param string $filePath Path to the .sql file.
 * @param array $dbConfig Database configuration array with keys: host, username, password, database.
 *
 * @return bool True on success, false on failure.
 */
function importLargeSqlFileToMySQL( $filePath, $dbConfig ) {
    $db_host = $dbConfig['host'] ?? '';

    if ( empty( $db_host ) ) {
        $db_host = 'localhost';
    }

    // Create a new PDO instance
    $dsn = "mysql:host={$db_host};dbname={$dbConfig['database']};charset=utf8";

    try {
        $pdo = new PDO( $dsn, $dbConfig['username'], $dbConfig['password'], [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ] );
    } catch ( PDOException $e ) {
        die( 'Connection failed: ' . $e->getMessage() );
    }

    // Ensure the file exists
    if ( ! file_exists( $filePath ) ) {
        die( 'File not found: $filePath' );
    }

    // Open the SQL file
    $handle = fopen( $filePath, 'r' );

    if ( $handle === false ) {
        die( 'Failed to open file: $filePath' );
    }

    // Read and execute the file line by line
    $query = '';

    try {
        $pdo->beginTransaction();

        while ( ( $line = fgets( $handle ) ) !== false ) {
            $trimmedLine = trim( $line );

            // Skip comments and empty lines
            if ( empty( $trimmedLine ) || strpos( $trimmedLine, '--' ) === 0 || strpos( $trimmedLine, '/*' ) === 0 || strpos( $trimmedLine, '//' ) === 0 ) {
                continue;
            }

            // Append line to current query
            $query .= $line;

            // If query ends with a semicolon, execute it
            if ( substr( trim( $query ), - 1 ) === ';' ) {
                // Check if the query is a CREATE TABLE statement
                if ( preg_match( '/CREATE TABLE `?(\w+)`?/', $query, $matches ) ) {
                    $tableName = $matches[1];
                    // Drop the table if it exists
                    $pdo->exec( "DROP TABLE IF EXISTS `$tableName`" );
                }

                try {
                    $pdo->exec( $query );
                } catch ( PDOException $e ) {
                    // Log error and continue
                    echo 'Error executing query: ' . $e->getMessage() . "\n";
                }

                $query = ''; // Reset query
            }
        }

        $pdo->commit();

        fclose( $handle );

        return true;
    } catch ( PDOException $e ) {
        if ( $pdo->inTransaction() ) {
            $pdo->rollBack();
        }

        // Query to get the size of the database
        $sql = "SELECT table_schema AS `Database`, 
                   SUM(data_length + index_length) AS `Size` 
            FROM information_schema.tables 
            WHERE table_schema = :database 
            GROUP BY table_schema";

        $size = false;

        try {
            $stmt = $pdo->prepare( $sql );
            $stmt->bindParam( ':database', $dbConfig['database'], PDO::PARAM_STR );
            $stmt->execute();
            $result = $stmt->fetch();
            $size   = $result ? (float) $result['Size'] : 0;
        } catch ( PDOException $e ) {
            die( 'Failed to get database size: ' . $e->getMessage() );
        }

        fclose( $handle );

        if ( $size ) {
            return true;
        } else {
            die( 'Import failed: ' . $e->getMessage() );
        }
    }
}

// Example usage
$dbConfig = [
    'host'     => $_GET['db_host'] ?? '',
    'username' => $_GET['db_user'] ?? '',
    'password' => $_GET['db_password'] ?? '',
    'database' => $_GET['db_name'] ?? '',
];

$filePath = $_GET['file'] ?? '';

if ( empty( $filePath ) || ! file_exists( $filePath ) ) {
    $filePath = 'database.sql';
}

if ( importLargeSqlFileToMySQL( $filePath, $dbConfig ) ) {
    echo 'Import successful!';
} else {
    echo 'Import failed.';
}
?>
</body>
</html>

 

Thêm tham số tùy chỉnh cho phân trang

Khi sử dụng phân trang tùy chỉnh cho một danh sách nào đó hiển thị trong trang single hoặc trang archive. Chúng ta sẽ không sử dụng tham số paged thông thường, thay vào đó mình sẽ truyền vào tham số pagi để sử dụng.

add_filter( 'hocwp_theme_paginate_links_args', function ( $args ) {
    if ( is_author() ) {
        $agent = new LDC_Agent( get_queried_object_id() );

        if ( $agent->is_valid() ) {
            $base = $args['base'] ?? '';

            if ( ! empty( $base ) ) {
                $base = str_replace( 'page/%#%/', '', $base );
                $base = remove_query_arg( 'pagi', $base );

                if ( str_contains( $base, '?' ) ) {
                    $base .= '&';
                } else {
                    $base .= '?';
                }

                $base .= 'pagi=%#%';

                $args['base'] = $base;
            }
        }
    }

    return $args;
} );

add_filter( 'hocwp_theme_current_paged', function ( $paged ) {
    if ( empty( $paged ) ) {
        $paged = absint( $_GET['pagi'] ?? '' );
    }

    return $paged;
} );

 

Hàm lấy user theo số lượng bài viết sử dụng $wpdb

Truy vấn người dùng theo số lượng bài viết, sắp xếp từ cao đến thấp, có thể tìm kiếm theo display_name hoặc user_login.

public function get_users_by_post_count( $post_type = 'post', $number = 5, $search = '', $role = 'agent' ) {
    global $wpdb;

    $sql = "SELECT {$wpdb->users}.ID, p.post_count FROM {$wpdb->users}";
    $sql .= " LEFT JOIN ( SELECT post_author, COUNT(*) AS post_count FROM {$wpdb->posts} WHERE post_type = %s GROUP BY post_author ) p ON {$wpdb->users}.id = p.post_author";

    if ( ! empty( $search ) ) {
        $sql .= " WHERE {$wpdb->users}.display_name LIKE '%" . $search . "%' OR {$wpdb->users}.user_login LIKE '%" . $search . "%'";
    }

    $sql .= " ORDER BY p.post_count DESC";

    $users = $wpdb->get_results( $wpdb->prepare( $sql, $post_type ) );

    if ( ! empty( $role ) ) {
        $lists = array();

        foreach ( $users as $obj ) {
            $user = get_user_by( 'id', $obj->ID );

            if ( $user instanceof WP_User ) {
                if ( in_array( $role, $user->roles ) ) {
                    $lists[] = $user;
                }
            }
        }

        $users = $lists;
    }

    if ( HT()->is_positive_number( $number ) ) {
        $users = array_slice( $users, 0, $number );
    }

    return $users;
}

 

Làm slide hình ảnh chạy sang trái phải đơn giản

Trong ví dụ này, mình có hơn 10 hình ảnh hiển thị theo chiều ngang, trong màn hình chỉ có thể hiển thị 10 hình ảnh mà thôi, hoặc thậm chí hiển thị ít hơn. Những hình ảnh còn lại sẽ nằm bên ngoài container, để xem được các hình ẩn này mình sẽ dùng 2 nút previous và next để chạy theo dạng slide.

Bên dưới là đoạn mã PHP và HTML ví dụ:

<?php
defined( 'ABSPATH' ) || exit;

if ( is_active_sidebar( 'home_banners' ) ) {
    ?>
    <div class="section home-banners">
        <div class="container">
            <div class="slider-box">
                <div class="banners d-flex no-wrap overflow-auto scrollbar-hide scroll-smooth">
                    <?php dynamic_sidebar( 'home_banners' ); ?>
                </div>
                <div class="absolute top-[50%] left-1 translate-y-[-50%] prev controls">
                    <button class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeSmall !bg-white mui-style-1j7qk7u"
                            tabindex="0" type="button">
                        <svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium mui-style-19xldyr" focusable="false"
                             aria-hidden="true" viewBox="0 0 24 24" data-testid="ArrowBackIosNewIcon">
                            <path d="M17.77 3.77 16 2 6 12l10 10 1.77-1.77L9.54 12z"></path>
                        </svg>
                        <span class="MuiTouchRipple-root mui-style-w0pj6f"></span></button>
                </div>
                <div class="absolute top-[50%] right-1 translate-y-[-50%] next controls">
                    <button class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeSmall !bg-white mui-style-1j7qk7u"
                            tabindex="0" type="button">
                        <svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium mui-style-19xldyr" focusable="false"
                             aria-hidden="true" viewBox="0 0 24 24" data-testid="ArrowForwardIosIcon">
                            <path d="M6.23 20.23 8 22l10-10L8 2 6.23 3.77 14.46 12z"></path>
                        </svg>
                        <span class="MuiTouchRipple-root mui-style-w0pj6f"></span></button>
                </div>
            </div>
        </div>
    </div>
    <?php
}

Đoạn code trên mình sẽ hiện thị các widget chứa hình ảnh vào thẻ div có class là banners.

Ảnh demo carousel đơn giản bằng Javascript
Ảnh demo carousel đơn giản bằng Javascript

Bên dưới là đoạn Javascript để xử lý các sự kiện click nút chạy sang phải và trái.

window.HOCWP_THEME = window.HOCWP_THEME || {};

jQuery(document).ready(function ($) {
    const BODY = $("body");

    HOCWP_THEME.FRONTEND = {
        init: function () {
            this.homeBanners();
        },
        homeBanners: function () {
            let offset = null,
                offsetLeft = null,
                offsetRight = null;

            function update_carousel(box, banners, margin_left = 0, end = false) {
                if (0 !== margin_left) {
                    margin_left += "px";
                }

                banners.animate({marginLeft: margin_left}, "slow");

                if (end) {
                    box.addClass("end");
                    box.removeClass("begin");
                } else {
                    box.addClass("begin");
                    box.removeClass("end");
                }

                banners.removeClass("change-margin");
                banners.attr("data-change-margin", 0);
            }

            function banners_carousel(element, init = false) {
                let box = element.closest(".slider-box"),
                    banners = box.find(".banners"),
                    gap = parseInt(banners.css("gap")),
                    widgets = banners.find(".widget"),
                    widget = widgets.first(),
                    step = widget.width(),
                    width = (step * widgets.length) + (gap * widgets.length) - gap,
                    hidden = width - box.width(),
                    direction,
                    margin;

                if (!offset) {
                    // Current offset left
                    offset = banners.offset().left;
                    // Keep offset left
                    offsetLeft = offset;
                    offsetRight = offsetLeft + box.width();
                }

                if (init) {
                    box.attr("data-offset-left", offsetLeft);
                    box.attr("data-current-offset", offset);
                    box.attr("data-offset-right", offsetRight);
                    box.attr("width", width);
                    box.attr("data-outside", hidden);
                    update_carousel(box, banners);

                    return {
                        step: offset,
                        direction: "default"
                    };
                }

                if (offset >= offsetLeft) {
                    box.addClass("begin");
                }

                if (element.hasClass("prev")) {
                    if (!box.hasClass("begin")) {
                        offset += step;
                        margin = "+";
                    }

                    direction = "prev";
                } else {
                    if (!box.hasClass("end")) {
                        offset -= step;
                        margin = "-";
                    }

                    direction = "next";
                }

                if (margin) {
                    let marginLeft = parseInt(banners.css("margin-left"));

                    if (banners.hasClass("change-margin")) {
                        step -= parseInt(banners.attr("data-change-margin"));
                        banners.removeClass("change-margin");
                        banners.attr("data-change-margin", 0);
                    }

                    banners.animate({marginLeft: margin + "=" + step + "px"}, {
                        duration: 500,
                        complete: function () {
                            marginLeft = parseInt(banners.css("margin-left"));

                            box.attr("data-margin", marginLeft);

                            if (0 === marginLeft || -0 === marginLeft || marginLeft === (gap - 2)) {
                                update_carousel(box, banners);
                            } else if (0 > marginLeft && (hidden + marginLeft) <= 0) {
                                // Fix last item has no gap
                                banners.animate({marginLeft: "+=" + gap + "px"}, "slow");
                                banners.attr("data-change-margin", gap);
                                banners.addClass("change-margin");

                                box.addClass("end");
                                box.removeClass("begin");
                            } else {
                                box.removeClass("begin end");
                            }

                            element.removeClass("waiting");

                            if ("prev" === direction && (Math.abs(marginLeft) < step || marginLeft > 0)) {
                                update_carousel(box, banners);
                            } else if ("next" === direction) {
                                if (Math.abs(marginLeft) > hidden) {
                                    update_carousel(box, banners);
                                } else if (Math.abs(Math.abs(marginLeft) - hidden) < step) {
                                    update_carousel(box, banners, -hidden, true);
                                }
                            }
                        }
                    });
                }

                box.attr("data-current-offset", offset);

                setTimeout(function () {
                    element.removeClass("waiting");
                }, 1000);

                return {
                    step: offset,
                    direction: direction
                };
            }

            banners_carousel(BODY.find(".home-banners .controls"), true);

            BODY.on("click", ".home-banners .controls", function (e) {
                e.preventDefault();
                let that = this,
                    element = $(that);

                if (!element.hasClass("waiting")) {
                    element.addClass("waiting");
                    banners_carousel(element);
                }
            });
        }
    };

    HOCWP_THEME.FRONTEND.init();
});

Với đoạn mã JS bên trên chúng ta chỉ có thể áp dụng cho các widget có cùng chiều rộng. Vì công thức tính chiều rộng lúc này sẽ lấy chiều rộng của 1 widget đầu tiên và nhân cho số lượng widget sẵn có. Để đoạn code chạy đúng hơn, mình cập nhật lại một chút để tính chiều rộng của các widget:

let box = element.closest(".slider-box"),
    banners = box.find(".banners"),
    gap = parseInt(banners.css("gap")),
    widgets = banners.find(".widget"),
    widget = widgets.first(),
    step = widget.width(),
    width = 0;

widgets.each(function () {
    let widgetWidth = $(this).width();

    if (step > widgetWidth) {
        step = widgetWidth;
    }

    width += widgetWidth + gap;
});

width -= gap;

let hidden = width - box.width(),
    direction,
    margin;

Cài đặt Object Cache với Redis trên plugin Litespeed Cache

Nếu bạn đang sử dụng plugin Litespeed Cache, phần Object Cache không kích hoạt hoặc kích hoạt sai thì sẽ thấy thông báo: LSCache caching functions on this page are currently unavailable!

Cài đặt Object Cache với Redis trên plugin Litespeed Cache
Cài đặt Object Cache với Redis trên plugin Litespeed Cache

Trạng thái kiểm tra kết nối của Object Cache sử dụng Memcached hoặc Redis là Thất bại. Trong bài viết này mình sẽ hướng dẫn cho bạn cách kích hoạt Object Cache bằng Redis trên plugin Litespeed Cache cho gói cPanel shared hosting của AZDIGI.

Cài đặt Object Cache với Redis trên plugin Litespeed Cache

Đầu tiên, bạn đăng nhập vào cPanel và tìm tới mục Advanced, nhấn vào plugin AZ Redis.

Kích hoạt AZ Redis trên cPanel
Kích hoạt AZ Redis trên cPanel

Tiếp đến, bạn nhấn nút Enable nếu chưa kích hoạt trước đó. Đợi một vài giây bạn sẽ thấy 1 đường dẫn Socket Path hiển thị lên, bạn sao chép đường dẫn này.

Tiếp theo, bạn cài đặt và kích hoạt plugin Litespeed Cache, sau đó vào menu Litespeed Cache → Cache → Object.

Cuối cùng, bạn điền đường dẫn socket vừa sao chép vào ô Host. Chọn Bật Object Cache, chọn phương pháp là Redis, nhập port là 0 như hình đầu tiên của bài viết. Chúc bạn thành công.

Nếu bạn có bất kỳ thắc mắc nào cần giải đáp, hãy gửi liên hệ ngay cho HocWP Team để được tư vấn nhé. Bên cạnh đó, bạn cũng có thể đăng ký theo dõi kênh YouTube Học WordPress để cập nhật kiến thức về quản lý và phát triển website nhé.

Khởi tạo slick, sự kiện on init và cập nhật tham số sau khi slide đã khởi tạo

Slick là plugin jQuery cho phép bạn tạo ra slide carousel một cách nhanh chóng. Bên dưới là ví dụ cách khởi tạo slick, gọi sự kiện init và thiết lập lại tham số tùy chọn sau khi slide đã khởi tạo.

(function () {
    let elements = $(".flexslider .slides"),
        settings = {
            autoplay: true,
            dots: true,
            fade: true,
            adaptiveHeight: false
        };

    if (body.hasClass("single")) {
        settings.autoplay = false;
    }

    elements.on("init", function (slick) {
        console.log("Slide initialized!");
    });

    elements.slick(settings);

    setTimeout(function () {
        // Update slide option after 1 second
        elements.slick("slickSetOption", "adaptiveHeight", true, true);
    }, 1000)
})();

 

Kết nối internet Việt Nam đi quốc tế bị ảnh hưởng do đứt cáp quang biển ở Biển Đỏ

Trong tuần qua, ít nhất 4 tuyến cáp quang biển chịu trách nhiệm truyền tải 97% lưu lượng internet đã bị hỏng tại Biển Đỏ. Các nhà cung cấp viễn thông đang báo cáo nhưng sự bất ổn tại Yemen đã làm ngăn cản các nhà khai thác sửa chữa chúng.

Đứt cáp quang biển ở Biển Đỏ
Đứt cáp quang biển ở Biển Đỏ

HGC Communications, một nhà điều hành mạng viễn thông tại Hồng Kong đã báo cáo về sự cố của 4 tuyến cáp quang biển, bao gồm: SEACOM, TGN, Africa Asia Europe-One và Europe India Gateway. HGC cho biết, sự cố đã làm ảnh hưởng đến 25% lưu lượng internet của họ, hiện đang được chuyển hướng qua Trung Quốc lục địa về phía Biển Đông thông qua Hoa Kỳ.

Do vậy, trong tuần qua các bạn sử dụng dịch vụ internet quốc tế hoặc sử dụng WordPress có các đường link tải dữ liệu từ server của WordPress thì sẽ bị lỗi timeout. Hiện chưa có lịch sửa chữa các sự cố này.

Xóa các scripts của WooCommerce trên các trang khác nhau

Nếu bạn muốn tăng tốc trang web của mình khi sử dụng WooCommerce, bạn có thể sử dụng hàm điều kiện để loại bỏ các style và script được tạo ra bởi plugin WooCommerce.

/**
 * Manage WooCommerce styles and scripts.
 */
function grd_woocommerce_script_cleaner() {
    
    // Remove the generator tag
    remove_action( 'wp_head', array( $GLOBALS['woocommerce'], 'generator' ) );

    // Unless we're in the store, remove all the cruft!
    if ( ! is_woocommerce() && ! is_cart() && ! is_checkout() ) {
        wp_dequeue_style( 'woocommerce_frontend_styles' );
        wp_dequeue_style( 'woocommerce-general');
        wp_dequeue_style( 'woocommerce-layout' );
        wp_dequeue_style( 'woocommerce-smallscreen' );
        wp_dequeue_style( 'woocommerce_fancybox_styles' );
        wp_dequeue_style( 'woocommerce_chosen_styles' );
        wp_dequeue_style( 'woocommerce_prettyPhoto_css' );
        wp_dequeue_script( 'selectWoo' );
        wp_deregister_script( 'selectWoo' );
        wp_dequeue_script( 'wc-add-payment-method' );
        wp_dequeue_script( 'wc-lost-password' );
        wp_dequeue_script( 'wc_price_slider' );
        wp_dequeue_script( 'wc-single-product' );
        wp_dequeue_script( 'wc-add-to-cart' );
        wp_dequeue_script( 'wc-cart-fragments' );
        wp_dequeue_script( 'wc-credit-card-form' );
        wp_dequeue_script( 'wc-checkout' );
        wp_dequeue_script( 'wc-add-to-cart-variation' );
        wp_dequeue_script( 'wc-single-product' );
        wp_dequeue_script( 'wc-cart' );
        wp_dequeue_script( 'wc-chosen' );
        wp_dequeue_script( 'woocommerce' );
        wp_dequeue_script( 'prettyPhoto' );
        wp_dequeue_script( 'prettyPhoto-init' );
        wp_dequeue_script( 'jquery-blockui' );
        wp_dequeue_script( 'jquery-placeholder' );
        wp_dequeue_script( 'jquery-payment' );
        wp_dequeue_script( 'fancybox' );
        wp_dequeue_script( 'jqueryui' );
    }
}
add_action( 'wp_enqueue_scripts', 'grd_woocommerce_script_cleaner', 999 );

Đối với các bạn sử dụng WordPressWooCommerce phiên bản mới, bạn có thể sử dụng hook bên dưới:

add_action('template_redirect', 'conditionally_disable_wc_assets');
/**
    * Remove WC stuff on non WC pages.
    */
function conditionally_disable_wc_assets()
{
    if (IF_CONDTION_MET) {
        // remove WC generator tag
        remove_filter('get_the_generator_html', 'wc_generator_tag', 10, 2);
        remove_filter('get_the_generator_xhtml', 'wc_generator_tag', 10, 2);
        // unload WC scripts
        remove_action('wp_enqueue_scripts', [WC_Frontend_Scripts::class, 'load_scripts']);
        remove_action('wp_print_scripts', [WC_Frontend_Scripts::class, 'localize_printed_scripts'], 5);
        remove_action('wp_print_footer_scripts', [WC_Frontend_Scripts::class, 'localize_printed_scripts'], 5);
        // remove "Show the gallery if JS is disabled"
        remove_action('wp_head', 'wc_gallery_noscript');
    }
}

Các bạn thay IF_CONDTION_MET thành câu điều kiện cần thiết để loại bỏ scripts của WooCommerce nhé.

Dữ liệu đơn vị hành chính Việt Nam đến cấp quận huyện, phường xã

Chia sẻ dữ liệu địa giới hành chính của Việt Nam (đơn vị hành chính Việt Nam), chi tiết từ cấp tỉnh thành phố đến cấp quận huyện, phường xã. Nếu bạn nào đang làm web mà cần đụng tới dữ liệu địa giới hành chính thì có thể tải về để dùng, bản cập nhật được tải ngày 15/02/2024.

Địa giới hành chính Việt Nam đến cấp xã
Địa giới hành chính Việt Nam đến cấp xã

Link tải: https://danhmuchanhchinh.gso.gov.vn/

Sau khi truy cập vào trang web, các bạn có thể xuất tập tin dưới dạng file Excel, sau đó tải lên Google Drive hoặc dùng bất kỳ công cụ nào khác để chuyển đổi sang đuôi cần sử dụng. Mình hay chuyển đổi file .xlsx thành file .csv để sử dụng.

Thêm column và sortable column vào bảng danh sách bài viết

Đoạn code ví dụ cách thêm column và sortable column vào bảng quản lý danh sách bài viết trong admin.

add_filter( 'manage_posts_columns', 'hocwp_theme_custom_post_columns_filter' );
function hocwp_theme_custom_post_columns_filter( $columns ) {
    $date = $columns['date'];
    unset( $columns['date'] );
    $columns['type'] = __( 'Type', 'rentinsingapore' );

    $columns['date'] = $date;

    return $columns;
}

add_action( 'manage_posts_custom_column', 'hocwp_theme_custom_post_column_action', 10, 2 );
function hocwp_theme_custom_post_column_action( $column, $post_id ) {
    switch ( $column ) {
        // display a thumbnail photo
        case 'type' :
            $type = get_post_meta( $post_id, 'type', true );

            if ( empty( $type ) ) {
                $type = 'room';
            }

            echo $type;
            break;
    }
}

add_filter( 'manage_edit-post_sortable_columns', 'hocwp_theme_custom_post_sortable_columns_filter' );
function hocwp_theme_custom_post_sortable_columns_filter( $columns ) {
    $columns['type'] = 'type';

    return $columns;
}

add_action( 'pre_get_posts', 'hocwp_theme_custom_sort_post_column' );
function hocwp_theme_custom_sort_post_column( $query ) {
    if ( ! is_admin() ) {
        return;
    }

    $orderby = $query->get( 'orderby' );

    if ( 'type' == $orderby ) {
        $query->set( 'meta_key', 'type' );
        $query->set( 'orderby', 'meta_value' );
    }
}

Thêm đoạn code bên trên vào tập tin functions.php của giao diện hoặc plugin.

Thêm column và sortable column vào bảng danh sách bài viết

Trong ví dụ bên trên, cột Type sẽ được thêm vào bảng danh sách bài viết. Khi nhấn chuột vào cột Type sẽ sắp xếp danh sách theo giá trị của cột.

Ở đây, tên cột sắp xếp đặt trùng với tên meta key lưu trong cơ sở dữ liệu nên giá trị type của tham số orderby và trường thiết lập meta_key của query là giống nhau.

Ví dụ này chỉ áp dụng cho post_typepost. Đối với page và các custom post type khác thì các hook sẽ thay đổi một chút thì mới có thể hoạt động.

Nếu chỉ áp dụng cho post type là post (bài viết) thì sử dụng các hook sau:

//add_filter( 'manage_posts_columns', array( $this, 'manage_posts_columns_filter' ), 99, 2 );
//add_action( 'manage_posts_custom_column', array( $this, 'manage_posts_custom_column_action' ), 99, 2 );
//add_filter( 'manage_edit-post_sortable_columns', 'hocwp_theme_custom_post_sortable_columns_filter' );

Nếu áp dụng thêm column cho tất cả các post type, bao gồm: post, page và custom post type thì áp dụng như code bên dưới:

<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly
}

class Content_To_Speech_Post_Columns {
    public function __construct() {
        $post_type = Content_To_Speech_Global()->get_option( 'post_type' );

        if ( is_array( $post_type ) ) {
            foreach ( $post_type as $pt ) {
                add_filter( 'manage_' . $pt . '_posts_columns', array( $this, 'manage_posts_columns_filter' ), 99, 2 );

                add_action( 'manage_' . $pt . '_posts_custom_column', array(
                    $this,
                    'manage_posts_custom_column_action'
                ), 99, 2 );

                add_filter( 'manage_edit-' . $pt . '_sortable_columns', array(
                    $this,
                    'manage_posts_sortable_columns_filter'
                ) );
            }
        }

        add_action( 'pre_get_posts', array( $this, 'pre_get_posts_action' ) );
    }

    public function pre_get_posts_action( $query ) {
        if ( is_admin() && $query instanceof WP_Query ) {
            global $pagenow;

            if ( 'edit.php' == $pagenow ) {
                $by    = $_GET['orderby'] ?? '';
                $order = $_GET['order'] ?? 'DESC';

                if ( 'audio_file' == $by ) {
                    $query->set( 'meta_key', CTS_CONST()::PM_AUDIO_FILE );
                    $query->set( 'orderby', 'meta_value' );
                    $query->set( 'order', $order );
                }
            }
        }
    }

    public function manage_posts_sortable_columns_filter( $columns ) {
        $columns['audio_file'] = 'audio_file';

        return $columns;
    }

    public function manage_posts_columns_filter( $posts_columns ) {
        $date = $posts_columns['date'] ?? '';
        unset( $posts_columns['date'] );
        $posts_columns['audio_file'] = __( 'Audio File', CTS_CONST()::TEXT_DOMAIN );

        if ( ! empty( $date ) ) {
            $posts_columns['date'] = $date;
        }

        return $posts_columns;
    }

    public function manage_posts_custom_column_action( $column_name, $post_id ) {
        if ( 'audio_file' == $column_name ) {
            $file = get_post_meta( $post_id, CTS_CONST()::PM_AUDIO_FILE, true );

            if ( empty( $file ) ) {
                ?>
                <button type="button"
                        class="button button-primary" data-ajax-button="1" data-do-action="generate_audio_file"
                        data-id="<?php echo esc_attr( $post_id ); ?>"><?php _e( 'Create Audio File', CTS_CONST()::TEXT_DOMAIN ); ?></button>
                <?php
            } else {
                Content_To_Speech_Global()->audio_html( $post_id );
                ?>
                <button type="button" class="button button-secondary play-audio"
                        data-target="Audio<?php echo esc_attr( $post_id ); ?>"><span
                            class="dashicons dashicons-controls-play"></span></button>
                <button type="button"
                        class="button button-primary" data-ajax-button="1" data-do-action="generate_audio_file"
                        data-id="<?php echo esc_attr( $post_id ); ?>"><?php _e( 'Update Audio File', CTS_CONST()::TEXT_DOMAIN ); ?></button>
                <?php
            }
        }
    }
}

new Content_To_Speech_Post_Columns();

 

Class chuyển nội dung bài viết thành giọng đọc file audio mp3

Class bên dưới sẽ mô tả cách chuyển nội dung bài viết thành giọng đọc (Text to Speech, Content to Speech).

<?php
defined( 'ABSPATH' ) || exit;

class CTS_Text_To_Speech {
    public $api_key;
    public $voice_id;
    public $text;
    public $error;
    public $response;
    private $voice;

    public function __construct( $api_key, $voice_id, $text ) {
        $this->set_api_key( $api_key );
        $this->set_voice_id( $voice_id );
        $this->set_text( $text );
        $this->error = new WP_Error();
    }

    public function get_voice() {
        $voices = $this->get_voice_ids();
        $this->sanitize_voices_response( $voices );

        if ( is_array( $voices ) ) {
            foreach ( $voices as $voice ) {
                if ( is_object( $voice ) && isset( $voice->voice_id ) && $this->voice_id == $voice->voice_id ) {
                    $this->voice = $voice;
                    break;
                }
            }
        }

        return $this->voice;
    }

    public function get_voice_ids() {
        $tr_name = 'elevenlabs_voice_ids';

        if ( false === ( $res = get_transient( $tr_name ) ) ) {
            $url = 'https://api.elevenlabs.io/v1/voices';
            $res = wp_remote_retrieve_body( wp_remote_get( $url ) );

            if ( ! empty( $res ) ) {
                $res = json_decode( $res );
                $this->sanitize_voices_response( $res );

                set_transient( $tr_name, $res );
            }
        }

        return $res;
    }

    public function set_api_key( $api_key ) {
        $this->api_key = $api_key;
    }

    private function sanitize_voices_response( &$res ) {
        if ( is_object( $res ) && isset( $res->voices ) ) {
            $res = $res->voices;
        }
    }

    public function set_voice_id( $voice_id ) {
        if ( empty( $voice_id ) || 'rand' == $voice_id || 'random' == $voice_id ) {
            $voices = $this->get_voice_ids();
            $this->sanitize_voices_response( $voices );

            if ( is_array( $voices ) ) {
                shuffle( $voices );

                foreach ( $voices as $obj ) {
                    if ( is_object( $obj ) && isset( $obj->voice_id ) ) {
                        $voice_id = $obj->voice_id;
                        break;
                    }
                }
            }
        }

        $this->voice_id = $voice_id;
    }

    public function set_text( $text ) {
        $this->text = $text;
    }

    public function get_base_url() {
        return 'https://api.elevenlabs.io/v1/text-to-speech/' . $this->voice_id;
    }

    public function calculate_timeout() {
        $count = str_word_count( strip_tags( $this->text ) );
        $count = ceil( $count / 150 );

        if ( $count < MINUTE_IN_SECONDS ) {
            $count = MINUTE_IN_SECONDS;
        }

        return $count;
    }

    public function generate( $type = 'remote' ) {
        if ( 'curl' == $type ) {
            $this->response = $this->curl();
        } else {
            $this->response = $this->remote();
        }

        if ( is_string( $this->response ) && str_contains( $this->response, 'detail' ) ) {
            $res = json_decode( $this->response );

            if ( is_object( $res ) && isset( $res->detail ) ) {
                $this->response = '';

                if ( is_object( $res->detail ) && isset( $res->detail->status ) ) {
                    $this->error->add( $res->detail->status, $res->detail->message );
                } else {
                    $this->error->add( 'response_detail', $res->detail );
                }
            }
        }

        return $this->response;
    }

    private function build_body() {
        return wp_json_encode( array(
            'text' => $this->text
        ) );
    }

    private function build_header() {
        return array(
            'Content-Type' => 'application/json',
            'xi-api-key'   => $this->api_key
        );
    }

    private function remote() {
        $args = array(
            'timeout' => $this->calculate_timeout(),
            'headers' => $this->build_header(),
            'body'    => $this->build_body()
        );

        $res = wp_remote_post( $this->get_base_url(), $args );

        if ( is_wp_error( $res ) ) {
            $this->error->merge_from( $res );
        }

        return wp_remote_retrieve_body( $res );
    }

    private function curl() {
        $curl = curl_init();

        $headers = $this->build_header();
        $header  = array();

        foreach ( $headers as $key => $value ) {
            $header[] = $key . ': ' . $value;
        }

        curl_setopt_array( $curl, [
            CURLOPT_URL            => $this->get_base_url(),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING       => '',
            CURLOPT_MAXREDIRS      => 10,
            CURLOPT_TIMEOUT        => $this->calculate_timeout(),
            CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST  => 'POST',
            CURLOPT_POSTFIELDS     => $this->build_body(),
            CURLOPT_HTTPHEADER     => $header
        ] );

        $response = curl_exec( $curl );
        $err      = curl_error( $curl );

        curl_close( $curl );

        if ( $err ) {
            $this->error->add( 'curl_error', $err );
        }

        return $response;
    }
}

Đoạn code ví dụ bên trên sử dụng API của ElevenLabs. Gói miễn phí được sử dụng 10.000 ký tự mỗi tháng.

Thêm column và sortable column vào bảng thành viên

Nếu bạn muốn thêm một cột bất kỳ vào bảng danh sách người dùng, bạn có thể tham khảo đoạn code bên dưới.

function hpxf_manage_users_sortable_columns_filter( $column ) {
    $column['date']        = 'date';
    $column['collections'] = 'collections';
    $column['posts']       = 'posts';
    $column['forum_posts'] = 'forum_posts';
    $column['media_files'] = 'media_files';
    $column['description'] = 'description';

    return $column;
}

add_filter( 'manage_users_sortable_columns', 'hpxf_manage_users_sortable_columns_filter' );

function hpxf_manage_users_columns_filter( $column ) {
    $date = $column['date'] ?? '';

    if ( ! isset( $column['posts'] ) ) {
        $column['posts'] = __( 'Posts', 'pixelify' );
    }

    $lists = array();

    foreach ( $column as $key => $text ) {
        $lists[ $key ] = $text;

        if ( 'posts' == $key ) {
            $lists['forum_posts'] = __( 'Forum Posts', 'pixelify' );
        }
    }

    $column = $lists;
    $column['description'] = __( 'Description', 'pixelify' );
    $column['collections'] = __( 'Collections', 'pixelify' );
    $column['media_files'] = __( 'Media Files', 'pixelify' );

    if ( ! empty( $date ) ) {
        $column['date'] = $date;
    } else {
        $column['date'] = __( 'Date', 'pixelify' );
    }

    return $column;
}

add_filter( 'manage_users_columns', 'hpxf_manage_users_columns_filter' );

function hpxf_manage_users_custom_column_action( $val, $column_name, $user_id ) {
    global $wpdb;

    switch ( $column_name ) {
        case 'date' :
            return get_the_author_meta( 'registered', $user_id );
        case 'collections':
            return count_user_posts( $user_id, 'collection' );
        case 'description':
            return get_the_author_meta( 'description', $user_id );
        case 'forum_posts':
            return count_user_posts( $user_id, 'font_forum' );
        case 'media_files':
            $count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts WHERE post_type = 'attachment' AND post_author = $user_id" );

            return sprintf( '<a href="%s" target="_blank">%s</a>', esc_url( admin_url( 'upload.php?author=' . $user_id ) ), $count );
    }

    return $val;
}

add_filter( 'manage_users_custom_column', 'hpxf_manage_users_custom_column_action', 10, 3 );

function pxf_pre_user_query_action( $query ) {
    if ( $query instanceof WP_User_Query ) {
        global $wpdb, $current_screen;

        // Only filter in the admin
        if ( ! is_admin() ) {
            return;
        }

        // Only filter on the users screen
        if ( ! ( isset( $current_screen ) && 'users' == $current_screen->id ) ) {
            return;
        }

        $orderby = $_GET['orderby'] ?? '';

        // Only filter if orderby is set to 'media_files'
        if ( 'media_files' == $orderby ) {
            // We need the order - default is ASC
            $order = $_GET['order'] ?? 'ASC';

            // Order the posts by product count
            $query->query_orderby = "ORDER BY ( SELECT COUNT(*) FROM {$wpdb->posts} posts WHERE posts.post_type = 'attachment' AND posts.post_author = {$wpdb->users}.id GROUP BY posts.post_author ) {$order}";
        } elseif ( 'collections' == $orderby ) {
            // We need the order - default is ASC
            $order = $_GET['order'] ?? 'ASC';

            // Order the posts by product count
            $query->query_orderby = "ORDER BY ( SELECT COUNT(*) FROM {$wpdb->posts} posts WHERE posts.post_type = 'collection' AND posts.post_author = {$wpdb->users}.id GROUP BY posts.post_author ) {$order}";
        } elseif ( 'posts' == $orderby ) {
            // We need the order - default is ASC
            $order = $_GET['order'] ?? 'ASC';

            // Order the posts by product count
            $query->query_orderby = "ORDER BY ( SELECT COUNT(*) FROM {$wpdb->posts} posts WHERE posts.post_type = 'post' AND posts.post_author = {$wpdb->users}.id GROUP BY posts.post_author ) {$order}";
        } elseif ( 'forum_posts' == $orderby ) {
            // We need the order - default is ASC
            $order = $_GET['order'] ?? 'ASC';

            // Order the posts by product count
            $query->query_orderby = "ORDER BY ( SELECT COUNT(*) FROM {$wpdb->posts} posts WHERE posts.post_type = 'font_forum' AND posts.post_author = {$wpdb->users}.id GROUP BY posts.post_author ) {$order}";
        } elseif ( 'date' == $orderby ) {
            $order = $_GET['order'] ?? 'ASC';

            $query->query_orderby = "ORDER BY user_registered " . $order;
        } elseif ( 'description' == $orderby ) {
            $order = $_GET['order'] ?? 'ASC';

            // Join the usermeta table to get the 'description' field
            $query->query_from .= " LEFT JOIN {$wpdb->usermeta} ON {$wpdb->users}.ID = {$wpdb->usermeta}.user_id AND meta_key = 'description'";

            // Set the orderby parameter to sort by 'description'
            $query->query_orderby = "ORDER BY {$wpdb->usermeta}.meta_value {$order}";
        }
    }
}

add_action( 'pre_user_query', 'pxf_pre_user_query_action', 1 );

Cái này cũng tương tự như cách Thêm column và sortable column vào bảng danh sách bài viết. Chỉ khác là dùng các tên hook khác nhau áp dụng cho bảng users.