1. Home
  2. WordPress
  3. Theme Development
  4. কাস্টম পোস্ট টাইপ

কাস্টম পোস্ট টাইপ

১. কাস্টম পোস্ট টাইপ রেজিস্ট্রেশন

প্রথমে, আমরা “Team” কাস্টম পোস্ট টাইপ রেজিস্টার করি। এই ফাংশনটি init হুকে সংযুক্ত করা হয়েছে।

/**
 * Register Team custom post type
 */
function rebelsoftt_register_team_post_type() {
    $labels = array(
        'name'                  => _x('Team Members', 'Post type general name', 'rebelsoftt'),
        'singular_name'         => _x('Team Member', 'Post type singular name', 'rebelsoftt'),
        'menu_name'             => _x('Team', 'Admin Menu text', 'rebelsoftt'),
        'name_admin_bar'        => _x('Team Member', 'Add New on Toolbar', 'rebelsoftt'),
        'add_new'               => __('Add New', 'rebelsoftt'),
        'add_new_item'          => __('Add New Team Member', 'rebelsoftt'),
        'new_item'              => __('New Team Member', 'rebelsoftt'),
        'edit_item'             => __('Edit Team Member', 'rebelsoftt'),
        'view_item'             => __('View Team Member', 'rebelsoftt'),
        'all_items'             => __('All Team Members', 'rebelsoftt'),
        'search_items'          => __('Search Team Members', 'rebelsoftt'),
        'parent_item_colon'     => __('Parent Team Members:', 'rebelsoftt'),
        'not_found'             => __('No team members found.', 'rebelsoftt'),
        'not_found_in_trash'    => __('No team members found in Trash.', 'rebelsoftt'),
        'featured_image'        => _x('Team Member Photo', 'Overrides the "Featured Image" phrase', 'rebelsoftt'),
        'set_featured_image'    => _x('Set team member photo', 'Overrides the "Set featured image" phrase', 'rebelsoftt'),
        'remove_featured_image' => _x('Remove team member photo', 'Overrides the "Remove featured image" phrase', 'rebelsoftt'),
        'use_featured_image'    => _x('Use as team member photo', 'Overrides the "Use as featured image" phrase', 'rebelsoftt'),
        'archives'              => _x('Team Member archives', 'The post type archive label used in nav menus', 'rebelsoftt'),
        'insert_into_item'      => _x('Insert into team member', 'Overrides the "Insert into post" phrase', 'rebelsoftt'),
        'uploaded_to_this_item' => _x('Uploaded to this team member', 'Overrides the "Uploaded to this post" phrase', 'rebelsoftt'),
        'filter_items_list'     => _x('Filter team members list', 'Screen reader text for the filter links heading on the post type listing screen', 'rebelsoftt'),
        'items_list_navigation' => _x('Team Members list navigation', 'Screen reader text for the pagination heading on the post type listing screen', 'rebelsoftt'),
        'items_list'            => _x('Team Members list', 'Screen reader text for the items list heading on the post type listing screen', 'rebelsoftt'),
    );

    $args = array(
        'labels'             => $labels,
        'public'             => true,
        'publicly_queryable' => true,
        'show_ui'            => true,
        'show_in_menu'       => true,
        'query_var'          => true,
        'rewrite'            => array('slug' => 'team'),
        'capability_type'    => 'post',
        'has_archive'        => true,
        'hierarchical'       => false,
        'menu_position'      => 20,
        'menu_icon'          => 'dashicons-groups',
        'supports'           => array('title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'),
        'show_in_rest'       => true,
    );

    register_post_type('team', $args);
}
add_action('init', 'rebelsoftt_register_team_post_type');

ব্যাখ্যা:

১.১ লেবেল অ্যারে ($labels)

এই অ্যারেতে পোস্ট টাইপের সমস্ত টেক্সট লেবেল সংজ্ঞায়িত করা হয়েছে। এটি ওয়ার্ডপ্রেস অ্যাডমিন প্যানেলে প্রদর্শিত বিভিন্ন টেক্সট নির্ধারণ করে। উদাহরণস্বরূপ:

  • name: এটি প্লুরাল ফর্মে পোস্ট টাইপের নাম (“Team Members”)
  • singular_name: এটি সিঙ্গুলার ফর্মে পোস্ট টাইপের নাম (“Team Member”)
  • menu_name: অ্যাডমিন মেনুতে প্রদর্শিত নাম (“Team”)
  • add_new_item: নতুন আইটেম যোগ করার ফর্মের হেডিং (“Add New Team Member”)

ওয়ার্ডপ্রেস অনুবাদের জন্য এখানে _x() এবং __() ফাংশন ব্যবহার করা হয়েছে, যেখানে rebelsoftt হল থিম বা প্লাগিনের টেক্সট ডোমেইন।

১.২ আর্গুমেন্ট অ্যারে ($args)

এই অ্যারেতে পোস্ট টাইপের বৈশিষ্ট্য এবং আচরণ নির্ধারণ করা হয়েছে:

  • public: true মানে এই পোস্ট টাইপ সর্বজনীনভাবে প্রদর্শিত হবে (ফ্রন্টএন্ডে)
  • publicly_queryable: true মানে এই পোস্ট টাইপ প্রকাশ্যে কোয়েরি করা যাবে
  • show_ui: true মানে এই পোস্ট টাইপের অ্যাডমিন ইন্টারফেস প্রদর্শিত হবে
  • show_in_menu: true মানে অ্যাডমিন মেনুতে এটি প্রদর্শিত হবে
  • rewrite: URL স্লাগ নির্ধারণ করে (“team”)
  • menu_position: অ্যাডমিন মেনুতে এর অবস্থান (20 হল Pages এর নিচে)
  • menu_icon: অ্যাডমিন মেনুতে আইকন (dashicons-groups হল একটি গ্রুপ/টিম আইকন)
  • supports: এই পোস্ট টাইপ কোন কোন ফিচার সাপোর্ট করবে (title, editor, thumbnail, excerpt, custom-fields)
  • show_in_rest: true মানে এটি REST API এবং গুটেনবার্গ এডিটরে ব্যবহার করা যাবে

১.৩ রেজিস্ট্রেশন ফাংশন

register_post_type() ফাংশনটি ওয়ার্ডপ্রেসে একটি নতুন পোস্ট টাইপ রেজিস্টার করে। প্রথম প্যারামিটার হল পোস্ট টাইপের নাম (‘team’) এবং দ্বিতীয় প্যারামিটার হল কনফিগারেশন অ্যারে ($args)।

add_action('init', 'rebelsoftt_register_team_post_type'); দ্বারা ওয়ার্ডপ্রেস init হুকে এই ফাংশনটি সংযুক্ত করা হয়েছে, যাতে ওয়ার্ডপ্রেস ইনিশিয়ালাইজেশন সময়ে এটি কার্যকর হয়।

২. কাস্টম ট্যাক্সোনমি রেজিস্ট্রেশন

এরপর, আমরা “Department” নামে একটি কাস্টম ট্যাক্সোনমি তৈরি করি:

/**
 * Register Team Department taxonomy
 */
function rebelsoftt_register_team_taxonomies() {
    // Add Department Taxonomy
    $labels = array(
        'name'              => _x('Departments', 'taxonomy general name', 'rebelsoftt'),
        'singular_name'     => _x('Department', 'taxonomy singular name', 'rebelsoftt'),
        'search_items'      => __('Search Departments', 'rebelsoftt'),
        'all_items'         => __('All Departments', 'rebelsoftt'),
        'parent_item'       => __('Parent Department', 'rebelsoftt'),
        'parent_item_colon' => __('Parent Department:', 'rebelsoftt'),
        'edit_item'         => __('Edit Department', 'rebelsoftt'),
        'update_item'       => __('Update Department', 'rebelsoftt'),
        'add_new_item'      => __('Add New Department', 'rebelsoftt'),
        'new_item_name'     => __('New Department Name', 'rebelsoftt'),
        'menu_name'         => __('Departments', 'rebelsoftt'),
    );

    $args = array(
        'hierarchical'      => true,
        'labels'            => $labels,
        'show_ui'           => true,
        'show_admin_column' => true,
        'query_var'         => true,
        'rewrite'           => array('slug' => 'department'),
        'show_in_rest'      => true,
    );

    register_taxonomy('department', array('team'), $args);
}
add_action('init', 'rebelsoftt_register_team_taxonomies');

ব্যাখ্যা:

২.১ লেবেল অ্যারে ($labels)

পোস্ট টাইপের মতোই, এই অ্যারেতে ট্যাক্সোনমির বিভিন্ন টেক্সট লেবেল সংজ্ঞায়িত করা হয়েছে। এগুলি অ্যাডমিন ইন্টারফেসে প্রদর্শিত হবে।

২.২ আর্গুমেন্ট অ্যারে ($args)

এখানে গুরুত্বপূর্ণ প্রপার্টিগুলি হল:

  • hierarchical: true মানে এটি ক্যাটেগরির মতো হবে (পিতা-পুত্র সম্পর্ক থাকতে পারে), false হলে এটি ট্যাগের মতো হবে
  • show_admin_column: true মানে টিম পোস্ট টাইপের তালিকায় একটি কলাম হিসাবে এটি প্রদর্শিত হবে
  • rewrite: URL স্লাগ নির্ধারণ করে (“department”)

২.৩ রেজিস্ট্রেশন ফাংশন

register_taxonomy() ফাংশন তিনটি প্যারামিটার নেয়:

  1. ট্যাক্সোনমির নাম (‘department’)
  2. যে পোস্ট টাইপে এই ট্যাক্সোনমি প্রযোজ্য হবে (এখানে শুধু ‘team’)
  3. কনফিগারেশন অ্যারে ($args)

৩. কাস্টম মেটা বক্স যোগ করা

এরপর, আমরা টিম মেম্বারদের জন্য তিনটি মেটা বক্স যোগ করি:

/**
 * Add custom meta boxes for team members
 */
function rebelsoftt_team_meta_boxes() {
    add_meta_box(
        'team_details',
        __('Team Member Details', 'rebelsoftt'),
        'rebelsoftt_team_details_callback',
        'team',
        'normal',
        'high'
    );
    
    add_meta_box(
        'team_skills',
        __('Team Member Skills', 'rebelsoftt'),
        'rebelsoftt_team_skills_callback',
        'team',
        'normal',
        'high'
    );
    
    add_meta_box(
        'team_social',
        __('Social Media Links', 'rebelsoftt'),
        'rebelsoftt_team_social_callback',
        'team',
        'normal',
        'high'
    );
}
add_action('add_meta_boxes', 'rebelsoftt_team_meta_boxes');

ব্যাখ্যা:

৩.১ add_meta_box() ফাংশন

এই ফাংশনটি ওয়ার্ডপ্রেসে কাস্টম মেটা বক্স যোগ করে। এর প্যারামিটারগুলি হল:

  1. id: মেটা বক্সের অনন্য আইডি
  2. title: মেটা বক্সের শিরোনাম
  3. callback: মেটা বক্সের কন্টেন্ট প্রদর্শনকারী ফাংশন
  4. screen: যে পোস্ট টাইপে এটি প্রদর্শিত হবে (এখানে ‘team’)
  5. context: মেটা বক্সের অবস্থান (‘normal’, ‘side’, বা ‘advanced’)
  6. priority: অবস্থানের প্রায়োরিটি (‘high’, ‘core’, ‘default’, বা ‘low’)

৩.২ হুক

add_action('add_meta_boxes', 'rebelsoftt_team_meta_boxes'); দ্বারা ওয়ার্ডপ্রেস add_meta_boxes হুকে এই ফাংশনটি সংযুক্ত করা হয়েছে। যখন ওয়ার্ডপ্রেস মেটা বক্সগুলি লোড করবে, তখন এই ফাংশনটি কার্যকর হবে।

৪. টিম মেম্বার ডিটেইলস মেটা বক্স

প্রথম মেটা বক্সটি টিম মেম্বারের পদবী/পজিশন সংরক্ষণ করে:

/**
 * Team details meta box callback
 */
function rebelsoftt_team_details_callback($post) {
    wp_nonce_field('rebelsoftt_team_details_nonce', 'team_details_nonce');
    
    $position = get_post_meta($post->ID, '_team_position', true);
    
    ?>
    <p>
        <label for="team_position"><?php _e('Position/Job Title:', 'rebelsoftt'); ?></label>
        <input type="text" id="team_position" name="team_position" value="<?php echo esc_attr($position); ?>" class="widefat">
    </p>
    <?php
}

ব্যাখ্যা:

৪.১ নোনস ফিল্ড

wp_nonce_field('rebelsoftt_team_details_nonce', 'team_details_nonce'); ফাংশনটি ফর্মে একটি নোনস ফিল্ড যোগ করে। নোনস (Number used ONCE) হল একটি সিকিউরিটি ফিচার যা CSRF (Cross-Site Request Forgery) আক্রমণ প্রতিরোধ করে।

৪.২ মেটা ডাটা পুনরুদ্ধার

get_post_meta($post->ID, '_team_position', true); ফাংশনটি পোস্টের মেটা ডাটা থেকে পজিশন/জব টাইটেল পুনরুদ্ধার করে।

  • প্রথম প্যারামিটার: পোস্টের আইডি
  • দ্বিতীয় প্যারামিটার: মেটা ফিল্ডের নাম (‘_team_position’)
  • তৃতীয় প্যারামিটার: true মানে একটি সিঙ্গেল ভ্যালু রিটার্ন করবে, false হলে একটি অ্যারে রিটার্ন করবে

৫. টিম মেম্বার স্কিলস মেটা বক্স

দ্বিতীয় মেটা বক্সটি টিম মেম্বারের দক্ষতা এবং তার শতকরা হার সংরক্ষণ করে:

/**
 * Team skills meta box callback - Improved version
 */
function rebelsoftt_team_skills_callback($post) {
    wp_nonce_field('rebelsoftt_team_skills_nonce', 'team_skills_nonce');
    
    // Get existing skills or initialize empty array
    $skills = get_post_meta($post->ID, '_team_skills', true);
    if (!is_array($skills) || empty($skills)) {
        $skills = array(
            array('name' => '', 'percentage' => '0')
        );
    }
    
    ?>
    <div id="team-skills-container">
        <?php foreach ($skills as $index => $skill) : ?>
        <div class="skill-row" style="margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid #eee;">
            <p>
                <label style="display: block; margin-bottom: 5px; font-weight: bold;"><?php _e('Skill Name:', 'rebelsoftt'); ?></label>
                <input type="text" name="skill_name[]" value="<?php echo esc_attr($skill['name']); ?>" class="widefat">
            </p>
            <p>
                <label style="display: block; margin-bottom: 5px; font-weight: bold;"><?php _e('Percentage (0-100):', 'rebelsoftt'); ?></label>
                <input type="number" name="skill_percentage[]" value="<?php echo esc_attr($skill['percentage']); ?>" min="0" max="100" class="widefat">
            </p>
            <?php if ($index > 0) : ?>
            <p>
                <button type="button" class="button remove-skill"><?php _e('Remove This Skill', 'rebelsoftt'); ?></button>
            </p>
            <?php endif; ?>
        </div>
        <?php endforeach; ?>
    </div>
    <p>
        <button type="button" class="button button-primary" id="add-skill"><?php _e('Add New Skill', 'rebelsoftt'); ?></button>
    </p>
    
    <script>
    document.addEventListener('DOMContentLoaded', function() {
        // Add new skill row
        document.getElementById('add-skill').addEventListener('click', function() {
            const container = document.getElementById('team-skills-container');
            const skillRowTemplate = `
                <div class="skill-row" style="margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid #eee;">
                    <p>
                        <label style="display: block; margin-bottom: 5px; font-weight: bold;"><?php _e('Skill Name:', 'rebelsoftt'); ?></label>
                        <input type="text" name="skill_name[]" value="" class="widefat">
                    </p>
                    <p>
                        <label style="display: block; margin-bottom: 5px; font-weight: bold;"><?php _e('Percentage (0-100):', 'rebelsoftt'); ?></label>
                        <input type="number" name="skill_percentage[]" value="0" min="0" max="100" class="widefat">
                    </p>
                    <p>
                        <button type="button" class="button remove-skill"><?php _e('Remove This Skill', 'rebelsoftt'); ?></button>
                    </p>
                </div>
            `;
            
            // Insert the new row
            const tempDiv = document.createElement('div');
            tempDiv.innerHTML = skillRowTemplate;
            container.appendChild(tempDiv.firstElementChild);
        });
        
        // Remove skill row - Use event delegation
        document.getElementById('team-skills-container').addEventListener('click', function(e) {
            if (e.target && e.target.classList.contains('remove-skill')) {
                const row = e.target.closest('.skill-row');
                if (row) {
                    row.parentNode.removeChild(row);
                }
            }
        });
    });
    </script>
    <?php
}

ব্যাখ্যা:

৫.১ ডেটা স্ট্রাকচার

স্কিলস একটি অ্যারে হিসাবে সংরক্ষিত হয়, যার প্রতিটি আইটেম নিজেও একটি অ্যারে যাতে ‘name’ এবং ‘percentage’ কী রয়েছে। যদি কোন স্কিল না থাকে, তবে একটি খালি স্কিল রো যোগ করা হয়।

৫.২ ডায়নামিক ফর্ম ফিল্ড

HTML এবং PHP দ্বারা একটি টেবিল যোগ করা হয়েছে যেখানে প্রতিটি স্কিলের জন্য দুটি ইনপুট ফিল্ড রয়েছে:

  • স্কিলের নাম
  • স্কিলের শতকরা হার (0-100)

প্রতিটি স্কিল রোতে একটি “Remove This Skill” বাটন থাকে (প্রথম রো বাদে)। আরও, একটি “Add New Skill” বাটন রয়েছে যা ব্যবহারকারীকে নতুন স্কিল যোগ করতে দেয়।

৫.৩ জাভাস্ক্রিপ্ট

এখানে জাভাস্ক্রিপ্ট দুটি ফাংশন যোগ করে:

  1. নতুন স্কিল রো যোগ করা: “Add New Skill” বাটনে ক্লিক করলে, একটি নতুন স্কিল রো তৈরি হয় এবং ফর্মে যোগ হয়।
  2. স্কিল রো মুছে ফেলা: “Remove This Skill” বাটনে ক্লিক করলে, সেই স্কিল রো মুছে যায়।

ইভেন্ট ডেলিগেশন ব্যবহার করে, মুছে ফেলার ফাংশনটি সমস্ত বর্তমান এবং ভবিষ্যতের “Remove” বাটনে কাজ করে।

৬. টিম মেম্বার সোশ্যাল মিডিয়া লিংকস মেটা বক্স

তৃতীয় মেটা বক্সটি টিম মেম্বারের সোশ্যাল মিডিয়া লিংক সংরক্ষণ করে:

/**
 * Team social media meta box callback
 */
function rebelsoftt_team_social_callback($post) {
    wp_nonce_field('rebelsoftt_team_social_nonce', 'team_social_nonce');
    
    $linkedin = get_post_meta($post->ID, '_team_linkedin', true);
    $twitter = get_post_meta($post->ID, '_team_twitter', true);
    $facebook = get_post_meta($post->ID, '_team_facebook', true);
    $instagram = get_post_meta($post->ID, '_team_instagram', true);
    $email = get_post_meta($post->ID, '_team_email', true);
    
    ?>
    <p>
        <label for="team_linkedin"><?php _e('LinkedIn URL:', 'rebelsoftt'); ?></label>
        <input type="url" id="team_linkedin" name="team_linkedin" value="<?php echo esc_url($linkedin); ?>" class="widefat">
    </p>
    <p>
        <label for="team_twitter"><?php _e('Twitter URL:', 'rebelsoftt'); ?></label>
        <input type="url" id="team_twitter" name="team_twitter" value="<?php echo esc_url($twitter); ?>" class="widefat">
    </p>
    <p>
        <label for="team_facebook"><?php _e('Facebook URL:', 'rebelsoftt'); ?></label>
        <input type="url" id="team_facebook" name="team_facebook" value="<?php echo esc_url($facebook); ?>" class="widefat">
    </p>
    <p>
        <label for="team_instagram"><?php _e('Instagram URL:', 'rebelsoftt'); ?></label>
        <input type="url" id="team_instagram" name="team_instagram" value="<?php echo esc_url($instagram); ?>" class="widefat">
    </p>
    <p>
        <label for="team_email"><?php _e('Email Address:', 'rebelsoftt'); ?></label>
        <input type="email" id="team_email" name="team_email" value="<?php echo esc_attr($email); ?>" class="widefat">
    </p>
    <?php
}

ব্যাখ্যা:

৬.১ ডাটা রিট্রিভাল

প্রতিটি সোশ্যাল মিডিয়া লিংক এবং ইমেল পুনরুদ্ধার করা হয় get_post_meta() ফাংশন ব্যবহার করে।

৬.২ ইনপুট ফিল্ড

এরপর সেগুলি প্রদর্শন করা হয় HTML ইনপুট ফিল্ড হিসাবে। লক্ষ্য করুন:

  • সোশ্যাল মিডিয়া লিংকগুলির জন্য type="url" ব্যবহার করা হয়েছে
  • ইমেলের জন্য type="email" ব্যবহার করা হয়েছে
  • লিংকগুলি esc_url() ফাংশন দ্বারা স্যানিটাইজ করা হয়েছে
  • ইমেল esc_attr() ফাংশন দ্বারা স্যানিটাইজ করা হয়েছে

৭. মেটা ডাটা সংরক্ষণ করা

সবশেষে, পোস্ট সেভ করার সময় মেটা ডাটা সংরক্ষণ করার ফাংশন:

/**
 * Save team member meta data
 */
function rebelsoftt_save_team_meta($post_id) {
    // Check if we're autosaving
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }
    
    // Check the user's permissions
    if (isset($_POST['post_type']) && 'team' == $_POST['post_type']) {
        if (!current_user_can('edit_post', $post_id)) {
            return;
        }
    }
    
    // Save team details
    if (isset($_POST['team_details_nonce']) && wp_verify_nonce($_POST['team_details_nonce'], 'rebelsoftt_team_details_nonce')) {
        if (isset($_POST['team_position'])) {
            update_post_meta($post_id, '_team_position', sanitize_text_field($_POST['team_position']));
        }
    }
    
    // Save team skills
    if (isset($_POST['team_skills_nonce']) && wp_verify_nonce($_POST['team_skills_nonce'], 'rebelsoftt_team_skills_nonce')) {
        $skills = array();
        
        if (isset($_POST['skill_name']) && isset($_POST['skill_percentage']) && is_array($_POST['skill_name']) && is_array($_POST['skill_percentage'])) {
            $skill_names = $_POST['skill_name'];
            $skill_percentages = $_POST['skill_percentage'];
            
            for ($i = 0; $i < count($skill_names); $i++) {
                if (!empty($skill_names[$i])) {
                    $skills[] = array(
                        'name' => sanitize_text_field($skill_names[$i]),
                        'percentage' => intval($skill_percentages[$i])
                    );
                }
            }
        }
        
        update_post_meta($post_id, '_team_skills', $skills);
    }
    
    // Save social media links
    if (isset($_POST['team_social_nonce']) && wp_verify_nonce($_POST['team_social_nonce'], 'rebelsoftt_team_social_nonce')) {
        if (isset($_POST['team_linkedin'])) {
            update_post_meta($post_id, '_team_linkedin', esc_url_raw($_POST['team_linkedin']));
        }
        
        if (isset($_POST['team_twitter'])) {
            update_post_meta($post_id, '_team_twitter', esc_url_raw($_POST['team_twitter']));
        }
        
        if (isset($_POST['team_facebook'])) {
            update_post_meta($post_id, '_team_facebook', esc_url_raw($_POST['team_facebook']));
        }
        
        if (isset($_POST['team_instagram'])) {
            update_post_meta($post_id, '_team_instagram', esc_url_raw($_POST['team_instagram']));
        }
        
        if (isset($_POST['team_email'])) {
            update_post_meta($post_id, '_team_email', sanitize_email($_POST['team_email']));
        }
    }
}
add_action('save_post', 'rebelsoftt_save_team_meta');

ব্যাখ্যা:

৭.১ সিকিউরিটি চেক

ফাংশনের শুরুতে কয়েকটি গুরুত্বপূর্ণ চেক রয়েছে:

  1. অটোসেভ চেক: DOING_AUTOSAVE কনস্ট্যান্ট দ্বারা চেক করা হয় যে পোস্ট অটোসেভ হচ্ছে কিনা। অটোসেভের সময় মেটা ডাটা সেভ করা হয় না।
  2. পারমিশন চেক: current_user_can('edit_post', $post_id) দ্বারা চেক করা হয় যে ব্যবহারকারীর পোস্ট এডিট করার অধিকার আছে কিনা।

৭.২ নোনস ভেরিফিকেশন

প্রতিটি মেটা বক্সের জন্য, wp_verify_nonce() ফাংশন দ্বারা নোনস ভেরিফাই করা হয়। এটি CSRF আক্রমণ প্রতিরোধ করে।

৭.৩ ডাটা সেভিং

তিন ধরনের ডাটা সেভ করা হয়:

  1. টিম ডিটেইলস: পজিশন/জব টাইটেল sanitize_text_field() দ্বারা স্যানিটাইজ করে সেভ করা হয়।
  2. টিম স্কিলস: সমস্ত স্কিল নেম এবং পারসেন্টেজ প্রসেস করে, একটি স্ট্রাকচারড অ্যারে তৈরি করা হয় এবং সেভ করা হয়।
  3. সোশ্যাল মিডিয়া লিংকস: প্রতিটি লিংক esc_url_raw() দ্বারা স্যানিটাইজ করা হয় (URL এর জন্য) এবং ইমেল sanitize_email() দ্বারা স্যানিটাইজ করা হয়।

৭.৪ আপডেট ফাংশন

update_post_meta() ফাংশন ব্যবহার করে সমস্ত ডাটা সেভ করা হয়। এই ফাংশনটি তিনটি প্যারামিটার নেয়:

  1. পোস্টের আইডি
  2. মেটা কী নাম (যেমন _team_position)
  3. মেটা ভ্যালু (স্যানিটাইজ করা ডাটা)

৭.৫ এক্শন হুক

add_action('save_post', 'rebelsoftt_save_team_meta'); দ্বারা ওয়ার্ডপ্রেস save_post হুকে এই ফাংশনটি সংযুক্ত করা হয়েছে, যাতে পোস্ট সেভ হওয়ার সময় এই ফাংশনটি কার্যকর হয়।

৮. ব্যবহারের উদাহরণ

আমরা এখন দেখব কিভাবে ওয়েবসাইটে এই কাস্টম পোস্ট টাইপ ব্যবহার করা যায়। নিচে একটি সাধারণ উদাহরণ দেওয়া হল:

<?php
/**
 * Template Name: Team Page
 */

get_header(); ?>

<div class="container team-page">
    <div class="row">
        <div class="col-12">
            <h1 class="page-title"><?php the_title(); ?></h1>
            <?php the_content(); ?>
        </div>
    </div>
    
    <div class="row team-members">
        <?php
        // আমরা team কাস্টম পোস্ট টাইপ থেকে সব টিম মেম্বার আনব
        $team_members = new WP_Query(array(
            'post_type' => 'team',
            'posts_per_page' => -1, // সব পোস্ট আনা
            'orderby' => 'menu_order', // ম্যানুয়াল অর্ডার অনুসারে
            'order' => 'ASC'
        ));
        
        if ($team_members->have_posts()) :
            while ($team_members->have_posts()) : $team_members->the_post();
                // মেটা ডাটা নেওয়া
                $position = get_post_meta(get_the_ID(), '_team_position', true);
                $skills = get_post_meta(get_the_ID(), '_team_skills', true);
                $linkedin = get_post_meta(get_the_ID(), '_team_linkedin', true);
                $twitter = get_post_meta(get_the_ID(), '_team_twitter', true);
                $facebook = get_post_meta(get_the_ID(), '_team_facebook', true);
                $instagram = get_post_meta(get_the_ID(), '_team_instagram', true);
                $email = get_post_meta(get_the_ID(), '_team_email', true);
                
                // ডিপার্টমেন্ট টার্ম আনা
                $departments = get_the_terms(get_the_ID(), 'department');
                ?>
                
                <div class="col-md-4 team-member">
                    <div class="team-member-inner">
                        <?php if (has_post_thumbnail()) : ?>
                            <div class="team-member-photo">
                                <?php the_post_thumbnail('medium', array('class' => 'img-fluid')); ?>
                            </div>
                        <?php endif; ?>
                        
                        <div class="team-member-info">
                            <h3 class="team-member-name"><?php the_title(); ?></h3>
                            
                            <?php if (!empty($position)) : ?>
                                <div class="team-member-position"><?php echo esc_html($position); ?></div>
                            <?php endif; ?>
                            
                            <?php if (!empty($departments) && !is_wp_error($departments)) : ?>
                                <div class="team-member-department">
                                    <?php
                                    $dept_names = array();
                                    foreach ($departments as $dept) {
                                        $dept_names[] = $dept->name;
                                    }
                                    echo esc_html(join(', ', $dept_names));
                                    ?>
                                </div>
                            <?php endif; ?>
                            
                            <div class="team-member-content">
                                <?php the_content(); ?>
                            </div>
                            
                            <?php if (!empty($skills) && is_array($skills)) : ?>
                                <div class="team-member-skills">
                                    <h4>দক্ষতা</h4>
                                    <?php foreach ($skills as $skill) : ?>
                                        <div class="skill">
                                            <div class="skill-name"><?php echo esc_html($skill['name']); ?></div>
                                            <div class="skill-bar">
                                                <div class="skill-progress" style="width: <?php echo intval($skill['percentage']); ?>%;"></div>
                                            </div>
                                            <div class="skill-percentage"><?php echo intval($skill['percentage']); ?>%</div>
                                        </div>
                                    <?php endforeach; ?>
                                </div>
                            <?php endif; ?>
                            
                            <?php if (!empty($linkedin) || !empty($twitter) || !empty($facebook) || !empty($instagram) || !empty($email)) : ?>
                                <div class="team-member-social">
                                    <?php if (!empty($linkedin)) : ?>
                                        <a href="<?php echo esc_url($linkedin); ?>" target="_blank" class="social-icon linkedin">
                                            <i class="fab fa-linkedin"></i>
                                        </a>
                                    <?php endif; ?>
                                    
                                    <?php if (!empty($twitter)) : ?>
                                        <a href="<?php echo esc_url($twitter); ?>" target="_blank" class="social-icon twitter">
                                            <i class="fab fa-twitter"></i>
                                        </a>
                                    <?php endif; ?>
                                    
                                    <?php if (!empty($facebook)) : ?>
                                        <a href="<?php echo esc_url($facebook); ?>" target="_blank" class="social-icon facebook">
                                            <i class="fab fa-facebook"></i>
                                        </a>
                                    <?php endif; ?>
                                    
                                    <?php if (!empty($instagram)) : ?>
                                        <a href="<?php echo esc_url($instagram); ?>" target="_blank" class="social-icon instagram">
                                            <i class="fab fa-instagram"></i>
                                        </a>
                                    <?php endif; ?>
                                    
                                    <?php if (!empty($email)) : ?>
                                        <a href="mailto:<?php echo esc_attr($email); ?>" class="social-icon email">
                                            <i class="fas fa-envelope"></i>
                                        </a>
                                    <?php endif; ?>
                                </div>
                            <?php endif; ?>
                        </div>
                    </div>
                </div>
                
                <?php
            endwhile;
            wp_reset_postdata();
        else :
            echo '<div class="col-12"><p>কোন টিম মেম্বার পাওয়া যায়নি।</p></div>';
        endif;
        ?>
    </div>
</div>

<?php get_footer(); ?>

ব্যাখ্যা:

৮.১ টেমপ্লেট সেটআপ

এটি একটি কাস্টম পেজ টেমপ্লেট যা টিম মেম্বারদের প্রদর্শন করে। হেডার এবং ফুটার অন্তর্ভুক্ত করা হয়েছে।

৮.২ WP_Query

WP_Query ক্লাস ব্যবহার করে ‘team’ কাস্টম পোস্ট টাইপের সমস্ত পোস্ট আনা হয়েছে।

৮.৩ টিম মেম্বার ডিসপ্লে

প্রতিটি টিম মেম্বারের জন্য, আমরা নিম্নলিখিত তথ্য প্রদর্শন করি:

  • ফিচার্ড ইমেজ (টিম মেম্বারের ছবি)
  • নাম (পোস্ট টাইটেল)
  • পজিশন/জব টাইটেল (মেটা ডাটা)
  • ডিপার্টমেন্ট (ট্যাক্সোনমি)
  • বর্ণনা (পোস্ট কন্টেন্ট)
  • স্কিলস (মেটা ডাটা) – প্রগ্রেস বার সহ
  • সোশ্যাল মিডিয়া লিংক (মেটা ডাটা)

৮.৪ স্যানিটাইজেশন

সিকিউরিটির জন্য, সমস্ত আউটপুট স্যানিটাইজ করা হয়েছে:

  • esc_html() টেক্সট আউটপুটের জন্য
  • esc_url() লিংকের জন্য
  • esc_attr() এট্রিবিউটের জন্য

৯. শর্টকোড ব্যবহার

আমরা একটি শর্টকোড তৈরি করতে পারি যাতে পেজ বা পোস্টে টিম মেম্বার প্রদর্শন করা যায়:

/**
 * Team shortcode [team_members]
 */
function rebelsoftt_team_shortcode($atts) {
    $atts = shortcode_atts(
        array(
            'count' => -1, // সমস্ত টিম মেম্বার
            'department' => '', // সবগুলো ডিপার্টমেন্ট
            'orderby' => 'menu_order', // ম্যানুয়াল অর্ডার
            'order' => 'ASC' // আরোহী ক্রম
        ),
        $atts,
        'team_members'
    );
    
    $args = array(
        'post_type' => 'team',
        'posts_per_page' => intval($atts['count']),
        'orderby' => sanitize_text_field($atts['orderby']),
        'order' => sanitize_text_field($atts['order'])
    );
    
    // যদি ডিপার্টমেন্ট নির্দিষ্ট করা থাকে
    if (!empty($atts['department'])) {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'department',
                'field' => 'slug',
                'terms' => explode(',', sanitize_text_field($atts['department']))
            )
        );
    }
    
    $team_members = new WP_Query($args);
    
    ob_start();
    
    if ($team_members->have_posts()) :
        echo '<div class="team-members-shortcode">';
        echo '<div class="row">';
        
        while ($team_members->have_posts()) : $team_members->the_post();
            // আগের উদাহরণে দেওয়া টিম মেম্বার HTML স্ট্রাকচার এখানে ব্যবহার করুন
            $position = get_post_meta(get_the_ID(), '_team_position', true);
            ?>
            <div class="col-md-4 team-member">
                <div class="team-member-inner">
                    <?php if (has_post_thumbnail()) : ?>
                        <div class="team-member-photo">
                            <?php the_post_thumbnail('medium', array('class' => 'img-fluid')); ?>
                        </div>
                    <?php endif; ?>
                    
                    <div class="team-member-info">
                        <h3 class="team-member-name"><?php the_title(); ?></h3>
                        
                        <?php if (!empty($position)) : ?>
                            <div class="team-member-position"><?php echo esc_html($position); ?></div>
                        <?php endif; ?>
                        
                        <div class="team-member-content">
                            <?php the_excerpt(); ?>
                        </div>
                        
                        <a href="<?php the_permalink(); ?>" class="read-more">আরো দেখুন</a>
                    </div>
                </div>
            </div>
            <?php
        endwhile;
        
        echo '</div>'; // .row
        echo '</div>'; // .team-members-shortcode
        
        wp_reset_postdata();
    else :
        echo '<p>কোন টিম মেম্বার পাওয়া যায়নি।</p>';
    endif;
    
    return ob_get_clean();
}
add_shortcode('team_members', 'rebelsoftt_team_shortcode');

ব্যাখ্যা:

৯.১ শর্টকোড এট্রিবিউট

  • count: কতগুলি টিম মেম্বার প্রদর্শন করবে (-1 মানে সবগুলি)
  • department: যে ডিপার্টমেন্টের টিম মেম্বার প্রদর্শন করবে (খালি থাকলে সবগুলি)
  • orderby: কিসের ভিত্তিতে সাজাবে (menu_order, title, date ইত্যাদি)
  • order: ক্রম (ASC বা DESC)

৯.২ আউটপুট বাফারিং

ob_start() এবং ob_get_clean() ফাংশন দ্বারা আউটপুট বাফার করা হয়, যাতে HTML সঠিকভাবে শর্টকোড আউটপুটে যোগ করা যায়।

৯.৩ ব্যবহার

এই শর্টকোড ব্যবহার করার জন্য, পেজ বা পোস্টে নিচের মতো কোড লিখতে হবে:

  • সমস্ত টিম মেম্বার প্রদর্শন: [team_members]
  • নির্দিষ্ট ডিপার্টমেন্ট: [team_members department="development,design"]
  • সীমিত সংখ্যক: [team_members count="6"]
  • নাম অনুসারে সাজানো: [team_members orderby="title" order="ASC"]

১০. CSS স্টাইলিং

আমরা এই কাস্টম পোস্ট টাইপের জন্য কিছু CSS যোগ করতে পারি:

/* টিম মেম্বার কার্ড স্টাইল */
.team-member {
    margin-bottom: 30px;
}

.team-member-inner {
    background-color: #fff;
    border-radius: 8px;
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
    overflow: hidden;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.team-member-inner:hover {
    transform: translateY(-5px);
    box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
}

.team-member-photo img {
    width: 100%;
    height: auto;
    display: block;
}

.team-member-info {
    padding: 20px;
}

.team-member-name {
    margin: 0 0 5px;
    font-size: 20px;
    color: #333;
}

.team-member-position {
    color: #666;
    font-size: 16px;
    margin-bottom: 10px;
}

.team-member-department {
    background-color: #f5f5f5;
    color: #555;
    font-size: 12px;
    padding: 3px 8px;
    border-radius: 4px;
    display: inline-block;
    margin-bottom: 15px;
}

.team-member-content {
    margin-bottom: 15px;
    font-size: 14px;
    line-height: 1.6;
    color: #777;
}

/* স্কিল বার স্টাইল */
.team-member-skills {
    margin-top: 20px;
}

.team-member-skills h4 {
    margin-bottom: 15px;
    font-size: 16px;
    border-bottom: 1px solid #eee;
    padding-bottom: 5px;
}

.skill {
    margin-bottom: 10px;
}

.skill-name {
    margin-bottom: 5px;
    font-weight: 500;
    font-size: 14px;
}

.skill-bar {
    height: 8px;
    background-color: #f0f0f0;
    border-radius: 4px;
    overflow: hidden;
    margin-bottom: 3px;
}

.skill-progress {
    height: 100%;
    background-color: #4A90E2;
    border-radius: 4px;
}

.skill-percentage {
    font-size: 12px;
    color: #888;
    text-align: right;
}

/* সোশ্যাল মিডিয়া আইকন স্টাইল */
.team-member-social {
    margin-top: 20px;
    display: flex;
    gap: 10px;
}

.social-icon {
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    color: white;
    font-size: 16px;
    transition: transform 0.3s ease, opacity 0.3s ease;
}

.social-icon:hover {
    transform: scale(1.1);
    opacity: 0.9;
}

.linkedin {
    background-color: #0077B5;
}

.twitter {
    background-color: #1DA1F2;
}

.facebook {
    background-color: #4267B2;
}

.instagram {
    background: linear-gradient(45deg, #f09433, #e6683c, #dc2743, #cc2366, #bc1888);
}

.email {
    background-color: #dd4b39;
}

/* রেসপন্সিভ স্টাইল */
@media (max-width: 991px) {
    .team-member {
        flex: 0 0 50%;
        max-width: 50%;
    }
}

@media (max-width: 767px) {
    .team-member {
        flex: 0 0 100%;
        max-width: 100%;
    }
}

১১. সারসংক্ষেপ

এই টিউটোরিয়ালে আমরা দেখলাম কিভাবে ওয়ার্ডপ্রেসে একটি কাস্টম পোস্ট টাইপ “Team” তৈরি করা যায়। এই কাস্টম পোস্ট টাইপ ব্যবহার করে আপনি নিম্নলিখিত কাজগুলি করতে পারেন:

  1. টিম মেম্বার তৈরি করা: অ্যাডমিন প্যানেল থেকে টিম মেম্বার যোগ করা
  2. ডিপার্টমেন্ট ক্যাটেগরি: টিম মেম্বারদের বিভিন্ন ডিপার্টমেন্টে সাজানো
  3. কাস্টম মেটা ডাটা: টিম মেম্বারের পজিশন, স্কিল, এবং সোশ্যাল মিডিয়া লিংক সংরক্ষণ করা
  4. ডায়নামিক ফিল্ড: জাভাস্ক্রিপ্ট ব্যবহার করে ডায়নামিকভাবে স্কিল যোগ/মুছে ফেলা
  5. টেমপ্লেট প্রদর্শন: টিম মেম্বারদের ওয়েবসাইটে প্রদর্শন করা
  6. শর্টকোড: যেকোনো পেজ বা পোস্টে টিম মেম্বার প্রদর্শন করার জন্য শর্টকোড ব্যবহার করা
  7. স্টাইলিং: CSS ব্যবহার করে টিম মেম্বারদের আকর্ষণীয়ভাবে প্রদর্শন করা

এই কাস্টম পোস্ট টাইপ আপনার ওয়েবসাইটে টিম সেকশন তৈরি করতে অত্যন্ত উপযোগী। আপনি আরো ফিচার যেমন টিম মেম্বারের ফিল্টারিং, সার্চিং, ইত্যাদি যোগ করতে পারেন।

১২. অতিরিক্ত ফিচার: অ্যাজাক্স ফিল্টারিং

আমরা এখন দেখব কিভাবে অ্যাজাক্স ব্যবহার করে ডিপার্টমেন্ট অনুসারে টিম মেম্বারদের ফিল্টার করা যায়:

/**
 * Team filtering with AJAX
 */
function rebelsoftt_team_filter_scripts() {
    if (is_page_template('template-team.php') || has_shortcode(get_the_content(), 'team_members')) {
        wp_enqueue_script('team-filter', get_template_directory_uri() . '/js/team-filter.js', array('jquery'), '1.0', true);
        
        wp_localize_script('team-filter', 'team_filter_vars', array(
            'ajaxurl' => admin_url('admin-ajax.php'),
            'nonce' => wp_create_nonce('team_filter_nonce')
        ));
    }
}
add_action('wp_enqueue_scripts', 'rebelsoftt_team_filter_scripts');

/**
 * AJAX handler for team filtering
 */
function rebelsoftt_filter_team_members() {
    // নোনস চেক
    if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'team_filter_nonce')) {
        wp_send_json_error('Invalid nonce');
        die();
    }
    
    // ডিপার্টমেন্ট পাওয়া
    $department = isset($_POST['department']) ? sanitize_text_field($_POST['department']) : '';
    
    // কোয়েরি আর্গুমেন্ট তৈরি
    $args = array(
        'post_type' => 'team',
        'posts_per_page' => -1,
        'orderby' => 'menu_order',
        'order' => 'ASC'
    );
    
    // যদি ডিপার্টমেন্ট নির্দিষ্ট করা থাকে এবং 'all' না হয়
    if (!empty($department) && $department !== 'all') {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'department',
                'field' => 'slug',
                'terms' => $department
            )
        );
    }
    
    $team_members = new WP_Query($args);
    
    ob_start();
    
    if ($team_members->have_posts()) :
        while ($team_members->have_posts()) : $team_members->the_post();
            // টিম মেম্বার HTML স্ট্রাকচার
            $position = get_post_meta(get_the_ID(), '_team_position', true);
            ?>
            <div class="col-md-4 team-member">
                <div class="team-member-inner">
                    <?php if (has_post_thumbnail()) : ?>
                        <div class="team-member-photo">
                            <?php the_post_thumbnail('medium', array('class' => 'img-fluid')); ?>
                        </div>
                    <?php endif; ?>
                    
                    <div class="team-member-info">
                        <h3 class="team-member-name"><?php the_title(); ?></h3>
                        
                        <?php if (!empty($position)) : ?>
                            <div class="team-member-position"><?php echo esc_html($position); ?></div>
                        <?php endif; ?>
                        
                        <div class="team-member-content">
                            <?php the_excerpt(); ?>
                        </div>
                        
                        <a href="<?php the_permalink(); ?>" class="read-more">আরো দেখুন</a>
                    </div>
                </div>
            </div>
            <?php
        endwhile;
        wp_reset_postdata();
    else :
        echo '<div class="col-12"><p>কোন টিম মেম্বার পাওয়া যায়নি।</p></div>';
    endif;
    
    $html = ob_get_clean();
    
    wp_send_json_success(array('html' => $html));
    die();
}
add_action('wp_ajax_filter_team_members', 'rebelsoftt_filter_team_members');
add_action('wp_ajax_nopriv_filter_team_members', 'rebelsoftt_filter_team_members');
jQuery(document).ready(function($) {
    // ডিপার্টমেন্ট ফিল্টার বাটন ক্লিক হ্যান্ডলার
    $('.department-filter-btn').on('click', function(e) {
        e.preventDefault();
        
        // অ্যাক্টিভ ক্লাস পরিবর্তন
        $('.department-filter-btn').removeClass('active');
        $(this).addClass('active');
        
        // ডিপার্টমেন্ট স্লাগ পাওয়া
        var department = $(this).data('department');
        
        // লোডিং স্টেট দেখানো
        $('.team-members').addClass('loading');
        
        // অ্যাজাক্স রিকোয়েস্ট
        $.ajax({
            url: team_filter_vars.ajaxurl,
            type: 'POST',
            data: {
                action: 'filter_team_members',
                department: department,
                nonce: team_filter_vars.nonce
            },
            success: function(response) {
                if (response.success) {
                    // টিম মেম্বার আপডেট করা
                    $('.team-members .row').html(response.data.html);
                    
                    // অ্যানিমেশন যোগ করা
                    $('.team-member').addClass('fade-in');
                    
                    // লোডিং স্টেট সরানো
                    $('.team-members').removeClass('loading');
                }
            },
            error: function() {
                console.log('Error loading team members');
                $('.team-members').removeClass('loading');
            }
        });
    });
});
<div class="department-filter">
    <button class="department-filter-btn active" data-department="all">সকল বিভাগ</button>
    
    <?php
    // সমস্ত ডিপার্টমেন্ট পাওয়া
    $departments = get_terms(array(
        'taxonomy' => 'department',
        'hide_empty' => true
    ));
    
    if (!empty($departments) && !is_wp_error($departments)) {
        foreach ($departments as $department) {
            echo '<button class="department-filter-btn" data-department="' . esc_attr($department->slug) . '">' . esc_html($department->name) . '</button>';
        }
    }
    ?>
</div>

<div class="team-members">
    <div class="row">
        <!-- টিম মেম্বার এখানে লোড হবে -->
    </div>
</div>
/* ফিল্টার বাটন স্টাইল */
.department-filter {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 30px;
}

.department-filter-btn {
    padding: 8px 16px;
    background-color: #f5f5f5;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    transition: all 0.3s ease;
}

.department-filter-btn.active {
    background-color: #4A90E2;
    color: white;
}

.department-filter-btn:hover {
    background-color: #e0e0e0;
}

.department-filter-btn.active:hover {
    background-color: #3A80D2;
}

/* লোডিং স্টেট */
.team-members.loading {
    opacity: 0.6;
    pointer-events: none;
}

/* অ্যানিমেশন */
@keyframes fadeIn {
    from { opacity: 0; transform: translateY(20px); }
    to { opacity: 1; transform: translateY(0); }
}

.team-member.fade-in {
    animation: fadeIn 0.5s ease forwards;
}

উপসংহার

এই বিস্তারিত টিউটোরিয়ালে আমরা দেখেছি কিভাবে ওয়ার্ডপ্রেসে একটি সম্পূর্ণ “Team” কাস্টম পোস্ট টাইপ তৈরি করা যায়। এই কোডটি আপনার ওয়েবসাইটে টিম মেম্বারদের সুন্দরভাবে প্রদর্শন করতে সাহায্য করবে। আপনি এই কোডটি আপনার প্রয়োজন অনুসারে পরিবর্তন করতে পারেন।

আশা করি এই টিউটোরিয়ালটি আপনার জন্য উপকারী হয়েছে। যদি আপনার কোন প্রশ্ন থাকে, তাহলে অবশ্যই জানাবেন।

How can we help?