CakePHPユーザーがbaserCMSを開発しやすくするためのカスタムフィールドを作ってみた。

baserCMSは**CakePHP**で作られているCMSです。本格的なカスタマイズを行う場合PHPユーザーなら理解しやすいのですが、CMS案件によくある【ちょっとした入力項目】を作るためのカスタムフィールドが存在しないので自分でプラグインを作る必要があります。
これだと**baserCMS自体の学習コストが必要**で敷居が高く、触ったことが無ければ見積もりも高くなり採用しにくいかと思います。なので**CakePHPの知識だけ**でカスタマイズ可能なフォーム項目を簡単に作成できるプラグインを作ってみました。

これと前回紹介した[BaserCMSのカスタムフィールドプラグインを4系対応して機能追加してみた](https://qiita.com/BigFly/items/9c75d61d6cdd80482edf) を組み合わせれば、結構色々な事出来ると思います。

[AddConfigプラグイン](https://github.com/BigFly3/AddConfig)

## AddConfigプラグインについて

CakePHPライクの設定ファイルを作成することで、管理画面上で登録更新可能なカスタムコンフィグのフォームを作成することができます。

```php:setting.php
$config['AddConfig']['form'][] = [ 
    'title' => 'ogp設定',
    'fields' => [
        'ogp_image' => [
            'required' => true,
            'label' => '画像',
            'parts' => [
                'type' => 'upload', //画像をアップロードする
            ],
            'validate' => [
                [
                    'rule' => ['notBlank'],
                    'message' => '画像を登録してください'
                ],
            ],
            'help' => 'ファイル形式 gif,jpg,png<br>推奨サイズは幅1200×高さ630です'
        ],
        'ogp_url' => [
            'required' => true,
            'label' => 'URL',
            'parts' => [
                'type' => 'text',
                'size'=>'70',
            ],
            'validate' => [
                [
                    'rule' => 'notBlank',
                    'message' => 'URLを入力してください'
                ],
                [
                    'rule' => 'url',
                    'message' => 'URLの形式で入力してください'
                ],
            ],
            'help' => 'OGPで表示される飛ばしたいURLを入力します'
        ]
    ]
];

```

こんな感じのフォームを作成できます。

![form-sample01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320569/c105dbf4-78bd-f118-5b7c-09b9fcbd0bb1.jpeg)

### 使用用途
- システム用の追加コンフィグ
- 運用のためのオリジナルフォーム項目 （緊急ニュースやスライドショー用の画像差し替えなど）

運用に入った後の機能追加なんかも、項目追加が簡単なので比較的に楽に改修が行えます。

### データの仕様
キー：値（１：１）の形でデータを登録する仕組みです。
登録されたデータは以下から全てのページで参照できます。

```php
controller → $this->addConfigs
view → $addConfig
```
BaserCMS【サイト基本設定】の仕組みと同じです。
\$this->siteConfigs、\$siteConfig


##コンフィグ設定で出来ること

- 用途ごとにグループ分けが可能
- CakePHPのモデル形式を使ったバリデーション設定
- CakePHPのformヘルパー形式(form->input)を使ったオプション設定
- element読み込みによるオリジナルのフォームパーツの作成
- 見出しの変更やヘルプ項目、項目前後へのテキスト挿入
- データを保存せずに入力チェックするデバッグモード



### 用途ごとにグループ分けが可能
フォーム項目の設定は、formグループごとに配列形式のデータを持てるようになっています。
スライド画像更新用など1グループずつファイル化して読み込むようにすれば使いまわしもしやすくなります。
配列番号を指定したり、configを並び替えれば後から順番変更することも可能です。

```php
$config['AddConfig']['form'][] = [グループ1...];
$config['AddConfig']['form'][] = [グループ2...];
$config['AddConfig']['form'][] = [グループ3...];
```
![form-sample02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320569/8e2e3471-bd0d-5e4b-8a89-f2ea7c630046.jpeg)

フォームグループはアコーディオン形式になっているので項目が増えても長くならずに収まると思います。
入力チェックでエラーがあった場合はエラーメッセージが出ているアコーディオンだけが開きます。

旧テーマの場合は他と体裁を合わせるため**見出し＋テーブル**が続く形になっていますが、
タグ構成を自由に変更可能な仕組みなのでelementファイルを少し修正すれば、アコーディオン化することも可能です。


### CakePHPのモデル形式を使ったバリデーション設定

```php
['fields']['キー名']['validate'] = [
    [
        'rule' => 'notBlank',
        'message' => '入力してください'
    ],
    [
        'rule' => ['maxLength', 255],
        'message' => '255文字以内で入力してください'
    ],
]
```
validateの箇所に [CakePHP2 バリーデーション](https://book.cakephp.org/2.0/ja/models/data-validation.html) の形で指定することができます。
正規表現を使ったり、AddConfigモデルにfunctionを追加して独自の入力チェックを作ることも可能です。


### CakePHPのformヘルパー(form->input)形式を使ったオプション設定

```php
['fields']['キー名']['parts'] = [
    'type' => 'select',
    //'default' => 'value03',　//デフォルトで選択する場合
    'empty' => "選択してください", //空要素
    'options' => [
        'value01' => '選択１',
        'value02' => '選択２',
        'value03' => '選択３',
        'value04' => '選択４'
    ],
]
```
[CakePHP2 formヘルパー](https://book.cakephp.org/2.0/ja/core-libraries/helpers/form.html#automagic-form-elements) のようにFormHelper::input(キー名, ['parts'])の形でセットします。

**create , end , submit , button , hidden , label** 辺りは仕様上想定をしていないので、あらかじめ除外しています。他にもあるかもですが使わないようにしてください。

実際使用するヘルパーはbaserCMSのBcFormヘルパーになるので独自のカスタマイズが入っています。editorとかdatePickerみたいなものが使えたり、textの文字数がカウント出来たりします。

### element読み込みによるオリジナルフォームパーツの作成
FormHelper::inputの形では使えないメソッドやJSを追加したリッチなパーツをElements/formpartsの中に実装して独自のパーツ作成ができます。

```php:elementを指定するとfileをElement/formpartsから探します
['fields']['キー名']['parts'] = [
    'type' => 'element',
    'file' => 'pref',
    'convertKey' => true //valueをIDではなく都道府県に設定する
]
```

```php:都道府県のselectを作成できる\$BcForm->prefTag()に合わせたサンプル
<?php if(!empty($name)):?>
<?php 
unset($options['type']);
unset($options['element']);
$options['class'] = 'bca-select__select';

$selected = isset($options['selected']) ? $options['selected'] : null; 
unset($options['selected']);
$convertKey = isset($options['convertKey']) ? $options['convertKey'] : false; 
unset($options['convertKey']);
?>

<div class="bca-select">
<?php echo $this->BcForm->prefTag($name, $selected, $options, $convertKey);?>
</div>
<?php endif;?>
```

### ヘルプや項目前後へのテキストなどの挿入可
設定にform前後などにテキストやタグを挿入できる領域も用意してあります。

```php:テキスト挿入サンプル
$config['AddConfig']['form'][] = [ 
    'titleBefore' => '見出しの前領域。アコーディオンに含まれない',
    'title' => 'アコーディオン・見出し',
    'titleAfter' => '見出しの後領域' ,
    'tableAfter' => 'テーブルの後領域。アコーディオンに含まれない' ,
    'fields' => [
        'text01' => [
            'parts' => ['type' => 'text','size' => 70],
            'labelBefore' => 'ラベル前領域', 
            'label' => 'テキスト', 
            'labelAfter' => 'ラベル後領域', 
            'inputBefore' => 'フォーム前領域',
            'inputAfter' => 'フォーム後領域',
            'help' => 'ヘルプアイコンの中に表示'
        ]
    ]
];
```

![sample-insertText.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320569/eaf3b1e7-31f1-29e7-6fc2-78be995b8b72.jpeg)

### データを保存せずに入力チェックするデバッグモード
お試しでフォーム項目を作成する場合、データを登録・更新せずに入力チェックを確認することもできます。正しい入力内容なら以下のようになります。

![sample-debug.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320569/67ab5345-2e53-0cec-2d36-6c52ee11605f.jpeg)


## 使用できるフォームパーツの種類
settings.phpと同階層にsample-form.phpが準備してあります。記述の参考にしてください。
sample-form.phpは作成できるフォームのサンプルとして、インストール時にデフォルトでincludeされています。

```php:実際に使用するときはincludeされている部分をコメントアウトしてください
// include "sample-form.php"; //$config['AddConfigDebug'] = true;がセットされています。
```

![form-sample.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320569/a434d016-6212-e3d7-1637-18cb0ca85fda.jpeg)


#### 配列系のデータ登録は？
モデルをちょっと弄ればシリアライズ化して登録も可能だと思うのですが、今のところ設定していません。
select + multipleなどでフォームを作成して場合、登録しようとするとロールバックします。

代わりに複数選択を登録できるようにするためのformpartsとして**select-text**を用意しています。

```php:select-textを使ったサンプル
$config['AddConfig']['form'][] = [ 
    'title' => 'マルチ選択サンプル',
    'fields' => [
        'multicheck01' => [
            'label' => 'マルチ選択',
            'inputBefore' => '配列系のデータを登録したいときに使います',
            'inputAfter' => '\'山田\',\'高橋\'のような登録形式になります。',
            'parts' => [
                'type' => 'element', 
                'file' => 'select-text',
                //'options' => ['1' => '山田','2' => '佐藤', '5' => '高橋','7' => '橋本']
                //'options' => ['山田','佐藤', '高橋','橋本']
                'options' => ['山田' => '山　田','佐藤' => '佐　藤', '高橋' => '高　橋','橋本' => '橋　本']
            ],
        ],
    ]
];
```

![sample-multi.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320569/890eb635-b932-b746-8edd-fbfb5bcc58a8.jpeg)


#### 管理画面から登録できる動的な選択肢の表示するには？
ブログカテゴリーやユーザーなどをfind('list')であらかじめ取得すると、optionsに動的な選択肢をセットすることが可能です。

```php:ブログカテゴリーから選択肢を作る場合
$BlogCategory = ClassRegistry::init('Blog.BlogCategory');
$options = $BlogCategory->find("list",['conditions' => ['BlogCategory.blog_content_id' => 2],  'fields' => ['BlogCategory.name','BlogCategory.title']]);
$config['AddConfig'][] = [
    'fields' => [
        'disp_categpry' => [
            'label' => '○○を表示するカテゴリー',
            'parts' => [
                'type' => 'select',
                'options'=> $options,  //$BlogCategoryで取得したカテゴリ項目をプルダウンで表示
            ],
            'inputAfter' => '選択肢に表示されない場合はブログカテゴリーに追加してください。',
        ],
    ]
]
```

## 出力用のヘルパー
$addConfig[キー名]をechoするだけでも良いかなと思ったんですが、自分で使ってみたら微妙に面倒くさかったのでヘルパーも作成しました。

\$this->AddConfig->関数名()

- field( \$key [ , \$escape = true ] )　キー名のデータを出力
- brfield( \$key [ , \$escape = true ] )　キー名のデータにある改行を&lt;br&gt;にして出力する

> どちらもデフォルトはタグをエスケープします。
値が文字列でない場合は無視されます。
f() , brf() も省略形として使えます。

- img( \$key [ ,$options ] )　uploadで登録した画像を表示します。

> オプションの指定はBcBaser->img とほぼ同じです。
'noimage'=>画像パスを設定した場合、登録なし時に表示する画像を設定できます。
'noimage'=>true にした場合は、baserCMS標準のnoimageが表示されます。


- get( \$key )　キー名のデータを取得
- set( \$key , \$value )　キー名に任意の値をセット
- getData()　setData()に登録している内部データを取得
- setData( [ \$array = \$addConfig ] )　出力のベースになる配列をセット。初期値は\$addConfig

> setData()は他の配列データをセットするとaddConfigヘルパーのメソッドが使用できます。
\$siteConfigやループの中でaddConfigヘルパーを使用するイメージです。
\$addConfigのデータで初期化したいときは、引数無しのsetData()を呼びます。

- is( \$key )　キー名の値が存在するかどうか
- in( \$key , \$needle )　select-text（複数選択で登録）したものに\$needleがマッチするかどうか
- explodeArray( \$key )　select-textで登録した値を配列にして返す。

> select-text(複数選択)で登録した値を使用する場合は in() や explodeArray() を使ってください。
単純にexplodeしただけだとシングルクォートが残ってしまいます。
また、値にシングルクォートやカンマが値に含まれている場合、
データ上では特定の文字列(ACQUOTE、ACCOMMA)に置換されているという理由もあります。


- getKeysFilter( \$keys )　[ key1, key2, key3, ... ] 指定したキーの配列のみを返す。
- getPrefixFilter( \$prefix [ ,\$drop_prefix = false ] )　\$prefixにマッチするキー名の配列を返す。

> $drop_prefix = trueにすると smtp_host, smtp_port などが、
取得した配列の中では host, port になります。

- splitGroupArray( \$array, \$delimiter='_')　getPrefixFilterなどで取得した配列のキー名を\$delimiterで分割して新たな配列グループを作成します。

```php
$array['feature_01_title'] = "特徴１";
$array['feature_02_img'] = "files/uploads/feature02.png";

$groupArray = $this->AddConfig->splitGroupArray($array);

echo $groupArray['feature']['01']['title'];
echo $groupArray['feature']['02']['img'];

のようにアクセスできます
```


上記ヘルパーを組み合わせると、以下のようなキー名で登録されたデータをforeachで出力出来るようになります。


```php
$addConfig['feature_01_title'] = "特徴１";
$addConfig['feature_01_text'] = "説明１テキスト";
$addConfig['feature_01_img'] = "files/uploads/feature01.png";
$addConfig['feature_02_title'] = "特徴２";
$addConfig['feature_02_text'] = "説明２テキスト";
$addConfig['feature_02_img'] = "files/uploads/feature02.png";
$addConfig['feature_03_title'] = "特徴３";
$addConfig['feature_03_text'] = "説明３テキスト";
$addConfig['feature_03_img'] = "files/uploads/feature03.png";


$filterArray = $this->AddConfig->getPrefixFilter('feature_',true); //prefix部分を削除
$feature = $this->AddConfig->splitGroupArray($filterArray);
if(!empty($feature)){
    foreach($feature as $key => $value){
        $this->AddConfig->setData($value); //ループでヘルパーを使用するために単グループをセット
        $this->AddConfig->field("title");
        $this->AddConfig->brfield("text");
        $this->AddConfig->img("img");
    }
    $this->AddConfig->setData(); //ヘルパー内部のデータを$addConfigに戻す
}

$this->AddConfig->field("feature_01_title"); //$addConfigのキーでアクセスできる
```


## 注意点など
#### インストール先はテーマ内が推奨です。
カスタマイズを自由に行うことを前提としたプラグインなので、
**app/Plugin/**の中よりも**theme/テーマ名/Plugin/** が良いと思います。
テーマの仕様に合わせて変更してください。

#### 必須マークはvalidateと連動していません。
表示したい場合は以下を設定してください。

    ['fields'][キー名]['required'] = true

#### フォーム項目にないデータは、更新・削除されません。
configを変更した場合、フォーム上に存在しないデータベースのデータはそのまま残ります。
configを元に戻したり同一のキー名のフォームを作成するとフォームデータとして復活します。

不必要な項目を削除したい場合は

    /admin/add_config/add_configs/

に登録データの一覧があるので、そこから削除することが可能です。
キー名や値の編集も出来るようになりました。

#### 何でもかんでもデータを突っ込まない。
簡単にデータ追加していける仕組みですが、グローバルの変数に追加されていくものなので注意してお使いください。
ブログなどで使うものなどは [PetitCustomField](https://qiita.com/BigFly/items/9c75d61d6cdd80482edf) を使用するようにしてください。

## 最後に
CakePHPの知識がある人なら、コアの知識が無くてもプラグイン内で色々試せるように作成しています。
特にViewに関してはフォームパーツ以外は、ほぼ自由に組み替え可能なので独自のカスタマイズがしやすいと思います。新テーマ対応も旧テーマ用から組み換えて作成しました。
データ登録さえ出来てしまえばテーマをいじるのは難しくないので、良かったらお試しください。