
oEmbed is a standard WordPress uses to automatically turn certain URLs into embedded content.
Here’s how it works:
- In the WordPress editor , you paste a URL on its own line.
- WordPress checks whether that URL belongs to a site that supports oEmbed.
- Behind the scenes, WordPress sends a request to the URL’s oEmbed endpoint.
- Using that response, WordPress replaces the plain URL with a rich embed block
That’s why pasting a link from your own WP site or another WP site usually works instantly.
Mu-Plugin to Embed Posts
I didn’t want to use that, so I asked one of my buddies to create a mu-plugin that does that for me.
Here it is
<?php
/**
* Plugin Name: Display Post Embeds
* Description: Display Posts using shortcodes
* Version: 6.4
* Author: TicoLibre
*
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Default subfolder within uploads directory
define('DPE_DEFAULT_SUBFOLDER', 'dev-guides');
// Default excerpt length for meta descriptions
define('DPE_DEFAULT_EXCERPT_LENGTH', 30);
/**
* Add settings page to WordPress admin
*/
function dpe_add_settings_page() {
add_options_page(
'Display Posts Settings',
'Display Posts',
'manage_options',
'display-posts',
'dpe_settings_page'
);
}
add_action( 'admin_menu', 'dpe_add_settings_page' );
/**
* Register settings
*/
function dpe_register_settings() {
register_setting(
'dpe_settings',
'dpe_images_subfolder',
array(
'sanitize_callback' => 'dpe_sanitize_subfolder',
'default' => DPE_DEFAULT_SUBFOLDER,
)
);
register_setting(
'dpe_settings',
'dpe_custom_title_field',
array(
'sanitize_callback' => 'sanitize_text_field',
)
);
register_setting(
'dpe_settings',
'dpe_custom_meta_field',
array(
'sanitize_callback' => 'sanitize_text_field',
)
);
register_setting(
'dpe_settings',
'dpe_excerpt_length',
array(
'sanitize_callback' => 'absint',
'default' => DPE_DEFAULT_EXCERPT_LENGTH,
)
);
}
add_action( 'admin_init', 'dpe_register_settings' );
/**
* Sanitize subfolder path
*
* @param string $path Subfolder path to sanitize.
* @return string Sanitized path.
*/
function dpe_sanitize_subfolder( $path ) {
$path = sanitize_text_field( $path );
// Block absolute paths
if ( ! empty( $path ) && substr( $path, 0, 1 ) === '/' ) {
add_settings_error(
'dpe_images_subfolder',
'absolute_path',
'Path cannot be absolute (starting with /) for security reasons.',
'error'
);
return DPE_DEFAULT_SUBFOLDER;
}
// Block Windows drive letters
if ( preg_match( '/^[a-zA-Z]:/', $path ) ) {
add_settings_error(
'dpe_images_subfolder',
'drive_letter',
'Path cannot contain drive letters for security reasons.',
'error'
);
return DPE_DEFAULT_SUBFOLDER;
}
// Block path traversal attempts
if ( strpos( $path, '..' ) !== false ) {
add_settings_error(
'dpe_images_subfolder',
'path_traversal',
'Path cannot contain ".." for security reasons.',
'error'
);
return DPE_DEFAULT_SUBFOLDER;
}
// Normalize slashes
$path = str_replace( '\\', '/', $path );
// Remove multiple consecutive slashes
$path = preg_replace( '#/+#', '/', $path );
// Only allow alphanumeric, underscore, hyphen, and forward slash
$path = preg_replace( '/[^a-zA-Z0-9\/_-]/', '', $path );
// Remove leading/trailing slashes
$path = trim( $path, '/' );
// Return default if empty after sanitization
if ( empty( $path ) ) {
return DPE_DEFAULT_SUBFOLDER;
}
return $path;
}
/**
* Get images directory info using WordPress upload_dir
*
* @return array|false Array with 'path' and 'url' keys, or false on failure.
*/
function dpe_get_images_location() {
$upload_dir = wp_upload_dir();
$subfolder = get_option( 'dpe_images_subfolder', DPE_DEFAULT_SUBFOLDER );
if ( $upload_dir['error'] ) {
if ( WP_DEBUG ) {
error_log( 'Display Post Embeds: wp_upload_dir error: ' . $upload_dir['error'] );
}
return false;
}
$images_path = trailingslashit( $upload_dir['basedir'] ) . $subfolder;
$images_url = trailingslashit( $upload_dir['baseurl'] ) . $subfolder;
// Validate that path is within uploads directory using string normalization
$normalized_images = wp_normalize_path( $images_path );
$normalized_uploads = wp_normalize_path( $upload_dir['basedir'] );
if ( strpos( $normalized_images, $normalized_uploads ) !== 0 ) {
if ( WP_DEBUG ) {
error_log( 'Display Post Embeds: Images path outside uploads directory' );
}
return false;
}
// Additional realpath check if directory exists (with trailing slash to prevent false positives)
if ( is_dir( $images_path ) ) {
$real_images_path = realpath( $images_path );
$real_upload_path = realpath( $upload_dir['basedir'] );
if ( $real_images_path && $real_upload_path ) {
// FIXED: Add trailing slash to prevent /uploads matching /uploads2
$real_upload_path_slash = trailingslashit( $real_upload_path );
$real_images_path_slash = trailingslashit( $real_images_path );
if ( strpos( $real_images_path_slash, $real_upload_path_slash ) !== 0 ) {
if ( WP_DEBUG ) {
error_log( 'Display Post Embeds: Images path outside uploads directory (realpath check)' );
}
return false;
}
}
}
return array(
'path' => trailingslashit( $images_path ),
'url' => trailingslashit( $images_url ),
);
}
/**
* Settings page HTML
*/
function dpe_settings_page() {
// Capability check
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'You do not have sufficient permissions to access this page.' );
}
// Handle cache clearing with proper capability check
if ( isset( $_POST['dpe_clear_cache'] ) &&
check_admin_referer( 'dpe_clear_cache_nonce' ) &&
current_user_can( 'manage_options' ) ) {
dpe_clear_image_cache();
echo '<div class="notice notice-success is-dismissible"><p><strong>Image cache cleared successfully!</strong></p></div>';
}
$location = dpe_get_images_location();
$upload_dir = wp_upload_dir();
?>
<div class="wrap">
<h1>Display Posts Settings</h1>
<div class="notice notice-info">
<p>
<strong>Note:</strong>
Images are stored in your WordPress uploads directory: <code><?php echo esc_html( $upload_dir['basedir'] ); ?></code>
</p>
</div>
<form method="post" action="options.php">
<?php
settings_fields( 'dpe_settings' );
settings_errors( 'dpe_images_subfolder' );
?>
<table class="form-table">
<tr valign="top">
<th scope="row">Images Subfolder</th>
<td>
<input type="text"
name="dpe_images_subfolder"
value="<?php echo esc_attr( get_option( 'dpe_images_subfolder', DPE_DEFAULT_SUBFOLDER ) ); ?>"
class="regular-text"
placeholder="<?php echo esc_attr( DPE_DEFAULT_SUBFOLDER ); ?>" />
<p class="description">
Subfolder within uploads directory (e.g., dev-guides or images/dev-guides)
</p>
</td>
</tr>
<tr valign="top">
<th scope="row">Custom Title Field</th>
<td>
<input type="text"
name="dpe_custom_title_field"
value="<?php echo esc_attr( get_option( 'dpe_custom_title_field', '' ) ); ?>"
class="regular-text"
placeholder="custom_title" />
<p class="description">
Custom field name for title (leave empty to use post title)
</p>
</td>
</tr>
<tr valign="top">
<th scope="row">Custom Meta Description Field</th>
<td>
<input type="text"
name="dpe_custom_meta_field"
value="<?php echo esc_attr( get_option( 'dpe_custom_meta_field', '' ) ); ?>"
class="regular-text"
placeholder="meta_description" />
<p class="description">
Custom field name for meta description (fallback: excerpt → first N words)
</p>
</td>
</tr>
<tr valign="top">
<th scope="row">Excerpt Length</th>
<td>
<input type="number"
name="dpe_excerpt_length"
value="<?php echo esc_attr( get_option( 'dpe_excerpt_length', DPE_DEFAULT_EXCERPT_LENGTH ) ); ?>"
class="small-text"
min="10"
max="100" />
<p class="description">
Number of words for auto-generated excerpts (default: <?php echo DPE_DEFAULT_EXCERPT_LENGTH; ?>)
</p>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
<hr>
<h2>Cache Management</h2>
<p>The plugin caches image lists daily for performance. If you've added/removed images via FTP and they're not appearing, clear the cache:</p>
<form method="post" style="margin-bottom: 20px;">
<?php wp_nonce_field( 'dpe_clear_cache_nonce' ); ?>
<input type="submit" name="dpe_clear_cache" class="button button-secondary" value="Clear All Image Caches" />
<span class="description" style="margin-left: 10px;">Removes all cached image data and old transients</span>
</form>
<?php
// Show cache status
$cache_count = dpe_count_active_caches();
?>
<div class="notice notice-info inline" style="margin: 0; padding: 10px;">
<p style="margin: 0;">
<strong>Cache Status:</strong>
<?php echo absint( $cache_count ); ?> active cache <?php echo $cache_count === 1 ? 'entry' : 'entries'; ?>
</p>
</div>
<hr>
<h2>Current Configuration</h2>
<?php if ( $location ) : ?>
<table class="widefat">
<tr>
<td><strong>Uploads Base Directory:</strong></td>
<td><code><?php echo esc_html( $upload_dir['basedir'] ); ?></code></td>
</tr>
<tr>
<td><strong>Images Subfolder:</strong></td>
<td><code><?php echo esc_html( get_option( 'dpe_images_subfolder', DPE_DEFAULT_SUBFOLDER ) ); ?></code></td>
</tr>
<tr>
<td><strong>Full Images URL:</strong></td>
<td><code><?php echo esc_url( $location['url'] ); ?></code></td>
</tr>
<tr>
<td><strong>Directory Status:</strong></td>
<td>
<?php
if ( is_dir( $location['path'] ) ) {
$file_count = dpe_count_images_in_directory( $location['path'] );
echo '<span style="color: green;">✓ Directory exists (' . absint( $file_count ) . ' images found)</span>';
} else {
echo '<span style="color: orange;">⚠ Directory does not exist. Create it manually at: <code>' . esc_html( $location['path'] ) . '</code></span>';
}
?>
</td>
</tr>
</table>
<?php else : ?>
<div class="notice notice-error">
<p><strong>Error:</strong> Unable to determine images location. Please check your WordPress uploads directory configuration.</p>
</div>
<?php endif; ?>
<hr>
<h2>Usage</h2>
<p>Use the shortcode: <code>[display_post id="123"]</code></p>
<p>Replace 123 with the actual post ID you want to display.</p>
<p><strong>Note:</strong> The plugin displays a random decorative image from your configured folder along with the post information.</p>
</div>
<?php
}
/**
* Count active image caches
*
* @return int Number of active cache entries.
*/
function dpe_count_active_caches() {
global $wpdb;
// FIXED: More precise - only count transients, exclude their timeout pairs
$count = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->options}
WHERE option_name LIKE %s
AND option_name NOT LIKE %s",
$wpdb->esc_like( '_transient_dpe_image_files_' ) . '%',
$wpdb->esc_like( '_transient_timeout_dpe_image_files_' ) . '%'
)
);
return absint( $count );
}
/**
* Count images in a directory
*
* @param string $path Directory path.
* @return int Number of image files found.
*/
function dpe_count_images_in_directory( $path ) {
$files = array();
foreach ( array( 'jpg', 'jpeg', 'png', 'gif', 'webp' ) as $ext ) {
$matches = glob( $path . "*.$ext" );
if ( $matches !== false ) {
$files = array_merge( $files, $matches );
} elseif ( WP_DEBUG ) {
error_log( "Display Post Embeds: glob() failed for extension $ext in path: $path" );
}
}
return count( $files );
}
/**
* Validate image filename
*
* @param string $filename Filename to validate.
* @return bool True if valid, false otherwise.
*/
function dpe_validate_image_filename( $filename ) {
// Remove any null bytes
$filename = str_replace( chr(0), '', $filename );
// Check for path traversal attempts
if ( strpos( $filename, '..' ) !== false ||
strpos( $filename, '/' ) !== false ||
strpos( $filename, '\\' ) !== false ) {
return false;
}
// Validate file extension
$allowed_extensions = array( 'jpg', 'jpeg', 'png', 'gif', 'webp' );
$file_extension = strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) );
if ( ! in_array( $file_extension, $allowed_extensions, true ) ) {
return false;
}
return true;
}
/**
* Get all image files from directory with caching
*
* @param string $images_path Full path to images directory.
* @return array|false Array of file paths or false on failure.
*/
function dpe_get_cached_image_files( $images_path ) {
// Date-based cache key - auto-expires daily
$transient_key = 'dpe_image_files_' . md5( $images_path . date('Y-m-d') );
// Try to get from cache
$files = get_transient( $transient_key );
if ( false !== $files ) {
return $files;
}
// Use cache locking to prevent race condition at midnight
$lock_key = 'dpe_building_cache_' . md5( $images_path );
// If another process is building the cache, wait briefly then try again
if ( get_transient( $lock_key ) ) {
// Wait 100ms and try to get cache again
usleep( 100000 );
$files = get_transient( $transient_key );
if ( false !== $files ) {
return $files;
}
}
// Set lock (5 second expiry in case of failure)
set_transient( $lock_key, 1, 5 );
// Build the file list
$files = array();
foreach ( array( 'jpg', 'jpeg', 'png', 'gif', 'webp' ) as $ext ) {
$matches = glob( $images_path . "*.$ext" );
if ( false === $matches ) {
if ( WP_DEBUG ) {
error_log( "Display Post Embeds: glob() failed for extension $ext in path: $images_path" );
}
continue;
}
$files = array_merge( $files, $matches );
}
// Cache for 24 hours
set_transient( $transient_key, $files, DAY_IN_SECONDS );
// Release lock
delete_transient( $lock_key );
return $files;
}
/**
* Get a random image from directory
*
* @return string|false Image URL or false on failure.
*/
function dpe_get_random_image() {
$location = dpe_get_images_location();
if ( ! $location ) {
return false;
}
// Check if directory exists
if ( ! is_dir( $location['path'] ) ) {
return false;
}
// Get cached file list
$files = dpe_get_cached_image_files( $location['path'] );
if ( empty( $files ) || ! is_array( $files ) ) {
return false;
}
// Get random file
$random_file = $files[ array_rand( $files ) ];
$filename = basename( $random_file );
// Validate filename for additional security
if ( ! dpe_validate_image_filename( $filename ) ) {
if ( WP_DEBUG ) {
error_log( 'Display Post Embeds: Invalid filename detected: ' . $filename );
}
return false;
}
// FIXED: Encode filename for URL safety (handles spaces, special chars)
return $location['url'] . rawurlencode( $filename );
}
/**
* Clear image cache
*/
function dpe_clear_image_cache() {
global $wpdb;
// FIXED: Wildcards OUTSIDE esc_like() to work properly
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
$wpdb->esc_like( '_transient_dpe_image_files_' ) . '%',
$wpdb->esc_like( '_transient_timeout_dpe_image_files_' ) . '%'
)
);
// Clear lock transients too
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
$wpdb->esc_like( '_transient_dpe_building_cache_' ) . '%',
$wpdb->esc_like( '_transient_timeout_dpe_building_cache_' ) . '%'
)
);
// Also clear from object cache if present
$location = dpe_get_images_location();
if ( $location ) {
// Clear today's cache from object cache
$transient_key = 'dpe_image_files_' . md5( $location['path'] . date('Y-m-d') );
wp_cache_delete( $transient_key, 'transient' );
$lock_key = 'dpe_building_cache_' . md5( $location['path'] );
wp_cache_delete( $lock_key, 'transient' );
}
}
add_action( 'update_option_dpe_images_subfolder', 'dpe_clear_image_cache' );
// Clear cache on attachment changes (for WordPress-uploaded images)
add_action( 'add_attachment', 'dpe_clear_image_cache' );
add_action( 'delete_attachment', 'dpe_clear_image_cache' );
/**
* Check if a post is publicly viewable (with backward compatibility)
*
* @param int|WP_Post|null $post Post ID or post object.
* @return bool True if publicly viewable, false otherwise.
*/
function dpe_is_post_publicly_viewable( $post = null ) {
// FIXED: Use native function if available (WP 5.7+)
if ( function_exists( 'is_post_publicly_viewable' ) ) {
return is_post_publicly_viewable( $post );
}
// Fallback for older WordPress versions
$post = get_post( $post );
if ( ! $post ) {
return false;
}
$post_type = get_post_type( $post );
$post_status = get_post_status( $post );
// Check if post type is viewable
$post_type_obj = get_post_type_object( $post_type );
if ( ! $post_type_obj ) {
return false;
}
$is_viewable_type = $post_type_obj->publicly_queryable ||
( $post_type_obj->_builtin && $post_type_obj->public );
if ( ! $is_viewable_type ) {
return false;
}
// Check if post status is viewable (publish, custom public statuses)
// Simple check: 'publish' is always viewable, others generally aren't
// This is a simplified fallback - native function is better
return ( 'publish' === $post_status );
}
/**
* Main shortcode function
*
* @param array $atts Shortcode attributes.
* @return string HTML output or empty string if post not found/invalid.
*/
function dpe_display_embed_post( $atts ) {
$atts = shortcode_atts(
array(
'id' => '',
),
$atts,
'display_post'
);
$post_id = intval( $atts['id'] );
if ( empty( $post_id ) ) {
return '';
}
$post = get_post( $post_id );
// Check if post exists and is publicly viewable (with backward compatibility)
if ( ! $post || ! dpe_is_post_publicly_viewable( $post ) ) {
return '';
}
// Get custom field names from settings
$custom_title_field = get_option( 'dpe_custom_title_field', '' );
$custom_meta_field = get_option( 'dpe_custom_meta_field', '' );
$excerpt_length = absint( get_option( 'dpe_excerpt_length', DPE_DEFAULT_EXCERPT_LENGTH ) );
// Get title - try custom field first, fallback to post title
$title = '';
if ( ! empty( $custom_title_field ) ) {
$title = get_post_meta( $post_id, $custom_title_field, true );
}
if ( empty( $title ) ) {
$title = get_the_title( $post_id );
}
// Get meta description - try custom field → excerpt → first N words
$meta_description = '';
if ( ! empty( $custom_meta_field ) ) {
$meta_description = get_post_meta( $post_id, $custom_meta_field, true );
}
if ( empty( $meta_description ) && ! empty( $post->post_excerpt ) ) {
$meta_description = wp_strip_all_tags( $post->post_excerpt );
}
if ( empty( $meta_description ) ) {
$meta_description = wp_trim_words( wp_strip_all_tags( $post->post_content ), $excerpt_length );
}
// Get random image from configured folder (primary source)
$image = dpe_get_random_image();
$is_random_image = (bool) $image;
// Fallback to featured image if no random image available
if ( ! $image ) {
$image = get_the_post_thumbnail_url( $post_id, 'medium_large' );
}
// Don't display if no image is available
if ( ! $image ) {
return '';
}
$permalink = get_permalink( $post_id );
// Create appropriate alt text
// Random decorative images get empty alt (per WCAG guidelines for decorative images)
// Featured images get descriptive alt
if ( $is_random_image ) {
$alt_text = ''; // Decorative image - empty alt is correct
} else {
$alt_text = sprintf( 'Featured image for %s', $title );
}
ob_start();
?>
<div class="not-prose my-10 group">
<a href="<?php echo esc_url( $permalink ); ?>"
class="flex flex-col md:flex-row items-stretch bg-surface-800/40 rounded-2xl overflow-hidden border border-slate-700/50 transition-all duration-300 hover:border-brand-500/50 hover:bg-surface-800/80 shadow-xl shadow-black/20 md:h-[230px]">
<div class="h-[160px] md:h-full md:w-[300px] shrink-0 overflow-hidden bg-surface-900">
<img src="<?php echo esc_url( $image ); ?>"
alt="<?php echo esc_attr( $alt_text ); ?>"
loading="lazy"
class="h-full w-full object-cover object-center opacity-80 group-hover:opacity-100 group-hover:scale-105 transition-all duration-500">
</div>
<div class="flex-1 p-6 flex flex-col justify-between">
<div>
<div class="flex items-center gap-2 mb-1">
<span class="font-mono text-[10px] font-bold uppercase tracking-widest text-brand-400 bg-brand-500/10 px-2 py-0.5 rounded">
Recommended Read
</span>
<div class="h-px flex-1 bg-slate-700/30"></div>
</div>
<p class="!mt-5 !font-mono !text-[15px] !font-bold !uppercase !tracking-widest !text-white !mb-5 group-hover:!text-brand-400 !transition-colors !leading-tight !line-clamp-2">
<?php echo esc_html( $title ); ?>
</p>
<p class="!font-mono text-slate-400 text-sm leading-relaxed line-clamp-3 md:line-clamp-3">
<?php echo esc_html( $meta_description ); ?>
</p>
</div>
</div>
</a>
</div>
<?php
return ob_get_clean();
}
add_shortcode( 'display_post', 'dpe_display_embed_post' );
How to Display the Post
All I have to do in my WordPress editor is create a shortcode and find the ID of the post I want to display.
This is an example of how the content within the shortcode block should be written:
[display_post id="61081"]
About the Title
Order of priority:
- Custom Title field (only if you configured one and filled it in)
- Normal WordPress post title
This is helpful if you are using a SEO Plugin, you just have to find out where that plugin is storing the meta title information.
About the Description
Order of priority:
- Custom Meta Description field (only if you configured one and filled it in)
- Post excerpt
- First 30 words of the post content
This is helpful if you are using a SEO Plugin, you just have to find out where that plugin is storing the meta descriptions
About the Image
Order of priority:
- Uses the folder set in Settings → Display Posts → Images Folder Path and randomly selects an image
- If The folder does not exist, or The folder exists but has no valid images, The plugin uses the post’s featured image
- If No images are found in the folder and no featured image on the post, The shortcode outputs nothing
I like using the images folder path to retrieve the images since those images are better than the featured ones.
The styles
I know most people using WordPress don’t style their sites using Tailwind CSS.
If that’s the case, you can ask one of your buddies to strip the plugin from all Tailwind classes and use vanilla CSS instead.
If you want to give Tailwind CSS a try, here it is a post to learn how to integrate that to WordPress.