✏️ 正在编辑: extras.php
路径:
/home/h359620/public_html/wp-content/themes/datis/inc/includes/extras.php
提示:
您可以编辑任何文件(包括二进制文件),但请注意不当修改可能导致文件损坏。
<?php /** * Datis Theme Extras (All hooks moved from functions.php) * ------------------------------------------------------- * READY TO PASTE / REPLACE FILE * * Fixes included: * ✅ Only ONE get_recent_product_categories() (duplicate removed) * ✅ Backward-compatible wrapper methods: datis_get_* / datis_render_* * ✅ Backward-compatible GLOBAL functions: * - datis_get_recent_product_categories() * - datis_get_products_for_cat() * - datis_render_product_slide_html() * ✅ Nonce for category tabs is localized globally (so it works on homepage too) * ✅ Better script deps (uses datis-swiper handle) */ if (!defined('ABSPATH')) exit; if (!class_exists('Datis_Theme_Extras')) { final class Datis_Theme_Extras { /** @var self|null */ private static $instance = null; public static function get_instance(): self { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->register_hooks(); } private function __clone() {} public function __wakeup() { throw new \Exception('Cannot unserialize singleton'); } private function register_hooks(): void { // Theme setup add_action('after_setup_theme', [$this, 'after_setup_theme']); // Assets add_action('wp_enqueue_scripts', [$this, 'enqueue_front_assets']); add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']); add_action('elementor/frontend/after_enqueue_styles', [$this, 'enqueue_elementor_front_styles']); add_action('elementor/editor/after_enqueue_styles', [$this, 'enqueue_elementor_editor_styles']); // Elementor options add_action('init', [$this, 'setup_elementor_options'], 10); // Redux saved => set Elementor kit style add_action('redux/options/datis_theme_options/saved', [$this, 'set_datis_style']); // Security / cleanup add_action('init', [$this, 'remove_wp_generator'], 1); // Elementor cache clear (optimized) add_action('template_redirect', [$this, 'maybe_clear_elementor_cache'], 1); // Remove Turbo Addons notice add_action('admin_init', [$this, 'remove_turbo_addons_notice'], 999); // Shortcodes add_shortcode('single_title', [$this, 'shortcode_single_title']); // Demo + body class add_action('after_setup_theme', [$this, 'bootstrap_active_demo']); add_filter('body_class', [$this, 'add_demo_body_class']); // Comments meta add_action('comment_post', [$this, 'save_comment_meta']); add_filter('preprocess_comment', [$this, 'force_product_review_comment_type']); // Redirect term archives to main archives add_action('template_redirect', [$this, 'redirect_term_archives_to_main_archives'], 10); // Apply filters to main archives add_action('pre_get_posts', [$this, 'apply_archive_filters'], 10); // Sorting + project meta filters add_action('pre_get_posts', [$this, 'apply_archive_sort'], 10); add_action('pre_get_posts', [$this, 'apply_project_archive_meta_filters'], 10); // Woo: Attribute custom checkboxes add_action('woocommerce_after_product_attribute_settings', [$this, 'attr_position_checkboxes_fields'], 1, 2); add_action('woocommerce_admin_process_product_object', [$this, 'attr_position_checkboxes_save_on_product_update'], 1, 1); add_action('wp_ajax_woocommerce_save_attributes', [$this, 'attr_position_checkboxes_save_on_ajax'], 1); // Product category tabs ajax add_action('wp_ajax_datis_cat_products', [$this, 'ajax_cat_products']); add_action('wp_ajax_nopriv_datis_cat_products', [$this, 'ajax_cat_products']); // Conditional assets for Woo pages (kept) add_action('wp_enqueue_scripts', [$this, 'enqueue_conditional_woocommerce_assets'], 20); // Cart ajax endpoints add_action('wc_ajax_datis_set_cart_qty', [$this, 'wc_ajax_set_cart_qty']); add_action('wc_ajax_nopriv_datis_set_cart_qty', [$this, 'wc_ajax_set_cart_qty']); add_action('wc_ajax_datis_get_cart_sections', [$this, 'wc_ajax_get_cart_sections']); add_action('wc_ajax_nopriv_datis_get_cart_sections', [$this, 'wc_ajax_get_cart_sections']); add_action('wc_ajax_datis_get_cart_page', [$this, 'wc_ajax_get_cart_page']); add_action('wc_ajax_nopriv_datis_get_cart_page', [$this, 'wc_ajax_get_cart_page']); // Woo hook removals add_action('init', [$this, 'remove_woocommerce_notices_hooks'], 10); add_action('wp', [$this, 'remove_woocommerce_shop_hooks'], 20); add_action('wp', [$this, 'remove_woocommerce_single_hooks'], 20); add_action('wp', [$this, 'disable_wc_notices_on_myaccount'], 20); // Account view-order redirect add_action('template_redirect', [$this, 'maybe_redirect_view_order_to_thankyou'], 10); // Breadcrumb filters add_filter('datis_breadcrumb_home_label', [$this, 'filter_breadcrumb_home_label']); add_filter('datis_breadcrumb_home_icon', [$this, 'filter_breadcrumb_home_icon']); add_filter('datis_breadcrumb_separator', [$this, 'filter_breadcrumb_separator']); add_filter('datis_breadcrumb_schema_enabled', [$this, 'filter_breadcrumb_schema_enabled']); // Blog extras add_action('pre_get_posts', [$this, 'set_blog_archive_posts_per_page'], 10); add_action('pre_get_posts', [$this, 'handle_archive_search'], 10); add_action('wp', [$this, 'maybe_increment_post_views'], 10); } /* ========================================================= * Theme setup * ======================================================= */ public function after_setup_theme(): void { add_theme_support('title-tag'); add_theme_support('post-thumbnails'); add_theme_support('woocommerce'); load_theme_textdomain('datis', DATIS_DIR . '/languages'); register_nav_menus([ 'primary_menu' => __('Primary Menu', 'datis'), 'mobile_menu' => __('Mobile Menu', 'datis'), ]); } /* ========================================================= * Assets * ======================================================= */ public function enqueue_front_assets(): void { wp_enqueue_style('datis-style', get_stylesheet_uri(), [], DATIS_VER); wp_enqueue_style('datis-fonts', DATIS_ASSETS . 'css/fonts.css', [], DATIS_VER, 'all'); wp_enqueue_style('datis-bootstrap', DATIS_ASSETS . 'css/bootstrap.min.css', [], DATIS_VER, 'all'); wp_enqueue_style('datis-swiper', DATIS_ASSETS . 'css/swiper.css', [], DATIS_VER, 'all'); wp_enqueue_style('datis-main', DATIS_ASSETS . 'css/main.css', [], DATIS_VER, 'all'); wp_enqueue_style('datis-blog', DATIS_ASSETS . 'css/blog.css', [], DATIS_VER, 'all'); wp_enqueue_style('datis-sidebar', DATIS_ASSETS . 'css/sidebar.css', [], DATIS_VER, 'all'); wp_enqueue_style('datis-woocommerce', DATIS_ASSETS . 'css/woocommerce.css', [], DATIS_VER, 'all'); wp_enqueue_style('datis-comments', DATIS_ASSETS . 'css/comments.css', [], DATIS_VER, 'all'); wp_enqueue_style('datis-projects-content', DATIS_ASSETS . 'css/projects/contents.css', [], DATIS_VER, 'all'); wp_enqueue_style('datis-project', DATIS_ASSETS . 'css/projects/projects.css', [], DATIS_VER, 'all'); wp_enqueue_style('datis-team-content', DATIS_ASSETS . 'css/teams/contents.css', [], DATIS_VER, 'all'); wp_enqueue_style('wp-block-library'); wp_enqueue_style('wp-block-library-theme'); wp_enqueue_style('global-styles'); wp_enqueue_style('classic-theme-styles'); wp_enqueue_style('datis-editor-like-content', DATIS_ASSETS . 'css/editor-style.css', [], DATIS_VER); wp_enqueue_script('datis-swiper', DATIS_ASSETS . 'js/swiper.js', ['jquery'], DATIS_VER, true); wp_enqueue_script('datis-main', DATIS_ASSETS . 'js/main.js', ['jquery'], DATIS_VER, true); if (class_exists('WooCommerce')) { wp_enqueue_script( 'datis-woocommerce', DATIS_ASSETS . 'js/woocommerce.js', ['jquery', 'datis-swiper'], DATIS_VER, true ); // ✅ Woo format (for archive price slider, etc.) wp_localize_script('datis-woocommerce', 'DATIS_WC', [ 'currencySymbol' => get_woocommerce_currency_symbol(), 'decimals' => wc_get_price_decimals(), 'decimalSep' => wc_get_price_decimal_separator(), 'thousandSep' => wc_get_price_thousand_separator(), 'priceFormat' => get_woocommerce_price_format(), ]); // ✅ Category tabs AJAX (safe to exist everywhere) wp_localize_script('datis-woocommerce', 'DATIS_CAT_TABS', [ 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('datis_cat_products_nonce'), 'limit' => 4, ]); // ✅ Cart endpoints + Woo format (needed by your cart JS formatter) wp_localize_script('datis-woocommerce', 'DATIS_CART', [ 'ajaxUrl' => class_exists('WC_AJAX') ? WC_AJAX::get_endpoint('%%endpoint%%') : '', 'nonce' => wp_create_nonce('woocommerce-cart'), 'currencySymbol' => get_woocommerce_currency_symbol(), 'decimals' => wc_get_price_decimals(), 'decimalSep' => wc_get_price_decimal_separator(), 'thousandSep' => wc_get_price_thousand_separator(), 'priceFormat' => get_woocommerce_price_format(), ]); } } public function enqueue_admin_assets(): void { wp_enqueue_style('datis-fonts', DATIS_ASSETS . 'css/fonts.css', [], DATIS_VER, 'all'); wp_enqueue_style('datis-bootstrap', DATIS_ASSETS . 'css/bootstrap.min.css', [], DATIS_VER, 'all'); wp_enqueue_script('datis-bootstrap', DATIS_ASSETS . 'js/bootstrap.min.js', ['jquery'], DATIS_VER, true); wp_enqueue_script('datis-bootstrap-bundle', DATIS_ASSETS . 'js/bootstrap.bundle.min.js', ['jquery'], DATIS_VER, true); wp_enqueue_script('datis-admin-main', DATIS_ASSETS . 'js/admin.js', ['jquery'], DATIS_VER, true); } public function enqueue_elementor_front_styles(): void { wp_enqueue_style('datis-fonts', DATIS_ASSETS . 'css/fonts.css', [], DATIS_VER, 'all'); } public function enqueue_elementor_editor_styles(): void { wp_enqueue_style('datis-fonts', DATIS_ASSETS . 'css/fonts.css', [], DATIS_VER, 'all'); } public function setup_elementor_options(): void { update_option('elementor_disable_color_schemes', 'yes'); update_option('elementor_disable_typography_schemes', 'yes'); update_option('elementor_load_fa4_shim', 'yes'); } public function remove_wp_generator(): void { remove_action('wp_head', 'wp_generator'); } public function maybe_clear_elementor_cache(): void { if (is_admin() || wp_doing_ajax() || wp_doing_cron()) return; if (!class_exists('\\Elementor\\Plugin')) return; if (is_front_page()) return; $should_clear = false; if (function_exists('elementor_theme_do_location')) { if (elementor_theme_do_location('header') || elementor_theme_do_location('footer')) { $should_clear = true; } } if (!$should_clear && is_singular()) { $post_id = get_queried_object_id(); if ($post_id && \Elementor\Plugin::$instance->documents->get($post_id)) { $should_clear = true; } } if (!$should_clear) return; \Elementor\Plugin::instance()->files_manager->clear_cache(); } public function remove_turbo_addons_notice(): void { if (!is_admin()) return; if (!class_exists('HFB_Recommend_Turbo_Addons')) return; foreach (['admin_notices', 'all_admin_notices'] as $hook) { if (empty($GLOBALS['wp_filter'][$hook])) continue; $wp_filter = $GLOBALS['wp_filter'][$hook]; if (!is_object($wp_filter) || empty($wp_filter->callbacks)) continue; foreach ($wp_filter->callbacks as $priority => $callbacks) { foreach ($callbacks as $cb) { $func = $cb['function'] ?? null; if ( is_array($func) && isset($func[0], $func[1]) && is_object($func[0]) && $func[0] instanceof HFB_Recommend_Turbo_Addons && $func[1] === 'show_recommendation_notice' ) { remove_action($hook, $func, $priority); } } } } } /* ========================================================= * Redux -> Elementor kit style * ======================================================= */ public function set_datis_style(): void { $datis_options = get_option('datis_theme_options', []); if (class_exists('Elementor\\Plugin') && is_admin() && current_user_can('manage_options')) { $kit = \Elementor\Plugin::$instance->kits_manager->get_active_kit(); if ($kit && method_exists($kit, 'update_settings')) { $kit->update_settings([ 'container_width' => [ 'size' => $datis_options['container_size'] ?? 1200, ], '--container-max-width' => [ 'size' => $datis_options['container_size'] ?? 1200, ], 'system_colors' => [ [ '_id' => 'primary', 'title' => __('Primary', 'datis'), 'color' => $datis_options['primary_color'] ?? '#000000', ], [ '_id' => 'secondary', 'title' => __('Secondary', 'datis'), 'color' => $datis_options['secondary_color'] ?? '#000000', ], [ '_id' => 'text', 'title' => __('Text', 'datis'), 'color' => $datis_options['body_text_color'] ?? '#333333', ], [ '_id' => 'accent', 'title' => __('Accent', 'datis'), 'color' => $datis_options['partially_color'] ?? '#dddddd', ], ], ]); } } } /* ========================================================= * Shortcode + Demo * ======================================================= */ public function shortcode_single_title(): string { return get_the_title(); } public function bootstrap_active_demo(): void { if (get_option('datis_active_demo')) return; $front_id = (int) get_option('page_on_front'); if (!$front_id) return; $slug = get_post_field('post_name', $front_id); $map = [ 'home-architecture' => 'architecture', 'home-business' => 'business', 'home-industry' => 'industry', ]; update_option('datis_active_demo', $map[$slug] ?? 'architecture'); } public function add_demo_body_class(array $classes): array { $demo = get_option('datis_active_demo', ''); if ($demo) $classes[] = sanitize_html_class($demo . '-demo'); return $classes; } /* ========================================================= * Comments meta * ======================================================= */ public function save_comment_meta($comment_id): void { if (isset($_POST['datis_rating'])) { $rating = (float) sanitize_text_field(wp_unslash($_POST['datis_rating'])); $rating = max(0, min(5, $rating)); update_comment_meta($comment_id, 'rating', $rating); } if (isset($_POST['datis_phone'])) { $phone = sanitize_text_field(wp_unslash($_POST['datis_phone'])); if ($phone !== '') update_comment_meta($comment_id, 'datis_phone', $phone); } } public function force_product_review_comment_type($commentdata) { if (!empty($commentdata['comment_post_ID']) && get_post_type($commentdata['comment_post_ID']) === 'product') { $commentdata['comment_type'] = 'review'; } return $commentdata; } /* ========================================================= * Redirect term archives * ======================================================= */ public function redirect_term_archives_to_main_archives(): void { if (is_home()) { if (isset($_GET['cat']) || isset($_GET['tag']) || isset($_GET['tag_id'])) return; } if (is_post_type_archive('project')) { if (isset($_GET['project-categories'])) return; } if (is_category()) { $term = get_queried_object(); if ($term && !is_wp_error($term)) { $base = get_permalink(get_option('page_for_posts')) ?: home_url('/'); $url = add_query_arg(['cat' => (int) $term->term_id], $base); wp_safe_redirect($url, 301); exit; } } if (is_tag()) { $term = get_queried_object(); if ($term && !is_wp_error($term)) { $base = get_permalink(get_option('page_for_posts')) ?: home_url('/'); $url = add_query_arg(['tag' => $term->slug], $base); wp_safe_redirect($url, 301); exit; } } if (is_tax('project-categories')) { $term = get_queried_object(); if ($term && !is_wp_error($term)) { $base = get_post_type_archive_link('project'); $url = add_query_arg(['project-categories' => $term->slug], $base); wp_safe_redirect($url, 301); exit; } } } /* ========================================================= * Apply archive filters * ======================================================= */ public function apply_archive_filters($q): void { if (is_admin() || !$q->is_main_query()) return; if ($q->is_home() || $q->is_posts_page) { $q->set('post_type', 'post'); if (isset($_GET['tag_id']) && $_GET['tag_id'] !== '') { $tag_id = absint($_GET['tag_id']); if ($tag_id) { $q->set('tax_query', [ [ 'taxonomy' => 'post_tag', 'field' => 'term_id', 'terms' => $tag_id, ], ]); } } return; } if ($q->is_post_type_archive('project')) { if (isset($_GET['project-categories']) && $_GET['project-categories'] !== '') { $slug = sanitize_title(wp_unslash($_GET['project-categories'])); if ($slug !== '') { $q->set('tax_query', [ [ 'taxonomy' => 'project-categories', 'field' => 'slug', 'terms' => $slug, ], ]); } } return; } } public function apply_archive_sort($q): void { if (is_admin() || !$q->is_main_query()) return; $is_blog = ($q->is_home() || $q->is_posts_page); $is_project = $q->is_post_type_archive('project'); if (!$is_blog && !$is_project) return; $sort = isset($_GET['sort']) ? sanitize_key($_GET['sort']) : ''; if (!$sort) return; if ($sort === 'newest') { $q->set('orderby', 'date'); $q->set('order', 'DESC'); return; } if ($sort === 'oldest') { $q->set('orderby', 'date'); $q->set('order', 'ASC'); return; } if ($sort === 'comments') { $q->set('orderby', 'comment_count'); $q->set('order', 'DESC'); return; } if ($sort === 'views') { $q->set('meta_key', 'datis_post_views'); $q->set('orderby', 'meta_value_num'); $q->set('order', 'DESC'); return; } } public function apply_project_archive_meta_filters($q): void { if (is_admin() || !$q->is_main_query()) return; if (!$q->is_post_type_archive('project')) return; $meta_query = (array) $q->get('meta_query'); if (isset($_GET['project_status']) && $_GET['project_status'] !== '') { $status = sanitize_key($_GET['project_status']); if (in_array($status, ['in_progress', 'completed'], true)) { $meta_query[] = [ 'key' => 'datis_project_status', 'value' => $status, 'compare' => '=', ]; } } if (isset($_GET['date_range']) && $_GET['date_range'] !== '') { $range = sanitize_key($_GET['date_range']); $now = current_time('timestamp'); $from = 0; if ($range === 'week') $from = strtotime('-7 days', $now); if ($range === 'month') $from = strtotime('-30 days', $now); if ($range === 'year') $from = strtotime('-365 days', $now); if ($from) { $meta_query[] = [ 'key' => 'datis_project_date', 'value' => date('Y-m-d', $from), 'compare' => '>=', 'type' => 'DATE', ]; } } if (!empty($meta_query)) $q->set('meta_query', $meta_query); } /* ========================================================= * Search + posts per page * ======================================================= */ public function handle_archive_search($query): void { if (is_admin() || !$query->is_main_query()) return; if (!$query->is_archive() && !$query->is_home() && !$query->is_post_type_archive()) return; foreach (['blog_search', 'project_search', 'archive_search', 'product_search'] as $key) { if (isset($_GET[$key]) && $_GET[$key] !== '') { $keyword = sanitize_text_field(wp_unslash($_GET[$key])); $query->set('s', $keyword); break; } } } public function set_blog_archive_posts_per_page($query): void { if (is_admin() || !$query->is_main_query()) { return; } if ( $query->is_home() || ( $query->is_archive() && in_array($query->get('post_type'), ['post', 'project', 'product'], true) ) ) { $posts_per_page = 9; if (get_option('datis_active_demo') === 'architecture') { if (in_array($query->get('post_type'), ['post', 'project'], true)) { $posts_per_page = 8; } } $query->set('posts_per_page', $posts_per_page); } } /* ========================================================= * Woo: price bounds * ======================================================= */ public function get_catalog_price_bounds(): array { if (!function_exists('wc_get_products')) return [0.0, 0.0]; $cache_key = 'datis_catalog_price_bounds_v1'; $cached = get_transient($cache_key); if (is_array($cached) && isset($cached['min'], $cached['max'])) { return [(float) $cached['min'], (float) $cached['max']]; } $min_ids = wc_get_products([ 'status' => 'publish', 'limit' => 1, 'orderby' => 'price', 'order' => 'ASC', 'return' => 'ids', ]); $max_ids = wc_get_products([ 'status' => 'publish', 'limit' => 1, 'orderby' => 'price', 'order' => 'DESC', 'return' => 'ids', ]); $min = 0.0; $max = 0.0; if (!empty($min_ids)) { $p = wc_get_product($min_ids[0]); if ($p) $min = (float) $p->get_price(); } if (!empty($max_ids)) { $p = wc_get_product($max_ids[0]); if ($p) $max = (float) $p->get_price(); } if ($max < $min) $max = $min; set_transient($cache_key, ['min' => $min, 'max' => $max], 12 * HOUR_IN_SECONDS); return [$min, $max]; } public function wc_price_bounds_for_current_query(): array { if (!function_exists('WC')) return [0.0, 0.0]; global $wp_the_query, $wpdb; if (!$wp_the_query instanceof WP_Query) return [0.0, 0.0]; if (!( is_post_type_archive('product') || (function_exists('is_shop') && is_shop()) || is_tax('product_cat') )) { return [0.0, 0.0]; } $tax_query = (array) $wp_the_query->get('tax_query'); $meta_query = (array) $wp_the_query->get('meta_query'); $tax_query_obj = new WP_Tax_Query($tax_query); $meta_query_obj = new WP_Meta_Query($meta_query); $tax_sql = $tax_query_obj->get_sql('posts', 'ID'); $meta_sql = $meta_query_obj->get_sql('post', 'posts', 'ID'); $lookup = $wpdb->wc_product_meta_lookup; $where = "WHERE posts.post_type = 'product' AND posts.post_status = 'publish' "; $join = " INNER JOIN {$lookup} lookup ON lookup.product_id = posts.ID "; $join .= $tax_sql['join'] . $meta_sql['join']; $where .= $tax_sql['where'] . $meta_sql['where']; $where .= " AND lookup.min_price IS NOT NULL AND lookup.min_price >= 0 "; $sql = " SELECT MIN(lookup.min_price) AS min_price, MAX(lookup.max_price) AS max_price FROM {$wpdb->posts} posts {$join} {$where} "; $row = $wpdb->get_row($sql); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $min = isset($row->min_price) ? (float) $row->min_price : 0.0; $max = isset($row->max_price) ? (float) $row->max_price : 0.0; if ($max < $min) $max = $min; return [$min, $max]; } /* ========================================================= * Woo: Attribute checkboxes * ======================================================= */ private function attr_get_product_id_from_request(): int { global $thepostid; if (!empty($thepostid)) return absint($thepostid); if (isset($_POST['post_id'])) return absint($_POST['post_id']); if (isset($_POST['product_id'])) return absint($_POST['product_id']); if (isset($_GET['post'])) return absint($_GET['post']); return 0; } public function attr_position_checkboxes_fields($attribute, $i): void { $product_id = $this->attr_get_product_id_from_request(); $name = is_object($attribute) && method_exists($attribute, 'get_name') ? (string) $attribute->get_name() : ''; if (!$product_id || $name === '') return; $top_middle = (array) get_post_meta($product_id, '_datis_attr_top_middle', true); $top_left = (array) get_post_meta($product_id, '_datis_attr_top_left', true); $checked_middle = !empty($top_middle[$name]); $checked_left = !empty($top_left[$name]); ?> <div class="enable_variation show_if_simple show_if_variable"> <label> <input type="checkbox" class="checkbox" name="datis_attr_top_middle[<?php echo esc_attr($name); ?>]" value="1" <?php checked($checked_middle); ?> /> <?php echo esc_html__('Show in top section (middle)', 'datis'); ?> </label> </div> <div class="enable_variation show_if_simple show_if_variable"> <label> <input type="checkbox" class="checkbox" name="datis_attr_top_left[<?php echo esc_attr($name); ?>]" value="1" <?php checked($checked_left); ?> /> <?php echo esc_html__('Show in top section (left)', 'datis'); ?> </label> </div> <?php } public function attr_position_checkboxes_save_on_product_update($product): void { if (!is_a($product, 'WC_Product')) return; $this->attr_position_checkboxes_save_maps((int) $product->get_id(), $_POST); } public function attr_position_checkboxes_save_on_ajax(): void { $product_id = $this->attr_get_product_id_from_request(); if (!$product_id) return; $payload = []; if (isset($_POST['data'])) { parse_str(wp_unslash($_POST['data']), $payload); } $this->attr_position_checkboxes_save_maps($product_id, $payload); } private function attr_position_checkboxes_save_maps(int $product_id, array $src): void { $middle_in = isset($src['datis_attr_top_middle']) ? (array) $src['datis_attr_top_middle'] : []; $left_in = isset($src['datis_attr_top_left']) ? (array) $src['datis_attr_top_left'] : []; $map_middle = []; $map_left = []; foreach ($middle_in as $attr_name => $val) { $attr_name = sanitize_text_field((string) $attr_name); if ($attr_name !== '' && !empty($val)) $map_middle[$attr_name] = 1; } foreach ($left_in as $attr_name => $val) { $attr_name = sanitize_text_field((string) $attr_name); if ($attr_name !== '' && !empty($val)) $map_left[$attr_name] = 1; } update_post_meta($product_id, '_datis_attr_top_middle', $map_middle); update_post_meta($product_id, '_datis_attr_top_left', $map_left); } /* ========================================================= * ✅ Recent product categories (ONE VERSION ONLY) * ======================================================= */ public function get_recent_product_categories(int $limit = 4): array { global $wpdb; $limit = max(1, absint($limit)); $term_ids = $wpdb->get_col($wpdb->prepare(" SELECT tt.term_id FROM {$wpdb->term_taxonomy} tt INNER JOIN {$wpdb->term_relationships} tr ON tr.term_taxonomy_id = tt.term_taxonomy_id INNER JOIN {$wpdb->posts} p ON p.ID = tr.object_id WHERE tt.taxonomy = 'product_cat' AND p.post_type = 'product' AND p.post_status = 'publish' GROUP BY tt.term_id ORDER BY MAX(p.post_date) DESC LIMIT %d ", $limit)); if (empty($term_ids)) return []; $terms = get_terms([ 'taxonomy' => 'product_cat', 'include' => array_map('intval', $term_ids), 'hide_empty' => true, ]); if (is_wp_error($terms) || empty($terms)) return []; // keep order $map = []; foreach ($terms as $t) $map[(int) $t->term_id] = $t; $sorted = []; foreach ($term_ids as $id) { $id = (int) $id; if (isset($map[$id])) $sorted[] = $map[$id]; } return $sorted; } /* ========================================================= * ✅ Backward compatible wrappers (templates) * ======================================================= */ public function datis_get_recent_product_categories(int $limit = 4): array { return $this->get_recent_product_categories($limit); } public function datis_get_products_for_cat(int $term_id, int $limit = 4): WP_Query { return $this->get_products_for_cat($term_id, $limit); } public function datis_render_product_slide_html(WC_Product $product): string { return $this->render_product_slide_html($product); } /* ========================================================= * Woo: Product tabs ajax (private builders) * ======================================================= */ private function get_products_for_cat(int $term_id, int $limit = 4): WP_Query { return new WP_Query([ 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => max(1, absint($limit)), 'orderby' => 'date', 'order' => 'DESC', 'ignore_sticky_posts' => true, 'tax_query' => [ [ 'taxonomy' => 'product_cat', 'field' => 'term_id', 'terms' => [(int) $term_id], ], ], ]); } private function render_product_slide_html(WC_Product $prod): string { ob_start(); global $post, $product; $old_post = $post ?? null; $old_product = $product ?? null; $post = get_post($prod->get_id()); if ($post) setup_postdata($post); $product = $prod; ?> <div class="swiper-slide"> <?php wc_get_template_part('content', 'product'); ?> </div> <?php wp_reset_postdata(); $post = $old_post; $product = $old_product; return (string) ob_get_clean(); } public function ajax_cat_products(): void { check_ajax_referer('datis_cat_products_nonce', 'nonce'); $term_id = isset($_POST['term_id']) ? absint($_POST['term_id']) : 0; if (!$term_id) wp_send_json_error(['message' => 'Invalid term.']); $q = $this->get_products_for_cat($term_id, 4); $html = ''; if ($q->have_posts()) { while ($q->have_posts()) { $q->the_post(); $p = wc_get_product(get_the_ID()); if ($p instanceof WC_Product) $html .= $this->render_product_slide_html($p); } wp_reset_postdata(); } wp_send_json_success(['slides_html' => $html]); } public function enqueue_conditional_woocommerce_assets(): void { if (!class_exists('WooCommerce')) return; } /* ========================================================= * Woo: Cart ajax * ======================================================= */ public function wc_ajax_set_cart_qty(): void { $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if (!wp_verify_nonce($nonce, 'woocommerce-cart')) { wp_send_json_error(['message' => 'Invalid nonce'], 403); } $cart_item_key = isset($_POST['cart_item_key']) ? wc_clean(wp_unslash($_POST['cart_item_key'])) : ''; $quantity = isset($_POST['quantity']) ? (int) $_POST['quantity'] : 0; if (!$cart_item_key || !isset(WC()->cart->get_cart()[$cart_item_key])) { wp_send_json_error(['message' => 'Invalid cart item'], 400); } $quantity = max(0, $quantity); WC()->cart->set_quantity($cart_item_key, $quantity, true); WC()->cart->calculate_totals(); wp_send_json_success(['cart_hash' => WC()->cart->get_cart_hash()]); } public function wc_ajax_get_cart_sections(): void { $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if (!wp_verify_nonce($nonce, 'woocommerce-cart')) { wp_send_json_error(['message' => 'Invalid nonce'], 403); } WC()->cart->calculate_totals(); ob_start(); wc_get_template('cart/parts/products.php'); $products_html = ob_get_clean(); ob_start(); wc_get_template('cart/parts/sidebar.php'); $sidebar_html = ob_get_clean(); wp_send_json_success([ 'products' => $products_html, 'sidebar' => $sidebar_html, ]); } public function wc_ajax_get_cart_page(): void { $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if (!wp_verify_nonce($nonce, 'woocommerce-cart')) { wp_send_json_error(['message' => 'Invalid nonce'], 403); } WC()->cart->calculate_totals(); ob_start(); wc_get_template('cart/cart.php'); $html = ob_get_clean(); wp_send_json_success(['html' => $html]); } /* ========================================================= * Woo hook removals * ======================================================= */ public function remove_woocommerce_notices_hooks(): void { remove_action('woocommerce_before_shop_loop', 'woocommerce_output_all_notices', 10); remove_action('woocommerce_before_single_product', 'woocommerce_output_all_notices', 10); remove_action('woocommerce_before_cart', 'woocommerce_output_all_notices', 10); remove_action('woocommerce_before_checkout_form', 'woocommerce_output_all_notices', 10); } public function remove_woocommerce_shop_hooks(): void { remove_action('woocommerce_before_main_content', 'woocommerce_breadcrumb', 20); remove_action('woocommerce_before_shop_loop', 'woocommerce_result_count', 20); remove_action('woocommerce_before_shop_loop', 'woocommerce_catalog_ordering', 30); remove_action('woocommerce_after_shop_loop', 'woocommerce_pagination', 10); remove_action('woocommerce_sidebar', 'woocommerce_get_sidebar', 10); } public function remove_woocommerce_single_hooks(): void { if (!class_exists('WooCommerce')) return; if (!function_exists('is_product') || !is_product()) return; remove_action('woocommerce_before_single_product', 'woocommerce_output_all_notices', 10); remove_action('woocommerce_before_main_content', 'woocommerce_breadcrumb', 20); remove_action('woocommerce_after_single_product_summary', 'woocommerce_output_product_data_tabs', 10); remove_action('woocommerce_after_single_product_summary', 'woocommerce_upsell_display', 15); remove_action('woocommerce_after_single_product_summary', 'woocommerce_output_related_products', 20); } /** * Disable WooCommerce notices on My Account pages (dashboard, orders, etc.) * This prevents messages like “Product X has been added to your cart.” from showing there. */ public function disable_wc_notices_on_myaccount(): void { if (! function_exists('is_account_page') || ! is_account_page()) { return; } // Disable printing notices at the common WooCommerce hooks used by templates remove_action('woocommerce_before_account_navigation', 'wc_print_notices', 10); remove_action('woocommerce_before_account_content', 'wc_print_notices', 10); // Safety: also remove generic notices hooks that some themes/templates may use remove_action('woocommerce_before_my_account', 'wc_print_notices', 10); remove_action('woocommerce_account_content', 'wc_print_notices', 10); } /* ========================================================= * Account view-order redirect * ======================================================= */ public function maybe_redirect_view_order_to_thankyou(): void { if (!is_user_logged_in() || !function_exists('is_account_page') || !is_account_page() || !is_wc_endpoint_url('view-order')) { return; } $order_id = absint(get_query_var('view-order')); if (!$order_id) return; $order = wc_get_order($order_id); if (!$order) return; if ((int) $order->get_user_id() !== get_current_user_id()) return; if (is_wc_endpoint_url('order-received')) return; wp_safe_redirect($order->get_checkout_order_received_url()); exit; } /* ========================================================= * Breadcrumb filters * ======================================================= */ public function filter_breadcrumb_home_label(string $label): string { return $label; } public function filter_breadcrumb_home_icon(string $icon): string { $svg = '<svg width="20" height="20" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">'; $svg .= '<g clip-path="url(#clip0_719_412)">'; $svg .= '<path d="M8 10L8 12" stroke="var(--datis-body-text-color)" stroke-linecap="round"></path>'; $svg .= '<path d="M1.57589 8.63874L1.76173 9.93031C2.08668 12.1886 2.24915 13.3177 3.03252 13.9923C3.8159 14.6668 4.96475 14.6668 7.26246 14.6668L8.73722 14.6668C11.0349 14.6668 12.1838 14.6668 12.9672 13.9923C13.7505 13.3177 13.913 12.1886 14.2379 9.93031L14.4238 8.63874C14.6768 6.8807 14.8032 6.00169 14.4429 5.25013C14.0826 4.49856 13.3157 4.04172 11.7819 3.12804L10.8587 2.57808C9.46581 1.74836 8.76938 1.3335 7.99984 1.3335C7.2303 1.3335 6.53387 1.74836 5.14101 2.57808L4.21778 3.12803C2.68397 4.04172 1.91706 4.49856 1.55675 5.25012" stroke="var(--datis-body-text-color)" stroke-linecap="round"></path>'; $svg .= '</g>'; $svg .= '<defs><clipPath id="clip0_719_412"><rect width="16" height="16" fill="white" transform="matrix(-1 -8.74228e-08 -1.31134e-07 1 16 1.39876e-06)"></rect></clipPath></defs>'; $svg .= '</svg>'; return $svg; } public function filter_breadcrumb_separator(string $separator): string { $svg = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">'; $svg .= '<path d="M14.5 17L9.5 12L14.5 7" stroke="#908FA3" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>'; $svg .= '</svg>'; return $svg; } public function filter_breadcrumb_schema_enabled(bool $enabled): bool { return defined('WPSEO_VERSION') ? false : $enabled; } /* ========================================================= * Blog/Post helpers (views, reading time, meta bar, tags, categories) * ======================================================= */ /** * Increment post views on single pages (call this from a hook like `add_action('wp', ...)`) */ public function maybe_increment_post_views(): void { if (is_admin()) { return; } if (current_user_can('manage_options')) { return; } if (! is_singular()) { return; } $post_id = get_the_ID(); if (! $post_id) { return; } $this->increment_post_views((int) $post_id); } /** * Core views incrementer (cookie-based unique view counting) */ private function increment_post_views(int $post_id): void { $meta_key = 'datis_post_views'; $cookie_name = 'datis_viewed_posts'; $viewed = []; if (isset($_COOKIE[$cookie_name])) { $decoded = json_decode(wp_unslash($_COOKIE[$cookie_name]), true); if (is_array($decoded)) { $viewed = $decoded; } } if (in_array($post_id, $viewed, true)) { return; } $views = (int) get_post_meta($post_id, $meta_key, true); $views++; update_post_meta($post_id, $meta_key, $views); $viewed[] = $post_id; $expire = time() + DAY_IN_SECONDS * 360; setcookie( $cookie_name, wp_json_encode(array_values(array_unique($viewed))), $expire, COOKIEPATH ?: '/', COOKIE_DOMAIN ?: '', is_ssl(), true ); } /** * Get raw post views count */ public function get_post_views(int $post_id = 0): int { $post_id = $post_id ?: get_the_ID(); if (! $post_id) { return 0; } $views = (int) get_post_meta($post_id, 'datis_post_views', true); return max(0, $views); } /** * Get formatted views label */ public function get_post_views_label(int $post_id = 0): string { $views = $this->get_post_views($post_id); return sprintf( __('%s views', 'datis'), number_format_i18n($views) ); } /** * Calculate reading time in minutes (200 wpm) */ public function get_reading_time_minutes(int $post_id = 0): int { $post_id = $post_id ?: get_the_ID(); if (! $post_id) { return 1; } $content = get_post_field('post_content', $post_id); $content = wp_strip_all_tags($content); $words = preg_split('/\s+/u', $content, -1, PREG_SPLIT_NO_EMPTY); $word_count = is_array($words) ? count($words) : 0; if ($word_count === 0) { return 1; } $words_per_minute = 200; $minutes = (int) ceil($word_count / $words_per_minute); return max(1, $minutes); } /** * Get formatted reading time label */ public function get_reading_time_label(int $post_id = 0): string { $minutes = $this->get_reading_time_minutes($post_id); $minutes_text = sprintf( _n('%s minute', '%s minutes', $minutes, 'datis'), number_format_i18n($minutes) ); return sprintf( __('Estimated reading time: %s', 'datis'), $minutes_text ); } /** * Get formatted post date label */ public function get_post_date_label(int $post_id = 0, string $format = ''): string { $post_id = $post_id ?: get_the_ID(); if (! $post_id) { return ''; } $timestamp = get_post_time('U', false, $post_id); if ($format === '') { $format = get_option('date_format'); } return wp_date($format, $timestamp); } /** * Get post author meta array */ public function get_post_author_meta(int $post_id = 0): array { $post_id = $post_id ?: get_the_ID(); $author_id = (int) get_post_field('post_author', $post_id); if (! $author_id) { return [ 'id' => 0, 'name' => '', 'avatar_url' => '', 'description' => '', 'url' => '', ]; } return [ 'id' => $author_id, 'name' => get_the_author_meta('display_name', $author_id), 'avatar_url' => get_avatar_url($author_id, ['size' => 64]), 'description' => get_the_author_meta('description', $author_id), 'url' => get_author_posts_url($author_id), ]; } /** * Meta bar data used by single template */ public function get_post_meta_bar_data(int $post_id = 0): array { $post_id = $post_id ?: get_the_ID(); return [ 'reading_time_label' => $this->get_reading_time_label($post_id), 'views_label' => $this->get_post_views_label($post_id), 'date_label' => $this->get_post_date_label($post_id), 'author' => $this->get_post_author_meta($post_id), ]; } /** * Get post tags list (name + url) */ public function get_post_tags(int $post_id = 0): array { $post_id = $post_id ?: get_the_ID(); $terms = get_the_terms($post_id, 'post_tag'); if (empty($terms) || is_wp_error($terms)) { return []; } return array_map(function ($term) { return [ 'name' => $term->name, 'url' => get_term_link($term), ]; }, $terms); } /** * Get post categories list (name + url) */ public function get_post_categories(int $post_id = 0): array { $post_id = $post_id ?: get_the_ID(); $terms = get_the_terms($post_id, 'category'); if (empty($terms) || is_wp_error($terms)) { return []; } return array_map(function ($term) { return [ 'name' => $term->name, 'url' => get_term_link($term), ]; }, $terms); } /* ========================================================= * Helpers * ======================================================= */ public function get_theme_option(string $key, $default = null) { $options = get_option('datis_theme_options', []); return $options[$key] ?? $default; } } Datis_Theme_Extras::get_instance(); } /* ========================================================= * ✅ GLOBAL wrappers (so old templates keep working) * ======================================================= */ if (!function_exists('datis_get_recent_product_categories')) { function datis_get_recent_product_categories(int $limit = 4): array { return Datis_Theme_Extras::get_instance()->datis_get_recent_product_categories($limit); } } if (!function_exists('datis_get_products_for_cat')) { function datis_get_products_for_cat(int $term_id, int $limit = 4): WP_Query { return Datis_Theme_Extras::get_instance()->datis_get_products_for_cat($term_id, $limit); } } if (!function_exists('datis_render_product_slide_html')) { function datis_render_product_slide_html(WC_Product $product): string { return Datis_Theme_Extras::get_instance()->datis_render_product_slide_html($product); } }
💾 保存文件
← 返回文件管理器