の前)に以下を追記するだけで使用可能: // require_once get_template_directory() . '/migration-tool.php'; // // 【機能】 // - 任意の投稿タイプ x タクソノミー x タームで絞り込んでエクスポート // - インポート時に既存投稿を上書き(タイトル不一致は警告) // - サイトURL・WordPressアドレスを自動または手動で置換 // - ACFフィールド・シリアライズデータ対応 // - WP6.5 / WP7 両対応 // ============================================================ // インポート時にスキップする画像関連メタキー // (移行先で画像IDが異なるため上書きしない) function migration_tool_skip_meta_keys() { return [ '_thumbnail_id', '_wp_attached_file', '_wp_attachment_metadata', ]; } // URL置換ヘルパー関数 // 文字列・配列・シリアライズ済みデータ内のURLを再帰的に置換する function migration_tool_replace_url( $value, $src_url, $dst_url ) { if ( empty( $src_url ) || $src_url === $dst_url ) return $value; if ( is_array( $value ) ) { return array_map( function( $v ) use ( $src_url, $dst_url ) { return migration_tool_replace_url( $v, $src_url, $dst_url ); }, $value ); } if ( ! is_string( $value ) ) return $value; // シリアライズ済みデータはunserialize → 置換 → reserialize $unserialized = @unserialize( $value ); if ( $unserialized !== false || $value === 'b:0;' ) { $replaced = migration_tool_replace_url( $unserialized, $src_url, $dst_url ); return serialize( $replaced ); } return str_replace( $src_url, $dst_url, $value ); } // ------------------------------------------------------- // 管理画面メニューに「移行ツール」を追加 // ------------------------------------------------------- add_action( 'admin_menu', function () { add_menu_page( '移行ツール', '移行ツール', 'manage_options', 'migration-tool', 'migration_tool_page', 'dashicons-migrate', 100 ); } ); // ------------------------------------------------------- // Ajax:投稿タイプに紐づくタクソノミー一覧を返す // ------------------------------------------------------- add_action( 'wp_ajax_migration_get_taxonomies', function () { if ( ! current_user_can( 'manage_options' ) ) wp_die( '権限がありません' ); check_ajax_referer( 'migration_tool_nonce', 'nonce' ); $post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( $_POST['post_type'] ) : ''; if ( ! $post_type ) wp_send_json_success( [] ); $taxonomies = get_object_taxonomies( $post_type, 'objects' ); $result = []; foreach ( $taxonomies as $slug => $tax ) { $result[] = [ 'slug' => $slug, 'label' => $tax->label ]; } wp_send_json_success( $result ); } ); // ------------------------------------------------------- // Ajax:タクソノミーのターム一覧を返す // ------------------------------------------------------- add_action( 'wp_ajax_migration_get_terms', function () { if ( ! current_user_can( 'manage_options' ) ) wp_die( '権限がありません' ); check_ajax_referer( 'migration_tool_nonce', 'nonce' ); $taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_text_field( $_POST['taxonomy'] ) : ''; if ( ! $taxonomy ) wp_send_json_success( [] ); $terms = get_terms( [ 'taxonomy' => $taxonomy, 'hide_empty' => false ] ); $result = []; if ( ! is_wp_error( $terms ) ) { foreach ( $terms as $term ) { $result[] = [ 'slug' => $term->slug, 'name' => $term->name, 'count' => $term->count ]; } } wp_send_json_success( $result ); } ); // ------------------------------------------------------- // Ajax:タームに紐づく投稿のID・タイトル一覧を返す // ------------------------------------------------------- add_action( 'wp_ajax_migration_get_posts', function () { if ( ! current_user_can( 'manage_options' ) ) wp_die( '権限がありません' ); check_ajax_referer( 'migration_tool_nonce', 'nonce' ); $post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( $_POST['post_type'] ) : ''; $taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_text_field( $_POST['taxonomy'] ) : ''; $term_slug = isset( $_POST['term_slug'] ) ? sanitize_text_field( $_POST['term_slug'] ) : ''; if ( ! $post_type || ! $taxonomy || ! $term_slug ) wp_send_json_success( [] ); $posts = get_posts( [ 'post_type' => $post_type, 'posts_per_page' => -1, 'post_status' => 'any', 'tax_query' => [ [ 'taxonomy' => $taxonomy, 'field' => 'slug', 'terms' => $term_slug, ] ], ] ); $result = array_map( function ( $p ) { return [ 'id' => $p->ID, 'title' => $p->post_title ]; }, $posts ); wp_send_json_success( $result ); } ); // ------------------------------------------------------- // エクスポート処理(admin_post フック) // フォームからPOSTされたときにJSONを生成してダウンロード // ------------------------------------------------------- add_action( 'admin_post_migration_export', function () { if ( ! current_user_can( 'manage_options' ) ) wp_die( '権限がありません' ); check_admin_referer( 'migration_tool_nonce' ); $post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( $_POST['post_type'] ) : ''; $taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_text_field( $_POST['taxonomy'] ) : ''; $term_slug = isset( $_POST['term_slug'] ) ? sanitize_text_field( $_POST['term_slug'] ) : ''; $id_input = isset( $_POST['export_ids'] ) ? sanitize_text_field( $_POST['export_ids'] ) : ''; $ids = []; // IDが直接指定された場合はIDを優先 if ( ! empty( $id_input ) ) { $ids = array_filter( array_map( 'intval', explode( ',', $id_input ) ) ); } // タクソノミー x タームで絞り込む if ( empty( $ids ) && $post_type && $taxonomy && $term_slug ) { $ids = get_posts( [ 'post_type' => $post_type, 'posts_per_page' => -1, 'post_status' => 'any', 'fields' => 'ids', 'tax_query' => [ [ 'taxonomy' => $taxonomy, 'field' => 'slug', 'terms' => $term_slug, ] ], ] ); } if ( empty( $ids ) ) { wp_die( 'エクスポート対象が見つかりません。条件を確認してください。' ); } $data = []; foreach ( $ids as $post_id ) { $post = get_post( $post_id ); if ( ! $post ) continue; // タクソノミーのスラッグをURLデコードして保存 $taxonomies = []; foreach ( get_object_taxonomies( $post->post_type ) as $tax ) { $terms = get_the_terms( $post_id, $tax ); if ( $terms && ! is_wp_error( $terms ) ) { $taxonomies[ $tax ] = array_map( 'urldecode', wp_list_pluck( $terms, 'slug' ) ); } } // カスタムフィールド(ACF含む)を取得・重複排除 $meta = get_post_meta( $post_id ); $meta_out = []; foreach ( $meta as $key => $values ) { $unique = array_values( array_unique( $values ) ); $meta_out[$key] = ( count( $unique ) === 1 ) ? $unique[0] : $unique; } $data[] = [ 'ID' => $post->ID, 'post_title' => $post->post_title, 'post_content' => $post->post_content, 'post_excerpt' => $post->post_excerpt, 'post_status' => $post->post_status, 'post_name' => $post->post_name, 'post_type' => $post->post_type, 'post_date' => $post->post_date, 'post_modified' => $post->post_modified, 'permalink' => get_permalink( $post_id ), 'taxonomies' => $taxonomies, 'meta' => $meta_out, ]; } // site_info にエクスポート元のURLを記録する $output = [ 'site_info' => [ 'site_url' => rtrim( site_url(), '/' ), 'home_url' => rtrim( home_url(), '/' ), ], 'posts' => $data, ]; $filename = 'export-' . $post_type . '-' . date( 'Ymd-His' ) . '.json'; header( 'Content-Type: application/json; charset=UTF-8' ); header( 'Content-Disposition: attachment; filename=' . $filename ); echo json_encode( $output, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT ); exit; } ); // ------------------------------------------------------- // インポート処理 // /wp-content/imports/ のJSONを読み込んで既存投稿に上書き // ------------------------------------------------------- add_action( 'admin_init', function () { if ( ! isset( $_POST['migration_import_action'] ) ) return; if ( ! current_user_can( 'manage_options' ) ) wp_die( '権限がありません' ); check_admin_referer( 'migration_tool_nonce' ); $filename = isset( $_POST['import_file'] ) ? sanitize_file_name( $_POST['import_file'] ) : ''; if ( ! $filename ) { set_transient( 'migration_tool_msg', [ 'error', 'ファイルが指定されていません。' ], 30 ); wp_redirect( admin_url( 'admin.php?page=migration-tool' ) ); exit; } $filepath = WP_CONTENT_DIR . '/imports/' . $filename; if ( ! file_exists( $filepath ) ) { set_transient( 'migration_tool_msg', [ 'error', 'ファイルが見つかりません: ' . $filename ], 30 ); wp_redirect( admin_url( 'admin.php?page=migration-tool' ) ); exit; } $json = file_get_contents( $filepath ); $decoded = json_decode( $json, true ); if ( ! is_array( $decoded ) ) { set_transient( 'migration_tool_msg', [ 'error', 'JSONの読み込みに失敗しました。' ], 30 ); wp_redirect( admin_url( 'admin.php?page=migration-tool' ) ); exit; } // 新形式(site_info + posts)と旧形式(配列のみ)の両方に対応 $site_info = $decoded['site_info'] ?? []; $data = isset( $decoded['posts'] ) ? $decoded['posts'] : $decoded; // URL置換の設定 $do_replace_url = ! empty( $_POST['replace_url'] ); $manual_src = isset( $_POST['manual_src_url'] ) ? rtrim( sanitize_text_field( $_POST['manual_src_url'] ), '/' ) : ''; $manual_dst = isset( $_POST['manual_dst_url'] ) ? rtrim( sanitize_text_field( $_POST['manual_dst_url'] ), '/' ) : ''; if ( $manual_src && $manual_dst ) { // 手動置換モード $replace_pairs = [ [ $manual_src, $manual_dst ] ]; } else { // 自動置換モード:JSONのsite_infoと移行先URLを比較 $replace_pairs = []; $dst_site_url = rtrim( site_url(), '/' ); $dst_home_url = rtrim( home_url(), '/' ); $src_site_url = rtrim( $site_info['site_url'] ?? '', '/' ); $src_home_url = rtrim( $site_info['home_url'] ?? '', '/' ); if ( $src_site_url && $src_site_url !== $dst_site_url ) { $replace_pairs[] = [ $src_site_url, $dst_site_url ]; } if ( $src_home_url && $src_home_url !== $dst_home_url && $src_home_url !== $src_site_url ) { $replace_pairs[] = [ $src_home_url, $dst_home_url ]; } } $skip_keys = migration_tool_skip_meta_keys(); $force_overwrite = ! empty( $_POST['force_overwrite'] ); $success = 0; $errors = []; $news = []; foreach ( $data as $item ) { $post_id = isset( $item['ID'] ) ? intval( $item['ID'] ) : 0; $existing = $post_id ? get_post( $post_id ) : null; // 本文・抜粋のURL置換 $post_content = $item['post_content'] ?? ''; $post_excerpt = $item['post_excerpt'] ?? ''; if ( $do_replace_url && ! empty( $replace_pairs ) ) { foreach ( $replace_pairs as $pair ) { $post_content = str_replace( $pair[0], $pair[1], $post_content ); $post_excerpt = str_replace( $pair[0], $pair[1], $post_excerpt ); } } $post_data = [ 'post_title' => $item['post_title'] ?? '', 'post_content' => $post_content, 'post_excerpt' => $post_excerpt, 'post_status' => $item['post_status'] ?? 'publish', 'post_name' => $item['post_name'] ?? '', 'post_type' => $item['post_type'] ?? 'post', 'post_date' => $item['post_date'] ?? '', ]; if ( $existing ) { // タイトル不一致かつ強制上書きOFFの場合はスキップ if ( $existing->post_title !== ( $item['post_title'] ?? '' ) && ! $force_overwrite ) { $errors[] = '⚠️ ID' . $post_id . ' のタイトルが不一致のためスキップしました。' . '(移行元:' . ( $item['post_title'] ?? '' ) . ' /' . ' 移行先:' . $existing->post_title . ')'; continue; } $post_data['ID'] = $post_id; $result = wp_update_post( $post_data, true ); } else { $result = wp_insert_post( $post_data, true ); $post_id = is_wp_error( $result ) ? 0 : $result; if ( ! is_wp_error( $result ) ) { $news[] = '🆕 ' . ( $item['post_title'] ?? '' ) . 'を新規作成しました。(新ID: ' . $post_id . ')'; } } if ( is_wp_error( $result ) ) { $errors[] = '❌ ' . ( $item['post_title'] ?? '不明' ) . ': ' . $result->get_error_message(); continue; } // カスタムフィールド(ACF含む)を上書き if ( ! empty( $item['meta'] ) && is_array( $item['meta'] ) ) { foreach ( $item['meta'] as $key => $value ) { if ( in_array( $key, $skip_keys, true ) ) continue; if ( $value === '' || $value === null || $value === [] ) continue; // URL置換 if ( $do_replace_url && ! empty( $replace_pairs ) ) { foreach ( $replace_pairs as $pair ) { $value = migration_tool_replace_url( $value, $pair[0], $pair[1] ); } } delete_post_meta( $post_id, $key ); if ( is_array( $value ) ) { foreach ( $value as $v ) { $store = @unserialize( $v ); add_post_meta( $post_id, $key, ( $store !== false || $v === 'b:0;' ) ? $store : $v ); } } else { $store = @unserialize( $value ); update_post_meta( $post_id, $key, ( $store !== false || $value === 'b:0;' ) ? $store : $value ); } } } // タクソノミーを上書き if ( ! empty( $item['taxonomies'] ) && is_array( $item['taxonomies'] ) ) { foreach ( $item['taxonomies'] as $taxonomy => $slugs ) { $term_ids = []; foreach ( (array) $slugs as $slug ) { $term = get_term_by( 'slug', $slug, $taxonomy ) ?: get_term_by( 'slug', urlencode( $slug ), $taxonomy ); if ( $term && ! is_wp_error( $term ) ) { $term_ids[] = $term->term_id; } } if ( ! empty( $term_ids ) ) { wp_set_object_terms( $post_id, $term_ids, $taxonomy ); } } } $success++; } $parts = []; if ( $success ) $parts[] = '✅ ' . $success . '件のインポートが完了しました。'; if ( ! empty( $news ) ) $parts[] = implode( ' / ', $news ); if ( ! empty( $errors ) ) $parts[] = implode( ' / ', $errors ); $msg = implode( ' ', $parts ) ?: 'インポート対象がありませんでした。'; $type = empty( $errors ) ? 'success' : 'warning'; set_transient( 'migration_tool_msg', [ $type, $msg ], 30 ); wp_redirect( admin_url( 'admin.php?page=migration-tool' ) ); exit; } ); // ------------------------------------------------------- // 移行ツールページの表示 // ------------------------------------------------------- function migration_tool_page() { $msg = get_transient( 'migration_tool_msg' ); if ( $msg ) { delete_transient( 'migration_tool_msg' ); $colors = [ 'success' => '#46b450', 'warning' => '#ffb900', 'error' => '#dc3232' ]; $color = $colors[ $msg[0] ] ?? '#46b450'; echo '
' . esc_html( $msg[1] ) . '
'; } $import_dir = WP_CONTENT_DIR . '/imports/'; if ( ! file_exists( $import_dir ) ) wp_mkdir_p( $import_dir ); $json_files = glob( $import_dir . '*.json' ) ?: []; $post_types = get_post_types( [ 'public' => true ], 'objects' ); $nonce = wp_create_nonce( 'migration_tool_nonce' ); echo '
'; echo '

🔄 移行ツール

'; echo '

任意の投稿タイプをJSON形式でエクスポート・インポートします。

'; echo '
'; // ========================================== // エクスポートパネル // ========================================== echo '
'; echo '

📤 エクスポート

'; echo '

投稿タイプ → タクソノミー → タームの順に選択してエクスポートします。
'; echo 'ダウンロードしたJSONを移行先の /wp-content/imports/ にFTPでアップロードしてください。

'; echo '
'; echo ''; wp_nonce_field( 'migration_tool_nonce' ); echo ''; // ① 投稿タイプ echo ''; // ② タクソノミー echo ''; // ③ ターム echo ''; // ④ 対象投稿一覧 echo ''; // IDを直接指定 echo ''; echo '
① 投稿タイプ'; echo '
または投稿IDを直接指定'; echo ''; echo '

カンマ区切りで複数指定可。IDを指定した場合はIDが優先されます。

'; echo '
'; echo '

'; echo '
'; // JavaScript(動的ドロップダウン) echo ''; echo '
'; // エクスポートパネル終了 // ========================================== // インポートパネル // ========================================== echo '
'; echo '

📥 インポート

'; echo '

FTPで /wp-content/imports/ にアップロードしたJSONを読み込み、既存の投稿に上書きインポートします。
'; echo 'アイキャッチ画像・空の値は既存データを保持します。

'; if ( empty( $json_files ) ) { echo '
'; echo '

📂 /wp-content/imports/ にJSONファイルが見つかりません。

'; echo '

FTPでアップロード後、ページを再読み込みしてください。

'; echo '
'; } else { echo '
'; wp_nonce_field( 'migration_tool_nonce' ); echo ''; echo ''; // ファイル選択 echo ''; // URL置換 echo ''; // タイトル不一致の場合 echo ''; echo '
インポートするファイル'; echo ''; echo '

' . count( $json_files ) . '件のファイルが見つかりました。

'; echo '
URL置換'; echo ''; echo '
'; echo '

🔄 自動置換(推奨)

'; echo '

JSONに記録された移行元URLを移行先URL(' . esc_html( home_url() ) . ')に自動置換します。

'; echo '

✏️ 手動置換(任意・入力時は手動が優先)

'; echo ''; echo ''; echo ''; echo ''; echo ''; echo '
置換前URL:
置換後URL:
'; echo ''; echo '
タイトル不一致の場合'; echo ''; echo '

⚠️ チェックを入れると、移行先に異なる投稿が登録されていても強制上書きされます。通常はチェック不要です。

'; echo '
'; echo '

'; echo '
'; } echo '
'; // インポートパネル終了 echo '
'; // flex終了 // 注意事項 echo '
'; echo '⚠️ 注意事項
'; echo '
'; // wrap終了 } 石乃家(いしのや) https://validator.w3.org/feed/docs/rss2.html ガーデンメモリアル千代田 流山市 法栄寺 樹木葬 彩華 湘南公園墓地 茅ヶ崎第二霊園 川崎清風霊園 調布霊園 櫻乃里ふなばし聖地 武蔵メモリアルコート 湘南フォレスト ヒルズ川崎聖地 柏メモリアルガーデン 【募集開始】令和7年度 新座市営墓園一般墓所使用者の公募が始まりました! 満年齢とは?数え年との違い・数え方・計算方法も解説 享年(きょうねん)とは?数え方や行年・没年との違いを解説 トップ 南川越霊園 公園墓地 彩の恵 平塚四之宮霊園 朝霞フォーシーズンメモリアル 櫻乃丘聖地霊園 天空の郷 聖地霊園 未来 小江戸聖地霊園 たきやま台墓苑 小田原中央霊園 あおぞら霊園 和光 磯子納骨堂 綾瀬蓼川霊園 天空の郷 座間霊園 天空の郷 湘南エバーグリーン 小田原富士見霊苑 日本庭園陵墓 紅葉亭 平和浄苑 都筑まどか霊園 メモリアルフォレスト多摩 彩の国フォーシーズンメモリアル「ラ・テール」 東京メモリアルパーク 公園墓地風の杜「湘南庭苑」 レ・リアン横浜 お気に入り 最近見たお墓 都筑港北霊園 三郷中央聖地 メモリアルパーク大磯 駅前霊園美南 彩光浄苑 吉川美南霊園 メモリアルパーク緑の丘 櫻乃丘聖地霊園 メモリアルガーデン梅郷聖地 アドミール座間 メモリアルパーク藤沢