0%

正威網路行銷工作室,專注於基隆、台北與全台客製化網頁設計,提供專業 RWD 響應式、SEO 搜尋引擎優化、品牌形象設計與網站維護託管服務。

搜尋文章
聯絡資訊
地址基隆市仁二路 220 號 5 樓
追蹤我們

WordPress 在任何位置列出指定文章:WP_Query 寫法,外站用 REST API 讀文章

WordPress 在任何位置列出指定文章:WP_Query 寫法,外站用 REST API 讀文章 封面

公司網站常有這種需求:

  • 首頁放「最新消息」分類的最新 5 篇。
  • 服務介紹頁下面,列出同一類的案例文章。
  • 另一個網站(例如集團的另一個品牌站)也要顯示這裡的最新文章。

這篇從最簡單的方法開始:先看內建區塊做不做得到,再用 WP_Query 寫一個短代碼,最後說明外站怎麼用 REST API 讀文章。

先試試內建的區塊

區塊編輯器已經有兩個現成的區塊,很多情況不用寫程式:

  • 「最新文章」區塊:選分類、篇數,要不要顯示日期、摘要、精選圖片,勾一勾就好。
  • 「查詢迴圈」區塊:版面可以自己排(標題、日期、圖片的位置),適合區塊佈景主題的首頁和分類頁。

用傳統編輯器、頁面編輯器,或要放在佈景主題的版型檔裡,才需要下面的寫法。

寫一個短代碼:[zw_posts]

放在子佈景主題的 functions.php,或用 Code Snippets 這類外掛新增:

PHP
// 短代碼 [zw_posts category="最新消息" count="5"]:列出指定分類的最新文章
add_shortcode( 'zw_posts', function ( $atts ) {
	$atts = shortcode_atts( array(
		'category' => '',     // 分類的代稱;中文分類名稱直接寫也可以
		'count'    => 5,      // 幾篇
		'orderby'  => 'date', // date 發佈日、modified 更新日、rand 隨機
	), $atts );

	$query = new WP_Query( array(
		'post_type'           => 'post',
		'posts_per_page'      => min( 20, max( 1, absint( $atts['count'] ) ) ),
		'category_name'       => sanitize_title( $atts['category'] ),
		'orderby'             => in_array( $atts['orderby'], array( 'date', 'modified', 'rand' ), true ) ? $atts['orderby'] : 'date',
		'ignore_sticky_posts' => true,
		'no_found_rows'       => true,
	) );

	if ( ! $query->have_posts() ) {
		return '';
	}
	$html = '<ul class="zw-posts">';
	while ( $query->have_posts() ) {
		$query->the_post();
		$html .= '<li><a href="' . esc_url( get_permalink() ) . '">' . esc_html( get_the_title() ) . '</a>'
			. ' <time datetime="' . esc_attr( get_the_date( 'c' ) ) . '">' . esc_html( get_the_date() ) . '</time></li>';
	}
	wp_reset_postdata();
	return $html . '</ul>';
} );

在頁面放一個「短代碼」區塊,填入:

純文字
[zw_posts category="最新消息" count="5"]

就會列出「最新消息」分類最新的 5 篇文章,有標題連結和日期。

WP_Query 的幾個設定

  • category_name 填分類的代稱。中文分類名稱的代稱,WordPress 存的是一串編碼過的文字,用 sanitize_title() 轉一次,直接寫中文名稱也能對上。測試時用「最新消息」這個中文分類,正確撈到該分類的文章。
  • posts_per_page 篇數,程式裡限制最多 20 篇。測試時故意填 count="100",只列出 20 篇以內(那個分類只有 7 篇,就列 7 篇)。
  • orderby 只接受 date、modified、rand 三種,填其他東西一律用發佈日排序。短代碼是在編輯器裡填的,會編輯文章的人都能改,參數要先檢查過再交給查詢。
  • ignore_sticky_posts 設成 true,忽略「置頂文章」的特殊待遇。不加的話,沒有指定分類時,置頂文章會被額外塞進列表,而且不算在篇數裡。測試時要列最新 3 篇,結果多了一篇 2020 年的置頂文章,變成 4 篇;加了這行就正常了。
  • no_found_rows 設成 true,告訴資料庫不用計算總共有幾篇。這個列表不需要分頁,省下一次計算,比較快。

用完要 wp_reset_postdata()

$query->the_post() 會把全域的「目前這篇文章」換成列表裡的文章。迴圈結束後一定要呼叫 wp_reset_postdata() 換回來,不然這一頁後面的留言、分享按鈕、相關文章,都可能抓到列表最後那篇。

標題用 esc_html() 輸出

文章標題可能有 &、< 這類符號。用 esc_html() 輸出,顯示的是文字本身,不會被瀏覽器當成 HTML 執行。

另一個網站要顯示這裡的文章:REST API

每個 WordPress 網站都有內建的 REST API,用網址就能讀到文章資料:

純文字
https://你的網址/wp-json/wp/v2/posts?per_page=5&_fields=title,link,date
  • per_page=5:最新 5 篇。
  • _fields=title,link,date:只要標題、網址、日期,資料量小很多。
  • 只要某個分類,加上 &categories=分類編號。分類編號在後台「文章 → 分類」,滑鼠移到分類名稱上,網址列裡 tag_ID= 後面的數字就是。

方法一:在對方網頁用 JavaScript 讀

對方網站不一定是 WordPress,放一段 HTML 就好:

HTML
<ul id="news-list"></ul>

<script>
// 換成對方 WordPress 網站的網址;per_page 是篇數
const api = 'https://example.com/wp-json/wp/v2/posts?per_page=5&_fields=title,link,date';

fetch(api)
  .then((res) => res.json())
  .then((posts) => {
    const list = document.getElementById('news-list');
    posts.forEach((post) => {
      const li = document.createElement('li');
      const a = document.createElement('a');
      a.href = post.link;
      // 標題可能有 &amp; 這類符號,先轉回一般文字
      a.textContent = new DOMParser().parseFromString(post.title.rendered, 'text/html').body.textContent;
      li.append(a, ' ', new Date(post.date).toLocaleDateString('zh-TW'));
      list.append(li);
    });
  })
  .catch(() => {
    document.getElementById('news-list').textContent = '最新消息暫時讀不到,請稍後再試。';
  });
</script>
  • 標題用 textContent 放進網頁,就算標題裡有奇怪的符號,也只會顯示成文字。DOMParser 那行是把 &amp; 這類符號轉回一般字元。
  • 跨網域可以直接讀。WordPress 的 REST API 會回應允許其他網域讀取的標頭。我們另外開了一個不同網址的網頁測試,成功讀到 5 篇文章的標題和日期。
  • 對方網站連不上時,顯示「最新消息暫時讀不到」,不會整塊空白。

缺點是文章列表是瀏覽器載入後才出現,搜尋引擎不一定看得到這些連結。

方法二:對方也是 WordPress,用 PHP 讀並快取

PHP
// 短代碼 [zw_remote_posts]:讀另一個 WordPress 網站的最新文章,存一小時快取
add_shortcode( 'zw_remote_posts', function () {
	$api   = 'https://example.com/wp-json/wp/v2/posts?per_page=5&_fields=title,link,date'; // 換成對方網站的網址
	$posts = get_transient( 'zw_remote_posts' );

	if ( false === $posts ) {
		$response = wp_remote_get( $api, array( 'timeout' => 5 ) );
		$posts    = array();
		if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
			$posts = (array) json_decode( wp_remote_retrieve_body( $response ), true );
		}
		// 讀不到也存起來,10 分鐘後再試,不要每個訪客都去等對方網站
		set_transient( 'zw_remote_posts', $posts, $posts ? HOUR_IN_SECONDS : 10 * MINUTE_IN_SECONDS );
	}

	if ( ! $posts ) {
		return '';
	}
	$html = '<ul class="zw-remote-posts">';
	foreach ( $posts as $post ) {
		$title = wp_strip_all_tags( html_entity_decode( $post['title']['rendered'] ?? '', ENT_QUOTES, 'UTF-8' ) );
		$html .= '<li><a href="' . esc_url( $post['link'] ?? '' ) . '">' . esc_html( $title ) . '</a></li>';
	}
	return $html . '</ul>';
} );
  • wp_remote_get() 是 WordPress 內建的連線函式,timeout 設 5 秒,對方網站很慢時不會把自己的網站拖住。
  • 讀到的資料存一小時(set_transient)。一小時內不管多少人來看,都只去對方網站讀一次。
  • 讀不到也存 10 分鐘。對方網站掛掉時,不會每個訪客都卡 5 秒等它。
  • 列表是在伺服器上組好的,搜尋引擎看得到這些連結。

測試結果:正常時列出對方網站最新 5 篇,標題裡的 & 正確顯示、HTML 標籤被拿掉;把網址改成連不上的位置,短代碼什麼都不顯示,頁面其他內容照常。

不想寫程式:RSS 區塊

只要顯示另一個網站的最新文章標題,區塊編輯器的「RSS」區塊填入對方的 RSS 網址(WordPress 網站是 網址/feed/)就可以,也有內建快取。只是版面能調整的地方比較少。

延伸閱讀

網站內容放對位置,客人才看得到。正威網路行銷是 25 年經驗的基隆網頁設計工作室,首頁消息、案例列表這類版面都會依你的內容規劃,台北與全台客戶也歡迎來聊聊。

分享這篇文章

返回文章列表更多「Wordpress代碼教學」文章

發表留言

還沒有人留言,歡迎第一個分享你的想法或問題。留言經審核後才會顯示,Email 不會公開。

讓我們一起打造
您的專屬網站!