Smart Custom Fields自分なりの応用まとめ　　

# カスタムフィールドの作成は一期一会…だと思ってた
Wordpressのサイトを作るごとに、毎回カスタムフィールドは管理画面からWeb上で操作して一から作っていた。
似たような項目を作る際も、2つサイト開いて右から左へ手動コピペ。Web上の管理画面でちまちまと。
こういうことはもうやらなくていいことに気づいたら色々活用できはじめたので、SmartCustomFieldsの活用メモを記す。

コード作るうえでとても参考になった記事。
SmartCustomFieldsのコードでの定義についてのまとめ
https://qiita.com/yousan/items/7cbd56308ecc0e2bb263

WordPress のカスタムフィールドを簡単・便利に使えるようになるプラグイン「Smart Custom Fields」作った。
http://2inc.org/blog/2014/10/09/4426/
Smart Custom Fieldを使ってカスタムフィールドを追加する
https://hyme.site/blog/posts/smart-custom-field/
WordPress】Smart Custom Fieldsをコード定義で管理する
https://notes.sharesl.net/articles/1341/


# SmartCustomFieldsの基本

```'home.php
$scf_meta = get_post_meta($post->ID);
echo('<pre>'); print_r($scf_meta); echo('</pre>');
```
こういうふうに使う。

# SmartCustomFieldsがいい理由
- 無料。繰り返しフィールドが無料なのがとても良い。
- function.phpに記述を保存しておけるので、新規サイト作成時も1ファイルの編集・コピペで一気にカスタムフィールドを作成できる。
- Web上に情報が結構多い。

# function.phpに書くやり方。
Web上の管理画面でやってくのも初めはとっつきやすいが、
これを覚えればあとからの項目微修正・追加変更もラクラクになるので絶対こっちでやったほうがいいです。

注意点としては、いままでWP管理画面のSCFの設定画面で確認しながらやってたと思いますが、
このやり方だとWP管理画面のSCF設定画面には何も反映されなくなるのでご注意を。
(scf定義データをDB保持→コード保持になるため）

・function_scf.phpなどを分離作成して読み込む方法

```'php:function.php
function-scf.phpとして分離した場合は、function.phpのどこかに以下を書く。
if (locate_template('functions-scf.php')) require_once locate_template('functions-scf.php');
```

・実践的な記述方法ひな形（コードコピペ）

```php:function-scf.php
// smart-cf-register-fieldsにフック
add_filter('smart-cf-register-fields','init_smartcustomfields',10,4);
function init_smartcustomfields($settings, $post_type, $post_id, $meta_type) {
    global $post;
    $my_setting = null;

    // post-type指定
    $spec_post_type = "page";
    if (in_array($post_type,array($spec_post_type))) {
        $scf_gp_count = 1;
        $scf_roop_gp_count = 1;
        $scf_gp_title = "{$spec_post_type}_scf_set";
        $my_setting = SCF::add_setting($scf_gp_title, $spec_post_type.'カスタムフィールド');
        $my_setting->add_group($scf_gp_title.'_gp'.$scf_gp_count++,false,array(
            array(
                'type'  => 'text',
                'name'  => 'フィールド1',
                'label' => 'フィールド1',
                'notes' => '例：フィールド1内容を書く',
            ),
            array(
                'type'  => 'wysiwyg',
                'name'  => 'フィールド2',
                'label' => 'フィールド2',
                'notes' => '',
            ),
        ));
        $my_setting->add_group($scf_roop_gp_count++.'_roop_gp',true,array(
            array(
                'type'  => 'image',
                'size'  => 'thumbnail',
                'name'  => 'フィールド3',
                'label' => 'フィールド3',
                'notes' => 'フィールド3画像をアップ。',
            ),
        ));
        $settings[] = $my_setting;
    }

    // post-type指定
    $spec_post_type = "post";
    if (in_array($post_type,array($spec_post_type))) {
        $scf_gp_count = 1;
        $scf_roop_gp_count = 1;
        $scf_gp_title = "{$spec_post_type}_scf_set";
        $my_setting = SCF::add_setting($scf_gp_title, $spec_post_type.'カスタムフィールド');
        $my_setting->add_group($scf_gp_title.'_gp'.$scf_gp_count++,true,array(
            array(
                'type'        => 'boolean',
                'name'        => 'フィールド4',
                'label'       => 'フィールド4',
                'default'     => 1,
                'true_label'  => __('表示','smart-custom-fields'),
                'false_label' => __('表示なし','smart-custom-fields'),
                'instruction' => "フィールド4を選択"
            ),
            array(
                'type'  => 'textarea',
                'rows'  => 2,
                'name'  => 'フィールド5',
                'label' => 'フィールド5',
                'notes' => '例：フィールド5内容',
            ),
            array(
                'type'      => 'relation',
                'post-type' => array('page'), // 表示する投稿タイプ
                'limit'     => 99,
                'name'      => 'フィールド6',
                'label'     => 'フィールド6',
                'notes'     => '検索ワードを入れた後、2，3秒待つと絞り込み結果が出ます',
                'instruction' => "フィールド6説明"
            ),
            array(
                'type'  => 'select',
                'name'  => 'フィールド7',
                'label' => 'フィールド7',
                'choices' => array(
                    '★☆☆' => '★☆☆',
                    '☆☆☆' => '☆☆☆',
                ),
                'default' => '☆☆☆',
                'notes' => 'フィールド7を選択',
            ),
            array(
                'type'        => 'datepicker',
                'default'     => date('Y/m/d'),
                'date_format' => 'yy/mm/dd',
                'max_date'    => '',
                'min_date'    => '',
                'name'      => 'フィールド8',
                'label'     => 'フィールド8',
                'notes' => 'フィールド8の日付を指定',
            ),
        ));
        $settings[] = $my_setting;
    }

    return $settings;
}
```
_gpとroop_gpで分けてるのは、ループ要素は後述の問題があるので分けてグループ化したほうがいいだろうという判断より。


# filter hookで「関連投稿」フィールドに表示する検索結果をカスタマイズする
scfのソースを見るとapply_filterと記述されている箇所があるのでそこにフックして検索結果をいじる。

```php:function-scf.php
// 関連投稿の一覧候補、検索条件のカスタム
add_filter(SCF_Config::PREFIX.'custom_related_posts_args', 'hook_my_related_posts_arg', 10, 3);
function hook_my_related_posts_arg($args, $name, $post_type) {
    global $post;
    if (in_array("page",$post_type) || in_array("post",$post_type)) {
        $args['post_parent'] = 0; // 親のみ表示とか適当に、order系は指定してもなぜか反映されない模様?
    }
    return $args;
}
// 関連投稿の初期表示をカスタム
add_filter(SCF_Config::PREFIX.'custom_related_posts_args_first_load', 'hook_my_related_posts_first_arg', 10, 3);
function hook_my_related_posts_first_arg($args, $name, $post_type) {
    // とりあえず10件初期表示じゃ少ないので増やす
    $args['posts_per_page'] = 30;
    return $args;
}
```

# カスタムフィールドの中身を初期化するやり方。
update_post_meta()を使う。
コードを一回走らせたら二度は初期化されないはずだけど、メントアウトして2度動かないように区別しておけばよりいいと思う。

```php:function-scf.php
add_action('admin_init','_init_scf_data');
function _init_scf_data(){
    // scfデータ初期化
    $func_init_cfield = function($post_id, $cfieldname, $val) {
        if ($tmp = get_post_meta($post_id,$cfieldname) || !empty($tmp)) return; // データが入ってる場合は何もしない, ヒットしなければ空の配列
        update_post_meta($post_id,$cfieldname,$val);
    };
    $func_init_cfield(999,"フィールド1","aaa");
    $func_init_cfield(999,"フィールド2","bbb");
    //$func_init_cfield(999,"フィールド3",get_template_directory_uri()."/assets/img/ccc.png");
}
```

ただ、画像については上記のようにURLを直貼り付ける形式だと、テスト環境→本番環境のURL移動に耐えられないようなので、
function.phpからWPのメディアアップロードを行う方法を試してみる。

```php:function-scf.php
    $func_wp_media_upload = function($file_path, $parent_post_id=0) {
        if (!file_exists($file_path)) return false;
        $filename = basename($file_path);
        $upload_file = wp_upload_bits($filename, null, file_get_contents($file_path));
        sleep(1);
        if (!$upload_file['error']) {
            $wp_filetype = wp_check_filetype($filename, null );
            $attachment = array(
                'post_mime_type' => $wp_filetype['type'],
                'post_parent' => $parent_post_id,
                'post_title' => preg_replace('/\.[^.]+$/', '', $filename),
                'post_content' => '',
                'post_status' => 'publish'
            );
            $attachment_id = wp_insert_attachment( $attachment, $upload_file['file'], $parent_post_id );
            sleep(0);
            if (!is_wp_error($attachment_id)) {
                require_once(ABSPATH . "wp-admin" . '/includes/image.php');
                $attachment_data = wp_generate_attachment_metadata( $attachment_id, $upload_file['file'] );
                wp_update_attachment_metadata( $attachment_id,  $attachment_data );
                return $attachment_id;
            }
        }
    };

    $func_init_cfield(999,"フィールド3",$func_wp_media_upload(get_template_directory()."/assets/img/ccc.png"));
```

これでWPのメディアライブラリの管理下に画像がアップロードされ、カスタムフィールドにはattachiment_idが保持されるようになった。
画像最適化等のプラグインが有効かされていると、メディアライブラリに二重に画像が登録されるなど不具合がでるみたい。
→アップロード待ちのsleepを追加したらうまく動き出した模様。



# 課題
SCFの問題なのか、Wordpressのmetaの持ち方の問題なのか、一つ大きめの課題がある。
**「繰り返しフィールド」の値が、カラ項目が詰められて配列に格納されてしまう問題**である。
たとえば、テキストAという繰り返しフィールドの項目が3つあったとする。
そのうちの1番目と3番目だけに値を入れた場合。
2番目がカラ値が格納されて3の長さを持つ配列が返ってくればいいのだが2の長さを持つ配列が返ってくる。

単純にあるものだけ出力すればいい箇所なら影響はすくなげだが、
例えばテキストA、画像Aという2つの項目を持つ「繰り返しフィールド」だったとしたらどうなるだろうか。

```
1)
テキストA → あああテスト１
画像A → クマの画像
2)
テキストA → いいいテスト２
画像A → 画像なし
3)
テキストA → うううテスト３
画像A → ウサギの画像
```
こういう入力がされるとget_post_meta()で返ってくる値は
3の長さのテキストA配列と、2の長さの画像A配列である。
このまま配列のキーを頼りに出力すると、セットがずれて出力がなされてしまう。

## 解決のヒントになるかもしれない情報
`$scf_set_mock = SCF::get("scf_set_gp999",$post->ID);`
というようにfunction_scf.phpに記述したグループ名を指定して取得すれば、
カラ項目が混じってても配列番号が保持されたまま値をとることができた気がする。
繰り返しフィールドだけ特別対応しなきゃいけないのも癪だが仕方ない。
取得方法は`get_post_meta()`か`SCF::get()`どちらかに統一すべきか。

```php:function-scf.php
// 無理やりだがカスタムフィールドの外枠のカウントを取得する
$scf_set_mock = null;
if ($roop_gp_no) { // 取得時にscfの定義順が分かれば決め打ちでとる
    $scf_set_mock = SCF::get("{$roop_gp_no}_roop_gp",$post->ID);
} else {
    for ($i=1; $i < 99; $i++) {
        $scf_set_mock = SCF::get("{$i}_roop_gp",$post->ID);
        if (is_array($scf_set_mock)) break;
    }
}
```