1768 lines
75 KiB
PHP
1768 lines
75 KiB
PHP
<?php
|
||
/**
|
||
* MING 明命 theme functions
|
||
*
|
||
* @package MING
|
||
*/
|
||
|
||
if ( ! defined( 'ABSPATH' ) ) {
|
||
exit;
|
||
}
|
||
|
||
define( 'MING_VERSION', '1.7.4' );
|
||
|
||
/**
|
||
* Theme setup.
|
||
*/
|
||
function ming_setup() {
|
||
load_theme_textdomain( 'ming', get_template_directory() . '/languages' );
|
||
|
||
add_theme_support( 'automatic-feed-links' );
|
||
add_theme_support( 'title-tag' );
|
||
add_theme_support( 'post-thumbnails' );
|
||
add_theme_support(
|
||
'html5',
|
||
array( 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption', 'style', 'script' )
|
||
);
|
||
add_theme_support(
|
||
'custom-logo',
|
||
array(
|
||
'height' => 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' );
|
||
|
||
/**
|
||
* Return the site's WordPress locale in a form suitable for comparisons.
|
||
*
|
||
* @return string
|
||
*/
|
||
function ming_site_locale() {
|
||
return strtolower( str_replace( '-', '_', get_locale() ) );
|
||
}
|
||
|
||
/**
|
||
* Build font stacks for the language selected in WordPress settings.
|
||
*
|
||
* Self-hosted TC font files are used for Traditional Chinese. Other site
|
||
* languages use the appropriate platform font stack and do not request a
|
||
* Chinese Traditional web font.
|
||
*
|
||
* @return string CSS custom-property declarations.
|
||
*/
|
||
function ming_font_stack_css() {
|
||
$locale = ming_site_locale();
|
||
$fonts = array(
|
||
'sans' => "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
||
'serif' => "Georgia, 'Times New Roman', serif",
|
||
'latin' => "'Cormorant Garamond', Georgia, 'Times New Roman', serif",
|
||
);
|
||
|
||
if ( preg_match( '/^zh_(?:hant(?:_|$)|tw(?:_|$)|hk(?:_|$)|mo(?:_|$))/', $locale ) ) {
|
||
$fonts = array(
|
||
'sans' => "'Noto Sans TC', 'Noto Sans CJK TC', 'PingFang TC', 'Microsoft JhengHei', 'Heiti TC', sans-serif",
|
||
'serif' => "'Noto Serif TC', 'Noto Serif CJK TC', 'Songti TC', 'PMingLiU', 'MingLiU', serif",
|
||
'latin' => "'Cormorant Garamond', 'Noto Serif TC', 'Noto Serif CJK TC', 'Songti TC', serif",
|
||
);
|
||
} elseif ( preg_match( '/^zh_/', $locale ) ) {
|
||
$fonts = array(
|
||
'sans' => "'Noto Sans SC', 'Noto Sans CJK SC', 'PingFang SC', 'Microsoft YaHei', 'Heiti SC', sans-serif",
|
||
'serif' => "'Noto Serif SC', 'Noto Serif CJK SC', 'Songti SC', SimSun, serif",
|
||
'latin' => "'Cormorant Garamond', 'Noto Serif SC', 'Noto Serif CJK SC', 'Songti SC', serif",
|
||
);
|
||
} elseif ( preg_match( '/^ja(?:_|$)/', $locale ) ) {
|
||
$fonts = array(
|
||
'sans' => "'Noto Sans JP', 'Hiragino Kaku Gothic ProN', 'Yu Gothic', Meiryo, sans-serif",
|
||
'serif' => "'Noto Serif JP', 'Hiragino Mincho ProN', 'Yu Mincho', serif",
|
||
'latin' => "'Cormorant Garamond', 'Noto Serif JP', 'Hiragino Mincho ProN', serif",
|
||
);
|
||
} elseif ( preg_match( '/^ko(?:_|$)/', $locale ) ) {
|
||
$fonts = array(
|
||
'sans' => "'Noto Sans KR', 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif",
|
||
'serif' => "'Noto Serif KR', AppleMyungjo, Batang, serif",
|
||
'latin' => "'Cormorant Garamond', 'Noto Serif KR', AppleMyungjo, serif",
|
||
);
|
||
}
|
||
|
||
return sprintf(
|
||
":root {\n\t--serif: %1\$s;\n\t--latin: %2\$s;\n\t--sans: %3\$s;\n}\n",
|
||
$fonts['serif'],
|
||
$fonts['latin'],
|
||
$fonts['sans']
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 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_add_inline_style( 'ming-main', ming_font_stack_css() );
|
||
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() {
|
||
echo '<link rel="icon" type="image/png" href="' . esc_url( get_template_directory_uri() . '/assets/img/logo-mark.png' ) . '">' . "\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();
|
||
?>
|
||
<span class="hcount"><?php echo esc_html( WC()->cart->get_cart_contents_count() ); ?></span>
|
||
<?php
|
||
$fragments['span.hcount'] = ob_get_clean();
|
||
return $fragments;
|
||
}
|
||
if ( function_exists( 'WC' ) || class_exists( 'WooCommerce' ) ) {
|
||
add_filter( 'woocommerce_add_to_cart_fragments', 'ming_cart_count_fragment' );
|
||
}
|
||
|
||
/**
|
||
* Related products: 3 items in 3 columns.
|
||
*
|
||
* @param array $args Args.
|
||
* @return array
|
||
*/
|
||
function ming_related_products_args( $args ) {
|
||
$args['posts_per_page'] = 3;
|
||
$args['columns'] = 3;
|
||
return $args;
|
||
}
|
||
add_filter( 'woocommerce_output_related_products_args', 'ming_related_products_args' );
|
||
|
||
/**
|
||
* Fallback primary menu: brand + shop structure.
|
||
*
|
||
* @param array $args Menu args.
|
||
* @return string
|
||
*/
|
||
function ming_fallback_menu( $args = array() ) {
|
||
$base = is_front_page() ? '' : esc_url( home_url( '/' ) );
|
||
$items = array(
|
||
ming_page_url_by_template( 'template-brand.php', $base . '#philosophy' ) => __( '關於我們', '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 .= '<a href="' . esc_attr( $url ) . '">' . esc_html( $label ) . '</a>';
|
||
}
|
||
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 '<p><label><strong>目標人數</strong><br><input type="number" name="ming_goal" value="' . esc_attr( $goal ) . '" min="1" style="width:140px"></label></p>';
|
||
echo '<p><label><strong>截止時間</strong>(留空表示長期進行)<br><input type="datetime-local" name="ming_deadline" value="' . esc_attr( $deadline ) . '"></label></p>';
|
||
echo '<p><strong>參與商品</strong></p><div style="max-height:260px;overflow:auto;border:1px solid #ddd;padding:8px">';
|
||
$prods = get_posts( array( 'post_type' => 'product', 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'title', 'order' => 'ASC' ) );
|
||
foreach ( $prods as $p ) {
|
||
printf(
|
||
'<label style="display:block;margin:4px 0"><input type="checkbox" name="ming_products[]" value="%d"%s> %s</label>',
|
||
$p->ID, checked( in_array( $p->ID, $selected, true ), true, false ), esc_html( $p->post_title )
|
||
);
|
||
}
|
||
if ( ! $prods ) { echo '<p>尚無商品。</p>'; }
|
||
echo '</div>';
|
||
}
|
||
|
||
function ming_campaign_stats_box( $post ) {
|
||
$products = array_map( 'intval', (array) get_post_meta( $post->ID, 'ming_products', true ) );
|
||
if ( ! $products ) { echo '<p>儲存並選擇參與商品後,這裡會顯示投票統計。</p>'; return; }
|
||
$goal = (int) get_post_meta( $post->ID, 'ming_goal', true );
|
||
$goal = $goal ? $goal : ming_vote_goal();
|
||
echo '<table class="widefat striped"><thead><tr><th>商品 / 顏色</th><th>票數</th><th>目標達成</th></tr></thead><tbody>';
|
||
$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 '<tr><td><a href="' . esc_url( get_edit_post_link( $pid ) ) . '">' . esc_html( get_the_title( $pid ) ) . '</a></td><td><strong>' . $v . '</strong></td><td>' . $pct . '%</td></tr>';
|
||
$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 '<tr><td style="padding-left:2em;color:#666">└ ' . esc_html( $cname ? $cname : ( '變體 #' . $vid ) ) . '</td><td>' . $sv . '</td><td></td></tr>';
|
||
}
|
||
}
|
||
}
|
||
echo '</tbody><tfoot><tr><th>總票數</th><th>' . $total . '</th><th></th></tr></tfoot></table>';
|
||
$voters = get_users( array( 'meta_key' => 'ming_voted_c' . $post->ID, 'fields' => array( 'ID', 'display_name', 'user_email' ) ) );
|
||
if ( $voters ) {
|
||
echo '<h4>投票會員(' . count( $voters ) . ')</h4><table class="widefat striped"><thead><tr><th>會員</th><th>信箱</th><th>支持的商品</th></tr></thead><tbody>';
|
||
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 '<tr><td>' . esc_html( $u->display_name ) . '</td><td>' . esc_html( $u->user_email ) . '</td><td>' . esc_html( implode( '、', $names ) ) . '</td></tr>';
|
||
}
|
||
echo '</tbody></table>';
|
||
} else {
|
||
echo '<p>尚無會員投票。</p>';
|
||
}
|
||
}
|
||
|
||
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 '<div class="ming-bacs-box"><h2 class="h2">匯款資訊</h2>';
|
||
if ( $gateway->instructions ) { echo wpautop( wptexturize( $gateway->instructions ) ); }
|
||
$accounts = $gateway->account_details;
|
||
if ( $accounts ) {
|
||
echo '<ul class="bacs-accounts">';
|
||
foreach ( $accounts as $a ) {
|
||
echo '<li>';
|
||
if ( ! empty( $a['account_name'] ) ) { echo '<span><b>戶名</b>' . esc_html( $a['account_name'] ) . '</span>'; }
|
||
if ( ! empty( $a['bank_name'] ) ) { echo '<span><b>銀行</b>' . esc_html( $a['bank_name'] ) . '</span>'; }
|
||
if ( ! empty( $a['account_number'] ) ) { echo '<span><b>帳號</b>' . esc_html( $a['account_number'] ) . '</span>'; }
|
||
if ( ! empty( $a['sort_code'] ) ) { echo '<span><b>分行代碼</b>' . esc_html( $a['sort_code'] ) . '</span>'; }
|
||
echo '</li>';
|
||
}
|
||
echo '</ul>';
|
||
}
|
||
echo '<p class="bacs-note">請於匯款時備註訂單編號 <b>#' . esc_html( $order->get_order_number() ) . '</b>,確認到帳後我們將盡快為您出貨。</p></div>';
|
||
}
|
||
|
||
/* 投票會員 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 '<span class="ming-stock in">現貨</span>';
|
||
} else {
|
||
echo '<span class="ming-stock out">缺貨</span>';
|
||
}
|
||
}
|
||
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(
|
||
'聯絡我們',
|
||
'<p>任何商品、訂單或合作問題,都歡迎與我們聯繫,我們將於 1–2 個工作日內回覆。</p>'
|
||
. '<h3>客服管道</h3><ul><li>客服信箱:請見頁腳「公司資訊」之客服信箱</li><li>服務時間:週一至週五 10:00–18:00(國定假日除外)</li></ul>'
|
||
. '<h3>大宗採購與企業禮贈</h3><p>企業禮贈、經銷通路與大宗訂購需求,請來信註明公司名稱、需求數量與預計用途,我們將由專人與您聯繫報價。</p>',
|
||
),
|
||
'faq' => array(
|
||
'常見問題 FAQ',
|
||
'<h3>陶瓷內膽可以裝碳酸飲料或牛奶嗎?</h3><p>可以。純陶瓷內膽抗酸鹼、耐腐蝕、不殘留味道,咖啡、茶、果汁、牛奶都適用;使用後建議儘快清洗晾乾。</p>'
|
||
. '<h3>如何清洗與保養?</h3><p>杯蓋、吸管與內膽皆可拆洗;請以中性清潔劑與海綿手洗,勿使用鋼刷,勿放入洗碗機高溫烘碗模式。</p>'
|
||
. '<h3>下單後多久出貨?</h3><p>現貨商品於確認款項後 1–3 個工作日內出貨;如遇缺貨或預購,商品頁會標示預計出貨時間。</p>'
|
||
. '<h3>如何查詢訂單?</h3><p>登入會員後可於「會員中心 → 訂單」查詢訂單狀態與匯款資訊。</p>',
|
||
),
|
||
'ordering-guide' => array(
|
||
'訂購須知',
|
||
'<h3>購物流程</h3><p>選購商品 → 加入購物車 → 填寫收件資料 → 選擇付款方式 → 完成訂單。未註冊會員亦可直接結帳。</p>'
|
||
. '<h3>付款方式</h3><p>目前提供銀行轉帳(ATM/網銀匯款)。下單後請於訂單頁面查看匯款帳號,並於 3 日內完成轉帳,逾期訂單將自動取消。</p>'
|
||
. '<h3>運費與配送</h3><p>台灣本島常溫配送,運費依結帳頁顯示為準;離島與海外配送請先來信詢問。</p>',
|
||
),
|
||
'return-policy' => array(
|
||
'退換貨政策',
|
||
'<h3>七天鑑賞期</h3><p>依消費者保護法,商品到貨翌日起算七天內可申請退貨(鑑賞期非試用期)。退回商品須為全新未使用、包裝完整。</p>'
|
||
. '<h3>不適用退貨之情形</h3><ul><li>已使用過或經清洗之飲具(衛生考量)</li><li>客製化商品</li><li>包裝或配件缺損</li></ul>'
|
||
. '<h3>瑕疵與換貨</h3><p>如收到瑕疵品或寄錯商品,請於到貨七天內拍照並來信客服信箱,我們將安排換貨並負擔來回運費。</p>'
|
||
. '<h3>退款方式</h3><p>退貨驗收完成後 7–10 個工作日內,依原付款方式退款。</p>',
|
||
),
|
||
'privacy-policy' => array(
|
||
'隱私權政策',
|
||
'<p>MING 明命(以下稱本公司)重視您的個人資料保護,本政策說明我們如何蒐集、使用與保護您的資料。</p>'
|
||
. '<h3>資料蒐集</h3><p>您於本站註冊、訂購或參與活動時,我們會蒐集姓名、聯絡方式、收件地址與訂單紀錄等必要資料。</p>'
|
||
. '<h3>使用目的</h3><p>僅用於訂單處理、配送、售後服務與會員活動通知;未經您同意,不會提供予無關之第三方。</p>'
|
||
. '<h3>資料安全與權利</h3><p>您的資料將以合理之技術與程序保護。您可隨時聯繫客服查詢、更正或請求刪除您的個人資料。</p>',
|
||
),
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 主題啟用時:建立頁腳資訊頁與三欄頁腳選單(已存在則跳過)。
|
||
*/
|
||
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 分享標籤 ---------- */
|
||
/**
|
||
* Return an Open Graph locale derived from the WordPress site language.
|
||
*
|
||
* @return string
|
||
*/
|
||
function ming_og_locale() {
|
||
$locale = ming_site_locale();
|
||
|
||
if ( 0 === strpos( $locale, 'zh_' ) ) {
|
||
if ( preg_match( '/(?:hk)/', $locale ) ) {
|
||
return 'zh_HK';
|
||
}
|
||
if ( preg_match( '/(?:mo)/', $locale ) ) {
|
||
return 'zh_MO';
|
||
}
|
||
|
||
return preg_match( '/(?:hant|tw)/', $locale ) ? 'zh_TW' : 'zh_CN';
|
||
}
|
||
|
||
$parts = explode( '_', $locale );
|
||
$language = isset( $parts[0] ) ? $parts[0] : 'en';
|
||
$region = '';
|
||
foreach ( array_reverse( $parts ) as $part ) {
|
||
if ( 2 === strlen( $part ) && ctype_alpha( $part ) ) {
|
||
$region = strtoupper( $part );
|
||
break;
|
||
}
|
||
}
|
||
|
||
if ( $region ) {
|
||
return $language . '_' . $region;
|
||
}
|
||
|
||
$default_regions = array(
|
||
'ar' => 'SA',
|
||
'de' => 'DE',
|
||
'en' => 'US',
|
||
'es' => 'ES',
|
||
'fr' => 'FR',
|
||
'it' => 'IT',
|
||
'ja' => 'JP',
|
||
'ko' => 'KR',
|
||
'nl' => 'NL',
|
||
'pl' => 'PL',
|
||
'pt' => 'BR',
|
||
'ru' => 'RU',
|
||
);
|
||
|
||
return isset( $default_regions[ $language ] ) ? $language . '_' . $default_regions[ $language ] : 'en_US';
|
||
}
|
||
|
||
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<!-- MING OG -->\n";
|
||
echo '<meta property="og:site_name" content="' . esc_attr( $site ) . '">' . "\n";
|
||
echo '<meta property="og:title" content="' . esc_attr( $title ) . '">' . "\n";
|
||
echo '<meta property="og:description" content="' . esc_attr( $desc ) . '">' . "\n";
|
||
echo '<meta property="og:url" content="' . esc_url( $url ) . '">' . "\n";
|
||
echo '<meta property="og:type" content="' . esc_attr( $type ) . '">' . "\n";
|
||
echo '<meta property="og:locale" content="' . esc_attr( ming_og_locale() ) . '">' . "\n";
|
||
if ( $image ) {
|
||
echo '<meta property="og:image" content="' . esc_url( $image ) . '">' . "\n";
|
||
}
|
||
echo '<meta name="twitter:card" content="summary_large_image">' . "\n";
|
||
echo '<meta name="twitter:title" content="' . esc_attr( $title ) . '">' . "\n";
|
||
echo '<meta name="twitter:description" content="' . esc_attr( $desc ) . '">' . "\n";
|
||
if ( $image ) {
|
||
echo '<meta name="twitter:image" content="' . esc_url( $image ) . '">' . "\n";
|
||
}
|
||
echo "<!-- /MING OG -->\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;
|
||
?>
|
||
<div class="ming-share" data-title="<?php echo esc_attr( $title ); ?>" data-text="<?php echo esc_attr( $text ); ?>" data-url="<?php echo esc_url( $url ); ?>">
|
||
<span class="ming-share-label"><?php echo esc_html( $args['label'] ); ?></span>
|
||
<button type="button" class="ming-share-btn native" hidden>
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="M7 8l5-5 5 5"/><path d="M5 13v6a1.5 1.5 0 0 0 1.5 1.5h11A1.5 1.5 0 0 0 19 19v-6"/></svg>
|
||
<?php esc_html_e( '分享', 'ming' ); ?>
|
||
</button>
|
||
<a class="ming-share-btn line" target="_blank" rel="noopener"
|
||
href="https://social-plugins.line.me/lineit/share?url=<?php echo rawurlencode( $url ); ?>&text=<?php echo rawurlencode( $text ); ?>">
|
||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 3C6.9 3 3 6.2 3 10.2c0 3.5 3.1 6.4 7.2 7 .3.1.7.2.8.4.1.2.1.5 0 .7l-.2 1.1c0 .2-.2.8.7.4.9-.4 4.7-2.7 6.4-4.7 1.2-1.3 1.8-2.7 1.8-4.4C21.5 6.2 17.1 3 12 3z"/></svg>
|
||
LINE
|
||
</a>
|
||
<a class="ming-share-btn fb" target="_blank" rel="noopener"
|
||
href="https://www.facebook.com/sharer/sharer.php?u=<?php echo rawurlencode( $url ); ?>"e=<?php echo rawurlencode( $text ); ?>">
|
||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M13.5 21v-7h2.4l.4-3h-2.8V9.1c0-.9.3-1.5 1.6-1.5h1.3V4.9c-.2 0-1-.1-1.9-.1-1.9 0-3.3 1.2-3.3 3.4V11H8.5v3h2.6v7h2.4z"/></svg>
|
||
Facebook
|
||
</a>
|
||
<button type="button" class="ming-share-btn copy">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10 14a5 5 0 0 0 7.1 0l2.1-2.1a5 5 0 0 0-7.1-7.1l-1.2 1.2"/><path d="M14 10a5 5 0 0 0-7.1 0l-2.1 2.1a5 5 0 0 0 7.1 7.1l1.2-1.2"/></svg>
|
||
<?php esc_html_e( '複製連結', 'ming' ); ?>
|
||
</button>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/* ---------- 品牌專欄:分類 / 範例文章 / 頁面與選單種子 ---------- */
|
||
|
||
/**
|
||
* 範例文章定義(繁中草稿,後台可自由修改)。
|
||
*
|
||
* @return array
|
||
*/
|
||
function ming_journal_seed_posts() {
|
||
return array(
|
||
'brand-journal' => array(
|
||
array(
|
||
'slug' => 'why-ceramic-liner',
|
||
'title' => '為什麼是陶瓷?—— 一只內膽杯的起點',
|
||
'imgkey' => '雙用',
|
||
'body' => '<p>每個品牌都有自己的起點。MING 的起點,來自一個很日常的困擾:保溫杯裡的金屬味。</p><p>咖啡放進不鏽鋼杯,下午再喝,總有一絲說不上來的味道;裝過牛奶的杯子,隔天泡茶,茶香裡總混著別的氣息。我們開始問:有沒有一種材質,可以忠實地裝下每一種飲品,又什麼都不留下?</p><p>答案是陶瓷。純陶瓷內膽抗酸鹼、耐腐蝕,不與飲品產生反應,也不殘留味道——裝過手沖咖啡的杯子,洗淨後再裝冷泡茶,味道依然乾淨。</p><blockquote>讓器物退到飲品後面,是我們對「好杯子」的定義。</blockquote><p>於是我們把陶瓷放進雙層真空結構裡,讓它既有陶瓷的純淨,也有保溫杯的鎖溫能力。這是 MING 的第一只杯子,也是我們想一直做下去的事。</p>',
|
||
),
|
||
array(
|
||
'slug' => 'meaning-of-ming',
|
||
'title' => '明與命:我們給品牌取名字的緣由',
|
||
'imgkey' => '漸層',
|
||
'body' => '<p>MING,取自「明」與「命」。</p><p>「明」,是光明,是看見,是對美好生活的嚮往;「命」,是生命,是人與自然之間的連結,也是萬物生生不息的延續。</p><p>品牌標誌的外圓,以時鐘與田地為意象——時間的流轉與土地的循環。我們相信,器物不是孤立的存在:一只杯子連結著裝進去的茶葉與咖啡豆,連結著種出它們的土地,也連結著使用它的每一個日常。</p><blockquote>以時間為序,以土地為根,以光明為引。</blockquote><p>這句話寫在 MING 的首頁,也寫在每一只杯子的設計裡。希望它陪伴你的每一天,都明亮而有根。</p>',
|
||
),
|
||
),
|
||
'media-coverage' => array(
|
||
array(
|
||
'slug' => 'media-plastic-free-trend',
|
||
'title' => '媒體報導|陶瓷內膽杯成為減塑新寵,MING 以「不串味」突圍',
|
||
'imgkey' => '冰霸',
|
||
'body' => '<p>(範例報導稿,實際上刊後可替換為媒體原文與連結)</p><p>隨著自帶杯政策與減塑意識升溫,隨行杯市場競爭激烈。近期主打「純陶瓷內膽」的品牌 MING 明命受到關注:有別於市售不鏽鋼內膽,陶瓷材質不殘留味道,裝咖啡、茶飲、牛奶都不串味,成為許多手搖飲愛好者的新選擇。</p><p>報導指出,MING 產品線涵蓋大容量冰霸杯、泡茶直飲雙用杯與輕巧迷你杯,全系列內膽皆可拆洗,瞄準「味道潔癖」與「好清洗」兩大痛點。</p><blockquote>「讓每一杯飲品,都喝得到它本來的味道。」——報導引述品牌理念</blockquote><p>未來若有媒體正式報導,可於後台直接編輯本篇,換上真實媒體名稱、日期與原文連結。</p>',
|
||
),
|
||
array(
|
||
'slug' => 'media-brand-interview',
|
||
'title' => '媒體專訪|從辦公室水杯到生活儀式:MING 的品牌觀察',
|
||
'imgkey' => '隨行',
|
||
'body' => '<p>(範例專訪稿,實際上刊後可替換為媒體原文與連結)</p><p>「杯子是少數一天會被使用好幾次的器物。」訪談中,MING 團隊談起品牌觀察:早上的咖啡、午後的茶、運動後的冰水——杯子參與了生活的每一段節奏,卻很少被認真對待。</p><p>因此 MING 在設計上格外重視「長期使用」的細節:抗酸鹼的陶瓷內膽、可全拆洗的結構、一提就走的提把。團隊表示,未來也將透過「新品許願池」邀請使用者投票決定下一批新品的顏色與容量,讓產品從使用者的日常裡長出來。</p><p>未來若有正式專訪刊出,可於後台直接編輯本篇內容。</p>',
|
||
),
|
||
),
|
||
'unboxing' => array(
|
||
array(
|
||
'slug' => 'unboxing-bingba-1000',
|
||
'title' => '開箱|冰霸杯:裝得下整個下午的冰美式',
|
||
'imgkey' => '冰霸',
|
||
'body' => '<p>(範例開箱文,歡迎於後台替換為真實使用者心得)</p><p>第一眼的印象是「大」。寬口設計讓加冰塊變得很從容,整杯手搖飲倒進去也沒問題。提把是一體成型的加粗設計,裝滿水提起來不勒手,從辦公室走到健身房一路拎著也不彆扭。</p><p>最驚豔的是內膽。下午裝了冰美式,晚上洗淨後裝溫水——完全沒有咖啡味殘留。陶瓷內膽摸起來是溫潤的霧面質感,和金屬內膽的冰冷感很不一樣。</p><ul><li>保冷實測:早上 8 點裝的冰飲,下午 2 點冰塊尚未全融</li><li>拆洗:杯蓋、吸管、內膽皆可拆,海綿可以伸到底</li><li>缺點:大容量裝滿後較有份量,適合定點或短程攜帶</li></ul>',
|
||
),
|
||
array(
|
||
'slug' => 'unboxing-brew-tea-week',
|
||
'title' => '實測|雙用杯泡茶一週心得:清洗真的不麻煩嗎?',
|
||
'imgkey' => '雙用',
|
||
'body' => '<p>(範例開箱文,歡迎於後台替換為真實使用者心得)</p><p>這一週我把辦公室的馬克杯換成 MING 雙用杯,早上直飲裝拿鐵,下午換上茶倉泡烏龍。重力鎖水的設計讓直飲與泡茶兩種模式切換很直覺,不用額外帶茶具。</p><p>原本最擔心的是清洗——茶垢向來是杯子的敵人。實測一週下來,陶瓷內膽用海綿加中性清潔劑就能洗淨,沒有用到科技海綿,也沒有殘留茶色。窄縫處搭配附贈概念中的小刷具會更徹底。</p><ul><li>優點:不串味是真的,茶與咖啡同杯無壓力</li><li>優點:茶倉濾網細,茶葉不會喝到嘴裡</li><li>注意:裝熱飲時外層仍建議握杯身,避免直接碰杯口金屬件</li></ul>',
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 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 = '<div class="ming-swatches" data-attribute="' . esc_attr( $attribute ) . '">';
|
||
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(
|
||
'<button type="button" class="ming-swatch%s" data-value="%s" aria-label="%s"><i style="background:%s"></i><span>%s</span></button>',
|
||
( $selected === $slug ? ' is-active' : '' ),
|
||
esc_attr( $slug ),
|
||
esc_attr( $term->name ),
|
||
esc_attr( $hex ? $hex : '#D8D2C4' ),
|
||
esc_html( $term->name )
|
||
);
|
||
}
|
||
$btns .= '</div>';
|
||
// 原生 select 保留(WooCommerce 變體 JS 需要),僅視覺隱藏.
|
||
return $btns . '<div class="ming-select-hidden" aria-hidden="false">' . $html . '</div>';
|
||
}
|
||
|
||
/* ============================================================
|
||
* 海報生成器(後台:產品 → 海報生成器)
|
||
* 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;
|
||
?>
|
||
<div class="wrap">
|
||
<h1>MING 海報生成器</h1>
|
||
<p class="description">選擇商品與文案,一鍵生成 1080×1350 社群豎版海報。每次生成的版式、配色與裝飾皆隨機,適合 LINE / IG / FB 分享。</p>
|
||
<?php if ( isset( $_GET['err'] ) ) : ?>
|
||
<div class="notice notice-error"><p><?php echo esc_html( wp_unslash( $_GET['err'] ) ); ?></p></div>
|
||
<?php endif; ?>
|
||
<?php if ( $created ) : $curl = wp_get_attachment_url( $created ); ?>
|
||
<div class="notice notice-success"><p>海報已生成!<?php if ( $curl ) : ?><a href="<?php echo esc_url( $curl ); ?>" target="_blank">預覽</a> / <a href="<?php echo esc_url( $curl ); ?>" download>下載</a><?php endif; ?></p></div>
|
||
<?php endif; ?>
|
||
<?php if ( ! function_exists( 'imagettftext' ) ) : ?>
|
||
<div class="notice notice-warning"><p>注意:主機 PHP 未啟用 GD / FreeType 擴充,目前無法生成海報。</p></div>
|
||
<?php endif; ?>
|
||
|
||
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="background:#fff;border:1px solid #dcdcde;padding:20px;max-width:760px">
|
||
<input type="hidden" name="action" value="ming_poster_generate">
|
||
<?php wp_nonce_field( 'ming_poster_generate' ); ?>
|
||
<h2 style="margin-top:0">生成新海報</h2>
|
||
<p>
|
||
<label><strong>商品</strong>(圖片與品名會合成進海報;選「通用」則只排文字)<br>
|
||
<select name="product_id" style="min-width:320px">
|
||
<option value="0">通用(不帶商品)</option>
|
||
<?php foreach ( $products as $p ) : ?>
|
||
<option value="<?php echo esc_attr( $p->ID ); ?>"><?php echo esc_html( $p->post_title ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select></label>
|
||
</p>
|
||
<p><strong>文案</strong>(隨機抽一句,或指定):</p>
|
||
<div style="max-height:180px;overflow:auto;border:1px solid #ddd;padding:10px;margin-bottom:10px">
|
||
<label style="display:block;margin:4px 0"><input type="radio" name="copy_pick" value="" checked> 隨機抽一句</label>
|
||
<?php foreach ( $copies as $c ) : ?>
|
||
<label style="display:block;margin:4px 0"><input type="radio" name="copy_pick" value="<?php echo esc_attr( $c ); ?>"> <?php echo esc_html( $c ); ?></label>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<p>
|
||
<label><strong>自訂文案</strong>(填寫後優先於上方選項,建議 16 字以內)<br>
|
||
<textarea name="copy_custom" rows="2" style="width:100%;max-width:560px" placeholder="例如:新款上市,陪你過整個夏天"></textarea></label>
|
||
</p>
|
||
<p><button type="submit" class="button button-primary button-hero">生成海報(隨機版式)</button></p>
|
||
</form>
|
||
|
||
<h2 style="margin-top:32px">生成記錄(<?php echo count( $log ); ?> 筆,保留最近 100 筆)</h2>
|
||
<?php if ( ! $log ) : ?>
|
||
<p>尚無記錄。</p>
|
||
<?php else : ?>
|
||
<table class="widefat striped" style="max-width:960px">
|
||
<thead><tr><th style="width:90px">縮圖</th><th>時間</th><th>商品</th><th>文案</th><th>操作</th></tr></thead>
|
||
<tbody>
|
||
<?php foreach ( $log as $i => $e ) : ?>
|
||
<tr>
|
||
<td><?php if ( ! empty( $e['aid'] ) ) : ?><a href="<?php echo esc_url( $e['url'] ); ?>" target="_blank"><?php echo wp_get_attachment_image( $e['aid'], array( 72, 90 ) ); ?></a><?php endif; ?></td>
|
||
<td><?php echo esc_html( $e['time'] ); ?></td>
|
||
<td><?php echo esc_html( $e['product'] ); ?></td>
|
||
<td><?php echo esc_html( $e['copy'] ); ?></td>
|
||
<td>
|
||
<a class="button button-small" href="<?php echo esc_url( $e['url'] ); ?>" target="_blank">預覽</a>
|
||
<a class="button button-small" href="<?php echo esc_url( $e['url'] ); ?>" download>下載</a>
|
||
<a class="button button-small" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=ming_poster_regen&idx=' . $i ), 'ming_poster_regen_' . $i ) ); ?>">換一版</a>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php
|
||
}
|