200, 'width' => 200, 'flex-height' => true, 'flex-width' => true, ) ); // WooCommerce support. add_theme_support( 'woocommerce' ); add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); register_nav_menus( array( 'primary' => __( '主選單', 'ming' ), 'footer' => __( '頁腳選單', 'ming' ), 'footer_service' => __( '頁腳:顧客服務', 'ming' ), 'footer_guide' => __( '頁腳:購物指南', 'ming' ), 'footer_brand' => __( '頁腳:品牌探索', 'ming' ), ) ); } add_action( 'after_setup_theme', 'ming_setup' ); /** * Enqueue assets. */ function ming_assets() { // 自托管字體(v1.5.6 起替代 Google Fonts 外鏈,避免 fonts.googleapis.com 加載緩慢) wp_enqueue_style( 'ming-fonts', get_template_directory_uri() . '/assets/css/fonts.css', array(), MING_VERSION ); wp_enqueue_style( 'ming-main', get_template_directory_uri() . '/assets/css/main.css', array( 'ming-fonts' ), MING_VERSION ); wp_enqueue_script( 'ming-main', get_template_directory_uri() . '/assets/js/main.js', array(), MING_VERSION, true ); // Pass image directory to JS (product grid). wp_localize_script( 'ming-main', 'MING_ASSETS', get_template_directory_uri() . '/assets/img/' ); // Vote module data. wp_localize_script( 'ming-main', 'MING_VOTE', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'ming_vote' ), 'goal' => ming_vote_goal(), ) ); } add_action( 'wp_enqueue_scripts', 'ming_assets' ); /** * Theme favicon. */ function ming_favicon() { $asset_uri = get_template_directory_uri() . '/assets/img/'; echo '' . "\n"; echo '' . "\n"; echo '' . "\n"; echo '' . "\n"; } add_action( 'wp_head', 'ming_favicon' ); /** * Find a front-end URL for a page using a given template file. * * @param string $template Template file name, e.g. 'template-brand.php'. * @param string $fallback Fallback URL. * @return string */ function ming_page_url_by_template( $template, $fallback = '#' ) { $pages = get_pages( array( 'meta_key' => '_wp_page_template', 'meta_value' => $template, 'number' => 1, ) ); if ( ! empty( $pages ) ) { return get_permalink( $pages[0]->ID ); } return $fallback; } /** * WooCommerce shop URL (or home fallback when WooCommerce is inactive). * * @return string */ function ming_shop_url() { if ( function_exists( 'wc_get_page_permalink' ) ) { return wc_get_page_permalink( 'shop' ); } return home_url( '/' ); } /** * Customizer: brand contact & social links. * * @param WP_Customize_Manager $wp_customize Customizer manager. */ function ming_customize_register( $wp_customize ) { $wp_customize->add_section( 'ming_brand', array( 'title' => __( 'MING 品牌資訊與社群', 'ming' ), 'description' => __( '頁腳的公司資訊、客服信箱與社群連結。留空的社群連結不會顯示。', 'ming' ), 'priority' => 30, ) ); $fields = array( 'ming_company' => array( __( '公司名稱', 'ming' ), '明序有限公司', 'text' ), 'ming_email' => array( __( '客服信箱', 'ming' ), '', 'email' ), 'ming_phone' => array( __( '客服電話', 'ming' ), '', 'text' ), 'ming_address' => array( __( '公司地址', 'ming' ), '', 'text' ), 'ming_taxid' => array( __( '統一編號', 'ming' ), '', 'text' ), 'ming_facebook' => array( __( 'Facebook 網址', 'ming' ), '', 'url' ), 'ming_instagram' => array( __( 'Instagram 網址', 'ming' ), '', 'url' ), 'ming_line' => array( __( 'LINE 官方帳號網址', 'ming' ), '', 'url' ), 'ming_vote_goal' => array( __( '許願池目標人數', 'ming' ), '100', 'number' ), ); foreach ( $fields as $id => $field ) { $wp_customize->add_setting( $id, array( 'default' => $field[1], 'sanitize_callback' => ( 'url' === $field[2] ) ? 'esc_url_raw' : ( ( 'email' === $field[2] ) ? 'sanitize_email' : 'sanitize_text_field' ), ) ); $wp_customize->add_control( $id, array( 'section' => 'ming_brand', 'label' => $field[0], 'type' => ( 'text' === $field[2] ) ? 'text' : $field[2], ) ); } // 品牌宣傳片(首頁影片區塊). $wp_customize->add_section( 'ming_video', array( 'title' => __( 'MING 品牌宣傳片', 'ming' ), 'description' => __( '首頁影片區塊。支援 YouTube 連結或後台媒體庫上傳的 mp4,站內播放不跳轉。', 'ming' ), 'priority' => 31, ) ); $wp_customize->add_setting( 'ming_video_enable', array( 'default' => '', 'sanitize_callback' => 'wp_validate_boolean' ) ); $wp_customize->add_control( 'ming_video_enable', array( 'section' => 'ming_video', 'label' => __( '啟用宣傳片區塊', 'ming' ), 'type' => 'checkbox' ) ); $wp_customize->add_setting( 'ming_video_source', array( 'default' => 'youtube', 'sanitize_callback' => 'sanitize_text_field' ) ); $wp_customize->add_control( 'ming_video_source', array( 'section' => 'ming_video', 'label' => __( '影片來源', 'ming' ), 'type' => 'radio', 'choices' => array( 'youtube' => __( 'YouTube 連結', 'ming' ), 'mp4' => __( '本機 mp4(媒體庫網址)', 'ming' ), ), ) ); $wp_customize->add_setting( 'ming_video_url', array( 'default' => '', 'sanitize_callback' => 'esc_url_raw' ) ); $wp_customize->add_control( 'ming_video_url', array( 'section' => 'ming_video', 'label' => __( '影片網址', 'ming' ), 'type' => 'url' ) ); $wp_customize->add_setting( 'ming_video_title', array( 'default' => '', 'sanitize_callback' => 'sanitize_text_field' ) ); $wp_customize->add_control( 'ming_video_title', array( 'section' => 'ming_video', 'label' => __( '標題', 'ming' ), 'type' => 'text' ) ); $wp_customize->add_setting( 'ming_video_desc', array( 'default' => '', 'sanitize_callback' => 'sanitize_textarea_field' ) ); $wp_customize->add_control( 'ming_video_desc', array( 'section' => 'ming_video', 'label' => __( '介紹詞', 'ming' ), 'type' => 'textarea' ) ); } add_action( 'customize_register', 'ming_customize_register' ); /** * AJAX cart fragment: keep the header cart count in sync. * * @param array $fragments Fragments. * @return array */ function ming_cart_count_fragment( $fragments ) { ob_start(); ?> cart->get_cart_contents_count() ); ?> __( '關於我們', 'ming' ), ming_page_url_by_template( 'template-ceramic.php', $base . '#core' ) => __( '陶瓷內膽', 'ming' ), ming_page_url_by_template( 'template-journal.php', $base . '#philosophy' ) => __( '品牌專欄', 'ming' ), ming_shop_url() => __( '全部商品', 'ming' ), ming_page_url_by_template( 'template-vote.php', $base . '#top' ) => __( '新品許願', 'ming' ), $base . '#contact' => __( '聯絡我們', 'ming' ), ); $out = ''; foreach ( $items as $url => $label ) { $out .= '' . esc_html( $label ) . ''; } return $out; } /* ============================================================ 投票活動系統(多活動版) ============================================================ */ function ming_vote_goal() { return max( 1, absint( get_theme_mod( 'ming_vote_goal', 100 ) ) ); } function ming_campaign_expired( $cid ) { $d = get_post_meta( $cid, 'ming_deadline', true ); return $d && strtotime( $d ) < current_time( 'timestamp' ); } function ming_campaign_total_votes( $cid ) { $total = 0; foreach ( (array) get_post_meta( $cid, 'ming_products', true ) as $pid ) { $total += (int) get_post_meta( $pid, 'ming_votes_c' . $cid, true ); } return $total; } /* 商品顏色(嚴格取自商品顏色屬性 / 變體 SKU 選項) */ function ming_product_colors( $prod ) { $hexmap = array( '黑' => '#2B2B26', '白' => '#F3EDE2', '米' => '#F3EDE2', '粉' => '#F0C7CE', '紅' => '#C0392B', '黃' => '#F2C230', '藍' => '#A8CBE0', '綠' => '#CFE3CE', '薄荷' => '#A8D8D2', '紫' => '#B9B1D6', '灰' => '#C0C4C0', '銀' => '#C9CDD2', '橘' => '#E8A04C', '金' => '#BFA06A', '棕' => '#8B6B4A', ); $names = array(); foreach ( $prod->get_attributes() as $att ) { $label = $att->is_taxonomy() ? wc_attribute_label( $att->get_name() ) : $att->get_name(); if ( ! preg_match( '/色|顏色|color|colour/iu', $label ) ) { continue; } foreach ( preg_split( '/[,,、|]/u', $prod->get_attribute( $att->get_name() ) ) as $n ) { $n = trim( $n ); if ( '' !== $n ) { $names[] = $n; } } } $out = array(); foreach ( array_unique( $names ) as $n ) { $hex = '#D8D2C4'; foreach ( $hexmap as $k => $v ) { if ( false !== mb_strpos( $n, $k ) ) { $hex = $v; break; } } $out[] = array( 'name' => $n, 'hex' => $hex ); } return array_slice( $out, 0, 6 ); } /** * 商品顏色選項(含變體圖與預選連結):色卡點點切換卡片圖用。 * 優先取 pa_color 變體(圖=變體圖,連結帶 ?attribute_pa_color= 預選);無變體時退回 ming_product_colors()。 */ function ming_product_color_options( $prod ) { $opts = array(); if ( $prod && $prod->is_type( 'variable' ) ) { // 不用 get_available_variations():它會略過未定價變體,許願池需要全部顏色. foreach ( $prod->get_children() as $vid ) { $var = wc_get_product( $vid ); if ( ! $var || 'publish' !== $var->get_status() ) { continue; } $aval = $var->get_attribute( 'pa_color' ); // 可能回傳 term name 或 slug. if ( '' === $aval ) { continue; } $term = get_term_by( 'slug', $aval, 'pa_color' ); if ( ! $term ) { $term = get_term_by( 'name', $aval, 'pa_color' ); } if ( ! $term ) { continue; } $hex = get_term_meta( $term->term_id, 'ming_hex', true ); $img = $var->get_image_id() ? wp_get_attachment_image_url( $var->get_image_id(), 'medium_large' ) : ''; $opts[] = array( 'name' => $term->name, 'slug' => $term->slug, 'hex' => $hex ? $hex : '#D8D2C4', 'img' => $img ? $img : '', 'vid' => (int) $vid, 'link' => add_query_arg( 'attribute_pa_color', $term->slug, get_permalink( $prod->get_id() ) ), ); } } if ( ! $opts ) { foreach ( ming_product_colors( $prod ) as $c ) { $c['slug'] = ''; $c['img'] = ''; $c['vid'] = 0; $c['link'] = get_permalink( $prod->get_id() ); $opts[] = $c; } } return $opts; } /* ---------- 投票活動 CPT(掛在「產品」選單下) ---------- */ add_action( 'init', 'ming_register_campaign' ); function ming_register_campaign() { register_post_type( 'ming_campaign', array( 'labels' => array( 'name' => __( '投票活動', 'ming' ), 'singular_name' => __( '投票活動', 'ming' ), 'add_new_item' => __( '發起新投票活動', 'ming' ), 'edit_item' => __( '編輯投票活動', 'ming' ), ), 'public' => true, 'show_in_menu' => 'edit.php?post_type=product', 'supports' => array( 'title' ), 'rewrite' => array( 'slug' => 'campaign' ), 'show_in_rest' => false, ) ); } /* ---------- 活動設定與投票情況(編輯畫面) ---------- */ add_action( 'add_meta_boxes', 'ming_campaign_metaboxes' ); function ming_campaign_metaboxes() { add_meta_box( 'ming_campaign_settings', __( '活動設定', 'ming' ), 'ming_campaign_settings_box', 'ming_campaign', 'normal', 'high' ); add_meta_box( 'ming_campaign_stats', __( '投票情況', 'ming' ), 'ming_campaign_stats_box', 'ming_campaign', 'normal', 'default' ); } function ming_campaign_settings_box( $post ) { wp_nonce_field( 'ming_campaign_save', 'ming_campaign_nonce' ); $goal = get_post_meta( $post->ID, 'ming_goal', true ); $goal = $goal ? $goal : ming_vote_goal(); $deadline = get_post_meta( $post->ID, 'ming_deadline', true ); $selected = array_map( 'intval', (array) get_post_meta( $post->ID, 'ming_products', true ) ); echo '

'; echo '

'; echo '

參與商品

'; $prods = get_posts( array( 'post_type' => 'product', 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'title', 'order' => 'ASC' ) ); foreach ( $prods as $p ) { printf( '', $p->ID, checked( in_array( $p->ID, $selected, true ), true, false ), esc_html( $p->post_title ) ); } if ( ! $prods ) { echo '

尚無商品。

'; } echo '
'; } function ming_campaign_stats_box( $post ) { $products = array_map( 'intval', (array) get_post_meta( $post->ID, 'ming_products', true ) ); if ( ! $products ) { echo '

儲存並選擇參與商品後,這裡會顯示投票統計。

'; return; } $goal = (int) get_post_meta( $post->ID, 'ming_goal', true ); $goal = $goal ? $goal : ming_vote_goal(); echo ''; $total = 0; foreach ( $products as $pid ) { $v = (int) get_post_meta( $pid, 'ming_votes_c' . $post->ID, true ); $total += $v; $pct = min( 100, round( $v / $goal * 100 ) ); echo ''; $prod = wc_get_product( $pid ); if ( $prod && $prod->is_type( 'variable' ) ) { foreach ( $prod->get_children() as $vid ) { $sv = (int) get_post_meta( $vid, 'ming_votes_c' . $post->ID, true ); $color = wc_get_product( $vid ); $cname = $color ? $color->get_attribute( 'pa_color' ) : ''; echo ''; } } } echo '
商品 / 顏色票數目標達成
' . esc_html( get_the_title( $pid ) ) . '' . $v . '' . $pct . '%
└ ' . esc_html( $cname ? $cname : ( '變體 #' . $vid ) ) . '' . $sv . '
總票數' . $total . '
'; $voters = get_users( array( 'meta_key' => 'ming_voted_c' . $post->ID, 'fields' => array( 'ID', 'display_name', 'user_email' ) ) ); if ( $voters ) { echo '

投票會員(' . count( $voters ) . ')

'; foreach ( $voters as $u ) { $voted = (array) get_user_meta( $u->ID, 'ming_voted_c' . $post->ID, true ); $names = array(); foreach ( $voted as $e ) { $parts = explode( ':', (string) $e ); $label = get_the_title( (int) $parts[0] ); if ( ! empty( $parts[1] ) ) { $vp = wc_get_product( (int) $parts[1] ); if ( $vp ) { $label .= '(' . $vp->get_attribute( 'pa_color' ) . ')'; } } $names[] = $label; } echo ''; } echo '
會員信箱支持的商品
' . esc_html( $u->display_name ) . '' . esc_html( $u->user_email ) . '' . esc_html( implode( '、', $names ) ) . '
'; } else { echo '

尚無會員投票。

'; } } add_action( 'save_post_ming_campaign', 'ming_save_campaign' ); function ming_save_campaign( $post_id ) { if ( ! isset( $_POST['ming_campaign_nonce'] ) || ! wp_verify_nonce( $_POST['ming_campaign_nonce'], 'ming_campaign_save' ) ) { return; } if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } if ( ! current_user_can( 'edit_post', $post_id ) ) { return; } update_post_meta( $post_id, 'ming_goal', max( 1, absint( $_POST['ming_goal'] ?? ming_vote_goal() ) ) ); $deadline = sanitize_text_field( $_POST['ming_deadline'] ?? '' ); update_post_meta( $post_id, 'ming_deadline', $deadline ); $products = array_map( 'absint', (array) ( $_POST['ming_products'] ?? array() ) ); update_post_meta( $post_id, 'ming_products', $products ); } /* ---------- 列表欄位 ---------- */ add_filter( 'manage_ming_campaign_posts_columns', function( $cols ) { $cols['ming_goal'] = __( '目標人數', 'ming' ); $cols['ming_deadline'] = __( '截止時間', 'ming' ); $cols['ming_items'] = __( '參與商品', 'ming' ); $cols['ming_votes'] = __( '總票數', 'ming' ); $cols['ming_status'] = __( '狀態', 'ming' ); unset( $cols['date'] ); return $cols; } ); add_action( 'manage_ming_campaign_posts_custom_column', function( $col, $post_id ) { if ( 'ming_goal' === $col ) { echo esc_html( get_post_meta( $post_id, 'ming_goal', true ) ?: ming_vote_goal() ); } if ( 'ming_deadline' === $col ) { $d = get_post_meta( $post_id, 'ming_deadline', true ); echo $d ? esc_html( date_i18n( 'Y/m/d H:i', strtotime( $d ) ) ) : '長期'; } if ( 'ming_items' === $col ) { echo count( (array) get_post_meta( $post_id, 'ming_products', true ) ); } if ( 'ming_votes' === $col ) { echo ming_campaign_total_votes( $post_id ); } if ( 'ming_status' === $col ) { echo ming_campaign_expired( $post_id ) ? '🔒 已截止' : '🟢 進行中'; } }, 10, 2 ); /* ---------- 投票 AJAX ---------- */ add_action( 'wp_ajax_ming_vote', 'ming_handle_vote' ); add_action( 'wp_ajax_nopriv_ming_vote', 'ming_handle_vote' ); function ming_handle_vote() { check_ajax_referer( 'ming_vote', 'nonce' ); if ( ! is_user_logged_in() ) { $login = function_exists( 'wc_get_page_permalink' ) ? wc_get_page_permalink( 'myaccount' ) : wp_login_url(); wp_send_json_error( array( 'need_login' => true, 'login_url' => $login ) ); } $cid = isset( $_POST['campaign_id'] ) ? absint( $_POST['campaign_id'] ) : 0; $pid = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0; if ( ! $cid || 'ming_campaign' !== get_post_type( $cid ) || 'publish' !== get_post_status( $cid ) ) { wp_send_json_error( array( 'message' => 'invalid_campaign' ) ); } if ( ming_campaign_expired( $cid ) ) { wp_send_json_error( array( 'message' => 'closed' ) ); } if ( ! in_array( $pid, array_map( 'intval', (array) get_post_meta( $cid, 'ming_products', true ) ), true ) ) { wp_send_json_error( array( 'message' => 'invalid_product' ) ); } $vid = isset( $_POST['variation_id'] ) ? absint( $_POST['variation_id'] ) : 0; if ( $vid && ( 'product_variation' !== get_post_type( $vid ) || (int) wp_get_post_parent_id( $vid ) !== $pid ) ) { $vid = 0; // 變體必須屬於該商品,否則視為未選色. } $uid = get_current_user_id(); $ukey = 'ming_voted_c' . $cid; $pkey = 'ming_votes_c' . $cid; $voted_raw = (array) get_user_meta( $uid, $ukey, true ); $voted = array(); foreach ( $voted_raw as $e ) { $voted[] = (int) explode( ':', (string) $e )[0]; } // 相容舊純 pid 與新 pid:vid 格式. $count = (int) get_post_meta( $pid, $pkey, true ); if ( in_array( $pid, $voted, true ) ) { wp_send_json_error( array( 'already' => true, 'count' => $count ) ); } $count++; update_post_meta( $pid, $pkey, $count ); $sku_count = 0; if ( $vid ) { $sku_count = (int) get_post_meta( $vid, $pkey, true ) + 1; update_post_meta( $vid, $pkey, $sku_count ); } $voted_raw[] = $vid ? ( $pid . ':' . $vid ) : (string) $pid; update_user_meta( $uid, $ukey, $voted_raw ); wp_send_json_success( array( 'count' => $count, 'vid' => $vid, 'sku_count' => $sku_count ) ); } /* ---------- 舊投票資料遷移到預設活動 ---------- */ add_action( 'init', 'ming_migrate_votes', 20 ); function ming_migrate_votes() { if ( get_option( 'ming_votes_migrated' ) ) { return; } if ( ! post_type_exists( 'product' ) ) { return; } if ( get_posts( array( 'post_type' => 'ming_campaign', 'posts_per_page' => 1, 'fields' => 'ids', 'post_status' => 'any' ) ) ) { update_option( 'ming_votes_migrated', 1 ); return; } $old = get_posts( array( 'post_type' => 'product', 'posts_per_page' => -1, 'meta_key' => 'ming_votes', 'fields' => 'ids', 'post_status' => 'any' ) ); if ( ! $old ) { update_option( 'ming_votes_migrated', 1 ); return; } $all = get_posts( array( 'post_type' => 'product', 'posts_per_page' => -1, 'post_status' => 'publish', 'fields' => 'ids' ) ); $cid = wp_insert_post( array( 'post_type' => 'ming_campaign', 'post_title' => '新品許願(第一期)', 'post_status' => 'publish' ) ); if ( ! $cid ) { return; } update_post_meta( $cid, 'ming_goal', ming_vote_goal() ); update_post_meta( $cid, 'ming_products', array_map( 'intval', $all ) ); foreach ( $old as $pid ) { update_post_meta( $pid, 'ming_votes_c' . $cid, (int) get_post_meta( $pid, 'ming_votes', true ) ); } foreach ( get_users( array( 'meta_key' => 'ming_voted_products', 'fields' => 'ID' ) ) as $uid ) { $v = (array) get_user_meta( $uid, 'ming_voted_products', true ); if ( $v ) { update_user_meta( $uid, 'ming_voted_c' . $cid, array_map( 'intval', $v ) ); } } update_option( 'ming_votes_migrated', 1 ); } /* ---------- 訂單詳情頁顯示銀行匯款資訊 ---------- */ add_action( 'woocommerce_view_order', 'ming_bacs_on_view_order', 5 ); function ming_bacs_on_view_order( $order_id ) { $order = wc_get_order( $order_id ); // bacs 下單後訂單狀態為「保留」(on-hold),needs_payment() 只對待付款/失敗有效,故改用 is_paid() 反向判斷 if ( ! $order || 'bacs' !== $order->get_payment_method() || $order->is_paid() ) { return; } $gateway = new WC_Gateway_BACS(); echo '

匯款資訊

'; if ( $gateway->instructions ) { echo wpautop( wptexturize( $gateway->instructions ) ); } $accounts = $gateway->account_details; if ( $accounts ) { echo ''; } echo '

請於匯款時備註訂單編號 #' . esc_html( $order->get_order_number() ) . ',確認到帳後我們將盡快為您出貨。

'; } /* 投票會員 meta 註冊(便於管理員校正) */ add_action( 'init', function() { foreach ( get_posts( array( 'post_type' => 'ming_campaign', 'posts_per_page' => -1, 'post_status' => 'any', 'fields' => 'ids' ) ) as $cid ) { register_meta( 'user', 'ming_voted_c' . $cid, array( 'type' => 'array', 'single' => true, 'show_in_rest' => array( 'schema' => array( 'type' => 'array', 'items' => array( 'type' => 'integer' ) ) ), 'auth_callback' => function( $a, $k, $uid ) { return current_user_can( 'edit_user', $uid ); }, ) ); } }, 25 ); /* ============================================================ * v1.6.0 — 宣傳片 / 搜尋 / 頁腳三欄 * ==========================================================*/ /** * 解析 YouTube 影片 ID(支援 watch、youtu.be、shorts、embed 等格式)。 * * @param string $url YouTube 網址。 * @return string */ function ming_youtube_id( $url ) { if ( preg_match( '~(?:youtube\.com/(?:watch\?.*v=|shorts/|embed/)|youtu\.be/)([A-Za-z0-9_-]{6,20})~', $url, $m ) ) { return $m[1]; } return ''; } /** * 商品列表卡片上的供貨狀態標籤(現貨/缺貨)——以絕對定位貼在商品圖左上角。 */ function ming_loop_stock_badge() { global $product; if ( ! $product || ! is_a( $product, 'WC_Product' ) ) { return; } if ( $product->is_in_stock() ) { echo '現貨'; } else { echo '缺貨'; } } add_action( 'woocommerce_before_shop_loop_item_title', 'ming_loop_stock_badge', 9 ); /** * 頁腳資訊頁占位文案(繁中草稿,後台可自由修改)。 * * @return array slug => [標題, 內容HTML] */ function ming_info_pages() { return array( 'contact-us' => array( '聯絡我們', '

任何商品、訂單或合作問題,都歡迎與我們聯繫,我們將於 1–2 個工作日內回覆。

' . '

客服管道

' . '

大宗採購與企業禮贈

企業禮贈、經銷通路與大宗訂購需求,請來信註明公司名稱、需求數量與預計用途,我們將由專人與您聯繫報價。

', ), 'faq' => array( '常見問題 FAQ', '

陶瓷內膽可以裝碳酸飲料或牛奶嗎?

可以。純陶瓷內膽抗酸鹼、耐腐蝕、不殘留味道,咖啡、茶、果汁、牛奶都適用;使用後建議儘快清洗晾乾。

' . '

如何清洗與保養?

杯蓋、吸管與內膽皆可拆洗;請以中性清潔劑與海綿手洗,勿使用鋼刷,勿放入洗碗機高溫烘碗模式。

' . '

下單後多久出貨?

現貨商品於確認款項後 1–3 個工作日內出貨;如遇缺貨或預購,商品頁會標示預計出貨時間。

' . '

如何查詢訂單?

登入會員後可於「會員中心 → 訂單」查詢訂單狀態與匯款資訊。

', ), 'ordering-guide' => array( '訂購須知', '

購物流程

選購商品 → 加入購物車 → 填寫收件資料 → 選擇付款方式 → 完成訂單。未註冊會員亦可直接結帳。

' . '

付款方式

目前提供銀行轉帳(ATM/網銀匯款)。下單後請於訂單頁面查看匯款帳號,並於 3 日內完成轉帳,逾期訂單將自動取消。

' . '

運費與配送

台灣本島常溫配送,運費依結帳頁顯示為準;離島與海外配送請先來信詢問。

', ), 'return-policy' => array( '退換貨政策', '

七天鑑賞期

依消費者保護法,商品到貨翌日起算七天內可申請退貨(鑑賞期非試用期)。退回商品須為全新未使用、包裝完整。

' . '

不適用退貨之情形

' . '

瑕疵與換貨

如收到瑕疵品或寄錯商品,請於到貨七天內拍照並來信客服信箱,我們將安排換貨並負擔來回運費。

' . '

退款方式

退貨驗收完成後 7–10 個工作日內,依原付款方式退款。

', ), 'privacy-policy' => array( '隱私權政策', '

MING 明命(以下稱本公司)重視您的個人資料保護,本政策說明我們如何蒐集、使用與保護您的資料。

' . '

資料蒐集

您於本站註冊、訂購或參與活動時,我們會蒐集姓名、聯絡方式、收件地址與訂單紀錄等必要資料。

' . '

使用目的

僅用於訂單處理、配送、售後服務與會員活動通知;未經您同意,不會提供予無關之第三方。

' . '

資料安全與權利

您的資料將以合理之技術與程序保護。您可隨時聯繫客服查詢、更正或請求刪除您的個人資料。

', ), ); } /** * 主題啟用時:建立頁腳資訊頁與三欄頁腳選單(已存在則跳過)。 */ function ming_seed_footer_content() { $pages = ming_info_pages(); $pageid = array(); foreach ( $pages as $slug => $p ) { $exist = get_page_by_path( $slug ); if ( $exist ) { $pageid[ $slug ] = $exist->ID; continue; } $pageid[ $slug ] = wp_insert_post( array( 'post_type' => 'page', 'post_status' => 'publish', 'post_name' => $slug, 'post_title' => $p[0], 'post_content' => $p[1], ) ); } $menus = array( 'footer_service' => array( '顧客服務', array( 'contact-us', 'faq' ) ), 'footer_guide' => array( '購物指南', array( 'ordering-guide', 'return-policy', 'privacy-policy' ) ), 'footer_brand' => array( '品牌探索', array() ), // 品牌探索改以自訂連結建置. ); $locations = get_theme_mod( 'nav_menu_locations', array() ); foreach ( $menus as $loc => $m ) { if ( ! empty( $locations[ $loc ] ) && wp_get_nav_menu_object( $locations[ $loc ] ) ) { continue; } $menu_id = wp_create_nav_menu( $m[0] ); if ( is_wp_error( $menu_id ) ) { continue; } foreach ( $m[1] as $slug ) { if ( ! empty( $pageid[ $slug ] ) && ! is_wp_error( $pageid[ $slug ] ) ) { wp_update_nav_menu_item( $menu_id, 0, array( 'menu-item-object-id' => $pageid[ $slug ], 'menu-item-object' => 'page', 'menu-item-type' => 'post_type', 'menu-item-status' => 'publish', ) ); } } $locations[ $loc ] = $menu_id; } // 品牌探索:品牌故事 / 陶瓷內膽 / 新品許願(自訂連結,依模板找頁面). $brand_menu = isset( $locations['footer_brand'] ) ? $locations['footer_brand'] : 0; if ( $brand_menu && ! wp_get_nav_menu_items( $brand_menu ) ) { $links = array( '品牌故事' => ming_page_url_by_template( 'template-brand.php', home_url( '/' ) . '#philosophy' ), '陶瓷內膽' => ming_page_url_by_template( 'template-ceramic.php', home_url( '/' ) . '#core' ), '新品許願' => ming_page_url_by_template( 'template-vote.php', home_url( '/' ) ), '線上商店' => ming_shop_url(), ); foreach ( $links as $title => $url ) { wp_update_nav_menu_item( $brand_menu, 0, array( 'menu-item-title' => $title, 'menu-item-url' => $url, 'menu-item-type' => 'custom', 'menu-item-status' => 'publish', ) ); } } set_theme_mod( 'nav_menu_locations', $locations ); } add_action( 'after_switch_theme', 'ming_seed_footer_content' ); /* ============================================================ * v1.7.0 — OG 標籤 / 許願分享 / 品牌專欄 / SKU 色卡 / 海報生成器 * ==========================================================*/ /* ---------- OG / Twitter 分享標籤 ---------- */ add_action( 'wp_head', 'ming_og_tags', 5 ); function ming_og_tags() { if ( is_admin() || ( function_exists( 'is_embed' ) && is_embed() ) ) { return; } $site = get_bloginfo( 'name' ); $title = wp_get_document_title(); $desc = ''; $type = 'website'; $url = home_url( '/' ); $image = get_template_directory_uri() . '/assets/img/hero.jpg'; if ( is_singular() ) { $p = get_post(); if ( $p ) { $url = get_permalink( $p ); $desc = trim( wp_strip_all_tags( $p->post_excerpt ? $p->post_excerpt : $p->post_content ) ); $desc = mb_substr( $desc, 0, 120 ); if ( has_post_thumbnail( $p ) ) { $thumb = get_the_post_thumbnail_url( $p, 'large' ); if ( $thumb ) { $image = $thumb; } } } if ( function_exists( 'is_product' ) && is_product() ) { $type = 'product'; $prod = wc_get_product( get_the_ID() ); if ( $prod && '' === $desc ) { $desc = trim( wp_strip_all_tags( $prod->get_short_description() ) ); } } elseif ( is_single() ) { $type = 'article'; } } elseif ( is_front_page() || is_home() ) { $desc = get_bloginfo( 'description' ); if ( '' === $desc ) { $desc = '以時間為序、以土地為根、以光明為引。MING 明命純陶瓷內膽杯,連結人與自然、生活與生命。'; } } if ( '' === $desc ) { $desc = $site; } if ( $image && 0 === strpos( $image, '/' ) ) { $image = home_url( $image ); } echo "\n\n"; echo '' . "\n"; echo '' . "\n"; echo '' . "\n"; echo '' . "\n"; echo '' . "\n"; echo '' . "\n"; if ( $image ) { echo '' . "\n"; } echo '' . "\n"; echo '' . "\n"; echo '' . "\n"; if ( $image ) { echo '' . "\n"; } echo "\n"; } /* ---------- 社群分享列(LINE / Facebook / 複製連結 / 原生分享) ---------- */ /** * 輸出分享按鈕列。行動裝置上由 JS 顯示系統原生分享按鈕。 * * @param array $args { title, text, url, label } */ function ming_share_bar( $args = array() ) { $args = wp_parse_args( $args, array( 'title' => wp_get_document_title(), 'text' => '', 'url' => get_permalink(), 'label' => __( '分享', 'ming' ), ) ); $url = $args['url'] ? $args['url'] : home_url( add_query_arg( null, null ) ); $title = $args['title']; $text = $args['text'] ? $args['text'] : $title; ?>
LINE Facebook
array( array( 'slug' => 'why-ceramic-liner', 'title' => '為什麼是陶瓷?—— 一只內膽杯的起點', 'imgkey' => '雙用', 'body' => '

每個品牌都有自己的起點。MING 的起點,來自一個很日常的困擾:保溫杯裡的金屬味。

咖啡放進不鏽鋼杯,下午再喝,總有一絲說不上來的味道;裝過牛奶的杯子,隔天泡茶,茶香裡總混著別的氣息。我們開始問:有沒有一種材質,可以忠實地裝下每一種飲品,又什麼都不留下?

答案是陶瓷。純陶瓷內膽抗酸鹼、耐腐蝕,不與飲品產生反應,也不殘留味道——裝過手沖咖啡的杯子,洗淨後再裝冷泡茶,味道依然乾淨。

讓器物退到飲品後面,是我們對「好杯子」的定義。

於是我們把陶瓷放進雙層真空結構裡,讓它既有陶瓷的純淨,也有保溫杯的鎖溫能力。這是 MING 的第一只杯子,也是我們想一直做下去的事。

', ), array( 'slug' => 'meaning-of-ming', 'title' => '明與命:我們給品牌取名字的緣由', 'imgkey' => '漸層', 'body' => '

MING,取自「明」與「命」。

「明」,是光明,是看見,是對美好生活的嚮往;「命」,是生命,是人與自然之間的連結,也是萬物生生不息的延續。

品牌標誌的外圓,以時鐘與田地為意象——時間的流轉與土地的循環。我們相信,器物不是孤立的存在:一只杯子連結著裝進去的茶葉與咖啡豆,連結著種出它們的土地,也連結著使用它的每一個日常。

以時間為序,以土地為根,以光明為引。

這句話寫在 MING 的首頁,也寫在每一只杯子的設計裡。希望它陪伴你的每一天,都明亮而有根。

', ), ), 'media-coverage' => array( array( 'slug' => 'media-plastic-free-trend', 'title' => '媒體報導|陶瓷內膽杯成為減塑新寵,MING 以「不串味」突圍', 'imgkey' => '冰霸', 'body' => '

(範例報導稿,實際上刊後可替換為媒體原文與連結)

隨著自帶杯政策與減塑意識升溫,隨行杯市場競爭激烈。近期主打「純陶瓷內膽」的品牌 MING 明命受到關注:有別於市售不鏽鋼內膽,陶瓷材質不殘留味道,裝咖啡、茶飲、牛奶都不串味,成為許多手搖飲愛好者的新選擇。

報導指出,MING 產品線涵蓋大容量冰霸杯、泡茶直飲雙用杯與輕巧迷你杯,全系列內膽皆可拆洗,瞄準「味道潔癖」與「好清洗」兩大痛點。

「讓每一杯飲品,都喝得到它本來的味道。」——報導引述品牌理念

未來若有媒體正式報導,可於後台直接編輯本篇,換上真實媒體名稱、日期與原文連結。

', ), array( 'slug' => 'media-brand-interview', 'title' => '媒體專訪|從辦公室水杯到生活儀式:MING 的品牌觀察', 'imgkey' => '隨行', 'body' => '

(範例專訪稿,實際上刊後可替換為媒體原文與連結)

「杯子是少數一天會被使用好幾次的器物。」訪談中,MING 團隊談起品牌觀察:早上的咖啡、午後的茶、運動後的冰水——杯子參與了生活的每一段節奏,卻很少被認真對待。

因此 MING 在設計上格外重視「長期使用」的細節:抗酸鹼的陶瓷內膽、可全拆洗的結構、一提就走的提把。團隊表示,未來也將透過「新品許願池」邀請使用者投票決定下一批新品的顏色與容量,讓產品從使用者的日常裡長出來。

未來若有正式專訪刊出,可於後台直接編輯本篇內容。

', ), ), 'unboxing' => array( array( 'slug' => 'unboxing-bingba-1000', 'title' => '開箱|冰霸杯:裝得下整個下午的冰美式', 'imgkey' => '冰霸', 'body' => '

(範例開箱文,歡迎於後台替換為真實使用者心得)

第一眼的印象是「大」。寬口設計讓加冰塊變得很從容,整杯手搖飲倒進去也沒問題。提把是一體成型的加粗設計,裝滿水提起來不勒手,從辦公室走到健身房一路拎著也不彆扭。

最驚豔的是內膽。下午裝了冰美式,晚上洗淨後裝溫水——完全沒有咖啡味殘留。陶瓷內膽摸起來是溫潤的霧面質感,和金屬內膽的冰冷感很不一樣。

', ), array( 'slug' => 'unboxing-brew-tea-week', 'title' => '實測|雙用杯泡茶一週心得:清洗真的不麻煩嗎?', 'imgkey' => '雙用', 'body' => '

(範例開箱文,歡迎於後台替換為真實使用者心得)

這一週我把辦公室的馬克杯換成 MING 雙用杯,早上直飲裝拿鐵,下午換上茶倉泡烏龍。重力鎖水的設計讓直飲與泡茶兩種模式切換很直覺,不用額外帶茶具。

原本最擔心的是清洗——茶垢向來是杯子的敵人。實測一週下來,陶瓷內膽用海綿加中性清潔劑就能洗淨,沒有用到科技海綿,也沒有殘留茶色。窄縫處搭配附贈概念中的小刷具會更徹底。

', ), ), ); } /** * v1.7.0 主題啟用種子:專欄分類 + 範例文章 + 專欄頁 + 關於我們更名 + 選單入口。 */ function ming_seed_journal_content() { // 1. 三個分類. $cats = array( 'brand-journal' => '品牌專欄', 'media-coverage' => '媒體報導', 'unboxing' => '開箱評測', ); $cat_ids = array(); foreach ( $cats as $slug => $name ) { $cat = get_category_by_slug( $slug ); if ( $cat ) { $cat_ids[ $slug ] = $cat->term_id; } else { $term = wp_insert_term( $name, 'category', array( 'slug' => $slug ) ); if ( ! is_wp_error( $term ) ) { $cat_ids[ $slug ] = $term['term_id']; } } } // 2. 專欄頁(模板:template-journal.php). $journal_page = get_page_by_path( 'journal' ); if ( ! $journal_page ) { $journal_id = wp_insert_post( array( 'post_type' => 'page', 'post_status' => 'publish', 'post_name' => 'journal', 'post_title' => '品牌專欄', 'post_content' => '', ) ); } else { $journal_id = $journal_page->ID; } if ( $journal_id && ! is_wp_error( $journal_id ) ) { update_post_meta( $journal_id, '_wp_page_template', 'template-journal.php' ); } // 3. 收集商品特色圖(供範例文章配圖). $thumb_by_key = array(); $thumb_pool = array(); if ( post_type_exists( 'product' ) ) { foreach ( get_posts( array( 'post_type' => 'product', 'posts_per_page' => -1, 'post_status' => 'publish' ) ) as $pp ) { $tid = (int) get_post_thumbnail_id( $pp->ID ); if ( ! $tid ) { continue; } $thumb_pool[] = $tid; foreach ( array( '冰霸', '雙用', '小清新', '漸層', '迷你', '隨行' ) as $key ) { if ( false !== mb_strpos( $pp->post_title, $key ) && ! isset( $thumb_by_key[ $key ] ) ) { $thumb_by_key[ $key ] = $tid; } } } } // 4. 範例文章(依 slug 跳過已存在). $pool_i = 0; foreach ( ming_journal_seed_posts() as $cat_slug => $posts ) { if ( empty( $cat_ids[ $cat_slug ] ) ) { continue; } foreach ( $posts as $p ) { $exist = get_page_by_path( $p['slug'], OBJECT, 'post' ); if ( $exist ) { continue; } $pid = wp_insert_post( array( 'post_type' => 'post', 'post_status' => 'publish', 'post_name' => $p['slug'], 'post_title' => $p['title'], 'post_content' => $p['body'], 'post_excerpt' => wp_trim_words( wp_strip_all_tags( $p['body'] ), 40, '…' ), 'post_category' => array( $cat_ids[ $cat_slug ] ), ) ); if ( ! $pid || is_wp_error( $pid ) ) { continue; } $tid = isset( $thumb_by_key[ $p['imgkey'] ] ) ? $thumb_by_key[ $p['imgkey'] ] : ( $thumb_pool ? $thumb_pool[ $pool_i % count( $thumb_pool ) ] : 0 ); $pool_i++; if ( $tid ) { set_post_thumbnail( $pid, $tid ); } } } // 5. 品牌故事頁更名為「關於我們」. foreach ( get_posts( array( 'post_type' => 'page', 'posts_per_page' => -1, 'post_status' => 'any', 'meta_key' => '_wp_page_template', 'meta_value' => 'template-brand.php' ) ) as $bp ) { if ( '品牌故事' === $bp->post_title ) { wp_update_post( array( 'ID' => $bp->ID, 'post_title' => '關於我們' ) ); } } // 6. 選單入口:主選單與頁腳「品牌探索」補上品牌專欄,並同步更名品牌故事→關於我們. $locations = get_theme_mod( 'nav_menu_locations', array() ); $journal_url = $journal_id && ! is_wp_error( $journal_id ) ? get_permalink( $journal_id ) : home_url( '/journal/' ); foreach ( array( 'primary', 'footer_brand' ) as $loc ) { if ( empty( $locations[ $loc ] ) ) { continue; } $menu_id = $locations[ $loc ]; $items = wp_get_nav_menu_items( $menu_id ); if ( ! $items ) { continue; } $has_journal = false; foreach ( $items as $it ) { if ( '品牌故事' === $it->title ) { wp_update_nav_menu_item( $menu_id, $it->ID, array( 'menu-item-title' => '關於我們', 'menu-item-url' => $it->url, 'menu-item-object-id' => $it->object_id, 'menu-item-object' => $it->object, 'menu-item-type' => $it->type, 'menu-item-status' => 'publish', ) ); } if ( false !== mb_strpos( $it->title, '專欄' ) ) { $has_journal = true; } } if ( ! $has_journal ) { wp_update_nav_menu_item( $menu_id, 0, array( 'menu-item-title' => '品牌專欄', 'menu-item-url' => $journal_url, 'menu-item-type' => 'custom', 'menu-item-status' => 'publish', ) ); } } } add_action( 'after_switch_theme', 'ming_seed_journal_content' ); /* ---------- SKU:全域顏色屬性 + 可變商品 + 色票占位圖 ---------- */ /* 伺服器 PATH_INFO 不支援非 ASCII slug:存檔時自動把含中文的 slug 轉為 ASCII */ add_action( 'wp_insert_post', 'ming_force_ascii_slug', 20, 2 ); function ming_force_ascii_slug( $post_id, $post ) { if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { return; } if ( in_array( $post->post_status, array( 'auto-draft', 'inherit', 'trash' ), true ) ) { return; } if ( ! $post->post_name || ! preg_match( '/[^\x21-\x7E]/', $post->post_name ) ) { return; } if ( ! in_array( $post->post_type, array( 'product', 'post', 'page', 'ming_campaign' ), true ) ) { return; } if ( 'product' === $post->post_type ) { $code = ming_series_code( $post->post_title ); $base = 'ming-' . strtolower( $code ? $code : 'item' ); if ( preg_match( '/(\d+)\s*(ml|毫升)/iu', $post->post_title, $mm ) ) { $base .= '-' . $mm[1]; } } elseif ( 'ming_campaign' === $post->post_type ) { $base = 'wish'; } else { $base = $post->post_type; } $new = wp_unique_post_slug( $base, $post_id, $post->post_status, $post->post_type, $post->post_parent ); if ( $new && $new !== $post->post_name ) { wp_update_post( array( 'ID' => $post_id, 'post_name' => $new ) ); } } /** 預設顏色(代碼對應 SKU 後綴,與亿看 ERP PROD_CD 編碼相容) */ function ming_color_presets() { return array( array( 'name' => '米白', 'slug' => 'off-white', 'code' => 'WH', 'hex' => '#F1EAD9', 'dark' => false ), array( 'name' => '墨綠', 'slug' => 'ink-green', 'code' => 'GN', 'hex' => '#3E4A3F', 'dark' => true ), array( 'name' => '暖棕', 'slug' => 'warm-brown', 'code' => 'BR', 'hex' => '#A4835F', 'dark' => true ), array( 'name' => '黛藍', 'slug' => 'mist-blue', 'code' => 'BL', 'hex' => '#5B7089', 'dark' => true ), ); } /** 系列代碼(依商品名關鍵字) */ function ming_series_code( $title ) { $map = array( '冰霸' => 'FATE', '雙用' => 'BREW', '小清新' => 'FRESH', '漸層' => 'GRAD', '迷你' => 'MINI', '隨行' => 'HNDL', ); foreach ( $map as $k => $v ) { if ( false !== mb_strpos( $title, $k ) ) { return $v; } } return ''; } /** * 以 GD 產生色票占位圖(色塊底 + 顏色名 + 商品名),回傳 attachment ID。 * 已存在同名檔案時直接重用。 * * @param string $color_name 顏色名(米白…) * @param string $hex 色值 * @param bool $dark 深色底(文字用淺色) * @param string $caption 商品名/容量說明 * @return int attachment ID(失敗回 0) */ function ming_make_swatch_image( $color_name, $hex, $dark, $caption ) { if ( ! function_exists( 'imagettftext' ) ) { return 0; } $upload = wp_upload_dir(); $slug = sanitize_title( $color_name . '-' . $caption ); $fname = 'ming-swatch-' . md5( $color_name . '|' . $caption ) . '.jpg'; $path = $upload['path'] . '/' . $fname; if ( ! file_exists( $path ) ) { $font = get_template_directory() . '/assets/fonts/NotoSerifTC-GD.ttf'; if ( ! file_exists( $font ) ) { return 0; } $w = 800; $h = 800; $im = imagecreatetruecolor( $w, $h ); list( $r, $g, $b ) = sscanf( $hex, '#%02x%02x%02x' ); $bg = imagecolorallocate( $im, $r, $g, $b ); $fg = $dark ? imagecolorallocate( $im, 245, 242, 234 ) : imagecolorallocate( $im, 44, 51, 40 ); $gold = $dark ? imagecolorallocate( $im, 191, 160, 106 ) : imagecolorallocate( $im, 158, 128, 74 ); imagefilledrectangle( $im, 0, 0, $w, $h, $bg ); // 外框細線. imagerectangle( $im, 34, 34, $w - 34, $h - 34, $gold ); // 顏色名(大字,置中偏上). $size = 120; $bb = imagettfbbox( $size, 0, $font, $color_name ); $tw = $bb[2] - $bb[0]; imagettftext( $im, $size, 0, (int) ( ( $w - $tw ) / 2 ), 400, $fg, $font, $color_name ); // 分隔線. imageline( $im, (int) ( $w / 2 ) - 60, 452, (int) ( $w / 2 ) + 60, 452, $gold ); // 商品名/容量. $size2 = 34; $bb2 = imagettfbbox( $size2, 0, $font, $caption ); $tw2 = $bb2[2] - $bb2[0]; imagettftext( $im, $size2, 0, (int) ( ( $w - $tw2 ) / 2 ), 530, $fg, $font, $caption ); // 品牌字. $size3 = 26; $brand = 'MING 明命 · 陶瓷內膽'; $bb3 = imagettfbbox( $size3, 0, $font, $brand ); $tw3 = $bb3[2] - $bb3[0]; imagettftext( $im, $size3, 0, (int) ( ( $w - $tw3 ) / 2 ), 620, $gold, $font, $brand ); imagejpeg( $im, $path, 88 ); imagedestroy( $im ); } // 重用已存在的 attachment. global $wpdb; $exist = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_wp_attached_file' AND meta_value = %s LIMIT 1", ltrim( str_replace( $upload['basedir'], '', $path ), '/' ) ) ); if ( $exist ) { return (int) $exist; } $aid = wp_insert_attachment( array( 'guid' => $upload['url'] . '/' . $fname, 'post_mime_type' => 'image/jpeg', 'post_title' => $color_name . ' ' . $caption . ' 色票', 'post_content' => '', 'post_status' => 'inherit', ), $path ); if ( ! $aid || is_wp_error( $aid ) ) { return 0; } require_once ABSPATH . 'wp-admin/includes/image.php'; wp_update_attachment_metadata( $aid, wp_generate_attachment_metadata( $aid, $path ) ); return $aid; } /** * v1.7.0 主題啟用種子:建立「顏色」全域屬性,將 6 款商品轉為可變商品(4 色 SKU)。 */ function ming_seed_skus() { if ( ! class_exists( 'WooCommerce' ) || ! post_type_exists( 'product' ) ) { return; } if ( ! function_exists( 'imagettftext' ) ) { return; } // 1. 建立全域屬性 pa_color. $tax = 'pa_color'; if ( ! taxonomy_exists( $tax ) && function_exists( 'wc_create_attribute' ) ) { $existing = wc_get_attribute_taxonomies(); $found = false; foreach ( $existing as $att ) { if ( 'color' === $att->attribute_name || '顏色' === $att->attribute_label ) { $found = true; } } if ( ! $found ) { wc_create_attribute( array( 'name' => '顏色', 'slug' => 'color', 'type' => 'select', 'order_by' => 'menu_order', 'has_archives' => false, ) ); delete_transient( 'wc_attribute_taxonomies' ); } } if ( ! taxonomy_exists( $tax ) ) { // 同一個 request 內手動註冊,才能立即建 term. register_taxonomy( $tax, 'product', array( 'hierarchical' => false, 'show_ui' => false, 'query_var' => true, 'rewrite' => false, ) ); } // 2. 建立顏色 term + 記錄 hex. $term_map = array(); // slug => ['term'=>WP_Term,'code'=>..,'hex'=>..,'dark'=>..] foreach ( ming_color_presets() as $c ) { $t = get_term_by( 'slug', $c['slug'], $tax ); if ( ! $t ) { $r = wp_insert_term( $c['name'], $tax, array( 'slug' => $c['slug'] ) ); if ( is_wp_error( $r ) ) { continue; } $t = get_term( $r['term_id'], $tax ); } update_term_meta( $t->term_id, 'ming_hex', $c['hex'] ); $term_map[ $c['slug'] ] = array_merge( $c, array( 'term_id' => (int) $t->term_id ) ); } if ( ! $term_map ) { return; } // 3. 逐商品轉可變商品. $attr_tax_id = 0; foreach ( wc_get_attribute_taxonomies() as $att ) { if ( 'pa_color' === wc_attribute_taxonomy_name( $att->attribute_name ) ) { $attr_tax_id = (int) $att->attribute_id; } } $products = get_posts( array( 'post_type' => 'product', 'posts_per_page' => -1, 'post_status' => 'publish', ) ); foreach ( $products as $pp ) { $product = wc_get_product( $pp->ID ); if ( ! $product || $product->is_type( 'variable' ) ) { continue; } $price = $product->get_regular_price(); if ( '' === $price ) { $price = $product->get_price(); } $stock = $product->get_stock_status(); $base = 'MING-' . ( ming_series_code( $pp->post_title ) ?: 'P' . $pp->ID ); // 屬性設定. $attr = new WC_Product_Attribute(); $attr->set_id( $attr_tax_id ); $attr->set_name( $tax ); $attr->set_options( array_map( function( $m ) { return $m['term_id']; }, $term_map ) ); $attr->set_position( 0 ); $attr->set_visible( true ); $attr->set_variation( true ); $new = new WC_Product_Variable( $pp->ID ); $new->set_attributes( array( $attr ) ); $new->save(); // 預設色:米白. $default_slug = 'off-white'; if ( isset( $term_map[ $default_slug ] ) ) { update_post_meta( $pp->ID, '_default_attributes', array( 'color' => $default_slug ) ); } // 建立變體. foreach ( $term_map as $slug => $c ) { // 容量取自標題本身,勿再拼接(避免「1200ml 1200ml」重複). $caption = trim( $pp->post_title ); $img_id = ming_make_swatch_image( $c['name'], $c['hex'], $c['dark'], $caption ); $v = new WC_Product_Variation(); $v->set_parent_id( $pp->ID ); $v->set_attributes( array( $tax => $slug ) ); if ( '' !== $price ) { $v->set_regular_price( $price ); } $v->set_sku( $base . '-' . $c['code'] ); $v->set_stock_status( $stock ); $v->set_status( 'publish' ); if ( $img_id ) { $v->set_image_id( $img_id ); } $v->save(); } wc_delete_product_transients( $pp->ID ); } } add_action( 'after_switch_theme', 'ming_seed_skus' ); /* ---------- 商品頁:顏色下拉換成色卡圓鈕 ---------- */ add_filter( 'woocommerce_dropdown_variation_attribute_options_html', 'ming_variation_swatches', 10, 2 ); function ming_variation_swatches( $html, $args ) { $attribute = isset( $args['attribute'] ) ? $args['attribute'] : ''; if ( 'pa_color' !== $attribute ) { return $html; } $product = $args['product']; $options = $args['options']; $selected = $args['selected']; $btns = '
'; foreach ( $options as $slug ) { $term = get_term_by( 'slug', $slug, $attribute ); if ( ! $term ) { continue; } $hex = get_term_meta( $term->term_id, 'ming_hex', true ); if ( ! $hex ) { foreach ( ming_color_presets() as $c ) { if ( $c['slug'] === $slug ) { $hex = $c['hex']; break; } } } $btns .= sprintf( '', ( $selected === $slug ? ' is-active' : '' ), esc_attr( $slug ), esc_attr( $term->name ), esc_attr( $hex ? $hex : '#D8D2C4' ), esc_html( $term->name ) ); } $btns .= '
'; // 原生 select 保留(WooCommerce 變體 JS 需要),僅視覺隱藏. return $btns . '
' . $html . '
'; } /* ============================================================ * 海報生成器(後台:產品 → 海報生成器) * GD 合成 1080×1350 社群豎版海報,隨機版式 × 文案 × 配色,保存生成記錄。 * ==========================================================*/ /** 品牌文案庫(可於後台自行擴充:ming_poster_copies filter) */ function ming_poster_copies() { $copies = array( '純陶瓷內膽,喝得到本來的味道', '裝得下冰美式,也裝得下山的清晨', '不與味道妥協', '把土地的溫度,握在手心', '以時間為序,以光明為引', '好杯子,讓飲品說話', '從今天起,只喝本來的味道', '明亮生命,自然連結', '一杯,一天,一種生活節奏', '陶瓷的純,時間的溫', ); return apply_filters( 'ming_poster_copies', $copies ); } /** 海報配色方案(背景 / 主文字 / 點綴) */ function ming_poster_palettes() { return array( array( '#F3EDE2', '#2C3328', '#BFA06A' ), // 米白 × 深綠 × 金 array( '#2C3328', '#F3EDE2', '#BFA06A' ), // 深綠 × 米白 × 金 array( '#A4835F', '#FBF8F0', '#2C3328' ), // 暖棕 × 米白 × 深綠 array( '#5B7089', '#F5F2EA', '#E9D9B8' ), // 黛藍 × 米白 × 淡金 array( '#EFE7D8', '#3E4A3F', '#BFA06A' ), // 紙色 × 墨綠 × 金 ); } /** * 依字體寬度自動換行(中文按字元切)。 * * @return array 各行文字 */ function ming_poster_wrap( $font, $size, $text, $max_w ) { $text = trim( $text ); if ( '' === $text ) { return array(); } $lines = array(); $line = ''; $chars = preg_split( '//u', $text, -1, PREG_SPLIT_NO_EMPTY ); foreach ( $chars as $ch ) { $try = $line . $ch; $bb = imagettfbbox( $size, 0, $font, $try ); $w = $bb[2] - $bb[0]; if ( $w > $max_w && '' !== $line ) { $lines[] = $line; $line = $ch; } else { $line = $try; } } if ( '' !== $line ) { $lines[] = $line; } return $lines; } /** 置中寫一行字,回傳基線 y 之後的高度資訊 */ function ming_poster_text_center( $im, $font, $size, $y, $text, $color, $w = 1080 ) { $bb = imagettfbbox( $size, 0, $font, $text ); $tw = $bb[2] - $bb[0]; imagettftext( $im, $size, 0, (int) ( ( $w - $tw ) / 2 - $bb[0] ), $y, $color, $font, $text ); } /** * 生成一張海報,寫入 uploads 並建立 attachment。 * * @param int $product_id 商品 ID * @param string $copy 主文案 * @return array|WP_Error { attachment_id, url, path } */ function ming_generate_poster( $product_id, $copy ) { if ( ! function_exists( 'imagettftext' ) ) { return new WP_Error( 'no_gd', '主機 PHP 未啟用 GD / FreeType,無法生成海報。' ); } $font = get_template_directory() . '/assets/fonts/NotoSerifTC-GD.ttf'; if ( ! file_exists( $font ) ) { return new WP_Error( 'no_font', '找不到海報字型檔(assets/fonts/NotoSerifTC-GD.ttf)。' ); } $prod = $product_id ? wc_get_product( $product_id ) : null; $W = 1080; $H = 1350; $palettes = ming_poster_palettes(); $pal = $palettes[ array_rand( $palettes ) ]; $layout = mt_rand( 1, 3 ); $im = imagecreatetruecolor( $W, $H ); list( $br, $bgc, $bb_ ) = sscanf( $pal[0], '#%02x%02x%02x' ); list( $tr, $tg, $tb ) = sscanf( $pal[1], '#%02x%02x%02x' ); list( $ar, $ag, $ab ) = sscanf( $pal[2], '#%02x%02x%02x' ); $c_bg = imagecolorallocate( $im, $br, $bgc, $bb_ ); $c_txt = imagecolorallocate( $im, $tr, $tg, $tb ); $c_gold = imagecolorallocate( $im, $ar, $ag, $ab ); imagefilledrectangle( $im, 0, 0, $W, $H, $c_bg ); // 外框(雙細線). imagerectangle( $im, 36, 36, $W - 36, $H - 36, $c_gold ); imagerectangle( $im, 46, 46, $W - 46, $H - 46, $c_gold ); // 商品圖(載入,依版式決定繪製時機). $img_res = null; if ( $prod ) { $thumb_id = get_post_thumbnail_id( $prod->get_id() ); $f = $thumb_id ? get_attached_file( $thumb_id ) : ''; if ( $f && file_exists( $f ) ) { $src = @imagecreatefromstring( file_get_contents( $f ) ); if ( $src ) { $img_res = $src; } } } // 品牌字標(上方). $brand_y = ( 1 === $layout ) ? 120 : 92; ming_poster_text_center( $im, $font, 30, $brand_y, 'M I N G 明 命', $c_gold ); // 依版式先畫上方圖,或僅設定文字起始位置. $text_top = 300; $sw = 0; $sh = 0; if ( $img_res ) { $sw = imagesx( $img_res ); $sh = imagesy( $img_res ); if ( 2 === $layout ) { // 紙卡圖在上、文字在下. $card_w = 720; $card_h = 620; $cx = (int) ( ( $W - $card_w ) / 2 ); $cy = 120; $paper = imagecolorallocate( $im, 251, 248, 240 ); imagefilledrectangle( $im, $cx, $cy, $cx + $card_w, $cy + $card_h, $paper ); imagerectangle( $im, $cx, $cy, $cx + $card_w, $cy + $card_h, $c_gold ); $dw = $card_w - 60; $dh = (int) ( $dw * $sh / $sw ); $maxh = $card_h - 60; if ( $dh > $maxh ) { $dh = $maxh; $dw = (int) ( $dh * $sw / $sh ); } imagecopyresampled( $im, $img_res, (int) ( $cx + ( $card_w - $dw ) / 2 ), (int) ( $cy + ( $card_h - $dh ) / 2 ), 0, 0, $dw, $dh, $sw, $sh ); $text_top = $cy + $card_h + 100; } elseif ( 3 === $layout ) { // 全幅上半圖 + 底部文字帶. $dw = $W - 92; $dh = (int) ( $dw * $sh / $sw ); $maxh = 620; if ( $dh > $maxh ) { $dh = $maxh; $dw = (int) ( $dh * $sw / $sh ); } $dx = (int) ( ( $W - $dw ) / 2 ); $dy = 46; imagecopyresampled( $im, $img_res, $dx, $dy, 0, 0, $dw, $dh, $sw, $sh ); imageline( $im, 92, $dy + $dh + 40, $W - 92, $dy + $dh + 40, $c_gold ); $text_top = $dy + $dh + 120; } else { // 字在上、圖在下(圖待文字排完後依剩餘空間繪製). $text_top = 220; } } // 主文案(自動換行,依行數調整字級;避免末行孤字). $copy = trim( $copy ); $size = 76; $lines = ming_poster_wrap( $font, $size, $copy, $W - 160 ); if ( count( $lines ) > 2 ) { $size = 62; $lines = ming_poster_wrap( $font, $size, $copy, $W - 160 ); } while ( count( $lines ) > 1 && mb_strlen( end( $lines ) ) <= 2 && $size > 52 ) { $size -= 8; $lines = ming_poster_wrap( $font, $size, $copy, $W - 160 ); } $y = $text_top; foreach ( $lines as $ln ) { ming_poster_text_center( $im, $font, $size, $y, $ln, $c_txt ); $y += (int) ( $size * 1.75 ); } // 裝飾線. imageline( $im, (int) ( $W / 2 ) - 70, $y - 18, (int) ( $W / 2 ) + 70, $y - 18, $c_gold ); $y += 56; if ( $img_res && 1 === $layout ) { // 圖在下:依剩餘高度等比縮放;商品資訊併入底部一行,避免重疊. $avail = ( $H - 250 ) - $y; $dw = 620; $dh = (int) ( $dw * $sh / $sw ); if ( $dh > $avail ) { $dh = max( 220, $avail ); $dw = (int) ( $dh * $sw / $sh ); } $dx = (int) ( ( $W - $dw ) / 2 ); $dy = (int) ( $y + ( $avail - $dh ) / 2 ); imageellipse( $im, (int) ( $W / 2 ), $dy + (int) ( $dh / 2 ), 720, 720, $c_gold ); imagecopyresampled( $im, $img_res, $dx, $dy, 0, 0, $dw, $dh, $sw, $sh ); $foot = ''; if ( $prod ) { $foot = $prod->get_name(); if ( '' !== $prod->get_price() ) { $foot .= ' | NT$ ' . number_format( (float) $prod->get_price() ); } } if ( $foot ) { ming_poster_text_center( $im, $font, 28, $H - 168, $foot, $c_txt ); } } elseif ( $prod ) { // 商品資訊(文字流內). ming_poster_text_center( $im, $font, 34, $y, $prod->get_name(), $c_txt ); $y += 64; $price = $prod->get_price(); if ( '' !== $price ) { ming_poster_text_center( $im, $font, 30, $y, 'NT$ ' . number_format( (float) $price ), $c_gold ); } } if ( $img_res ) { imagedestroy( $img_res ); } // 底部小字. ming_poster_text_center( $im, $font, 24, $H - 92, '純陶瓷內膽 · 抗酸鹼 · 不串味', $c_txt ); // 存檔. $upload = wp_upload_dir(); $dir = $upload['basedir'] . '/ming-posters'; if ( ! is_dir( $dir ) ) { wp_mkdir_p( $dir ); } $fname = 'poster-' . gmdate( 'Ymd-His' ) . '-' . mt_rand( 100, 999 ) . '.jpg'; $path = $dir . '/' . $fname; imagejpeg( $im, $path, 90 ); imagedestroy( $im ); $aid = wp_insert_attachment( array( 'guid' => $upload['baseurl'] . '/ming-posters/' . $fname, 'post_mime_type' => 'image/jpeg', 'post_title' => '海報 ' . ( $prod ? $prod->get_name() : '通用' ) . ' ' . gmdate( 'Y-m-d H:i' ), 'post_content' => '', 'post_status' => 'inherit', ), $path ); if ( ! $aid || is_wp_error( $aid ) ) { return new WP_Error( 'attach_fail', '海報已生成但寫入媒體庫失敗。' ); } require_once ABSPATH . 'wp-admin/includes/image.php'; wp_update_attachment_metadata( $aid, wp_generate_attachment_metadata( $aid, $path ) ); return array( 'attachment_id' => $aid, 'url' => wp_get_attachment_url( $aid ), 'path' => $path, ); } /** 生成記錄(option,最多保留 100 筆) */ function ming_poster_log( $entry = null ) { $log = get_option( 'ming_poster_log', array() ); if ( null === $entry ) { return $log; } array_unshift( $log, $entry ); $log = array_slice( $log, 0, 100 ); update_option( 'ming_poster_log', $log, false ); return $log; } /* ---------- 海報生成器:後台頁面 ---------- */ add_action( 'admin_menu', function() { add_submenu_page( 'edit.php?post_type=product', 'MING 海報生成器', '海報生成器', 'manage_woocommerce', 'ming-poster', 'ming_poster_admin_page' ); } ); /** 生成動作處理 */ add_action( 'admin_post_ming_poster_generate', function() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( '權限不足' ); } check_admin_referer( 'ming_poster_generate' ); $pid = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0; $copy = ''; if ( isset( $_POST['copy_custom'] ) && '' !== trim( $_POST['copy_custom'] ) ) { $copy = sanitize_textarea_field( wp_unslash( $_POST['copy_custom'] ) ); } elseif ( isset( $_POST['copy_pick'] ) && '' !== $_POST['copy_pick'] ) { $copy = sanitize_text_field( wp_unslash( $_POST['copy_pick'] ) ); } else { $copies = ming_poster_copies(); $copy = $copies[ array_rand( $copies ) ]; } $copy = str_replace( array( "\r\n", "\r" ), ' ', $copy ); $res = ming_generate_poster( $pid, $copy ); if ( is_wp_error( $res ) ) { wp_safe_redirect( admin_url( 'edit.php?post_type=product&page=ming-poster&err=' . rawurlencode( $res->get_error_message() ) ) ); exit; } ming_poster_log( array( 'time' => current_time( 'Y-m-d H:i' ), 'product' => $pid ? get_the_title( $pid ) : '通用', 'copy' => $copy, 'aid' => $res['attachment_id'], 'url' => $res['url'], ) ); wp_safe_redirect( admin_url( 'edit.php?post_type=product&page=ming-poster&created=' . $res['attachment_id'] ) ); exit; } ); /** 重新生成(沿用記錄的商品與文案,重新抽版式配色) */ add_action( 'admin_post_ming_poster_regen', function() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( '權限不足' ); } $idx = isset( $_GET['idx'] ) ? absint( $_GET['idx'] ) : -1; check_admin_referer( 'ming_poster_regen_' . $idx ); $log = ming_poster_log(); if ( ! isset( $log[ $idx ] ) ) { wp_safe_redirect( admin_url( 'edit.php?post_type=product&page=ming-poster' ) ); exit; } $entry = $log[ $idx ]; $pid = 0; if ( '通用' !== $entry['product'] ) { $p = get_page_by_title( $entry['product'], OBJECT, 'product' ); if ( $p ) { $pid = $p->ID; } } $res = ming_generate_poster( $pid, $entry['copy'] ); if ( is_wp_error( $res ) ) { wp_safe_redirect( admin_url( 'edit.php?post_type=product&page=ming-poster&err=' . rawurlencode( $res->get_error_message() ) ) ); exit; } ming_poster_log( array( 'time' => current_time( 'Y-m-d H:i' ), 'product' => $entry['product'], 'copy' => $entry['copy'], 'aid' => $res['attachment_id'], 'url' => $res['url'], ) ); wp_safe_redirect( admin_url( 'edit.php?post_type=product&page=ming-poster&created=' . $res['attachment_id'] ) ); exit; } ); /** 後台頁面輸出 */ function ming_poster_admin_page() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( '權限不足' ); } $copies = ming_poster_copies(); $products = get_posts( array( 'post_type' => 'product', 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'title', 'order' => 'ASC' ) ); $log = ming_poster_log(); $created = isset( $_GET['created'] ) ? absint( $_GET['created'] ) : 0; ?>

MING 海報生成器

選擇商品與文案,一鍵生成 1080×1350 社群豎版海報。每次生成的版式、配色與裝飾皆隨機,適合 LINE / IG / FB 分享。

海報已生成!預覽 / 下載

注意:主機 PHP 未啟用 GD / FreeType 擴充,目前無法生成海報。

生成新海報

文案(隨機抽一句,或指定):

生成記錄( 筆,保留最近 100 筆)

尚無記錄。

$e ) : ?>
縮圖時間商品文案操作
預覽 下載 換一版