WordPressで外部サイトや内部ページへのリンクを紹介する際、単純なテキストリンクよりも視覚的に魅力的なリンクボックス(リンクカード)を表示したいことがありますよね。

この記事では、URLを指定するだけで自動的にタイトル・サムネイル・抜粋文を取得し、美しいリンクボックスを生成するWordPressショートコードの作成方法を解説します。

完成イメージ

ショートコード [linkbox href="https://example.com/"] を記述するだけで、上記のようなリンクボックスが自動生成されます。

特徴

  • 自動メタデータ取得: URLからタイトル・説明文・OGP画像を自動取得
  • パフォーマンス最適化: 投稿保存時に一度だけ変換、表示時は高速
  • target属性対応: 内部リンク・外部リンクの使い分けが可能
  • レスポンシブ対応: モバイルでも美しく表示
  • WordPressライク: デフォルトテーマに馴染むデザイン

実装方法

1. PHPコードの実装

以下のコードを functions.php に追加します。

<?php
//投稿保存時にショートコードをHTMLに変換する機能

//投稿保存時のフック
add_action('save_post', 'convert_linkbox_shortcodes_to_html', 10, 3);

function convert_linkbox_shortcodes_to_html($post_id, $post, $update) {
    //自動保存やリビジョンを除外
    if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
        return;
    }
    
    //投稿タイプを限定(必要に応じて調整)
    if (!in_array($post->post_type, array('post', 'page'))) {
        return;
    }
    
    //無限ループを防ぐためのフラグ
    if (get_transient('linkbox_processing_' . $post_id)) {
        return;
    }
    
    $content = $post->post_content;
    
    //linkboxショートコードが含まれているかチェック
    if (strpos($content, '[linkbox') === false) {
        return;
    }
    
    //処理中フラグを設定(30秒)
    set_transient('linkbox_processing_' . $post_id, true, 30);
    
    //ショートコードを検索して変換(target属性も考慮)
    $updated_content = preg_replace_callback(
        '/[linkboxs+href=["']([^"']+)["'](?:s+target=["']([^"']+)["'"])?s*]/',
        'convert_single_linkbox_to_html',
        $content
    );
    
    //内容が変更された場合のみ更新
    if ($updated_content !== $content) {
        //無限ループを防ぐため、フックを一時的に削除
        remove_action('save_post', 'convert_linkbox_shortcodes_to_html', 10);
        
        wp_update_post(array(
            'ID' => $post_id,
            'post_content' => $updated_content
        ));
        
        //フックを再追加
        add_action('save_post', 'convert_linkbox_shortcodes_to_html', 10, 3);
    }
    
    //処理中フラグを削除
    delete_transient('linkbox_processing_' . $post_id);
}

//個別のlinkboxショートコードをHTMLに変換
function convert_single_linkbox_to_html($matches) {
    $url = trim($matches[1]);
    $target = isset($matches[2]) ? trim($matches[2]) : ''; //target属性を取得
    
    //URLを検証
    if (!filter_var($url, FILTER_VALIDATE_URL)) {
        return '<p>無効なURLです: ' . esc_html($url) . '</p>';
    }
    
    //メタデータを取得
    $metadata = get_url_metadata_for_conversion($url);
    
    if (!$metadata) {
        return '<p>URLの情報を取得できませんでした: ' . esc_html($url) . '</p>';
    }
    
    //target属性の処理
    $target_attr = '';
    if ($target === '_blank') {
        $target_attr = ' target="_blank"';
    }
    
    //HTMLを生成
    $html = sprintf(
        '<a href="%s" class="linkbox my-1.5r"%s>
<strong class="linkbox-title">%s</strong>
%s
<span class="linkbox-excerpt">%s
<u class="linkbox-more">続きを読む</u>
</span>
</a>',
        esc_url($url),
        $target_attr, //target属性を条件付きで追加
        esc_html($metadata['title']),
        $metadata['image'] ? sprintf(
            '<picture class="linkbox-thumbnail">
<img width="300" height="200" src="%s?ver=20250901110912" alt="%s" loading="lazy" />
</picture>',
            esc_url($metadata['image']),
            esc_attr($metadata['title'])
        ) : '',
        esc_html($metadata['description'])
    );
    
    return $html;
}

//変換用のメタデータ取得関数(保存時専用)
function get_url_metadata_for_conversion($url) {
    //タイムアウトを長めに設定(保存時なので多少時間をかけても良い)
    $response = wp_remote_get($url, array(
        'timeout' => 30,
        'user-agent' => 'Mozilla/5.0 (compatible; WordPressBot/1.0)',
        'headers' => array(
            'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        ),
        'redirection' => 5,
        'sslverify' => false
    ));

    if (is_wp_error($response)) {
        return false;
    }

    $status_code = wp_remote_retrieve_response_code($response);
    if ($status_code !== 200) {
        return false;
    }

    $html = wp_remote_retrieve_body($response);

    if (empty($html)) {
        return false;
    }

    //HTMLをパース
    $dom = new DOMDocument();
    libxml_use_internal_errors(true);
    
    $html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
    
    if (!$dom->loadHTML($html)) {
        libxml_clear_errors();
        return false;
    }
    
    libxml_clear_errors();
    $xpath = new DOMXPath($dom);

    $metadata = array(
        'title' => '',
        'description' => '',
        'image' => ''
    );

    //タイトルを取得
    $og_title = $xpath->query('//meta[@property="og:title"]/@content');
    if ($og_title->length > 0) {
        $metadata['title'] = trim($og_title->item(0)->nodeValue);
    } else {
        $title_tag = $xpath->query('//title');
        if ($title_tag->length > 0) {
            $metadata['title'] = trim($title_tag->item(0)->nodeValue);
        }
    }

    //説明文を取得
    $og_description = $xpath->query('//meta[@property="og:description"]/@content');
    if ($og_description->length > 0) {
        $metadata['description'] = trim($og_description->item(0)->nodeValue);
    } else {
        $meta_description = $xpath->query('//meta[@name="description"]/@content');
        if ($meta_description->length > 0) {
            $metadata['description'] = trim($meta_description->item(0)->nodeValue);
        }
    }

    //画像を取得
    $og_image = $xpath->query('//meta[@property="og:image"]/@content');
    if ($og_image->length > 0) {
        $image_url = trim($og_image->item(0)->nodeValue);
        if (strpos($image_url, 'http') !== 0) {
            $parsed_url = parse_url($url);
            if ($parsed_url && isset($parsed_url['scheme']) && isset($parsed_url['host'])) {
                $base_url = $parsed_url['scheme'] . '://' . $parsed_url['host'];
                if (strpos($image_url, '/') === 0) {
                    $image_url = $base_url . $image_url;
                } else {
                    $image_url = $base_url . '/' . $image_url;
                }
            }
        }
        $metadata['image'] = $image_url;
    }

    //説明文の長さを制限
    if (mb_strlen($metadata['description']) > 130) {
        $metadata['description'] = mb_substr($metadata['description'], 0, 130) . '...';
    }

    return $metadata;
}

//下書き時のプレビュー機能
function shortcode_linkbox_preview($atts, $content = '') {
    $atts = shortcode_atts(array(
        'href' => '',
        'target' => '',
    ), $atts);

    $url = trim($atts['href']);
    $target = trim($atts['target']);

    if (empty($url)) {
        return '';
    }

    if (!filter_var($url, FILTER_VALIDATE_URL)) {
        return '<p>無効なURLです: ' . esc_html($url) . '</p>';
    }

    $target_display = $target === '_blank' ? ' (新しいタブで開く)' : ' (同じタブで開く)';

    return '<div style="border: 2px dashed #ccc; padding: 10px; margin: 10px 0;">
        <strong>Linkboxプレビュー:</strong> ' . esc_html($url) . $target_display . '<br>
        <small>※保存時に実際のリンクボックスに変換されます</small>
    </div>';
}

add_shortcode('linkbox', 'shortcode_linkbox_preview');
?>

2. CSSスタイルの追加

WordPressテーマのCSSファイルまたは管理画面の「外観」→「カスタマイズ」→「追加CSS」に以下を追加します。

/* .linkbox */
.linkbox {
    display: flow-root;
    padding: 1.5rem;
    line-height: 1.55;
    font-size: clamp(0.8125rem, calc(0.8125rem + ((1vw - 0.225rem) * 0.1563)), 0.875rem);
    font-weight: 400;
    border: 1px solid hsl(223, 6%, 84%);
    box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
    background: hsl(223, 6%, 100%);
    color: hsl(223, 6%, 53%);
    text-decoration: none;
    transition: all 0.3s ease;
}

.linkbox:hover {
    border-color: #007cba;
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    text-decoration: none;
}

.linkbox .linkbox-thumbnail {
    float: left;
    max-width: calc(160px + 1.25rem);
    /* padding: 上  右  下  左 */
    padding: calc(0.5lh - 0.5em) 1.25rem 0.08lh 0pc;
}

.linkbox .linkbox-thumbnail img {
    width: 100%;
    height: 100%;
    /* アスペクト比(横 / 縦) */
    aspect-ratio: 3 / 2;
    border-radius: 4px;
}

.linkbox .linkbox-title {
    line-height: 1.33;
    font-size: clamp(1rem, calc(1rem + ((1vw - 0.225rem) * 0.4688)), 1.1875rem);
    font-weight: 600;
    color: #1F2023;
    margin-bottom: 1rem;
}

.linkbox .linkbox-excerpt {
    margin: 0;
}

.linkbox .linkbox-more {
    color: hsl(223, 6%, 74%);
    text-decoration: underline;
}

/* レスポンシブ対応 */
@media (max-width: 600px) {
    .linkbox .linkbox-thumbnail {
        float: none;
        max-width: 100%;
        padding: 0 0 1rem 0;
    }
}

使用方法

基本的な使い方

URLの情報を取得できませんでした: https://example.com/page/

新しいタブで開く場合


external-site.com  - Situs web ini dijual! - external site Sumber daya dan Informasi.

Situs web ini dijual! external-site.com adalah sumber pertama dan terbaik Anda untuk semua informasi yang Anda cari. Mulai dari to...
続きを読む


動作の仕組み

1. 投稿保存時の自動変換

ショートコードは投稿保存時に自動的にHTMLに変換されます。これにより:

  • 高速表示: 表示時はHTTPリクエスト不要
  • 安定性: 外部サイトの状況に依存しない
  • SEO効果: 静的HTMLなので検索エンジンも認識

2. メタデータ取得の優先順位

  1. タイトル: og:title<title>タグ
  2. 説明文: og:descriptionmeta description
  3. 画像: og:image

3. エラーハンドリング

  • 無効なURL
  • HTTPエラー
  • メタデータ取得失敗
  • HTMLパースエラー

すべて適切に処理され、エラー時は代替表示されます。

カスタマイズ

説明文の文字数制限を変更

//130文字から200文字に変更
if (mb_strlen($metadata['description']) > 200) {
    $metadata['description'] = mb_substr($metadata['description'], 0, 200) . '...';
}

対象投稿タイプを追加

//カスタム投稿タイプも対象に含める
if (!in_array($post->post_type, array('post', 'page', 'custom_post_type'))) {
    return;
}

タイムアウト時間の調整

//15秒に短縮
'timeout' => 15,

まとめ

このショートコードを使用することで:

簡単導入: ショートコード1行でリンクボックス作成
高パフォーマンス: 保存時変換で高速表示
美しいデザイン: WordPressデフォルトに馴染むスタイル
柔軟性: 内部・外部リンクの使い分け対応
保守性: エラーハンドリングも万全

WordPressサイトでのリンク紹介がより魅力的になること間違いなしです!

参考


この記事が役に立ったら、いいねやストックをお願いします! 🙏