' . __( 'Settings', '16deza-table-cell-extras' ) . ''; array_unshift( $links, $settings_link ); return $links; } /* ============================================================ エディター Assets の登録・エンキュー ============================================================ */ add_action( 'enqueue_block_editor_assets', 'tcex_enqueue_editor_assets' ); function tcex_enqueue_editor_assets() { $js_file = TCEX_PLUGIN_DIR . 'build/index.js'; if ( file_exists( $js_file ) ) { $asset_file = TCEX_PLUGIN_DIR . 'build/index.asset.php'; $asset = file_exists( $asset_file ) ? require $asset_file : [ 'dependencies' => [], 'version' => TCEX_VERSION ]; wp_enqueue_script( 'rich-table-cells-editor', TCEX_PLUGIN_URL . 'build/index.js', array_merge( $asset['dependencies'], [ 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-rich-text', 'wp-hooks', 'wp-i18n', 'wp-data', 'wp-compose', ] ), $asset['version'], true ); /* プラグイン設定を JS に渡す */ wp_localize_script( 'rich-table-cells-editor', 'tcexSettings', [ 'allowOutsideTable' => (bool) get_option( 'tcex_allow_outside_table', false ), ] ); } /* フロントエンド共通スタイル(エディター専用スタイルを含む)もエディターに読み込む */ wp_enqueue_style( 'rich-table-cells-frontend', TCEX_PLUGIN_URL . 'assets/css/frontend.css', [ 'wp-edit-blocks' ], TCEX_VERSION ); /* JS の翻訳ファイルを登録 */ wp_set_script_translations( 'rich-table-cells-editor', '16deza-table-cell-extras', TCEX_PLUGIN_DIR . 'languages' ); } /* ============================================================ フロントエンド用スタイルの読み込み ============================================================ */ add_action( 'wp_enqueue_scripts', 'tcex_enqueue_frontend_assets' ); function tcex_enqueue_frontend_assets() { wp_enqueue_style( 'rich-table-cells-frontend', TCEX_PLUGIN_URL . 'assets/css/frontend.css', [], TCEX_VERSION ); } /* ============================================================ ヘルパー:BorderBoxControl 形式 {color, style, width} → CSS 文字列 ============================================================ */ function tcex_border_obj_css( $obj ) { if ( ! is_array( $obj ) ) { return ''; } $width = isset( $obj['width'] ) ? (string) $obj['width'] : ''; $style = isset( $obj['style'] ) ? (string) $obj['style'] : 'solid'; $color = isset( $obj['color'] ) ? (string) $obj['color'] : ''; if ( '' === $width ) { return ''; } if ( ! preg_match( '/^\d+px$/', $width ) ) { return ''; } $allowed = [ 'solid', 'dashed', 'dotted', 'double', 'groove', 'ridge', 'inset', 'outset', 'none' ]; if ( ! in_array( $style, $allowed, true ) ) { return ''; } if ( '' !== $color && ! preg_match( '/^#[0-9a-fA-F]{3,8}$/', $color ) ) { $color = ''; } return $width . ' ' . $style . ( '' !== $color ? ' ' . $color : '' ); } /* ============================================================ render_block フィルター:セル背景色・vertical-align・横スクロールをフロントエンド HTML に適用 ============================================================ */ add_filter( 'render_block', 'tcex_render_table_block', 10, 2 ); function tcex_render_table_block( $block_content, $block ) { if ( 'core/table' !== $block['blockName'] ) { return $block_content; } $colors = isset( $block['attrs']['tcxCellBgColors'] ) ? $block['attrs']['tcxCellBgColors'] : []; $va_map = isset( $block['attrs']['tcxVerticalAlign'] ) ? $block['attrs']['tcxVerticalAlign'] : []; $bdr_map = isset( $block['attrs']['tcxCellBorders'] ) ? $block['attrs']['tcxCellBorders'] : []; $mw_map = isset( $block['attrs']['tcxCellMinWidth'] ) ? $block['attrs']['tcxCellMinWidth'] : []; $ha_map = isset( $block['attrs']['tcxCellHAlign'] ) ? $block['attrs']['tcxCellHAlign'] : []; $has_colors = ! empty( $colors ) && is_array( $colors ); $has_va = ! empty( $va_map ) && is_array( $va_map ); $has_bdr = ! empty( $bdr_map ) && is_array( $bdr_map ); $has_mw = ! empty( $mw_map ) && is_array( $mw_map ); $has_ha = ! empty( $ha_map ) && is_array( $ha_map ); /* 処理対象がない場合はそのまま返す */ if ( ! $has_colors && ! $has_va && ! $has_bdr && ! $has_mw && ! $has_ha ) { return $block_content; } $output = $block_content; /* ---- DOM を使う処理(背景色 + vertical-align + text-align + border)---- */ if ( $has_colors || $has_va || $has_bdr || $has_mw || $has_ha ) { $dom = new DOMDocument( '1.0', 'UTF-8' ); libxml_use_internal_errors( true ); $dom->loadHTML( '
' . $output . '' ); libxml_clear_errors(); $table = $dom->getElementsByTagName( 'table' )->item( 0 ); if ( $table ) { $section_map = [ 'head' => 'thead', 'body' => 'tbody', 'foot' => 'tfoot', ]; /* ---- セル背景色 ---- */ if ( $has_colors ) { foreach ( $colors as $key => $color ) { if ( ! preg_match( '/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $color ) ) { continue; } $parts = explode( '_', $key, 3 ); if ( count( $parts ) < 3 ) { continue; } $sec = $parts[0]; $ri = (int) $parts[1]; $ci = (int) $parts[2]; if ( ! isset( $section_map[ $sec ] ) ) { continue; } $sections = $table->getElementsByTagName( $section_map[ $sec ] ); if ( 0 === $sections->length ) { continue; } $rows = $sections->item(0)->getElementsByTagName( 'tr' ); if ( $ri >= $rows->length ) { continue; } $cell_index = 0; foreach ( $rows->item( $ri )->childNodes as $cell ) { if ( XML_ELEMENT_NODE !== $cell->nodeType ) { continue; } if ( $cell_index === $ci ) { $existing = preg_replace( '/background-color\s*:[^;]*;?\s*/', '', $cell->getAttribute( 'style' ) ); $new_style = trim( $existing ); $new_style = ( $new_style ? rtrim( $new_style, ';' ) . '; ' : '' ) . 'background-color:' . esc_attr( $color ) . ';'; $cell->setAttribute( 'style', trim( $new_style ) ); break; } $cell_index++; } } } /* ---- vertical-align ---- */ if ( $has_va ) { $allowed_va = [ 'top', 'middle', 'bottom' ]; foreach ( $va_map as $key => $va ) { if ( ! in_array( $va, $allowed_va, true ) ) { continue; } $parts = explode( '_', $key, 3 ); if ( count( $parts ) < 3 ) { continue; } $sec = $parts[0]; $ri = (int) $parts[1]; $ci = (int) $parts[2]; if ( ! isset( $section_map[ $sec ] ) ) { continue; } $sections = $table->getElementsByTagName( $section_map[ $sec ] ); if ( 0 === $sections->length ) { continue; } $rows = $sections->item(0)->getElementsByTagName( 'tr' ); if ( $ri >= $rows->length ) { continue; } $cell_index = 0; foreach ( $rows->item( $ri )->childNodes as $cell ) { if ( XML_ELEMENT_NODE !== $cell->nodeType ) { continue; } if ( $cell_index === $ci ) { $existing = preg_replace( '/vertical-align\s*:[^;]*;?\s*/', '', $cell->getAttribute( 'style' ) ); $new_style = trim( $existing ); $new_style = ( $new_style ? rtrim( $new_style, ';' ) . '; ' : '' ) . 'vertical-align:' . $va . ';'; $cell->setAttribute( 'style', trim( $new_style ) ); break; } $cell_index++; } } } /* ---- text-align (horizontal) ---- */ if ( $has_ha ) { $allowed_ha = [ 'left', 'center', 'right' ]; foreach ( $ha_map as $key => $ha ) { if ( ! in_array( $ha, $allowed_ha, true ) ) { continue; } $parts = explode( '_', $key, 3 ); if ( count( $parts ) < 3 ) { continue; } $sec = $parts[0]; $ri = (int) $parts[1]; $ci = (int) $parts[2]; if ( ! isset( $section_map[ $sec ] ) ) { continue; } $sections = $table->getElementsByTagName( $section_map[ $sec ] ); if ( 0 === $sections->length ) { continue; } $rows = $sections->item(0)->getElementsByTagName( 'tr' ); if ( $ri >= $rows->length ) { continue; } $cell_index = 0; foreach ( $rows->item( $ri )->childNodes as $cell ) { if ( XML_ELEMENT_NODE !== $cell->nodeType ) { continue; } if ( $cell_index === $ci ) { $existing = preg_replace( '/text-align\s*:[^;]*;?\s*/', '', $cell->getAttribute( 'style' ) ); $new_style = trim( $existing ); $new_style = ( $new_style ? rtrim( $new_style, ';' ) . '; ' : '' ) . 'text-align:' . esc_attr( $ha ) . ';'; $cell->setAttribute( 'style', trim( $new_style ) ); break; } $cell_index++; } } } /* ---- border(per-side object 形式対応)---- */ if ( $has_bdr ) { foreach ( $bdr_map as $key => $bdr_val ) { $parts = explode( '_', $key, 3 ); if ( count( $parts ) < 3 ) { continue; } $sec = $parts[0]; $ri = (int) $parts[1]; $ci = (int) $parts[2]; if ( ! isset( $section_map[ $sec ] ) ) { continue; } $sections = $table->getElementsByTagName( $section_map[ $sec ] ); if ( 0 === $sections->length ) { continue; } $rows = $sections->item(0)->getElementsByTagName( 'tr' ); if ( $ri >= $rows->length ) { continue; } $cell_index = 0; foreach ( $rows->item( $ri )->childNodes as $cell ) { if ( XML_ELEMENT_NODE !== $cell->nodeType ) { continue; } if ( $cell_index === $ci ) { /* 既存の border 系プロパティを全除去 */ $existing = preg_replace( '/border(?:-(?:top|right|bottom|left))?\s*:[^;]*;?\s*/i', '', $cell->getAttribute( 'style' ) ); $new_style = trim( $existing ); if ( is_array( $bdr_val ) ) { $is_per_side = array_key_exists( 'top', $bdr_val ) || array_key_exists( 'right', $bdr_val ) || array_key_exists( 'bottom', $bdr_val ) || array_key_exists( 'left', $bdr_val ); if ( $is_per_side ) { foreach ( [ 'top', 'right', 'bottom', 'left' ] as $side ) { if ( ! isset( $bdr_val[ $side ] ) || ! is_array( $bdr_val[ $side ] ) ) { continue; } $css = tcex_border_obj_css( $bdr_val[ $side ] ); if ( '' !== $css ) { $new_style = ( $new_style ? rtrim( $new_style, ';' ) . '; ' : '' ) . 'border-' . $side . ':' . $css . ';'; } } } else { $css = tcex_border_obj_css( $bdr_val ); if ( '' !== $css ) { $new_style = ( $new_style ? rtrim( $new_style, ';' ) . '; ' : '' ) . 'border:' . $css . ';'; } } } $cell->setAttribute( 'style', trim( $new_style ) ); break; } $cell_index++; } } } /* ---- Cell Min Width(列単位)---- */ if ( $has_mw ) { foreach ( $mw_map as $col_key => $mw_val ) { $ci = (int) str_replace( 'c', '', $col_key ); /* 旧キー "0" も新キー "c0" も両方対応 */ $mw_val = (string) $mw_val; if ( ! preg_match( '/^\d+$/', $mw_val ) ) { continue; } $mw_css = 'min-width:' . $mw_val . 'em;'; foreach ( $section_map as $sec => $tag ) { $sections = $table->getElementsByTagName( $tag ); if ( 0 === $sections->length ) { continue; } $rows = $sections->item(0)->getElementsByTagName( 'tr' ); for ( $ri = 0; $ri < $rows->length; $ri++ ) { $cell_index = 0; foreach ( $rows->item( $ri )->childNodes as $cell ) { if ( XML_ELEMENT_NODE !== $cell->nodeType ) { continue; } if ( $cell_index === $ci ) { $existing = preg_replace( '/min-width\s*:[^;]*;?\s*/', '', $cell->getAttribute( 'style' ) ); $new_style = trim( $existing ); $new_style = ( $new_style ? rtrim( $new_style, ';' ) . '; ' : '' ) . $mw_css; $cell->setAttribute( 'style', trim( $new_style ) ); break; } $cell_index++; } } } } } /* body 直下の子ノードを文字列化 */ $body = $dom->getElementsByTagName( 'body' )->item( 0 ); if ( $body ) { $output = ''; foreach ( $body->childNodes as $child ) { $output .= $dom->saveHTML( $child ); } } } } return $output; } /* ============================================================ 設定ページ ============================================================ */ add_action( 'admin_menu', 'tcex_add_settings_page' ); function tcex_add_settings_page() { add_options_page( __( '16Deza Table Cell Extras – Settings', '16deza-table-cell-extras' ), __( 'Table Cell Extras', '16deza-table-cell-extras' ), 'manage_options', 'table-cell-extras', 'tcex_render_settings_page' ); } add_action( 'admin_init', 'tcex_register_settings' ); function tcex_register_settings() { register_setting( 'tcex_options_group', 'tcex_allow_outside_table', [ 'type' => 'boolean', 'default' => false, 'sanitize_callback' => 'rest_sanitize_boolean', ] ); } function tcex_render_settings_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } ?>