%s', esc_url( admin_url( 'options-general.php?page=16deza-block-curator' ) ), esc_html__( 'Settings', '16deza-block-curator' ) ); array_unshift( $links, $settings_link ); return $links; } /** * Register the settings page under Settings menu. */ public function add_settings_page() { add_options_page( __( 'Block Curator', '16deza-block-curator' ), __( 'Block Curator', '16deza-block-curator' ), 'manage_options', '16deza-block-curator', array( $this, 'render_settings_page' ) ); } /** * Enqueue admin styles and scripts. * * @param string $hook Current admin page hook. */ public function enqueue_assets( $hook ) { if ( 'settings_page_16deza-block-curator' !== $hook ) { return; } // Styles. wp_enqueue_style( '16deza-block-curator-admin', BLOCUR_PLUGIN_URL . 'admin/css/admin.css', array(), BLOCUR_VERSION ); // Admin JS (loaded in footer). No wp-blocks dependency needed — icons are static SVG. wp_enqueue_script( '16deza-block-curator-admin', BLOCUR_PLUGIN_URL . 'admin/js/admin.js', array( 'jquery' ), BLOCUR_VERSION, true ); // [1] Settings page interactions (extracted from inline script). wp_enqueue_script( '16deza-block-curator-settings', BLOCUR_PLUGIN_URL . 'admin/js/admin-settings.js', array(), BLOCUR_VERSION, true ); // Register JS translation file (must be called after wp_enqueue_script). wp_set_script_translations( '16deza-block-curator-admin', '16deza-block-curator', BLOCUR_PLUGIN_DIR . 'languages' ); // Pass PHP data to JS. $title_map = array( 'amazon-kindle' => 'Amazon Kindle', 'animoto' => 'Animoto', 'bluesky' => 'Bluesky', 'cloudup' => 'Cloudup', 'crowdsignal' => 'Crowdsignal', 'dailymotion' => 'Dailymotion', 'flickr' => 'Flickr', 'imgur' => 'Imgur', 'issuu' => 'Issuu', 'kickstarter' => 'Kickstarter', 'mixcloud' => 'Mixcloud', 'pinterest' => 'Pinterest', 'pocket-casts' => 'Pocket Casts', 'reddit' => 'Reddit', 'reverbnation' => 'ReverbNation', 'scribd' => 'Scribd', 'smugmug' => 'SmugMug', 'soundcloud' => 'SoundCloud', 'speaker-deck' => 'Speaker Deck', 'spotify' => 'Spotify', 'ted' => 'TED', 'tiktok' => 'TikTok', 'tumblr' => 'Tumblr', 'twitter' => 'Twitter / X', 'videopress' => 'VideoPress', 'vimeo' => 'Vimeo', 'wolfram-cloud'=> 'Wolfram Cloud', 'wordpress' => 'WordPress', 'wordpress-tv' => 'WordPress.tv', 'youtube' => 'YouTube', ); $variations_data = array(); foreach ( Blocur_Core::$embed_variations as $slug ) { $variations_data[] = array( 'slug' => $slug, 'title' => isset( $title_map[ $slug ] ) ? $title_map[ $slug ] : ucwords( str_replace( '-', ' ', $slug ) ), ); } // Build embed variation icon map from static SVG data. $embed_icons = array(); foreach ( Blocur_Core::$embed_variations as $slug ) { $icon_html = $this->get_block_icon_html( 'embed/' . $slug ); $embed_icons[ $slug ] = $icon_html; } wp_localize_script( '16deza-block-curator-admin', 'blocurData', array( 'embedVariations' => $variations_data, 'disabledEmbedVariations' => get_option( 'blocur_disabled_embed_variations', array() ), 'embedIcons' => $embed_icons, 'i18n' => array( 'embedVariationLabel' => __( 'Display settings for each embedded service (visual control using display:none)', '16deza-block-curator' ), 'embedVariationNote' => __( '* This is not a functional limitation as it is hidden using CSS. It may stop working after a WordPress update.', '16deza-block-curator' ), ), ) ); } /** * Handle form submission. */ public function handle_save() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Permission denied.', '16deza-block-curator' ) ); } check_admin_referer( 'blocur_save', 'blocur_nonce' ); // Save exempt_admin option. $exempt_admin = isset( $_POST['blocur_exempt_admin'] ) ? '1' : '0'; update_option( 'blocur_exempt_admin', $exempt_admin ); // [v0.1.13] ブロックチェックボックスは「チェック=有効」方式に変更。 // フォームから送信された有効ブロックリストを読み取り、 // 親ブロックが有効な場合は子ブロックも自動的に有効にする。 $submitted_enabled = array(); if ( isset( $_POST['blocur_enabled'] ) && is_array( $_POST['blocur_enabled'] ) ) { $submitted_enabled = array_map( 'sanitize_text_field', wp_unslash( $_POST['blocur_enabled'] ) ); } // 全登録ブロックを取得。 $registered = WP_Block_Type_Registry::get_instance()->get_all_registered(); $all_blocks = array_keys( $registered ); // 親ブロックが有効なら、対応する子ブロックも有効にする。 $parent_child_map = self::get_parent_child_map(); foreach ( $parent_child_map as $parent => $children ) { if ( in_array( $parent, $submitted_enabled, true ) ) { foreach ( $children as $child ) { if ( in_array( $child, $all_blocks, true ) && ! in_array( $child, $submitted_enabled, true ) ) { $submitted_enabled[] = $child; } } } } // 無効ブロック = 全登録ブロック − 有効ブロック。 $disabled = array_values( array_diff( $all_blocks, $submitted_enabled ) ); // Virtual WooCommerce product-collection variations (JS-only, not in PHP registry). // These appear as virtual entries in the settings UI but are never in $all_blocks, // so they must be handled separately: if not submitted as enabled → mark as disabled. $virtual_wc_variations = array( 'woocommerce/product-collection/best-sellers', 'woocommerce/product-collection/by-brand', 'woocommerce/product-collection/by-category', 'woocommerce/product-collection/by-tag', 'woocommerce/product-collection/cross-sells', 'woocommerce/product-collection/featured', 'woocommerce/product-collection/hand-picked', 'woocommerce/product-collection/new-arrivals', 'woocommerce/product-collection/on-sale', 'woocommerce/product-collection/related', 'woocommerce/product-collection/top-rated', 'woocommerce/product-collection/upsells', ); foreach ( $virtual_wc_variations as $vname ) { if ( ! in_array( $vname, $submitted_enabled, true ) ) { $disabled[] = $vname; } } update_option( 'blocur_disabled_blocks', $disabled ); // Save enabled embed variations (v0.1.13: checked=enabled). $all_embed_variations = Blocur_Core::$embed_variations; $submitted_enabled_embeds = array(); if ( isset( $_POST['blocur_enabled_embed_variations'] ) && is_array( $_POST['blocur_enabled_embed_variations'] ) ) { $submitted_enabled_embeds = array_map( 'sanitize_text_field', wp_unslash( $_POST['blocur_enabled_embed_variations'] ) ); } $disabled_variations = array_values( array_diff( $all_embed_variations, $submitted_enabled_embeds ) ); update_option( 'blocur_disabled_embed_variations', $disabled_variations ); wp_safe_redirect( add_query_arg( array( 'page' => '16deza-block-curator', 'updated' => '1', ), admin_url( 'options-general.php' ) ) ); exit; } /** * 親ブロック → 子ブロック のマッピング。 * UI上は親ブロックのみ表示し、保存時に子ブロックを自動同期する。 * * @return array 親ブロック名 => 子ブロック名の配列 */ public static function get_parent_child_map() { return array( 'core/accordion' => array( 'core/accordion-heading', 'core/accordion-item', 'core/accordion-panel' ), 'core/buttons' => array( 'core/button' ), 'core/columns' => array( 'core/column' ), 'core/comments' => array( 'core/comment-author-name', 'core/comment-content', 'core/comment-date', 'core/comment-edit-link', 'core/comment-reply-link', 'core/comments-pagination', 'core/comments-pagination-next', 'core/comments-pagination-numbers', 'core/comments-pagination-previous', 'core/comments-title', ), 'core/list' => array( 'core/list-item' ), 'core/navigation' => array( 'core/navigation-link', 'core/navigation-submenu', 'core/home-link', 'core/navigation-overlay-close' ), 'core/page-list' => array( 'core/page-list-item' ), 'core/playlist' => array( 'core/playlist-track' ), 'core/social-links' => array( 'core/social-link' ), 'core/tabs' => array( 'core/tab-panel', 'core/tab-panels', 'core/tab-list' ), // ── WooCommerce ───────────────────────────────────────────────────────────── 'woocommerce/cart' => array( 'woocommerce/cart-items-block', 'woocommerce/cart-line-items-block', 'woocommerce/cart-order-summary-block', 'woocommerce/cart-order-summary-coupon-form-block', 'woocommerce/cart-order-summary-discount-block', 'woocommerce/cart-order-summary-fee-block', 'woocommerce/cart-order-summary-heading-block', 'woocommerce/cart-order-summary-shipping-block', 'woocommerce/cart-order-summary-subtotal-block', 'woocommerce/cart-order-summary-tax-block', 'woocommerce/cart-express-payment-block', 'woocommerce/cart-cross-sells-block', 'woocommerce/cart-cross-sells-products-block', 'woocommerce/proceed-to-checkout-block', 'woocommerce/cart-accepted-payment-methods-block', 'woocommerce/filled-cart-block', 'woocommerce/empty-cart-block', 'woocommerce/cart-totals-block', ), 'woocommerce/checkout' => array( 'woocommerce/checkout-actions-block', 'woocommerce/checkout-billing-address-block', 'woocommerce/checkout-contact-information-block', 'woocommerce/checkout-express-payment-block', 'woocommerce/checkout-fields-block', 'woocommerce/checkout-order-information-block', 'woocommerce/checkout-order-note-block', 'woocommerce/checkout-order-summary-block', 'woocommerce/checkout-order-summary-coupon-form-block', 'woocommerce/checkout-order-summary-discount-block', 'woocommerce/checkout-order-summary-fee-block', 'woocommerce/checkout-order-summary-shipping-block', 'woocommerce/checkout-order-summary-subtotal-block', 'woocommerce/checkout-order-summary-tax-block', 'woocommerce/checkout-payment-block', 'woocommerce/checkout-pickup-options-block', 'woocommerce/checkout-shipping-address-block', 'woocommerce/checkout-shipping-methods-block', 'woocommerce/checkout-totals-block', 'woocommerce/checkout-terms-block', 'woocommerce/checkout-additional-information-block', // Order-confirmation inner blocks — with and without '-block' suffix. // (Prefix 'woocommerce/order-confirmation-' is also caught dynamically.) 'woocommerce/order-confirmation-downloads-block', 'woocommerce/order-confirmation-downloads', 'woocommerce/order-confirmation-status-block', 'woocommerce/order-confirmation-status', 'woocommerce/order-confirmation-totals-block', 'woocommerce/order-confirmation-totals', 'woocommerce/order-confirmation-totals-wrapper-block', 'woocommerce/order-confirmation-totals-wrapper', 'woocommerce/order-confirmation-shipping-address-block', 'woocommerce/order-confirmation-shipping-address', 'woocommerce/order-confirmation-shipping-address-wrapper', 'woocommerce/order-confirmation-billing-address-block', 'woocommerce/order-confirmation-billing-address', 'woocommerce/order-confirmation-billing-address-wrapper', 'woocommerce/order-confirmation-additional-fields-block', 'woocommerce/order-confirmation-additional-fields', 'woocommerce/order-confirmation-additional-fields-list', 'woocommerce/order-confirmation-additional-information', 'woocommerce/order-confirmation-summary', 'woocommerce/order-confirmation-order-details', ), 'woocommerce/mini-cart' => array( 'woocommerce/mini-cart-shopping-button-block', 'woocommerce/mini-cart-title-block', 'woocommerce/mini-cart-title-label-block', 'woocommerce/mini-cart-title-items-counter-block', 'woocommerce/mini-cart-footer-block', 'woocommerce/mini-cart-products-table-block', 'woocommerce/mini-cart-cart-button-block', 'woocommerce/mini-cart-checkout-button-block', 'woocommerce/filled-mini-cart-contents-block', 'woocommerce/empty-mini-cart-contents-block', 'woocommerce/mini-cart-contents', 'woocommerce/mini-cart-contents-block', 'woocommerce/mini-cart-items-block', 'woocommerce/mini-cart-items', ), 'woocommerce/product-collection' => array( 'woocommerce/product-template', 'woocommerce/product-collection-toolbar', 'woocommerce/product-collection-no-results-block', 'woocommerce/product-collection-no-results', // Actual registered name (no -block suffix). 'woocommerce/product-results-count', ), 'woocommerce/product-filters' => array( // Legacy filter blocks. 'woocommerce/attribute-filter', 'woocommerce/price-filter', 'woocommerce/stock-filter', 'woocommerce/rating-filter', // Current filter blocks (product-filter-* prefix also caught dynamically). 'woocommerce/product-filter-active', 'woocommerce/product-filter-attribute', 'woocommerce/product-filter-price', 'woocommerce/product-filter-price-slider', 'woocommerce/product-filter-rating', 'woocommerce/product-filter-removable-chips', 'woocommerce/product-filter-status', 'woocommerce/product-filter-stock-status', 'woocommerce/product-filter-chips', 'woocommerce/product-filter-clear', 'woocommerce/product-filter-list', 'woocommerce/product-filter-checkbox', 'woocommerce/product-filter-color', 'woocommerce/product-filter-sale-status', // Filter utility blocks grouped here for settings-UI simplicity. 'woocommerce/active-filters', // アクティブなフィルターコントロール ), 'woocommerce/filter-wrapper' => array( 'woocommerce/dropdown', ), 'woocommerce/accordion-group' => array( 'woocommerce/accordion-item', 'woocommerce/accordion-panel', 'woocommerce/accordion-header', ), 'woocommerce/product-gallery' => array( 'woocommerce/product-gallery-large-image', 'woocommerce/product-gallery-thumbnails', 'woocommerce/product-gallery-large-image-next-previous', 'woocommerce/product-gallery-pager', ), 'woocommerce/product-reviews' => array( 'woocommerce/product-reviews-title', 'woocommerce/product-review-template', 'woocommerce/product-reviews-pagination', 'woocommerce/product-reviews-pagination-numbers', 'woocommerce/product-reviews-pagination-previous', 'woocommerce/product-review-author-name', 'woocommerce/product-reviews-pagination-next', 'woocommerce/product-review-rating', 'woocommerce/product-review-date', 'woocommerce/product-review-content', ), 'woocommerce/single-product' => array( 'woocommerce/product-button', 'woocommerce/add-to-cart-form', 'woocommerce/add-to-cart-with-options', 'woocommerce/product-sale-badge', 'woocommerce/product-sku', 'woocommerce/product-meta', 'woocommerce/product-average-rating', 'woocommerce/product-rating', 'woocommerce/product-price', 'woocommerce/product-title', 'woocommerce/product-stock-indicator', 'woocommerce/product-summary', 'woocommerce/product-image', 'woocommerce/product-image-gallery', 'woocommerce/product-rating-stars', 'woocommerce/product-rating-count', 'woocommerce/product-rating-counter', // Alternative name variant. 'woocommerce/product-description', 'woocommerce/product-details', 'woocommerce/product-specifications', 'woocommerce/product-quantity', // WooCommerce 商品要素 inner block. // Note: woocommerce/related-products is a standalone insertable block // and must NOT be listed here — kept visible in settings UI. ), ); } /** * Dynamically detect WooCommerce inner blocks that should be hidden from the * settings UI. Blocks whose block.json 'parent' metadata is set to a * WooCommerce-namespaced block cannot be freely inserted by users (they live * exclusively inside a specific parent block) and therefore do not need to * appear as individual toggle items in Block Curator's settings page. * * This dynamic detection complements the static get_parent_child_map() list * and automatically covers new inner blocks added by WooCommerce updates. * * @return string[] Registered block names to exclude from the settings UI. */ private static function get_auto_hidden_wc_blocks() { static $cache = null; if ( null !== $cache ) { return $cache; } /* * Blocks whose name starts with any of these prefixes are specialized * inner blocks that cannot be freely inserted by users, regardless of * whether their block.json declares a 'parent'. Matching by prefix * covers all variants without requiring exact name enumeration. * * Prefixes must end with a separator (/ or -) to avoid false positives. */ $hidden_prefixes = array( 'woocommerce/order-confirmation-', // Order-confirmation page inner blocks. 'woocommerce/add-to-cart-with-options/', // ATC-with-options sub-blocks (slash notation). 'woocommerce/add-to-cart-with-options-', // ATC-with-options sub-blocks (hyphen notation). 'woocommerce/product-filter-', // Individual filter display/type blocks. ); /* * Blocks that declare a WooCommerce parent in block.json but are still * independently insertable from the editor inserter. They must remain * visible in the settings UI so site administrators can control them. */ $visible_exceptions = array( 'woocommerce/related-products', // 関連商品 — standalone insertable. 'woocommerce/cart-cross-sells-products', // クロスセル — standalone insertable. ); /* * Blocks that are always hidden from the settings UI regardless of * parent metadata — experimental, email-only, or not available in the * standard block inserter. */ $always_hidden = array( 'woocommerce/accordion-group', // __experimental:true — not in standard inserter. 'woocommerce/catalog-sorting', // Shop/archive context only; not in general inserter. 'woocommerce/email-content', // "inserter":false, email:true — email templates only. 'woocommerce/breadcrumbs', // Store breadcrumbs — not in standard inserter. ); $hidden = array(); $registry = WP_Block_Type_Registry::get_instance(); foreach ( $registry->get_all_registered() as $block_name => $block_type ) { $should_hide = false; // 1. Check 'parent' metadata: restricted to a WooCommerce parent → inner block. if ( ! empty( $block_type->parent ) ) { foreach ( $block_type->parent as $parent_name ) { if ( 0 === strncmp( $parent_name, 'woocommerce/', 12 ) ) { $should_hide = true; break; } } } // 2. Check name prefix: known inner-block name patterns. if ( ! $should_hide ) { foreach ( $hidden_prefixes as $prefix ) { if ( 0 === strncmp( $block_name, $prefix, strlen( $prefix ) ) ) { $should_hide = true; break; } } } if ( $should_hide && ! in_array( $block_name, $visible_exceptions, true ) ) { $hidden[] = $block_name; } } $cache = array_unique( array_merge( $hidden, $always_hidden ) ); return $cache; } /** * Return allowed HTML tags/attributes for wp_kses() when outputting block icon SVGs. * * @return array */ private function allowed_svg_kses() { $svg_args = array( 'svg' => array( 'class' => true, 'xmlns' => true, 'viewbox' => true, 'aria-hidden' => true, 'focusable' => true, 'width' => true, 'height' => true, ), 'g' => array( 'fill' => true ), 'path' => array( 'd' => true, 'fill' => true, 'fill-rule' => true, 'clip-rule' => true, 'stroke' => true, 'stroke-width' => true ), 'rect' => array( 'x' => true, 'y' => true, 'width' => true, 'height' => true, 'rx' => true, 'ry' => true, 'fill' => true ), 'circle' => array( 'cx' => true, 'cy' => true, 'r' => true, 'fill' => true ), 'polygon' => array( 'points' => true, 'fill' => true ), 'span' => array( 'class' => true, 'aria-hidden' => true, 'style' => true ), ); return $svg_args; } /** * [2] Return inline SVG markup for a block icon. * Covers all core blocks with the same paths WordPress uses in the inserter. * Falls back to a generic block-outline icon for unknown blocks. * * @param string $block_name e.g. "core/paragraph". * @return string Safe HTML (inline SVG). */ private function get_block_icon_html( $block_name ) { static $icons = null; if ( null === $icons ) { $icons = array( // ── Icons extracted from live block inserter HTML ────────────────────────── // ── Core blocks ───────────────────────────────────────────────────────── 'core/accordion' => '', 'core/archives' => '', 'core/audio' => '', 'core/avatar' => '', 'core/buttons' => '', 'core/calendar' => '', 'core/categories' => '', 'core/code' => '', 'core/columns' => '', 'core/comments' => '', 'core/cover' => '', 'core/details' => '', 'core/embed' => '', 'core/file' => '', 'core/freeform' => '', 'core/gallery' => '', 'core/group' => '', 'core/grid' => '', 'core/row' => '', 'core/stack' => '', 'core/heading' => '', 'core/html' => '', 'core/image' => '', 'core/latest-comments' => '', 'core/latest-posts' => '', 'core/list' => '', 'core/loginout' => '', 'core/math' => '', 'core/media-text' => '', 'core/more' => '', 'core/navigation' => '', 'core/nextpage' => '', 'core/page-list' => '', 'core/paragraph' => '', 'core/post-author' => '', 'core/post-author-biography' => '', 'core/post-author-name' => '', 'core/post-comments-count' => '', 'core/post-comments-form' => '', 'core/post-comments-link' => '', 'core/post-content' => '', 'core/post-date' => '', 'core/post-excerpt' => '', 'core/post-featured-image' => '', 'core/post-title' => '', 'core/preformatted' => '', 'core/pullquote' => '', 'core/query' => '', 'core/query-pagination-previous' => '', 'core/query-pagination-next' => '', 'core/quote' => '', 'core/read-more' => '', 'core/rss' => '', 'core/search' => '', 'core/separator' => '', 'core/shortcode' => '', 'core/site-logo' => '', 'core/site-tagline' => '', 'core/site-title' => '', 'core/social-links' => '', 'core/spacer' => '', 'core/table' => '', 'core/tag-cloud' => '', 'core/template-part' => '', 'core/term-count' => '', 'core/term-description' => '', 'core/term-name' => '', 'core/terms-query' => '', 'core/verse' => '', 'core/video' => '', // ── WordPress 7.0 新規ブロック ──────────────────────────────────────── 'core/icon' => '', 'core/breadcrumbs' => '', 'core/post-time-to-read' => '', // ── WordPress 7.1 新規ブロック ──────────────────────────────────────── 'core/playlist' => '', 'core/tabs' => '', // ── アイコン補完 (WP7.0以前・インサーター非表示の内部ブロック含む) ───── 'core/post-comments' => '', 'core/post-navigation-link' => '', 'core/post-template' => '', 'core/post-terms' => '', 'core/query-pagination' => '', 'core/query-pagination-numbers' => '', 'core/query-title' => '', 'core/term-template' => '', // ── Embed variations ──────────────────────────────────────────────────── 'embed/amazon-kindle' => '', 'embed/animoto' => '', 'embed/bluesky' => '', 'embed/cloudup' => '', 'embed/crowdsignal' => '', 'embed/dailymotion' => '', 'embed/flickr' => '', 'embed/imgur' => '', 'embed/issuu' => '', 'embed/kickstarter' => '', 'embed/mixcloud' => '', 'embed/pinterest' => '', 'embed/pocket-casts' => '', 'embed/reddit' => '', 'embed/reverbnation' => '', 'embed/scribd' => '', 'embed/smugmug' => '', 'embed/soundcloud' => '', 'embed/speaker-deck' => '', 'embed/spotify' => '', 'embed/ted' => '', 'embed/tiktok' => '', 'embed/tumblr' => '', 'embed/twitter' => '', 'embed/videopress' => '', 'embed/vimeo' => '', 'embed/wolfram-cloud' => '', 'embed/wordpress' => '', 'embed/wordpress-tv' => '', 'embed/youtube' => '', // ── WooCommerce ─────────────────────────────────────────────────────────────── 'woocommerce/all-reviews' => '', 'woocommerce/cart' => '', 'woocommerce/cart-link' => '', 'woocommerce/category-description' => '', 'woocommerce/category-title' => '', 'woocommerce/checkout' => '', 'woocommerce/classic-shortcode' => '', 'woocommerce/coupon-code' => '', 'woocommerce/customer-account' => '', 'woocommerce/featured-category' => '', 'woocommerce/featured-product' => '', 'woocommerce/mini-cart' => '', 'woocommerce/payment-method-icons' => '', 'woocommerce/product-categories' => '', 'woocommerce/product-collection' => '', 'woocommerce/product-filters' => '', 'woocommerce/product-review-form' => '', 'woocommerce/product-search' => '', 'woocommerce/reviews-by-category' => '', 'woocommerce/reviews-by-product' => '', 'woocommerce/single-product' => '', 'woocommerce/store-notices' => '', // ── WooCommerce (top-level blocks without icons) ────────────────────────── 'woocommerce/email-content' => '', 'woocommerce/handpicked-products' => '', 'woocommerce/legacy-template' => '', 'woocommerce/product-best-sellers' => '', 'woocommerce/product-gallery' => '', 'woocommerce/product-new' => '', 'woocommerce/product-query' => '', 'woocommerce/product-reviews' => '', 'woocommerce/product-top-rated' => '', 'woocommerce/products-by-attribute'=> '', 'woocommerce-payments/multi-currency-switcher' => '', // Additional WooCommerce icons (v0.1.16) 'woocommerce/accordion-group' => '', 'woocommerce/accordion-header' => '', 'woocommerce/accordion-item' => '', 'woocommerce/accordion-panel' => '', 'woocommerce/active-filters' => '', 'woocommerce/all-products' => '', 'woocommerce/breadcrumb' => '', 'woocommerce/breadcrumbs' => '', 'woocommerce/catalog-sorting' => '', 'woocommerce/coming-soon' => '', 'woocommerce/dropdown' => '', 'woocommerce/filter-wrapper' => '', 'woocommerce/page-content-wrapper' => '', 'woocommerce/product-category' => '', 'woocommerce/product-on-sale' => '', 'woocommerce/product-tag' => '', // Extracted from wc-block-library.js (WooCommerce 11.1.0). 'woocommerce/related-products' => '', 'woocommerce/cart-cross-sells-products' => '', // product-collection JS-only sub-types (no PHP block.json — virtual entries). // Icons extracted from wc-blocks-vendors.js / inserter HTML (WooCommerce 11.1.0). 'woocommerce/product-collection/best-sellers' => '', 'woocommerce/product-collection/by-brand' => '', 'woocommerce/product-collection/by-category' => '', 'woocommerce/product-collection/by-tag' => '', 'woocommerce/product-collection/cross-sells' => '', 'woocommerce/product-collection/featured' => '', 'woocommerce/product-collection/hand-picked' => '', 'woocommerce/product-collection/new-arrivals' => '', 'woocommerce/product-collection/on-sale' => '', 'woocommerce/product-collection/related' => '', 'woocommerce/product-collection/top-rated' => '', 'woocommerce/product-collection/upsells' => '', ); } $path_d = isset( $icons[ $block_name ] ) ? $icons[ $block_name ] : ''; return ''; } /** * Render the settings page HTML. */ public function render_settings_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $disabled_blocks = get_option( 'blocur_disabled_blocks', array() ); $exempt_admin = get_option( 'blocur_exempt_admin', '1' ); $blocks_by_cat = Blocur_Core::get_blocks_by_category(); /* * Align the WooCommerce block list in the settings UI with the block inserter. * * 1. Whitelist: only show PHP-registered blocks that actually appear in the * block inserter (WooCommerce 11.1.0). Old standalone blocks that have been * superseded by product-collection variations (e.g. woocommerce/product-new) * are intentionally excluded here so users see exactly what they can insert. * * 2. Virtual entries: all 12 product-collection sub-type variations are * registered client-side only (no block.json / PHP registry entry) and are * therefore invisible to get_blocks_by_category(). Adding them as virtual * entries lets admins control each variation from the settings UI. * Their full variation names (woocommerce/product-collection/*) are stored in * blocur_disabled_blocks; the JS enforcement script (block-editor-restrict.js * Case B) unregisters the corresponding variation when it finds the name in * the disabled list. */ if ( isset( $blocks_by_cat['woocommerce'] ) ) { // WooCommerce blocks that actually appear in the block inserter (master list). // Keep this in sync with the inserter; add entries here when WooCommerce // adds new top-level insertable blocks in future releases. $wc_inserter_blocks = array( 'woocommerce/all-reviews', 'woocommerce/cart', 'woocommerce/cart-link', 'woocommerce/category-description', 'woocommerce/category-title', 'woocommerce-classic-shortcode/cart', 'woocommerce-classic-shortcode/checkout', 'woocommerce/checkout', 'woocommerce/coupon-code', 'woocommerce/customer-account', 'woocommerce/featured-category', 'woocommerce/featured-product', 'woocommerce/mini-cart', 'woocommerce/payment-method-icons', 'woocommerce/product-categories', 'woocommerce/product-collection', 'woocommerce/product-filters', 'woocommerce/product-review-form', 'woocommerce/product-search', 'woocommerce/reviews-by-category', 'woocommerce/reviews-by-product', 'woocommerce/single-product', 'woocommerce/store-notices', ); // Filter: keep only blocks that are in the inserter whitelist. $blocks_by_cat['woocommerce']['blocks'] = array_values( array_filter( $blocks_by_cat['woocommerce']['blocks'], function ( $block ) use ( $wc_inserter_blocks ) { return in_array( $block['name'], $wc_inserter_blocks, true ); } ) ); // Virtual entries: all 12 product-collection variations (JS-only, WC 11.1.0). $virtual_wc_variations = array( array( 'name' => 'woocommerce/product-collection/best-sellers', 'title' => __( 'Best Sellers', '16deza-block-curator' ) ), array( 'name' => 'woocommerce/product-collection/by-brand', 'title' => __( 'Products by Brand', '16deza-block-curator' ) ), array( 'name' => 'woocommerce/product-collection/by-category', 'title' => __( 'Products by Category', '16deza-block-curator' ) ), array( 'name' => 'woocommerce/product-collection/by-tag', 'title' => __( 'Products by Tag', '16deza-block-curator' ) ), array( 'name' => 'woocommerce/product-collection/cross-sells', 'title' => __( 'Cross-Sells', '16deza-block-curator' ) ), array( 'name' => 'woocommerce/product-collection/featured', 'title' => __( 'Featured', '16deza-block-curator' ) ), array( 'name' => 'woocommerce/product-collection/hand-picked', 'title' => __( 'Hand-Picked', '16deza-block-curator' ) ), array( 'name' => 'woocommerce/product-collection/new-arrivals', 'title' => __( 'New Arrivals', '16deza-block-curator' ) ), array( 'name' => 'woocommerce/product-collection/on-sale', 'title' => __( 'On Sale', '16deza-block-curator' ) ), array( 'name' => 'woocommerce/product-collection/related', 'title' => __( 'Related Products', '16deza-block-curator' ) ), array( 'name' => 'woocommerce/product-collection/top-rated', 'title' => __( 'Top Rated', '16deza-block-curator' ) ), array( 'name' => 'woocommerce/product-collection/upsells', 'title' => __( 'Upsells', '16deza-block-curator' ) ), ); $blocks_by_cat['woocommerce']['blocks'] = array_merge( $blocks_by_cat['woocommerce']['blocks'], $virtual_wc_variations ); // Re-sort alphabetically by title (matches get_blocks_by_category() order). usort( $blocks_by_cat['woocommerce']['blocks'], function ( $a, $b ) { return strcmp( $a['title'], $b['title'] ); } ); } if ( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] == 'POST' ) { if ( ! check_admin_referer( 'blocur_save', 'blocur_nonce') ) { wp_die('セキュリティエラー:この操作は許可されていません。'); // 検証失敗時(不正アクセス)の処理 } $updated = isset( $_GET['updated'] ) && '1' === $_GET['updated']; } else { $updated = ''; } ?>

v

$cat ) : ?>
/