
Shortcode'y z ostatnimi, przyszłymi wpisami lub wpisami danego autora
Spis treści
Shortcode’y w WordPress to potężne narzędzie do tworzenia dynamicznych elementów treści. W 2026 roku, mimo że Gutenberg Bloki są preferowanym rozwiązaniem, shortcode’y wciąż mają swoje zastosowanie – szczególnie dla zaawansowanych funkcjonalności wymagających logiki PHP.
W tym przewodniku nauczysz się tworzyć profesjonalne shortcode’y do wyświetlania list wpisów, które są bezpieczne, wydajne i zgodne z najlepszymi praktykami WordPress.
Dlaczego shortcode’y w 2026?
Mimo że Gutenberg Bloki są przyszłością WordPress, shortcode’y mają swoje miejsce:
Zalety shortcode’ów:
- Szybka implementacja – nie wymagają kompilacji JavaScript
- Dostęp do pełnej logiki PHP – łatwy dostęp do bazy danych, hooków WordPress
- Kompatybilność wsteczna – działają w Classic Editor i Gutenberg
- Prostota – dla prostych funkcji nie potrzebujesz React/JSX
Kiedy używać shortcode’ów:
- Dynamiczne listy wpisów z zaawansowaną logiką
- Integracje z zewnętrznymi API
- Funkcje wymagające bezpośredniego dostępu do PHP
Kiedy używać Bloków Gutenberg:
- Edytowalne interfejsy użytkownika
- Wizualne edytowanie w Gutenbergu
- Złożone layouty z wieloma opcjami
Podstawowa struktura shortcode’a
Każdy shortcode w WordPress składa się z trzech elementów:
- Funkcja callback – wykonuje logikę i zwraca HTML
- Rejestracja shortcode’a –
add_shortcode() - Atrybuty – parametry przekazywane przez użytkownika
Minimalny przykład
/**
* Basic shortcode structure
*/
function wppoland_basic_shortcode( $atts, $content = null, $tag = '' ) {
// $atts - array of attributes
// $content - content between opening and closing tags
// $tag - shortcode tag name
// Parse attributes with defaults
$atts = shortcode_atts( [
'number' => 5,
], $atts, $tag );
// Your logic here
$output = '<div class="shortcode-output">';
$output .= 'Number: ' . esc_html( $atts['number'] );
$output .= '</div>';
return $output;
}
add_shortcode( 'my_shortcode', 'wppoland_basic_shortcode' );
Użycie:
[my_shortcode number="10"]
Zaawansowany shortcode do list wpisów
Oto kompletna, produkcyjna wersja shortcode’a do wyświetlania list wpisów:
/**
* Advanced post list shortcodes
* Supports: latest posts, category posts, author posts, future posts
*
* @param array $atts Shortcode attributes
* @param string|null $content Content between tags (not used)
* @param string $tag Shortcode tag name
* @return string HTML output
*/
function wppoland_post_lists_shortcode( $atts, $content = null, $tag ) {
// Security: Only allow specific tags
$allowed_tags = ['latest_posts', 'category_posts', 'author_posts', 'future_posts'];
if ( ! in_array( $tag, $allowed_tags, true ) ) {
return '';
}
// Get current post context
global $post;
$current_post_id = $post ? $post->ID : 0;
$current_author_id = $post ? $post->post_author : 0;
// Parse and sanitize attributes
$atts = shortcode_atts( [
'number' => 5,
'exclude_current' => 'yes',
'orderby' => 'date',
'order' => 'DESC',
'category' => '',
'author' => '',
'post_type' => 'post',
'show_date' => 'yes',
'show_excerpt' => 'no',
'class' => '',
], $atts, $tag );
// Sanitize inputs
$number = absint( $atts['number'] );
$number = min( $number, 50 ); // Limit to 50 for performance
$exclude_current = $atts['exclude_current'] === 'yes';
$orderby = sanitize_key( $atts['orderby'] );
$order = strtoupper( $atts['order'] ) === 'ASC' ? 'ASC' : 'DESC';
$post_type = sanitize_key( $atts['post_type'] );
$show_date = $atts['show_date'] === 'yes';
$show_excerpt = $atts['show_excerpt'] === 'yes';
$css_class = sanitize_html_class( $atts['class'] );
// Build query arguments based on shortcode tag
$query_args = [
'post_type' => $post_type,
'posts_per_page' => $number,
'orderby' => $orderby,
'order' => $order,
'post_status' => 'publish',
'no_found_rows' => true, // Performance: don't count total posts
'update_post_meta_cache' => false, // Performance: don't cache meta
'update_post_term_cache' => false, // Performance: don't cache terms
];
// Exclude current post if needed
if ( $exclude_current && $current_post_id > 0 ) {
$query_args['post__not_in'] = [ $current_post_id ];
}
// Build query based on shortcode type
switch ( $tag ) {
case 'latest_posts':
// No additional args needed - shows latest posts
break;
case 'category_posts':
// Get categories from current post
if ( $current_post_id > 0 ) {
$categories = get_the_category( $current_post_id );
if ( ! empty( $categories ) ) {
$category_id = $categories[0]->term_id;
$query_args['cat'] = $category_id;
} else {
// No categories found, return empty
return '';
}
} else {
// Not in post context, return empty
return '';
}
break;
case 'author_posts':
// Use current post author or specified author
if ( ! empty( $atts['author'] ) ) {
$author_id = absint( $atts['author'] );
} elseif ( $current_author_id > 0 ) {
$author_id = $current_author_id;
} else {
return '';
}
$query_args['author'] = $author_id;
break;
case 'future_posts':
// Show scheduled posts
$query_args['post_status'] = 'future';
$query_args['orderby'] = 'date';
$query_args['order'] = 'ASC';
break;
}
// Override category if specified
if ( ! empty( $atts['category'] ) && $tag !== 'category_posts' ) {
$category_id = absint( $atts['category'] );
if ( $category_id > 0 ) {
$query_args['cat'] = $category_id;
}
}
// Execute query
$posts_query = new WP_Query( $query_args );
// Check if we have posts
if ( ! $posts_query->have_posts() ) {
wp_reset_postdata();
return '';
}
// Build output
$output = '<ul class="post-list post-list-' . esc_attr( $tag );
if ( ! empty( $css_class ) ) {
$output .= ' ' . esc_attr( $css_class );
}
$output .= '">';
while ( $posts_query->have_posts() ) {
$posts_query->the_post();
$output .= '<li class="post-list-item post-list-item-' . get_the_ID() . '">';
$output .= '<a href="' . esc_url( get_permalink() ) . '" ';
$output .= 'title="' . esc_attr( get_the_title() ) . '">';
$output .= esc_html( get_the_title() );
$output .= '</a>';
// Show date if requested
if ( $show_date ) {
$output .= ' <span class="post-date">';
$output .= '<time datetime="' . esc_attr( get_the_date( 'c' ) ) . '">';
$output .= esc_html( get_the_date() );
$output .= '</time>';
$output .= '</span>';
}
// Show excerpt if requested
if ( $show_excerpt ) {
$excerpt = get_the_excerpt();
if ( ! empty( $excerpt ) ) {
$output .= '<p class="post-excerpt">' . esc_html( wp_trim_words( $excerpt, 20 ) ) . '</p>';
}
}
$output .= '</li>';
}
$output .= '</ul>';
// Reset post data (CRITICAL!)
wp_reset_postdata();
return $output;
}
// Register all shortcodes
add_shortcode( 'latest_posts', 'wppoland_post_lists_shortcode' );
add_shortcode( 'category_posts', 'wppoland_post_lists_shortcode' );
add_shortcode( 'author_posts', 'wppoland_post_lists_shortcode' );
add_shortcode( 'future_posts', 'wppoland_post_lists_shortcode' );
Użycie shortcode’ów
Podstawowe przykłady
Najnowsze wpisy:
[latest_posts number="5"]
Wpisy z tej samej kategorii:
[category_posts number="3" exclude_current="yes"]
Wpisy tego samego autora:
[author_posts number="5" show_date="yes"]
Zaplanowane wpisy:
[future_posts number="10"]
Zaawansowane opcje
Z datą i fragmentem:
[latest_posts number="5" show_date="yes" show_excerpt="yes"]
Z niestandardową klasą CSS:
[category_posts number="3" class="my-custom-class"]
Z konkretną kategorią:
[latest_posts category="5" number="10"]
Z konkretnym autorem:
[author_posts author="1" number="5"]
Sortowanie:
[latest_posts number="5" orderby="title" order="ASC"]
Bezpieczeństwo i walidacja
1. Sanityzacja danych wejściowych
Zawsze sanityzuj dane użytkownika:
// ❌ ZŁE - brak sanityzacji
$number = $atts['number'];
// ✅ DOBRE - sanityzacja
$number = absint( $atts['number'] );
$number = min( $number, 50 ); // Limit maksymalny
2. Escapowanie outputu
Zawsze escapuj dane wyjściowe:
// ❌ ZŁE - brak escapowania
$output = '<a href="' . get_permalink() . '">' . get_the_title() . '</a>';
// ✅ DOBRE - escapowanie
$output = '<a href="' . esc_url( get_permalink() ) . '">';
$output .= esc_html( get_the_title() ) . '</a>';
3. Nonce dla edytowalnych shortcode’ów
Jeśli shortcode pozwala na edycję (np. przez AJAX), użyj nonce:
/**
* Shortcode with nonce for AJAX operations
*/
function wppoland_editable_shortcode( $atts ) {
$nonce = wp_create_nonce( 'wppoland_shortcode_action' );
$output = '<div class="editable-content" data-nonce="' . esc_attr( $nonce ) . '">';
$output .= 'Content here';
$output .= '</div>';
return $output;
}
Optymalizacja wydajności
1. Ograniczenie zapytań do bazy
// ✅ DOBRE - optymalizacja WP_Query
$query_args = [
'no_found_rows' => true, // Nie licz wszystkich postów
'update_post_meta_cache' => false, // Nie cache'uj meta
'update_post_term_cache' => false, // Nie cache'uj termów
];
2. Cache’owanie wyników
Dla często używanych shortcode’ów warto cache’ować wyniki:
/**
* Cached shortcode output
*/
function wppoland_cached_post_list( $atts, $content = null, $tag ) {
// Create cache key
$cache_key = 'wppoland_shortcode_' . $tag . '_' . md5( serialize( $atts ) );
// Try to get from cache
$output = get_transient( $cache_key );
if ( false === $output ) {
// Generate output
$output = wppoland_post_lists_shortcode( $atts, $content, $tag );
// Cache for 1 hour
set_transient( $cache_key, $output, HOUR_IN_SECONDS );
}
return $output;
}
3. Lazy Loading dla długich list
Dla list z wieloma wpisami rozważ lazy loading:
/**
* Shortcode with lazy loading support
*/
function wppoland_lazy_post_list( $atts ) {
$atts = shortcode_atts( [
'number' => 10,
'load_more' => 'yes',
], $atts );
$output = '<div class="lazy-post-list" data-posts="' . esc_attr( $atts['number'] ) . '">';
$output .= '<div class="post-list-container">';
// Initial posts loaded via AJAX
$output .= '</div>';
if ( $atts['load_more'] === 'yes' ) {
$output .= '<button class="load-more-posts">Wczytaj więcej</button>';
}
$output .= '</div>';
return $output;
}
Stylowanie shortcode’ów
CSS dla list wpisów
/* Basic styling */
.post-list {
list-style: none;
padding: 0;
margin: 1.5em 0;
}
.post-list-item {
padding: 0.75em 0;
border-bottom: 1px solid #e0e0e0;
}
.post-list-item:last-child {
border-bottom: none;
}
.post-list-item a {
text-decoration: none;
color: #333;
font-weight: 500;
transition: color 0.2s;
}
.post-list-item a:hover {
color: #0073aa;
}
.post-date {
display: block;
font-size: 0.875em;
color: #666;
margin-top: 0.25em;
}
.post-excerpt {
font-size: 0.9em;
color: #555;
margin-top: 0.5em;
line-height: 1.5;
}
Testowanie shortcode’ów
Unit testy
/**
* Test shortcode functionality
*/
class Wppoland_Shortcode_Test extends WP_UnitTestCase {
public function test_latest_posts_shortcode() {
// Create test posts
$post1 = $this->factory->post->create( [
'post_title' => 'Test Post 1',
'post_date' => '2026-01-01 12:00:00'
] );
$post2 = $this->factory->post->create( [
'post_title' => 'Test Post 2',
'post_date' => '2026-01-02 12:00:00'
] );
// Test shortcode
$output = do_shortcode( '[latest_posts number="2"]' );
// Assertions
$this->assertStringContainsString( 'Test Post 1', $output );
$this->assertStringContainsString( 'Test Post 2', $output );
}
}
Migracja do Gutenberg Bloków (2026)
Shortcode’y są przydatne, ale Gutenberg Bloki są przyszłością. Oto jak zmigrować:
1. Utwórz Block z render_callback
/**
* Register block that uses shortcode logic
*/
register_block_type( 'wppoland/post-list', [
'attributes' => [
'number' => [
'type' => 'number',
'default' => 5,
],
'showDate' => [
'type' => 'boolean',
'default' => true,
],
],
'render_callback' => function( $attributes ) {
// Reuse shortcode logic
return wppoland_post_lists_shortcode( $attributes, null, 'latest_posts' );
},
] );
2. Stopniowa migracja
- Faza 1: Shortcode’y działają równolegle z blokami
- Faza 2: Nowe treści używają bloków
- Faza 3: Stare shortcode’y są automatycznie konwertowane
Podsumowanie: Best Practices
✅ DOBRE praktyki
- Sanityzacja – zawsze sanityzuj dane wejściowe
- Escapowanie – escapuj wszystkie dane wyjściowe
- Performance – używaj
no_found_rows, cache’uj wyniki - Reset postdata – zawsze wywołuj
wp_reset_postdata() - Dokumentacja – dokumentuj wszystkie atrybuty
- Testowanie – pisz testy dla shortcode’ów
❌ ZŁE praktyki
- Brak sanityzacji – nigdy nie ufaj danym użytkownika
- Brak escapowania – XSS vulnerabilities
- Zapominanie wp_reset_postdata() – psuje główną pętlę
- Nieskończone zapytania – zawsze limituj liczbę postów
- Hardcoded wartości – używaj atrybutów i filtrów
Przykłady użycia w praktyce
Sekcja “Zobacz również”
<h2>Zobacz również</h2>
[category_posts number="3" exclude_current="yes" show_date="yes"]
Sekcja “Najnowsze wpisy”
<h2>Najnowsze wpisy</h2>
[latest_posts number="5" show_excerpt="yes" class="featured-posts"]
Sekcja “Inne wpisy autora”
<h2>Inne wpisy tego autora</h2>
[author_posts number="5" exclude_current="yes"]
Kalendarz wydarzeń (zaplanowane wpisy)
<h2>Nadchodzące wydarzenia</h2>
[future_posts number="10" orderby="date" order="ASC"]
Shortcode’y w WordPress to potężne narzędzie, które w 2026 roku wciąż ma zastosowanie. Pamiętaj o bezpieczeństwie, wydajności i stopniowej migracji do Gutenberg Bloków.