<?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>複製功能 &#8211; 小豬日常</title>
	<atom:link href="https://piglife.tw/tag/%e8%a4%87%e8%a3%bd%e5%8a%9f%e8%83%bd/feed/" rel="self" type="application/rss+xml" />
	<link>https://piglife.tw</link>
	<description>Hello World，一個紀錄生活與學習的地方</description>
	<lastBuildDate>Fri, 19 Dec 2025 05:29:55 +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>複製功能 &#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-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>
	</channel>
</rss>
