<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>MemberPress &#8211; 小豬日常</title>
	<atom:link href="https://piglife.tw/tag/memberpress/feed/" rel="self" type="application/rss+xml" />
	<link>https://piglife.tw</link>
	<description>Hello World，一個紀錄生活與學習的地方</description>
	<lastBuildDate>Sat, 20 Dec 2025 03:46:29 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9</generator>

<image>
	<url>https://piglife.tw/wp-content/uploads/2017/10/cropped-logo-1-32x32.png</url>
	<title>MemberPress &#8211; 小豬日常</title>
	<link>https://piglife.tw</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>使用 MemberPress 交易完成鉤子自動寄送訂閱確認信</title>
		<link>https://piglife.tw/technical-notes/memberpress-transaction-email/</link>
					<comments>https://piglife.tw/technical-notes/memberpress-transaction-email/#respond</comments>
		
		<dc:creator><![CDATA[小豬]]></dc:creator>
		<pubDate>Sat, 20 Dec 2025 03:46:29 +0000</pubDate>
				<category><![CDATA[技術筆記]]></category>
		<category><![CDATA[MemberPress]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[wp_mail]]></category>
		<category><![CDATA[交易完成]]></category>
		<category><![CDATA[自動寄信]]></category>
		<category><![CDATA[訂閱通知]]></category>
		<guid isPermaLink="false">https://piglife.tw/technical-notes/memberpress-transaction-email/</guid>

					<description><![CDATA[介紹如何利用 MemberPress 交易完成事件自動寄送訂閱方案付款成功的確認信，包含交易資料判斷...]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">前言</h2>
<p>這段程式碼示範如何在 WordPress 使用 MemberPress 交易完成事件鉤子，自動寄送訂閱方案的確認電子郵件。適合使用 MemberPress 管理會員訂閱，並希望自動化寄送付款成功通知的工程師或自學者。</p>
<h2 class="wp-block-heading">交易完成事件監聽</h2>
<p>透過 <code>add_action</code> 監聽 <code>mepr-event-transaction-completed</code> 事件，當交易完成時觸發自訂函式。函式內部先檢查事件與交易物件有效性，避免錯誤執行。</p>
<pre><code class="lang-php language-php php">add_action(&#039;mepr-event-transaction-completed&#039;, function($event) {
  if(!is_object($event)) return;

  $txn = $event-&gt;get_data();
  if(!is_object($txn)) return;
  // ...
});</code></pre>
<h2 class="wp-block-heading">交易資料擷取與條件判斷</h2>
<p>取得交易相關資訊如使用者 ID、方案 ID、交易 ID、付款狀態與金額，並做以下條件過濾：</p>
<ul>
<li>使用者、方案、交易 ID 必須有效</li>
<li>交易狀態必須為 <code>complete</code></li>
<li>可選擇只寄首次付款通知（此範例預設為每次付款皆寄）</li>
<li>防止重複寄送，透過交易文章 meta 標記</li>
</ul>
<p>這些判斷確保只有有效且未重複的交易會觸發郵件寄送。</p>
<h2 class="wp-block-heading">根據方案選擇郵件模板</h2>
<p>根據方案 ID（範例中為 626 月付方案與 639 年付方案）選擇對應的郵件主旨與 HTML 內容。郵件內容包含隱藏文字供郵件摘要使用，以及簡潔的訂閱資訊與操作連結。</p>
<pre><code class="lang-php language-php php">if($membership_id === 626) {
  $subject = $subject_626;
  $message = $message_626;
} elseif($membership_id === 639) {
  $subject = $subject_639;
  $message = $message_639;
} else {
  return;
}</code></pre>
<h2 class="wp-block-heading">寄送郵件與防重複標記</h2>
<p>使用 WordPress 內建的 <code>wp_mail</code> 函式寄送 HTML 格式郵件，並在成功寄出後，透過 <code>update_post_meta</code> 設定交易文章的自訂 meta，避免重複寄信。</p>
<pre><code class="lang-php language-php php">$headers = [&#039;Content-Type: text/html; charset=UTF-8&#039;];
$sent = wp_mail($user-&gt;user_email, $subject, $message, $headers);

if($sent) {
  update_post_meta($txn_id, &#039;_custom_plan_email_sent&#039;, &#039;yes&#039;);
}</code></pre>
<h2 class="wp-block-heading">實務應用與優化建議</h2>
<ul>
<li>可依照不同方案擴充更多模板，提升客製化體驗</li>
<li>建議搭配會員資料完整性檢查，確保郵件內容正確</li>
<li>若有多語系需求，可整合翻譯函式動態產生郵件內容</li>
<li>透過設定參數控制是否只寄首次付款通知，靈活調整行銷策略</li>
</ul>
<h2 class="wp-block-heading">常見問題與注意事項</h2>
<ul>
<li>確認 MemberPress 交易狀態名稱是否與程式碼一致，避免漏寄</li>
<li>郵件內容中圖片 URL 請替換成實際可用路徑</li>
<li>測試時注意交易狀態與交易金額，避免誤寄測試郵件</li>
</ul>
<h2 class="wp-block-heading">完整程式碼</h2>
<pre><code class="lang-php language-php php">&lt;?php
add_action(&#039;mepr-event-transaction-completed&#039;, function($event) {
  if(!is_object($event)) return;

  // true = 只寄首次；false = 每次扣款都寄
  $SEND_FIRST_PAYMENT_ONLY = false;

  $txn = $event-&gt;get_data();
  if(!is_object($txn)) return;

  // 交易基本資訊
  $user_id       = intval($txn-&gt;user_id ?? 0);
  $membership_id = intval($txn-&gt;product_id ?? 0);
  $txn_id        = intval($txn-&gt;id ?? 0); // MemberPress 交易文章 ID
  $total         = floatval($txn-&gt;total ?? 0);
  $status        = strval($txn-&gt;status ?? &#039;&#039;);

  if($user_id &lt;= 0 || $membership_id &lt;= 0 || $txn_id &lt;= 0) return;

  // 僅完成交易才寄（雙保險）
  if($status !== &#039;complete&#039;) return;

  // 避免 $0 交易（試用 / 全額折扣）也發信
//   if($total &lt;= 0) return;

  // 只寄首次付款？
  if($SEND_FIRST_PAYMENT_ONLY === true &amp;&amp; method_exists($txn,&#039;is_first_payment&#039;) &amp;&amp; !$txn-&gt;is_first_payment()) {
    return;
  }

  // 防重複寄送：用交易 ID 做旗標
  if(get_post_meta($txn_id, &#039;_custom_plan_email_sent&#039;, true) === &#039;yes&#039;) {
    return;
  }

  $user = get_user_by(&#039;id&#039;, $user_id);
  if(!$user || empty($user-&gt;user_email)) return;

  $product_name = get_the_title($membership_id);

  // ===== 月付 626 =====
  $subject_626 = &#039;感謝您購買 &#039;.$product_name;
  $message_626 = &lt;&lt;&lt;HTML
&lt;div style=&quot;display: none; overflow: hidden; line-height: 1px; opacity: 0; max-height: 0; max-width: 0;&quot;&gt;你已成功訂閱 。你的訂閱方案將會自動續訂：月付 $129，可隨時取消。&lt;/div&gt;
&lt;table style=&quot;width: 100%; background: #f5f6f8; margin: 0; padding: 0;&quot; role=&quot;presentation&quot;&gt;
&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;
&lt;table style=&quot;width: 600px; margin: 0 auto;&quot; role=&quot;presentation&quot;&gt;
&lt;tr&gt;&lt;td style=&quot;padding: 0px 24px; background: #ffffff;&quot; align=&quot;left&quot;&gt;
&lt;img src=&quot;&quot; width=&quot;200&quot; /&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;padding: 32px 24px; background: #ffffff; font-family: Helvetica, Arial, &#039;PingFang TC&#039;, &#039;Microsoft JhengHei&#039;, sans-serif; color: #111827;&quot;&gt;
&lt;table role=&quot;presentation&quot; width=&quot;100%&quot;&gt;
&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;6&quot;&gt;
&lt;div style=&quot;width: 3px; height: 22px; background: #39A297; border-radius: 2px; margin-top: 6px;&quot;&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;padding-left: 10px;&quot;&gt;
&lt;p style=&quot;font-size: 16px; line-height: 1.75; margin: 0;&quot;&gt;你已成功訂閱 &lt;strong&gt;&lt;/strong&gt; 。&lt;/p&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;

&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 16px; line-height: 1.75; margin: 0;&quot;&gt;你的訂閱方案將會自動續訂：&lt;strong&gt;月付 $129&lt;/strong&gt;&lt;br&gt;你可以隨時取消。&lt;/p&gt;

&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;a style=&quot;background: #39A297; border-radius: 8px; padding: 12px 18px; display: inline-block; color: #ffffff; text-decoration: none;&quot; href=&quot;https://.com/account/&quot; target=&quot;_blank&quot;&gt;點擊這裡登入&lt;/a&gt;

&lt;div style=&quot;height: 28px;&quot;&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 14px; color: #6b7280;&quot;&gt;如有任何問題，請於聯絡我們留言你的需求。&lt;/p&gt;
&lt;p style=&quot;font-size: 16px;&quot;&gt; 團隊&lt;/p&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
HTML;

  // ===== 年付 639 =====
  $subject_639 = &#039;感謝您購買 &#039;.$product_name;
  $message_639 = &lt;&lt;&lt;HTML
&lt;div style=&quot;display: none;overflow: hidden;line-height: 1px;opacity: 0;max-height: 0;max-width: 0&quot;&gt;你已成功訂閱 。你的訂閱方案將會自動續訂：年付 $1,290，可隨時取消。&lt;/div&gt;
&lt;table style=&quot;width: 100%;background: #f5f6f8;margin: 0;padding: 0&quot; role=&quot;presentation&quot;&gt;
&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;
&lt;table style=&quot;width: 600px;margin: 0 auto&quot; role=&quot;presentation&quot;&gt;
&lt;tr&gt;&lt;td style=&quot;padding: 0 24px;background: #ffffff&quot; align=&quot;left&quot;&gt;
&lt;img src=&quot;https://.com/wp-content/uploads/2025/10/forGoogleLogo.png&quot; width=&quot;200&quot; /&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;padding: 32px 24px;background: #ffffff;font-family: Helvetica, Arial, &#039;PingFang TC&#039;, &#039;Microsoft JhengHei&#039;, sans-serif;color: #111827&quot;&gt;
&lt;table role=&quot;presentation&quot; width=&quot;100%&quot;&gt;&lt;tr&gt;&lt;td valign=&quot;top&quot; width=&quot;6&quot;&gt;
&lt;div style=&quot;width: 3px;height: 22px;background: #16a34a;border-radius: 2px;margin-top: 6px&quot;&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;padding-left: 10px&quot;&gt;
&lt;p style=&quot;font-size: 16px;line-height: 1.75;margin: 0&quot;&gt;你已成功訂閱 &lt;strong&gt;&lt;/strong&gt; 。&lt;/p&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;

&lt;div style=&quot;height: 16px&quot;&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 16px;line-height: 1.75;margin: 0&quot;&gt;你的訂閱方案將會自動續訂：&lt;strong&gt;年付 $1,290&lt;/strong&gt;
&lt;br /&gt;你可以隨時取消。&lt;/p&gt;

&lt;div style=&quot;height: 16px&quot;&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 16px;line-height: 1.75;margin: 0&quot;&gt;別忘了到會員資訊裡留下你的收件資料，&lt;br /&gt;讓我們能夠把贈品正確地寄送到你手上。&lt;/p&gt;

&lt;div style=&quot;height: 16px&quot;&gt;&lt;/div&gt;
&lt;a style=&quot;background: #39A297;border-radius: 8px;padding: 12px 18px;display: inline-block;color: #ffffff;text-decoration: none&quot; href=&quot;#&quot; target=&quot;_blank&quot;&gt;設定你的收件資料&lt;/a&gt;

&lt;div style=&quot;height: 28px&quot;&gt;&lt;/div&gt;
&lt;p style=&quot;font-size: 14px;color: #6b7280;&quot;&gt;如有任何問題，請於聯絡我們留言你的需求。&lt;/p&gt;
&lt;p style=&quot;font-size: 16px;&quot;&gt; 團隊&lt;/p&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
HTML;

  // 套用對應模板
  if($membership_id === 626) {
    $subject = $subject_626;
    $message = $message_626;
  } elseif($membership_id === 639) {
    $subject = $subject_639;
    $message = $message_639;
  } else {
    return;
  }

  // 寄送
  $headers = [&#039;Content-Type: text/html; charset=UTF-8&#039;];
  $sent = wp_mail($user-&gt;user_email, $subject, $message, $headers);

  // 設定防重複旗標（只有寄成功才標記）
  if($sent) {
    update_post_meta($txn_id, &#039;_custom_plan_email_sent&#039;, &#039;yes&#039;);
  }
});</code></pre>]]></content:encoded>
					
					<wfw:commentRss>https://piglife.tw/technical-notes/memberpress-transaction-email/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>MemberPress 會員列表新增電話、地址與即時複製功能教學</title>
		<link>https://piglife.tw/technical-notes/memberpress-members-extra-columns/</link>
					<comments>https://piglife.tw/technical-notes/memberpress-members-extra-columns/#respond</comments>
		
		<dc:creator><![CDATA[小豬]]></dc:creator>
		<pubDate>Fri, 19 Dec 2025 05:29:55 +0000</pubDate>
				<category><![CDATA[技術筆記]]></category>
		<category><![CDATA[AJAX]]></category>
		<category><![CDATA[MemberPress]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[後台擴充]]></category>
		<category><![CDATA[會員管理]]></category>
		<category><![CDATA[複製功能]]></category>
		<guid isPermaLink="false">https://piglife.tw/technical-notes/memberpress-members-extra-columns/</guid>

					<description><![CDATA[介紹如何在 MemberPress 會員列表中新增電話、地址、贈品寄送狀態欄位，並實作即時切換與一鍵...]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">前言</h2>
<p>這段 PHP 程式碼主要用於 WordPress 的 MemberPress 外掛會員管理頁面，目的是在會員列表中新增電話、地址、贈品寄送狀態及一鍵複製欄位。適合需要擴充會員管理功能、提升後台操作效率的工程師與自學者。</p>
<h2 class="wp-block-heading">新增會員列表欄位表頭</h2>
<p>透過 <code>mepr-admin-members-cols</code> 過濾器，將電話、地址、贈品已寄送與複製按鈕四個欄位加入會員列表的表頭。這樣可以讓後台管理員一目了然看到更多會員資訊。</p>
<pre><code class="lang-php language-php php">add_filter(&#039;mepr-admin-members-cols&#039;, function($cols){
  $cols[&#039;mepr_phone&#039;]   = __(&#039;電話&#039;, &#039;memberpress&#039;);
  $cols[&#039;mepr_address&#039;] = __(&#039;地址&#039;, &#039;memberpress&#039;);
  $cols[&#039;mepr_sent&#039;]    = __(&#039;贈品已寄送&#039;, &#039;memberpress&#039;);
  $cols[&#039;mepr_copy&#039;]    = __(&#039;複製&#039;, &#039;memberpress&#039;);
  return $cols;
});</code></pre>
<h2 class="wp-block-heading">渲染會員列表每列的欄位內容</h2>
<p>使用 <code>mepr_members_list_table_row</code> 過濾器，依照欄位名稱輸出對應的會員資料：</p>
<ul>
<li>電話與地址從使用者 meta 取得，地址欄位會截斷顯示並加上完整 tooltip。</li>
<li>贈品已寄送欄位為 checkbox，狀態可即時切換並透過 AJAX 更新。</li>
<li>複製欄位提供一鍵複製會員姓名、電話與地址的功能，方便快速取得會員聯絡資訊。</li>
</ul>
<pre><code class="lang-php language-php php">add_filter(&#039;mepr_members_list_table_row&#039;, function($attributes, $rec, $column_name, $column_display_name){
  $user = get_user_by(&#039;login&#039;, $rec-&gt;username);
  if(!$user){ return; }

  $first = get_user_meta($user-&gt;ID, &#039;first_name&#039;, true);
  $last  = get_user_meta($user-&gt;ID, &#039;last_name&#039;, true);
  $full  = trim(implode(&#039; &#039;, array_filter([$first, $last])));
  if($full === &#039;&#039;) { $full = $user-&gt;display_name ?: $user-&gt;user_login; }

  $phone = get_user_meta($user-&gt;ID, &#039;mepr_phone&#039;, true);
  $addr  = get_user_meta($user-&gt;ID, &#039;mepr_address&#039;, true);

  if($column_name === &#039;mepr_phone&#039;){
    echo &#039;&lt;td &#039;.$attributes.&#039;&gt;&#039;.esc_html($phone ?: &#039;&mdash;&#039;).&#039;&lt;/td&gt;&#039;;
  }

  if($column_name === &#039;mepr_address&#039;){
    $short = mb_strimwidth(wp_strip_all_tags((string)$addr), 0, 80, &#039;&hellip;&#039;, &#039;UTF-8&#039;);
    echo &#039;&lt;td &#039;.$attributes.&#039; title=&quot;&#039;.esc_attr($addr).&#039;&quot;&gt;&#039;.esc_html($short ?: &#039;&mdash;&#039;).&#039;&lt;/td&gt;&#039;;
  }

  if($column_name === &#039;mepr_sent&#039;){
    $checked = get_user_meta($user-&gt;ID, &#039;mepr_sent&#039;, true) ? &#039;checked&#039; : &#039;&#039;;
    $nonce   = wp_create_nonce(&#039;mepr_sent_nonce&#039;);
    echo &#039;&lt;td &#039;.$attributes.&#039; style=&quot;text-align:center&quot;&gt;
      &lt;label style=&quot;display:inline-flex;align-items:center;gap:.4em;cursor:pointer;&quot;&gt;
        &lt;input type=&quot;checkbox&quot; class=&quot;mepr-sent-toggle&quot;
               data-user=&quot;&#039;.esc_attr($user-&gt;ID).&#039;&quot;
               data-nonce=&quot;&#039;.esc_attr($nonce).&#039;&quot; &#039;.$checked.&#039; /&gt;
        &lt;span&gt;&#039;.($checked ? esc_html__(&#039;Yes&#039;,&#039;memberpress&#039;) : esc_html__(&#039;No&#039;,&#039;memberpress&#039;)).&#039;&lt;/span&gt;
      &lt;/label&gt;
    &lt;/td&gt;&#039;;
  }

  if($column_name === &#039;mepr_copy&#039;){
    $copy_text = &quot;Full Name: {$full}\nPhone: &quot; . ($phone ?: &#039;&#039;) . &quot;\nAddress: &quot; . ($addr ? wp_strip_all_tags($addr) : &#039;&#039;);
    echo &#039;&lt;td &#039;.$attributes.&#039; style=&quot;text-align:center&quot;&gt;
      &lt;button type=&quot;button&quot; class=&quot;button button-small mepr-copy-btn&quot;
              data-copy=&quot;&#039;.esc_attr($copy_text).&#039;&quot;
              title=&quot;&#039;.esc_attr__(&#039;Copy Full Name / Phone / Address&#039;,&#039;memberpress&#039;).&#039;&quot;&gt;
        &#039;.esc_html__(&#039;複製&#039;,&#039;memberpress&#039;).&#039;
      &lt;/button&gt;
    &lt;/td&gt;&#039;;
  }
}, 10, 4);</code></pre>
<h2 class="wp-block-heading">後台頁尾加入 JavaScript 強化互動</h2>
<p>透過 <code>admin_print_footer_scripts</code> 動作，在會員頁面插入 JavaScript：</p>
<ul>
<li>切換「贈品已寄送」checkbox 時，使用 AJAX 非同步更新後端資料，並即時更新 UI 狀態。</li>
<li>複製按鈕則利用 Clipboard API 或備援方案將會員資訊複製到剪貼簿，並顯示複製成功提示。</li>
</ul>
<p>此設計提升管理者操作便利性，避免頻繁刷新頁面。</p>
<h2 class="wp-block-heading">AJAX 後端處理贈品寄送狀態更新</h2>
<p>使用 <code>wp_ajax_mepr_toggle_sent</code> 來驗證權限與 nonce，確保安全後更新使用者 meta。若驗證失敗或使用者不存在，會回傳錯誤訊息。</p>
<pre><code class="lang-php language-php php">add_action(&#039;wp_ajax_mepr_toggle_sent&#039;, function(){
  if( ! current_user_can(&#039;manage_options&#039;) ){
    wp_send_json_error([&#039;message&#039; =&gt; &#039;無權限&#039;], 403);
  }
  if( ! check_ajax_referer(&#039;mepr_sent_nonce&#039;, &#039;_wpnonce&#039;, false) ){
    wp_send_json_error([&#039;message&#039; =&gt; &#039;Nonce 驗證失敗&#039;], 400);
  }
  $user_id = isset($_POST[&#039;user_id&#039;]) ? absint($_POST[&#039;user_id&#039;]) : 0;
  $value   = isset($_POST[&#039;value&#039;]) ? sanitize_text_field($_POST[&#039;value&#039;]) : &#039;0&#039;;
  if( !$user_id || ! get_user_by(&#039;id&#039;, $user_id) ){
    wp_send_json_error([&#039;message&#039; =&gt; &#039;使用者不存在&#039;], 404);
  }
  update_user_meta($user_id, &#039;mepr_sent&#039;, $value === &#039;1&#039; ? 1 : 0);
  wp_send_json_success([&#039;nonce&#039; =&gt; wp_create_nonce(&#039;mepr_sent_nonce&#039;)]);
});</code></pre>
<h2 class="wp-block-heading">簡單的後台樣式調整</h2>
<p>為了讓新增欄位版面更整齊，使用 <code>admin_head</code> 動作插入 CSS，調整欄寬與置中對齊。</p>
<h2 class="wp-block-heading">實務應用與延伸</h2>
<p>這套擴充方案適合需要在 MemberPress 後台快速查看會員詳細聯絡資訊並標記贈品寄送狀態的情境。未來可延伸加入更多欄位或改為前端 AJAX 分頁載入，提升大會員數的效能。</p>
<h2 class="wp-block-heading">常見問題與注意事項</h2>
<ul>
<li>AJAX 權限與 nonce 驗證不可少，避免安全漏洞。</li>
<li>複製功能在非 HTTPS 或不支援 Clipboard API 的環境會退回使用 textarea 方案。</li>
<li>地址欄位截斷避免版面過長，但完整資料仍可透過 tooltip 查看。</li>
</ul>
<h2 class="wp-block-heading">完整程式碼</h2>
<pre><code class="lang-php language-php php">
&lt;?php
/**
 * MemberPress Members 列表欄位：電話/地址/贈品已寄送/複製（可即時切換）
 * 放 WPCode（PHP Snippet，Run Everywhere）
 */

/* === 1) 新增欄位表頭（mepr-admin-members-cols） === */
add_filter(&#039;mepr-admin-members-cols&#039;, function($cols){
  $cols[&#039;mepr_phone&#039;]   = __(&#039;電話&#039;, &#039;memberpress&#039;);
  $cols[&#039;mepr_address&#039;] = __(&#039;地址&#039;, &#039;memberpress&#039;);
  $cols[&#039;mepr_sent&#039;]    = __(&#039;贈品已寄送&#039;, &#039;memberpress&#039;);
  $cols[&#039;mepr_copy&#039;]    = __(&#039;複製&#039;, &#039;memberpress&#039;); // 一鍵複製按鈕
  return $cols;
});

/* === 2) 渲染每一列內容（mepr_members_list_table_row） === */
add_filter(&#039;mepr_members_list_table_row&#039;, function($attributes, $rec, $column_name, $column_display_name){
  $user = get_user_by(&#039;login&#039;, $rec-&gt;username);
  if(!$user){ return; }

  // 共用：取 Full Name / Phone / Address
  $first = get_user_meta($user-&gt;ID, &#039;first_name&#039;, true);
  $last  = get_user_meta($user-&gt;ID, &#039;last_name&#039;, true);
  $full  = trim(implode(&#039; &#039;, array_filter([$first, $last])));
  if($full === &#039;&#039;) { $full = $user-&gt;display_name ?: $user-&gt;user_login; }

  $phone = get_user_meta($user-&gt;ID, &#039;mepr_phone&#039;, true);
  $addr  = get_user_meta($user-&gt;ID, &#039;mepr_address&#039;, true);

  if($column_name === &#039;mepr_phone&#039;){
    echo &#039;&lt;td &#039;.$attributes.&#039;&gt;&#039;.esc_html($phone ?: &#039;&mdash;&#039;).&#039;&lt;/td&gt;&#039;;
  }

  if($column_name === &#039;mepr_address&#039;){
    $short = mb_strimwidth(wp_strip_all_tags((string)$addr), 0, 80, &#039;&hellip;&#039;, &#039;UTF-8&#039;);
    echo &#039;&lt;td &#039;.$attributes.&#039; title=&quot;&#039;.esc_attr($addr).&#039;&quot;&gt;&#039;.esc_html($short ?: &#039;&mdash;&#039;).&#039;&lt;/td&gt;&#039;;
  }

  if($column_name === &#039;mepr_sent&#039;){
    $checked = get_user_meta($user-&gt;ID, &#039;mepr_sent&#039;, true) ? &#039;checked&#039; : &#039;&#039;;
    $nonce   = wp_create_nonce(&#039;mepr_sent_nonce&#039;);
    echo &#039;&lt;td &#039;.$attributes.&#039; style=&quot;text-align:center&quot;&gt;
      &lt;label style=&quot;display:inline-flex;align-items:center;gap:.4em;cursor:pointer;&quot;&gt;
        &lt;input type=&quot;checkbox&quot; class=&quot;mepr-sent-toggle&quot;
               data-user=&quot;&#039;.esc_attr($user-&gt;ID).&#039;&quot;
               data-nonce=&quot;&#039;.esc_attr($nonce).&#039;&quot; &#039;.$checked.&#039; /&gt;
        &lt;span&gt;&#039;.($checked ? esc_html__(&#039;Yes&#039;,&#039;memberpress&#039;) : esc_html__(&#039;No&#039;,&#039;memberpress&#039;)).&#039;&lt;/span&gt;
      &lt;/label&gt;
    &lt;/td&gt;&#039;;
  }

  if($column_name === &#039;mepr_copy&#039;){
    $copy_text = &quot;Full Name: {$full}\nPhone: &quot; . ($phone ?: &#039;&#039;) . &quot;\nAddress: &quot; . ($addr ? wp_strip_all_tags($addr) : &#039;&#039;);
    echo &#039;&lt;td &#039;.$attributes.&#039; style=&quot;text-align:center&quot;&gt;
      &lt;button type=&quot;button&quot; class=&quot;button button-small mepr-copy-btn&quot;
              data-copy=&quot;&#039;.esc_attr($copy_text).&#039;&quot;
              title=&quot;&#039;.esc_attr__(&#039;Copy Full Name / Phone / Address&#039;,&#039;memberpress&#039;).&#039;&quot;&gt;
        &#039;.esc_html__(&#039;複製&#039;,&#039;memberpress&#039;).&#039;
      &lt;/button&gt;
    &lt;/td&gt;&#039;;
  }
}, 10, 4);

/* === 3) 後台頁尾插入 JS（僅 Members 頁） === */
add_action(&#039;admin_print_footer_scripts&#039;, function(){
  if( ! is_admin() ) return;
  if( ! (isset($_GET[&#039;page&#039;]) &amp;&amp; $_GET[&#039;page&#039;] === &#039;memberpress-members&#039;) ) return;

  $ajax_url = admin_url(&#039;admin-ajax.php&#039;);
  ?&gt;
  &lt;script&gt;
  (function(){
    // 已寄送：切換寫入
    function setSentLabel(td, checked){
      var s = td.querySelector(&#039;span&#039;);
      if(s){ s.textContent = checked ? &#039;Yes&#039; : &#039;No&#039;; }
    }

    document.addEventListener(&#039;change&#039;, function(e){
      var el = e.target;
      if(!el.classList.contains(&#039;mepr-sent-toggle&#039;)) return;

      var td    = el.closest(&#039;td&#039;);
      var uid   = el.getAttribute(&#039;data-user&#039;);
      var nonce = el.getAttribute(&#039;data-nonce&#039;);
      var val   = el.checked ? &#039;1&#039; : &#039;0&#039;;
      setSentLabel(td, el.checked); // 先行更新 UI

      var xhr = new XMLHttpRequest();
      var fd = new FormData();
      fd.append(&#039;action&#039;, &#039;mepr_toggle_sent&#039;);
      fd.append(&#039;user_id&#039;, uid);
      fd.append(&#039;value&#039;, val);
      fd.append(&#039;_wpnonce&#039;, nonce);
      xhr.open(&#039;POST&#039;, &#039;&lt;?php echo esc_js($ajax_url); ?&gt;&#039;, true);
      xhr.onreadystatechange = function(){
        if(xhr.readyState === 4){
          try{
            var res = JSON.parse(xhr.responseText||&#039;{}&#039;);
            if(!res.success){
              el.checked = !el.checked; setSentLabel(td, el.checked);
              alert(res.data &amp;&amp; res.data.message ? res.data.message : &#039;更新失敗&#039;);
            }else if(res.data &amp;&amp; res.data.nonce){
              el.setAttribute(&#039;data-nonce&#039;, res.data.nonce);
            }
          }catch(err){
            el.checked = !el.checked; setSentLabel(td, el.checked);
            alert(&#039;伺服器回應格式錯誤&#039;);
          }
        }
      };
      xhr.send(fd);
    }, false);

    // 一鍵複製
    document.addEventListener(&#039;click&#039;, async function(e){
      var btn = e.target.closest(&#039;.mepr-copy-btn&#039;);
      if(!btn) return;
      var text = btn.getAttribute(&#039;data-copy&#039;) || &#039;&#039;;
      try{
        if(navigator.clipboard &amp;&amp; window.isSecureContext){
          await navigator.clipboard.writeText(text);
        }else{
          var ta = document.createElement(&#039;textarea&#039;);
          ta.value = text;
          ta.style.position = &#039;fixed&#039;;
          ta.style.opacity = &#039;0&#039;;
          document.body.appendChild(ta);
          ta.focus();
          ta.select();
          document.execCommand(&#039;copy&#039;);
          document.body.removeChild(ta);
        }
        btn.classList.add(&#039;mepr-copied&#039;);
        btn.textContent = &#039;已複製&#039;;
        setTimeout(function(){ btn.classList.remove(&#039;mepr-copied&#039;); btn.textContent = &#039;複製&#039;; }, 1200);
      }catch(err){
        alert(&#039;複製失敗，請手動複製。\n\n&#039; + text);
      }
    }, false);
  })();
  &lt;/script&gt;
  &lt;?php
});

/* === 4) AJAX 後端：儲存 mepr_sent === */
add_action(&#039;wp_ajax_mepr_toggle_sent&#039;, function(){
  if( ! current_user_can(&#039;manage_options&#039;) ){
    wp_send_json_error([&#039;message&#039; =&gt; &#039;無權限&#039;], 403);
  }
  if( ! check_ajax_referer(&#039;mepr_sent_nonce&#039;, &#039;_wpnonce&#039;, false) ){
    wp_send_json_error([&#039;message&#039; =&gt; &#039;Nonce 驗證失敗&#039;], 400);
  }
  $user_id = isset($_POST[&#039;user_id&#039;]) ? absint($_POST[&#039;user_id&#039;]) : 0;
  $value   = isset($_POST[&#039;value&#039;]) ? sanitize_text_field($_POST[&#039;value&#039;]) : &#039;0&#039;;
  if( !$user_id || ! get_user_by(&#039;id&#039;, $user_id) ){
    wp_send_json_error([&#039;message&#039; =&gt; &#039;使用者不存在&#039;], 404);
  }
  update_user_meta($user_id, &#039;mepr_sent&#039;, $value === &#039;1&#039; ? 1 : 0);
  wp_send_json_success([&#039;nonce&#039; =&gt; wp_create_nonce(&#039;mepr_sent_nonce&#039;)]);
});

/* === 5) 簡單樣式 === */
add_action(&#039;admin_head&#039;, function(){
  if( ! (isset($_GET[&#039;page&#039;]) &amp;&amp; $_GET[&#039;page&#039;] === &#039;memberpress-members&#039;) ) return;
  echo &#039;
&lt;style&gt;
    th#mepr_phone{width:140px}
    th#mepr_address{width:260px}
    th#mepr_sent{width:110px;text-align:center}
    th#mepr_copy{width:90px;text-align:center}
    td.column-mepr_sent, td.column-mepr_copy{text-align:center}
    .mepr-copy-btn.button-small{line-height:22px;height:24px;padding:0 8px}
    .mepr-copy-btn.mepr-copied{opacity:.8}
  &lt;/style&gt;&#039;;
});</code></pre>]]></content:encoded>
					
					<wfw:commentRss>https://piglife.tw/technical-notes/memberpress-members-extra-columns/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>WordPress 結合 MemberPress 與 ACF 自動更新文章存取權限狀態</title>
		<link>https://piglife.tw/technical-notes/wordpress-memberpress-acf-access-status/</link>
					<comments>https://piglife.tw/technical-notes/wordpress-memberpress-acf-access-status/#respond</comments>
		
		<dc:creator><![CDATA[小豬]]></dc:creator>
		<pubDate>Wed, 17 Dec 2025 22:20:39 +0000</pubDate>
				<category><![CDATA[技術筆記]]></category>
		<category><![CDATA[acf]]></category>
		<category><![CDATA[MemberPress]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://piglife.tw/technical-notes/wordpress-memberpress-acf-access-status/</guid>

					<description><![CDATA[介紹如何在 WordPress 結合 MemberPress 與 ACF，自動更新文章的存取權限狀態...]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">前言</h2>
<p>在使用 WordPress 搭配會員管理外掛 MemberPress 時，常見需求是判斷文章是否有設定存取權限，並將此狀態同步到自訂欄位（ACF）中，方便前端或查詢邏輯使用。這段程式碼示範如何在文章儲存時自動檢查 MemberPress 的存取規則，並更新 ACF 的 True/False 欄位，適合熟悉 WordPress 開發並使用 MemberPress 與 ACF 的工程師。</p>
<h2 class="wp-block-heading">文章儲存時自動同步存取權限</h2>
<p>透過 <code>save_post</code> 鉤子，在文章儲存或更新時觸發檢查。程式碼會先排除自動儲存與修訂版本，避免重複執行。接著確認文章類型是否為支援的 <code>post</code> 或 <code>page</code>，並檢查 MemberPress 與 ACF 是否存在。</p>
<h3 class="wp-block-heading">重要程式碼片段</h3>
<pre><code class="lang-php language-php php">if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
    return;
}

$post_type = get_post_type($post_id);
if (!in_array($post_type, array(&#039;post&#039;, &#039;page&#039;))) {
    return;
}

if (!class_exists(&#039;MeprRule&#039;) || !function_exists(&#039;update_field&#039;)) {
    return;
}</code></pre>
<p>接著使用 <code>MeprRule::get_rules($post)</code> 取得該文章的 MemberPress 存取規則，判斷是否有規則存在，並更新 ACF 的 <code>ks_post_access</code> 欄位。</p>
<pre><code class="lang-php language-php php">$rules = MeprRule::get_rules($post);
$has_rule = !empty($rules);

update_field(&#039;ks_post_access&#039;, $has_rule, $post_id);</code></pre>
<h2 class="wp-block-heading">當 MemberPress 規則變更時批次更新所有文章</h2>
<p>除了單篇文章儲存時更新，也會在 MemberPress 規則新增、修改或刪除時觸發批次更新所有文章的存取狀態，確保資料一致。</p>
<pre><code class="lang-php language-php php">function ks_update_all_posts_access_status() {
    if (!function_exists(&#039;update_field&#039;)) {
        return;
    }
    $posts = get_posts(array(
        &#039;post_type&#039; =&gt; &#039;post&#039;,
        &#039;post_status&#039; =&gt; &#039;publish&#039;,
        &#039;numberposts&#039; =&gt; -1,
        &#039;fields&#039; =&gt; &#039;ids&#039;
    ));
    foreach ($posts as $post_id) {
        $post = get_post($post_id);
        $rules = MeprRule::get_rules($post);
        $has_rule = !empty($rules);
        update_field(&#039;ks_post_access&#039;, $has_rule, $post_id);
    }
}</code></pre>
<h2 class="wp-block-heading">後台介面批次更新功能</h2>
<p>為方便管理，新增 WordPress 後台子選單，讓管理者可以手動觸發更新所有文章的存取權限狀態，並提供操作說明。</p>
<pre><code class="lang-php language-php php">function ks_add_bulk_update_access_status() {
    add_submenu_page(
        &#039;edit.php&#039;,
        &#039;Update Access Status&#039;,
        &#039;Update Access Status&#039;,
        &#039;manage_options&#039;,
        &#039;ks-bulk-update-access-status&#039;,
        &#039;ks_bulk_update_access_status_page&#039;
    );
}
add_action(&#039;admin_menu&#039;, &#039;ks_add_bulk_update_access_status&#039;);</code></pre>
<p>介面中包含按鈕與確認提示，確保操作安全。</p>
<h2 class="wp-block-heading">實務應用與延伸</h2>
<ul>
<li>透過 ACF 的 <code>ks_post_access</code> 欄位，前端查詢可以輕鬆過濾有存取限制的文章。</li>
<li>可結合 GreenShift Query Loop 或其他查詢工具，提升前端顯示效率。</li>
<li>若會員規則較複雜，建議優化批次更新邏輯，避免效能瓶頸。</li>
<li>可加入快取機制或非同步處理，減少儲存時的阻塞。</li>
</ul>
<h2 class="wp-block-heading">常見問題與注意事項</h2>
<ul>
<li>請確保已建立名為 <code>ks_post_access</code> 的 True/False ACF 欄位，且欄位名稱與程式碼一致。</li>
<li>若 MemberPress 或 ACF 外掛未啟用，程式會自動跳過，避免錯誤。</li>
<li>批次更新文章時，若文章數量龐大，可能會耗時較久，建議在低流量時段執行。</li>
</ul>
<h2 class="wp-block-heading">完整程式碼</h2>
<pre><code class="lang-php language-php php">
&lt;?php
// 在文章儲存/更新時自動檢查並更新 ACF 欄位
function ks_update_mepr_access_status($post_id) {
    // 避免自動儲存時執行
    if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
        return;
    }

    // 檢查是否為支援的文章類型
    $post_type = get_post_type($post_id);
    if (!in_array($post_type, array(&#039;post&#039;, &#039;page&#039;))) {
        return;
    }

    // 檢查是否有 MemberPress 和 ACF
    if (!class_exists(&#039;MeprRule&#039;) || !function_exists(&#039;update_field&#039;)) {
        return;
    }

    $post = get_post($post_id);
    if (!$post) {
        return;
    }

    // 檢查該文章是否有 MemberPress 規則
    $rules = MeprRule::get_rules($post);
    $has_rule = !empty($rules);

    // 更新 ACF 欄位
    update_field(&#039;ks_post_access&#039;, $has_rule, $post_id);

    // 可選：記錄 log 用於除錯
    error_log(&quot;KS Post ID: $post_id - Access Rule Status: &quot; . ($has_rule ? &#039;Yes&#039; : &#039;No&#039;));
}

// 掛載到文章儲存的 hook
add_action(&#039;save_post&#039;, &#039;ks_update_mepr_access_status&#039;, 20, 1);

// 也可以掛載到 MemberPress 規則更新時
add_action(&#039;mepr-rule-saved&#039;, &#039;ks_update_all_posts_access_status&#039;);
add_action(&#039;mepr-rule-deleted&#039;, &#039;ks_update_all_posts_access_status&#039;);

// 當 MemberPress 規則變更時，更新所有相關文章
function ks_update_all_posts_access_status() {
    if (!function_exists(&#039;update_field&#039;)) {
        return;
    }

    $posts = get_posts(array(
        &#039;post_type&#039; =&gt; &#039;post&#039;,
        &#039;post_status&#039; =&gt; &#039;publish&#039;,
        &#039;numberposts&#039; =&gt; -1,
        &#039;fields&#039; =&gt; &#039;ids&#039;
    ));

    foreach ($posts as $post_id) {
        $post = get_post($post_id);
        $rules = MeprRule::get_rules($post);
        $has_rule = !empty($rules);

        update_field(&#039;ks_post_access&#039;, $has_rule, $post_id);
    }
}

// 管理後台增加批量更新功能
function ks_add_bulk_update_access_status() {
    add_submenu_page(
        &#039;edit.php&#039;,
        &#039;Update Access Status&#039;,
        &#039;Update Access Status&#039;,
        &#039;manage_options&#039;,
        &#039;ks-bulk-update-access-status&#039;,
        &#039;ks_bulk_update_access_status_page&#039;
    );
}
add_action(&#039;admin_menu&#039;, &#039;ks_add_bulk_update_access_status&#039;);

function ks_bulk_update_access_status_page() {
    if (isset($_POST[&#039;ks_update_all&#039;])) {
        ks_update_all_posts_access_status();
        echo &#039;&lt;div class=&quot;notice notice-success&quot;&gt;&lt;p&gt;所有文章的存取權限狀態已更新完成！&lt;/p&gt;&lt;/div&gt;&#039;;
    }

    ?&gt;
    &lt;div class=&quot;wrap&quot;&gt;

&lt;h1&gt;KS 更新文章存取權限狀態&lt;/h1&gt;

&lt;p&gt;此功能會檢查所有文章的 MemberPress 存取權限設定，並更新對應的 ACF 欄位。&lt;/p&gt;

        &lt;form method=&quot;post&quot;&gt;
            &lt;input type=&quot;submit&quot; name=&quot;ks_update_all&quot; class=&quot;button-primary&quot; value=&quot;更新所有文章狀態&quot; 
                   onclick=&quot;return confirm(&#039;確定要更新所有文章的存取權限狀態嗎？&#039;);&quot;&gt;
        &lt;/form&gt;
    &lt;/div&gt;
    &lt;?php
}</code></pre>]]></content:encoded>
					
					<wfw:commentRss>https://piglife.tw/technical-notes/wordpress-memberpress-acf-access-status/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>自訂 MemberPress 貨幣格式為無小數點整數顯示</title>
		<link>https://piglife.tw/technical-notes/memberpress-currency-integer-format/</link>
					<comments>https://piglife.tw/technical-notes/memberpress-currency-integer-format/#respond</comments>
		
		<dc:creator><![CDATA[小豬]]></dc:creator>
		<pubDate>Sun, 14 Dec 2025 22:20:27 +0000</pubDate>
				<category><![CDATA[技術筆記]]></category>
		<category><![CDATA[filter]]></category>
		<category><![CDATA[MemberPress]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://piglife.tw/technical-notes/memberpress-currency-integer-format/</guid>

					<description><![CDATA[示範如何利用 MemberPress 的 mepr_format_currency filter，自...]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">前言</h2>
<p>在使用 MemberPress 這類會員付費外掛時，預設的貨幣格式通常會包含小數點，像是「$123.00」。有些商業需求或設計風格希望價格以整數顯示，去除小數點部分。本文示範如何透過 WordPress 的 filter 機制，將 MemberPress 的貨幣格式調整成無小數點的整數顯示，並保留貨幣符號原本的位置。</p>
<p>這篇文章適合對 WordPress 外掛開發有基礎認識，並且需要自訂 MemberPress 付款顯示格式的工程師或自學者。</p>
<h2 class="wp-block-heading">透過 filter 自訂貨幣格式</h2>
<p>MemberPress 提供 <code>mepr_format_currency</code> 這個 filter，可以攔截並修改貨幣字串的輸出。這段程式碼利用匿名函式接收三個參數：</p>
<ul>
<li><code>$rstr</code>：原始格式化後的貨幣字串</li>
<li><code>$number</code>：數字部分</li>
<li><code>$show_symbol</code>：是否顯示貨幣符號</li>
</ul>
<p>接著使用 MemberPress 內建的 <code>MeprUtils::format_currency_float</code> 函式，將數字四捨五入到 0 位小數，轉成字串。</p>
<pre><code class="lang-php language-php php">$no_decimals = (string) MeprUtils::format_currency_float((float) $number, 0);</code></pre>
<h2 class="wp-block-heading">保留貨幣符號原本位置</h2>
<p>為了不破壞原本貨幣符號的位置（有些貨幣符號在前，有些在後），程式碼判斷原字串 <code>$rstr</code> 是否以貨幣符號開頭：</p>
<pre><code class="lang-php language-php php">if (strpos($rstr, $symbol) === 0) {
  return $symbol . $no_decimals;
} else {
  return $no_decimals . &#039; &#039; . $symbol;
}</code></pre>
<p>這樣可以維持「$123」或「123 $」的格式一致性。</p>
<h2 class="wp-block-heading">實務應用與注意事項</h2>
<ul>
<li>適用於價格不需要小數點顯示的場景，例如整數金額訂閱方案。</li>
<li>若未來有多種貨幣符號，需確認 <code>MeprOptions::fetch()</code> 取得的符號是否正確。</li>
<li>由於是使用匿名函式，若需移除此 filter，需注意無法直接使用 <code>remove_filter</code>。</li>
</ul>
<h2 class="wp-block-heading">延伸優化方向</h2>
<ul>
<li>可根據不同貨幣類型動態調整小數位數。</li>
<li>加入千分位分隔符號增強可讀性。</li>
<li>支援多語系貨幣格式。</li>
</ul>
<h2 class="wp-block-heading">完整程式碼</h2>
<pre><code class="lang-php language-php php">&lt;?php
// functions.php 或自訂外掛
add_filter(&#039;mepr_format_currency&#039;, function($rstr, $number, $show_symbol) {
  $mepr_options = MeprOptions::fetch();

  // 將數字改成 0 位小數（四捨五入）
  $no_decimals = (string) MeprUtils::format_currency_float((float) $number, 0);

  if (!$show_symbol) {
    return $no_decimals;
  }

  // 簡單依據原字串是否以貨幣符號開頭，維持原有符號位置
  $symbol = $mepr_options-&gt;currency_symbol;
  if (strpos($rstr, $symbol) === 0) {
    // 原本是「$123.00」這種前置符號
    return $symbol . $no_decimals;
  } else {
    // 原本是「123.00 $」這種後置符號
    return $no_decimals . &#039; &#039; . $symbol;
  }
}, 10, 3);</code></pre>]]></content:encoded>
					
					<wfw:commentRss>https://piglife.tw/technical-notes/memberpress-currency-integer-format/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>WordPress 會員中心新增追蹤文章清單功能實作</title>
		<link>https://piglife.tw/technical-notes/wordpress-user-follow-posts/</link>
					<comments>https://piglife.tw/technical-notes/wordpress-user-follow-posts/#respond</comments>
		
		<dc:creator><![CDATA[小豬]]></dc:creator>
		<pubDate>Sat, 13 Dec 2025 22:20:48 +0000</pubDate>
				<category><![CDATA[技術筆記]]></category>
		<category><![CDATA[Flexbox]]></category>
		<category><![CDATA[MemberPress]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://piglife.tw/technical-notes/wordpress-user-follow-posts/</guid>

					<description><![CDATA[介紹如何在 WordPress 會員中心新增追蹤文章分頁，並以 Flexbox 版型呈現使用者收藏的...]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">前言</h2>
<p>這段 PHP 程式碼用於在 WordPress 會員中心（使用 MemberPress 插件）新增一個「追蹤文章」的分頁，讓使用者可以快速查看自己收藏或追蹤的文章列表。適合需要在會員系統中提供個人化內容管理的網站開發者與自學者。</p>
<h2 class="wp-block-heading">新增會員中心導覽標籤</h2>
<p>透過 <code>mepr_account_nav</code> action，程式碼在會員中心導覽列新增一個名為「追蹤文章」的標籤。當使用者點擊此標籤時，會帶入 <code>action=user-follow</code> 的 URL 參數，並且根據當前頁面狀態動態加上 CSS 樣式以標示為當前選中狀態。</p>
<pre><code class="lang-php language-php php">add_action(&#039;mepr_account_nav&#039;, function($action){
  $active = (isset($_GET[&#039;action&#039;]) &amp;&amp; $_GET[&#039;action&#039;] === &#039;user-follow&#039;) ? &#039;mepr-active-nav-tab&#039; : &#039;&#039;;
  echo &#039;&lt;span class=&quot;mepr-nav-item user-follow &#039; . esc_attr($active) . &#039;&quot;&gt;&lt;a href=&quot;/account/?action=user-follow&quot;&gt;追蹤文章&lt;/a&gt;&lt;/span&gt;&#039;;
});</code></pre>
<h2 class="wp-block-heading">顯示追蹤文章清單內容</h2>
<p>使用 <code>mepr_account_nav_content</code> action 來判斷是否為 <code>user-follow</code> 頁面，若是則取得該會員收藏的文章 ID。此處利用 <code>get_user_favorites</code> 函式（需額外插件或自訂函式支持）取得文章列表，並以 Flexbox 版型顯示。</p>
<h3 class="wp-block-heading">文章資料與版型處理</h3>
<ul>
<li>透過 <code>get_the_post_thumbnail</code> 取得文章縮圖，若無縮圖則顯示預設佔位元素。</li>
<li>文章分類顯示為角標，若無分類則顯示「文章」。</li>
<li>若文章有特定欄位 <code>ks_post_access</code> 標示有權限，則在角標旁顯示鎖頭圖示。</li>
</ul>
<h3 class="wp-block-heading">文章列表輸出範例</h3>
<pre><code class="lang-php language-php php">foreach ($post_ids as $post_id) {
  $has_access = get_field(&#039;ks_post_access&#039;, $post_id);
  $thumb_html = get_the_post_thumbnail($post_id, &#039;medium_large&#039;, [&#039;class&#039; =&gt; &#039;fi-img&#039;, &#039;loading&#039;=&gt;&#039;lazy&#039;, &#039;decoding&#039;=&gt;&#039;async&#039;]);
  if(empty($thumb_html)) {
    $thumb_html = &#039;&lt;div class=&quot;fi-img fi-img--placeholder&quot; aria-hidden=&quot;true&quot;&gt;&lt;/div&gt;&#039;;
  }
  $cats = get_the_category($post_id);
  $badge = (!empty($cats) &amp;&amp; !is_wp_error($cats)) ? $cats[0]-&gt;name : &#039;文章&#039;;
  $permalink = get_permalink($post_id);
  $title = get_the_title($post_id);

  echo &#039;&lt;a class=&quot;follow-item&quot; href=&quot;&#039; . esc_url($permalink) . &#039;&quot;&gt;&#039;
       . &#039;&lt;div class=&quot;fi-thumb&quot;&gt;&#039; . $thumb_html
       . &#039;&lt;div class=&quot;fi-badge&quot;&gt;&lt;span class=&quot;fi-tag&quot;&gt;&#039; . esc_html($badge) . &#039;&lt;/span&gt;&#039;;
  if ($has_access) {
    echo &#039;&lt;span class=&quot;fi-access-icon&quot;&gt;(鎖頭 SVG)&lt;/span&gt;&#039;;
  }
  echo &#039;&lt;/div&gt;&lt;/div&gt;&#039;
       . &#039;&lt;div class=&quot;fi-content&quot;&gt;&lt;h3 class=&quot;fi-title&quot;&gt;&#039; . esc_html($title) . &#039;&lt;/h3&gt;&lt;/div&gt;&#039;
       . &#039;&lt;/a&gt;&#039;;
}</code></pre>
<h2 class="wp-block-heading">實務應用與延伸</h2>
<p>此功能適合用於會員制網站，讓使用者能方便管理自己感興趣的文章。可進一步結合 AJAX 技術實現無刷新更新，或加入取消追蹤功能提升互動性。若文章有付費限制，鎖頭圖示能明確提示使用者權限狀態。</p>
<h2 class="wp-block-heading">常見問題與注意事項</h2>
<ul>
<li><code>get_user_favorites</code> 函式需確認是否有安裝相應收藏功能插件，否則會回傳空陣列。</li>
<li>權限欄位 <code>ks_post_access</code> 為自訂欄位，需確保文章有正確設定。</li>
<li>SVG 圖示直接內嵌於 HTML，方便自訂樣式與顯示，避免外部資源依賴。</li>
</ul>
<h2 class="wp-block-heading">完整程式碼</h2>
<pre><code class="lang-php language-php php">// Account &gt; 追蹤清單 tab
add_action(&#039;mepr_account_nav&#039;, function($action){
  $active = (isset($_GET[&#039;action&#039;]) &amp;&amp; $_GET[&#039;action&#039;] === &#039;user-follow&#039;) ? &#039;mepr-active-nav-tab&#039; : &#039;&#039;;
  echo &#039;&lt;span class=&quot;mepr-nav-item user-follow &#039; . esc_attr($active) . &#039;&quot;&gt;&lt;a href=&quot;/account/?action=user-follow&quot;&gt;追蹤文章&lt;/a&gt;&lt;/span&gt;&#039;;
});

// Account &gt; 追蹤清單內容：Flex 版型
add_action(&#039;mepr_account_nav_content&#039;, function($action){
  if($action !== &#039;user-follow&#039;) return;

  // Favorite Posts
  $filters = [
    &#039;post_type&#039; =&gt; [&#039;post&#039;],
    &#039;status&#039;    =&gt; [&#039;publish&#039;],
  ];
  $post_ids = function_exists(&#039;get_user_favorites&#039;)
    ? get_user_favorites(get_current_user_id(), null, $filters)
    : [];

  if (empty($post_ids)) {
    echo &#039;
&lt;p&gt;尚無追蹤的文章&lt;/p&gt;&#039;;
    return;
  }

  echo &#039;&lt;h2 class=&quot;follow-title&quot;&gt;&lt;svg width=&quot;32&quot; height=&quot;32&quot; viewBox=&quot;0 0 32 32&quot; fill=&quot;none&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;path d=&quot;M8 7C8 6.44772 8.44772 6 9 6H23C23.5523 6 24 6.44772 24 7V25.1827C24 25.5564 23.6051 25.798 23.2724 25.6279L17.3659 22.6075C16.5081 22.1689 15.4919 22.1689 14.6341 22.6075L8.72764 25.6279C8.39495 25.798 8 25.5564 8 25.1827V7Z&quot; fill=&quot;#131314&quot; stroke=&quot;#131314&quot; stroke-width=&quot;1.5&quot;/&gt;
&lt;path d=&quot;M15.8153 9.44399C15.8837 9.27973 16.1163 9.27973 16.1847 9.44399L17.5401 12.7029C17.5689 12.7721 17.634 12.8194 17.7088 12.8254L21.227 13.1075C21.4044 13.1217 21.4763 13.343 21.3411 13.4587L18.6606 15.7549C18.6037 15.8037 18.5788 15.8802 18.5962 15.9532L19.4151 19.3864C19.4564 19.5594 19.2682 19.6962 19.1163 19.6035L16.1043 17.7637C16.0402 17.7246 15.9598 17.7246 15.8957 17.7637L12.8837 19.6035C12.7318 19.6962 12.5436 19.5594 12.5849 19.3864L13.4038 15.9532C13.4212 15.8802 13.3963 15.8037 13.3394 15.7549L10.6589 13.4587C10.5237 13.343 10.5957 13.1217 10.773 13.1075L14.2912 12.8254C14.366 12.8194 14.4311 12.7721 14.4599 12.7029L15.8153 9.44399Z&quot; fill=&quot;white&quot;/&gt;
&lt;/svg&gt;我的追蹤文章&lt;/h2&gt;&#039;;

  echo &#039;&lt;div class=&quot;follow-list&quot;&gt;&#039;;
  foreach ($post_ids as $post_id) {
    // 檢查是否有 access 權限
    $has_access = get_field(&#039;ks_post_access&#039;, $post_id);

    // 縮圖
    $thumb_html = get_the_post_thumbnail(
      $post_id,
      &#039;medium_large&#039;,
      [&#039;class&#039; =&gt; &#039;fi-img&#039;, &#039;loading&#039;=&gt;&#039;lazy&#039;, &#039;decoding&#039;=&gt;&#039;async&#039;]
    );
    if(empty($thumb_html)){
      $thumb_html = &#039;&lt;div class=&quot;fi-img fi-img--placeholder&quot; aria-hidden=&quot;true&quot;&gt;&lt;/div&gt;&#039;;
    }

    // 角標：第一個分類，無則顯示「文章」
    $cats  = get_the_category($post_id);
    $badge = (!empty($cats) &amp;&amp; !is_wp_error($cats)) ? $cats[0]-&gt;name : &#039;文章&#039;;
    $permalink = get_permalink($post_id);
    $title     = get_the_title($post_id);

    echo &#039;&lt;a class=&quot;follow-item&quot; href=&quot;&#039; . esc_url($permalink) . &#039;&quot;&gt;&#039;
         . &#039;&lt;div class=&quot;fi-thumb&quot;&gt;&#039;
         .   $thumb_html .   
        &#039;&lt;div class=&quot;fi-badge&quot;&gt;
        &lt;span class=&quot;fi-tag&quot;&gt;&#039; . esc_html($badge) . &#039;&lt;/span&gt;&#039;;
        // 如果有 access 權限，顯示鎖頭圖標 
        if ($has_access) { 
            echo &#039;&lt;span class=&quot;fi-access-icon&quot;&gt;&lt;svg width=&quot;10&quot; height=&quot;14&quot; viewBox=&quot;0 0 10 14&quot; fill=&quot;none&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;path d=&quot;M5 0C6.65685 0 8 1.34315 8 3V5H9C9.55228 5 10 5.44772 10 6V13C10 13.5523 9.55229 14 9 14H1C0.447715 14 4.02663e-09 13.5523 0 13V6C0 5.44772 0.447715 5 1 5H2V3C2 1.34315 3.34315 0 5 0ZM5.17969 6.86328C5.10631 6.71493 4.8948 6.71508 4.82129 6.86328L4.16504 8.19238C4.13591 8.25128 4.07967 8.29225 4.01465 8.30176L2.54785 8.51465C2.38396 8.53863 2.31885 8.74079 2.4375 8.85645L3.49805 9.89062C3.54512 9.93657 3.56678 10.0025 3.55566 10.0674L3.30566 11.5273C3.27776 11.6906 3.44903 11.8153 3.5957 11.7383L4.90723 11.0488C4.96546 11.0183 5.03554 11.0182 5.09375 11.0488L6.40527 11.7383C6.55191 11.8152 6.72321 11.6906 6.69531 11.5273L6.44434 10.0674C6.43323 10.0026 6.45503 9.93656 6.50195 9.89062L7.56348 8.85645C7.68218 8.74074 7.61618 8.5385 7.45215 8.51465L5.98633 8.30176C5.92122 8.2923 5.86411 8.25136 5.83496 8.19238L5.17969 6.86328ZM5 1C3.89543 1 3 1.89543 3 3V5H7V3C7 1.89543 6.10457 1 5 1Z&quot; fill=&quot;white&quot;/&gt;&lt;/svg&gt;&lt;/span&gt;&#039;; 
    }

            echo &#039;&lt;/div&gt;&#039;;

    echo   &#039;&lt;/div&gt;&#039;
         . &#039;&lt;div class=&quot;fi-content&quot;&gt;&#039;
         .   &#039;&lt;h3 class=&quot;fi-title&quot;&gt;&#039; . esc_html($title) . &#039;&lt;/h3&gt;&#039;
         . &#039;&lt;/div&gt;&#039;
         . &#039;&lt;/a&gt;&#039;;
  }
  echo &#039;&lt;/div&gt;&#039;;
});</code></pre>]]></content:encoded>
					
					<wfw:commentRss>https://piglife.tw/technical-notes/wordpress-user-follow-posts/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>利用短碼控制內容顯示：結合 MemberPress 權限判斷的實作範例</title>
		<link>https://piglife.tw/technical-notes/mepr-access-view-shortcode/</link>
					<comments>https://piglife.tw/technical-notes/mepr-access-view-shortcode/#respond</comments>
		
		<dc:creator><![CDATA[小豬]]></dc:creator>
		<pubDate>Thu, 11 Dec 2025 22:20:38 +0000</pubDate>
				<category><![CDATA[技術筆記]]></category>
		<category><![CDATA[MemberPress]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://piglife.tw/technical-notes/mepr-access-view-shortcode/</guid>

					<description><![CDATA[介紹如何使用 WordPress 短碼結合 MemberPress 存取規則，根據會員權限動態顯示不...]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">前言</h2>
<p>在 WordPress 網站中，常見需求是根據使用者的會員權限來控制內容的顯示。這段 PHP 程式碼示範如何利用短碼（shortcode）結合 MemberPress 的存取規則，根據不同的權限狀態呈現不同內容。適合已有 WordPress 和 MemberPress 基礎，想自訂內容權限顯示的工程師或自學者。</p>
<h2 class="wp-block-heading">短碼設計與參數說明</h2>
<p>此短碼名稱為 <code>ks_mepr_access_view</code>，接受一個參數 <code>type</code>，用來決定顯示的內容類型：</p>
<ul>
<li><code>public</code>：公開內容，無需會員權限</li>
<li><code>member</code>：會員專屬內容，需通過 MemberPress 規則授權</li>
<li><code>noaccess</code>：未授權會員或訪客看到的內容</li>
</ul>
<p>使用範例：</p>
<pre><code class="lang-php language-php php">[ks_mepr_access_view type=&quot;member&quot;]會員專屬內容[/ks_mepr_access_view]</code></pre>
<h2 class="wp-block-heading">權限判斷流程解析</h2>
<h3 class="wp-block-heading">取得文章與規則</h3>
<pre><code class="lang-php language-php php">$post = get_post();
$rules = MeprRule::get_rules($post);</code></pre>
<p>先取得當前文章物件，再用 MemberPress API 取得該文章綁定的存取規則。</p>
<h3 class="wp-block-heading">判斷使用者登入與權限</h3>
<pre><code class="lang-php language-php php">$user = wp_get_current_user();
$is_logged_in = is_user_logged_in();
$has_access = false;

if ($is_logged_in &amp;&amp; !empty($rules)) {
    $member = new MeprUser($user-&gt;ID);
    foreach ($rules as $rule) {
        if ($member-&gt;has_access_from_rule($rule-&gt;ID)) {
            $has_access = true;
            break;
        }
    }
}</code></pre>
<p>確認使用者是否登入，且文章有設定規則，接著檢查使用者是否符合任一規則，若符合即標記為有存取權限。</p>
<h3 class="wp-block-heading">根據條件回傳內容</h3>
<pre><code class="lang-php language-php php">if (!$has_rule &amp;&amp; $type === &#039;public&#039;) {
    return do_shortcode($content); // 無保護，公開內容
}

if ($has_rule &amp;&amp; $has_access &amp;&amp; $type === &#039;member&#039;) {
    return do_shortcode($content); // 有保護且有權限，顯示會員內容
}

if ($has_rule &amp;&amp; !$has_access &amp;&amp; $type === &#039;noaccess&#039;) {
    return do_shortcode($content); // 有保護但無權限，顯示無權限內容
}

return &#039;&#039;;</code></pre>
<p>依據是否有規則、是否有權限，以及 <code>type</code> 參數決定是否顯示內容，未符合條件則回傳空字串。</p>
<h2 class="wp-block-heading">實務應用與擴充建議</h2>
<ul>
<li>可用於文章、頁面或自訂文章類型中，靈活控制不同會員等級的內容呈現。</li>
<li>可結合前端樣式或 JavaScript，提升使用者體驗，例如顯示提示訊息或引導登入。</li>
<li>若需更複雜的權限判斷，可擴充短碼參數，或搭配其他會員系統 API。</li>
</ul>
<h2 class="wp-block-heading">常見問題與注意事項</h2>
<ul>
<li>確保 MemberPress 已正確設定存取規則並綁定文章。</li>
<li>使用者權限判斷依賴 MemberPress API，若插件更新需確認相容性。</li>
<li>短碼內容內仍可使用其他短碼，<code>do_shortcode</code> 可確保內嵌短碼正常解析。</li>
</ul>
<h2 class="wp-block-heading">完整程式碼</h2>
<pre><code class="lang-php language-php php">&lt;?php
function ks_mepr_access_view_shortcode($atts, $content = null) {
    $atts = shortcode_atts([
        &#039;type&#039; =&gt; &#039;public&#039;, // 可為 public, member, noaccess
    ], $atts);

    $post = get_post();
    if (!$post) {
        return &#039;&#039;;
    }

    $type = strtolower($atts[&#039;type&#039;]);
    $rules = MeprRule::get_rules($post);
    $has_rule = !empty($rules);

    $user = wp_get_current_user();
    $is_logged_in = is_user_logged_in();
    $has_access = false;

    if ($is_logged_in &amp;&amp; $has_rule) {
        $member = new MeprUser($user-&gt;ID);
        foreach ($rules as $rule) {
            if ($member-&gt;has_access_from_rule($rule-&gt;ID)) {
                $has_access = true;
                break;
            }
        }
    }

    // 顯示條件
    if (!$has_rule &amp;&amp; $type === &#039;public&#039;) {
        return do_shortcode($content); // 沒有保護 &rarr; 公開
    }

    if ($has_rule &amp;&amp; $has_access &amp;&amp; $type === &#039;member&#039;) {
        return do_shortcode($content); // 有保護+通過 &rarr; 會員
    }

    if ($has_rule &amp;&amp; !$has_access &amp;&amp; $type === &#039;noaccess&#039;) {
        return do_shortcode($content); // 有保護+沒通過（不論登入與否）&rarr; 無權限
    }

    return &#039;&#039;;
}
add_shortcode(&#039;ks_mepr_access_view&#039;, &#039;ks_mepr_access_view_shortcode&#039;);</code></pre>]]></content:encoded>
					
					<wfw:commentRss>https://piglife.tw/technical-notes/mepr-access-view-shortcode/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
