Worspressで数百件を一気に登録したい！（しかもACFにも対応したい）


＊問題が出る場合があったので若干の修正をしています。

# WPでデータ数百件を一気に登録したい！

ということで、CSVでの登録ができるプラグインをGoogle検索したところ、「Really Simple CSV Importer」が今の定番らしくよく使われているらしい。

その他のものも試したが、帯に短し襷に長しという感じでいまいちフィットしない。

Really Simple CSV Importer（以下RSCIと記述）は、なによりシンプルで軽量かつフィルターフックでカスタマイズも結構できそうなので、「Really Simple CSV Importer」を使うえるかテストを開始。


## Advanced Custom Fieldの繰り返し（リピート）フィールドに対応したい！(しかも画像フィールド)

RSCIは、Simple Custom FieldやCustom Field Suiteには対応しているが、Advanced custom Field（ACF）には対応していない。
しかし、ネットを見ていると**really_simple_csv_importer_save_metaフィルターフック**を使って対応していてる記事を何件があったので簡単にできるかなとテストを開始。
（いやここからが長い道のりになるとは）

## いや、できませんがな〜！

Qiitaをはじめネットで検索したやり方で色々試したのですがうまくできませんでした。
（こちらの作成したCSVデータの作り方とかが悪いのかもしれないのですが）

そこで、ACFの公式サイトにてデータベースにどの様に入っているのかの情報がないかざっくり見たのですが…。
しかし、探し方が悪くて見つけられず。

## phpMyADminで、SQL文たたいてデータの格納内容を確認

あぁどうしようと思っていたところ、こんなページを発見！

https://hijiriworld.com/web/wp-data-register/

https://0forest.com/wordpress-database/#index_id

大まかにこんな感じで格納されているんだとわかり、実際にデータベースでどの様に格納されているかチェックをしてみました。

## それでわかったこと

### 1. ACFで管理画面で入力欄（フィールドグループ）を設定した時データベースへの格納方法

#### フィールドグループ

- フィールドグループのデータは、"posts"テーブルに格納される。
- post_typeフィールドには、"acf-field-group"として格納される
- post_titleフィールドには、「タイトル」が格納される
- post_excerptフィールドには、「タイトル」をBase64エンコードした文字列が格納される

#### 各フィールド

- 各フィールドは"posts"テーブルに格納される
- post_typeフィールドには、"acf-field"として格納される
- post_titleフィールドには「ラベル」が格納
- post_excerptフィールドには名前（meta key）が格納される(\*1)
- 繰り返し（リピート）の子データは、post_parentフィールドに親のIDが格納される
- post_nameフィールドには**フィールドキー(\*2)**が格納される


### 2.投稿ページの各フィールドの入力値のデータベースへの格納方法

- 各データはpostmetaテーブルに格納される
- 投稿IDは"post_id"フィールドに格納され、ACF上の名前（meta key）は、meta_keyフィールドに格納され、入力データはmeta_valueにそれぞれ格納される
- 繰り返しフィールド（親）の場合は、meta_keyフィールドにACF上の名前（meta key）を、meta_valueフィールドには、サブフィールドの数を入れる
- 入力の値とは別レコードに投稿IDに紐づけて、「\_（アンダーバー）+meta key(\*1)」をmeta_keyフィールドに、フィールド名のフィールドキー(*2)をmeta_valueに格納する。

**実際に自分でphpMyAdminなりでSQL文叩いて確認するとよく理解できるかも。*

### フィールドキー（Field Key）とは

ACF独自のキーで、設定した各フィールドの固有の認識キーとなります。
管理画面場では通常表示されていませんが、ACFの管理画面ページで「表示オプション」を開くと表示のチェックができます。

![スクリーンショット 2019-10-12 19.29.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34746/869d9c6e-70d1-8052-1419-495f94f7dc88.png)


## ACFのフィールドタイプごとの違い

ACFには本当にたくさんのフィールドタイプがあります。
全部を把握するのは時間がかかるので、チェックボックスと関連のみ確認しました。

チェックボックスや関連はpostmetaテーブルのmeta_valueフィールドへシリアライズされた配列として格納されます。

### チェックボックスに関して

ネットで調べると、データを格納するさいにはシリアライズする様になっているサンプルが多いのですが、実際には配列を入れると自動でシリアライズされます。
なので、シリアライズしたデータですと２回シリアライズされてしまいます。

このあたりは、今のバージョンのACFが配列の値は自動でシリアライズされるのかなと想像。


### 関連フィールドに関して

こちらは、なぜかカンマ切りのテキストにして代入してあげるとシリアライズされて登録されてしまいます。

ACFはフィールドタイプに関しての処理がまちまちなのかなと。
（この辺は深追いしませんでした）

あと、関連フィールドを作るときは、IDを格納する様に設定します。

## 画像の登録

最初、[RS CSV Importer Media Add-On](https://wordpress.org/plugins/rs-csv-importer-media-addon/) を使おうと思ったのですが、いかんせ登録数と一緒に登録する画像の数が多すぎて、サーバーのメモリー不足や一回のプロセスのupload制限の容量オーバーになってしまいます。

５０件づつなどにすれば、登録できるんだけどそれでは面倒だなと。
また、内容を変更したCSVファイルで、再度アップして更新したい場合などは、RS CSV Importer Media Add-Onではまた、画像を指定URLから持ってきてしまい、同じ画像を複数登録してしまうという問題も。

そこで発想の転換として、画像は先に登録してしまう。
CSVファイルに記載している画像名からメディアのIDを取得して登録という方法に変更しました！


そこで、こんな感じの関数で対応。
＊2020.8.24若干修正しました。

```php

/**
 * 画像を投稿に関連させる関数
 */
function update_attachment_post_parent( $img_id, $post_id ) {
    global $wpdb;
    $post_related = $wpdb->prepare( "UPDATE $wpdb->posts SET post_parent=%d WHERE post_type='attachment' AND ID=%d", esc_sql($post_id), esc_sql($img_id));
    return $wpdb->query($post_related);
}

/**
 * アタッチする画像の画像名からIDを取得する関数
 */
function get_attachment_id($v) {
    global $wpdb;
    $img_ids = [];

    $img_pattern = '/(.*)\.(jpg|png|gif|svg|JPG|PNG|GIF|SVG)/';
    $num_pattern = '(\d+)';

    if(preg_match($img_pattern,$v,$m)) {

    $img_title = $m[1];
    $imgGetSQL = "SELECT ID
    FROM $wpdb->posts WHERE post_type = 'attachment' AND post_mime_type LIKE 'image%' AND post_title = '%s' LIMIT 1";

    $img_prepared = $wpdb->prepare($imgGetSQL,esc_sql($img_title));
    $img_ids = $wpdb->get_col($img_prepared);

    } elseif(preg_match($num_pattern,$v,$num_m)) {
        $img_ids[0] = $v;
    }
    
    return $img_ids;
}

```

### アイキャッチ画像もアップロード済みアイテムから取得

そうすると今度はアイキャッチ画像の登録もすでにアップしたメディから設定したくなります。
アイキャッチ（post_thumbnail）の登録についてのフィルターフック、**really_simple_csv_importer_save_thumbnail**も用意されています。

*Really Simple CSV Importerえらい！*

アイキャッチ（thumbnail）を登録する際には投稿のIDが必要なのですが、初めて挿入する場合は、まだIDが確定しません。なので、CSVファイルにpost_idの項目を作って、そこにID（数値）を入れておく必要があります。
ただ、このIDはすでにあるものですと、上書きしてしまうので、使われていないIDを入れておく必要があります。
＊場合により問題があったので修正2020.0824

```php
/**
 * for post thumbnail
 */
function really_simple_csv_importer_save_thumbnail_filter( $post_thumbnail, $post, $is_update ) {

    global $wpdb;
    
    if(strpos($post_thumbnail,',') !== false) {
        $thumnail_arry = explode(',',$post_thumbnail);
        $post_thumbnail = $thumnail_arry[0];
        $caption = $thumnail_arry[1];
    }
    $thumb_id = get_attachment_id($post_thumbnail);
    //$img_url = get_attachment_url($post_thumbnail);
    $upload_dir = wp_upload_dir();

    if(empty($thumb_id)) {
        return;
    }

    /**
     * キャプションを挿入
     */
    if(isset($caption) && $caption !== '') {
        $wpdb->query( $wpdb->prepare(
            "UPDATE $wpdb->posts SET post_excerpt=%s WHERE ID=%d AND post_type='attachment'",
            esc_sql($caption),
            esc_sql($thumb_id[0])
        ) );
    }

    //$img_path = str_replace($upload_dir['baseurl'],$upload_dir['basedir'],$img_url[0]);

    if(isset($post['ID'])) {
        $post_id = $post['ID'];
        //if(has_post_thumbnail($post_id) === false) {
            set_post_thumbnail( $post_id, $thumb_id[0]);
        //}
        return;
        
    } else {
        if(isset($post['import_id'])) {
            $post_id = $post['import_id'];
        } else {
            $sqlMaxID = "SELECT ID
            FROM $wpdb->posts
            WHERE ID = (SELECT MAX(ID) FROM $wpdb->posts)";
            $max_id = $wpdb->get_col($sqlMaxID);
            $next_id = $max_id[0] + 1;
            $post_id = $next_id;
        }
        $post_thumbID = $thumb_id[0];
        $metakey = '_thumbnail_id';

        $wpdb->query( $wpdb->prepare( 
        "
        INSERT INTO $wpdb->postmeta
        ( post_id, meta_key, meta_value )
        VALUES ( %d, %s, %s )
        ", 
        esc_sql($post_id),
        esc_sql($metakey), 
        esc_sql($post_thumbID)
        ) );
    }

    update_attachment_post_parent( $thumb_id[0], $post_id );
    
        return;

}
add_filter( 'really_simple_csv_importer_save_thumbnail', 'really_simple_csv_importer_save_thumbnail_filter', 10, 3 );
```


## CSVファイルのフォーマット

登録する際のフォーマットはこの様にしました。

- ACFに対応するには頭に「acf_」をつけます。
- 繰り返しのフィールド（親）の場合には、後ろに「-repeat」をつけます。
- 繰り返しのサブフィールドの場合には、「親フィールド名_数字_フィールド名」とします。
- 関連フィールドには後ろに「-related」をつけます。

例)

| acf_title　　| acf_slider-img_1_imag-item|acf_slider-img_2_imag-item  |acf_slider-img-repeat   |
|:--------------------|:--------------------------|:-----------------------|:-----------------------|
|           タイトル     |     画像サブフィールド1の画像名 |  画像サブフィールド2の画像名　　|画像フィールドの数（この場合2） |

*基本的な発想やコードなどは、http://bashalog.c-brains.jp/19/07/01-120000.php がよくまとまっていて参考にさせていただきました。*

# アドオンプラグインとして設定

Really Simple CSV Importerの補助的（アドオン）なプラグインとして設定しました。

ソースコード
＊場合によっては問題が出たので修正2020.8.24

```PHP:acf-importer-plus.php
<?php

/*  Plugin Name: acf-csv-importer-plusr  */

function really_simple_csv_importer_save_meta_acf($meta, $post, $isUpdate)
{
    global $wpdb;
    /**
     * 
     * $postにはCSVファイルでpost_idで指定した値として$post['import_id']に格納される
     * そのpost_idがすでに登録されている場合は、$post['ID']に格納されます
     */
    if(isset($post['ID'])) {
        $post_id = $post['ID'];
    } elseif(isset($post['import_id'])) {
        $post_id = $post['import_id'];
    } else {
        $sqlMaxID = "SELECT ID
        FROM $wpdb->posts
        WHERE ID = (SELECT MAX(ID) FROM $wpdb->posts)";
        $max_id = $wpdb->get_col($sqlMaxID);
        $next_id = $max_id[0] + 1;
        $post_id = $next_id;
    }
    /**
     * acf-fieldのフィールドキーを取得するためのクエリ
     */
    $sqlNormal = "SELECT post_name
    FROM $wpdb->posts
    WHERE post_type = 'acf-field' AND post_excerpt = '%s' LIMIT 1";
    /**
     * 関連フィールドのためのクエリ
     * ポストタイトルからIDを取得する
     */
    $relatedSQL = "SELECT ID
    FROM $wpdb->posts
    WHERE post_title = '%s' AND post_status != 'inherit' AND post_type = '%s'  LIMIT 1";

    $metaResult = [];
    // 各値をacfで登録した場合と同じになるようにメタデータに追加
    foreach ($meta as $key => $value) {

        $fildKeyStr = '';
        $subKeyStr = '';
        /**
         * captionの時はカスタムフィールドに入れない
         */
        if(strpos($key,"caption") !== false) {
            continue;
        }

        /*
         * acfのフィールドは"acf_"を先頭に付ける。
         * acf以外はそのまま何もしない。
         */
        if (strpos($key, "acf_") === false) {   
            $metaResult[$key] = $value;
            continue;
        }

        // acf_の文字列を取り除く
        $acfKey = preg_replace('/^acf_(.*)/', '$1', $key);
        /**
         * 繰り返しフィールド（親）の設定
         */
        if(preg_match('/^(.*)-repeat$/',$acfKey)) {

            //　繰り返しフィールドの数が0の場合は空データを入れる
            if( $value === '0') {
                $metaResult[$acfKey] = '';
            } else {
                 $metaResult[$acfKey] = $value;
            }
        }
        /**
         * 関連フィールドの設定
         */
        elseif(preg_match('/^(.*)-related$/',$acfKey,$relatedKey)) {

            $releted_arr = [];
            $acf_val_arr = [];
            /**
             * 関連先のポストタイプ名を取得する
             */
            if($value !== '' && strpos($value,',') !== false) {
                $relate_arr = array_map('trim',explode(',',$value));
            } else {
                $relate_arr[] = $value;
            }
            foreach ($relate_arr as $k => $v) {
                $relate_prepared = $wpdb->prepare($relatedSQL, esc_sql($v),esc_sql($relatedKey[1]));
                $relate_ids = $wpdb->get_col($relate_prepared);
                if(!empty($relate_ids)) {
                    $acf_val_arr[] = $relate_ids[0];
                }
            }
            if(!empty($acf_val_arr)) {
                $metaResult[$acfKey] = $acf_val_arr;
            } else {
                $metaResult[$acfKey] = '';
            }
        }
        /**
         * 画像フィールドに対応
         */
        elseif(preg_match('/^(.*)-image$/',$acfKey)) {
            
            /**
             * 画像名から登録されているメディアのIDを取得
             */
            $img_ids = get_attachment_id($value);

            if(!empty($img_ids)) {
                $metaResult[$acfKey] = $img_ids[0];
                //画像を投稿に関連付けする
                update_attachment_post_parent( $img_ids[0], $post_id );
            }
        }
        /**
         * チェックボックスに対応
         */
        elseif(preg_match('/^(.*)-checkbox$/',$acfKey)) {
            $checkbox_arr = [];
            if($value !== '' && strpos($value,',') !== false) {
                $checkbox_arr = explode(',',$value);
            } else {
                $checkbox_arr[] = $value;
            }
            $metaResult[$acfKey] = $checkbox_arr;
        }
        /**
         * その他
         *  */ 
        else {

            $metaResult[$acfKey] = $value;
        }
        
        /**
         * フィールドキーをpostmetaの各post_idごとに挿入する設定
         *  
         * */ 
        if(preg_match('/^(.*)_[0-9]{1,}_(.*)/', $acfKey, $keyMatches)){    // 繰り返しサブフィールド対応

            if (isset($keyMatches[2])) {
                $subKeyStr = $keyMatches[2];

                /*サブフィールドのフィールドキーを取得して挿入する設定*/ 
                $sub_prepared = $wpdb->prepare($sqlNormal, esc_sql($subKeyStr));
                $sub_fieldKey = $wpdb->get_col($sub_prepared);
                // アンダーバー＋キー名にフィールドキーの値を当てる
                if(!empty($sub_fieldKey)) {
                    $metaResult["_" . $acfKey] = $sub_fieldKey[0];
                }
            }

        } else { 
            // そのほかのフィールド対応
            /*フィールドキーを取得して挿入する設定*/
            $prepared = $wpdb->prepare($sqlNormal, esc_sql($acfKey));
            $fieldKey = $wpdb->get_col($prepared);
            // アンダーバー＋キー名にフィールドキーの値を当てる
            if(!empty($fieldKey)) {
                $metaResult["_" . $acfKey] = $fieldKey[0];
            }
        }

    }
    return $metaResult;
}

add_filter('really_simple_csv_importer_save_meta', 'really_simple_csv_importer_save_meta_acf', 10, 3);

/**
 * for post thumbnail
 */
function really_simple_csv_importer_save_thumbnail_filter( $post_thumbnail, $post, $is_update ) {

    global $wpdb;
    
    if(strpos($post_thumbnail,',') !== false) {
        $thumnail_arry = explode(',',$post_thumbnail);
        $post_thumbnail = $thumnail_arry[0];
        $caption = $thumnail_arry[1];
    }
    $thumb_id = get_attachment_id($post_thumbnail);
    //$img_url = get_attachment_url($post_thumbnail);
    $upload_dir = wp_upload_dir();

    if(empty($thumb_id)) {
        return;
    }

    /**
     * キャプションを挿入
     */
    if(isset($caption) && $caption !== '') {
        $wpdb->query( $wpdb->prepare(
            "UPDATE $wpdb->posts SET post_excerpt=%s WHERE ID=%d AND post_type='attachment'",
            esc_sql($caption),
            esc_sql($thumb_id[0])
        ) );
    }

    //$img_path = str_replace($upload_dir['baseurl'],$upload_dir['basedir'],$img_url[0]);

    if(isset($post['ID'])) {
        $post_id = $post['ID'];
        //if(has_post_thumbnail($post_id) === false) {
            set_post_thumbnail( $post_id, $thumb_id[0]);
        //}
        return;
        
    } else {
        if(isset($post['import_id'])) {
            $post_id = $post['import_id'];
        } else {
            $sqlMaxID = "SELECT ID
            FROM $wpdb->posts
            WHERE ID = (SELECT MAX(ID) FROM $wpdb->posts)";
            $max_id = $wpdb->get_col($sqlMaxID);
            $next_id = $max_id[0] + 1;
            $post_id = $next_id;
        }
        $post_thumbID = $thumb_id[0];
        $metakey = '_thumbnail_id';

        $wpdb->query( $wpdb->prepare( 
        "
        INSERT INTO $wpdb->postmeta
        ( post_id, meta_key, meta_value )
        VALUES ( %d, %s, %s )
        ", 
        esc_sql($post_id),
        esc_sql($metakey), 
        esc_sql($post_thumbID)
        ) );
    }

    update_attachment_post_parent( $thumb_id[0], $post_id );
    
        return;

}
add_filter( 'really_simple_csv_importer_save_thumbnail', 'really_simple_csv_importer_save_thumbnail_filter', 10, 3 );

/**
 * termの登録の時のフック
 * とりあえずコメントアウト
 * 未登録のtermの場合などでの設定で、名前とslugを別の値として登録する様にもできそう
 */
/*
function really_simple_csv_importer_save_tax_filter( $tax, $post, $is_update ) {
	
	// Fix misspelled taxonomy
	if (isset($tax['actors'])) {
		$_actors = array();
		foreach ($tax['actors'] as $actor) {
			if ($actor == 'Johnny Dep') {
				$actor = 'Johnny Depp';
			}
			$_actors[] = $actor;
		}
		$tax['actors'] = $_actors;
    }

    print_r($tax);
	
	return $tax;
}
add_filter( 'really_simple_csv_importer_save_tax', 'really_simple_csv_importer_save_tax_filter', 10, 3 );
*/


/**
 * ここから独自の関数群
 */

/**
 * アタッチする画像の画像名からIDを取得する関数
 */
function get_attachment_id($v) {
    global $wpdb;
    $img_ids = [];

    //$replace_texts = array('.jpg','.png','.gif','.svg','.JPG','.PNG','.GIF','.SVG');
    //$img_title = str_replace($replace_texts,'',$v);
    $img_pattern = '/(.*)\.(jpg|png|gif|svg|JPG|PNG|GIF|SVG)/';
    $num_pattern = '(\d+)';

    if(preg_match($img_pattern,$v,$m)) {

    $img_title = $m[1];
    $imgGetSQL = "SELECT ID
    FROM $wpdb->posts WHERE post_type = 'attachment' AND post_mime_type LIKE 'image%' AND post_title = '%s' LIMIT 1";

    $img_prepared = $wpdb->prepare($imgGetSQL,esc_sql($img_title));
    $img_ids = $wpdb->get_col($img_prepared);

    } elseif(preg_match($num_pattern,$v,$num_m)) {
        $img_ids[0] = $v;
    }
    
    return $img_ids;
}
/**
 * アタッチする画像のURLを取得
 */
/*
function get_attachment_url($v) {
    global $wpdb;

    $replace_texts = array('.jpg','.png','.gif','.svg','.JPG','.PNG','.GIF','.SVG');
    $img_title = str_replace($replace_texts,'',$v);
    
    $upload_dir   = wp_upload_dir();

    $imgPathSQL = "SELECT guid
    FROM $wpdb->posts WHERE post_type = 'attachment' AND post_mime_type LIKE 'image%' AND post_title = '%s' LIMIT 1";

    $img_prepared = $wpdb->prepare($imgPathSQL,esc_sql($img_title));
    $img_url = $wpdb->get_col($img_prepared);

    return $img_url;

}
*/

/**
 * 画像を投稿に関連させる関数
 */
function update_attachment_post_parent( $img_id, $post_id ) {
    global $wpdb;
    $post_related = $wpdb->prepare( "UPDATE $wpdb->posts SET post_parent=%d WHERE post_type='attachment' AND ID=%d", esc_sql($post_id), esc_sql($img_id));
    return $wpdb->query($post_related);
}

```

こんな感じでアドオン的なプラグインとして作り、一気に登録ができました。

カスタマイズは各条件によって自由にできるのがReally simple CSV Importerの良いところかと思います。


参考URL
http://bashalog.c-brains.jp/19/07/01-120000.php
https://hijiriworld.com/web/wp-data-register/
https://xakuro.com/blog/wordpress/1276/