HumHubの新規ユーザー登録画面のカスタマイズ

## 概要
HumHubの新規ユーザー登録画面の、登録項目のカスタマイズ方法

## 関連
- [HumHubというCMSを使ってSNSを秒で作る](https://qiita.com/Esfahan/items/974687234566ef6b069d)

## 対象ページ
確認メール本文のリンクをクリックした後のページ

## URL
/index.php?r=user%2Fregistration&token=xxxxxxxxxxxx

<img width="499" alt="Screen Shot 2018-02-12 at 18.05.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/105936/916379d3-0789-8a57-0911-638e9cd3d6bf.png">


## debug
こちらのページは、パラメータのtokenが有効でないと閲覧出来ないページだが、
protected/humhub/modules/user/controllers/RegistrationController.php
61 - 69行目をコメントアウトすることで、閲覧できるようになる。

```php:RegistrationController.php
 45     /**
 46      * Registration Form
 47      *
 48      * @return type
 49      * @throws HttpException
 50      */
 51     public function actionIndex()
 52     {
 53         $registration = new Registration();
 54
 55         /**
 56          * @var \yii\authclient\BaseClient
 57          */
 58         $authClient = null;
 59         $inviteToken = Yii::$app->request->get('token', '');
 60
 61         //if ($inviteToken != '') {
 62         //    $this->handleInviteRegistration($inviteToken, $registration);
 63         //} elseif (Yii::$app->session->has('authClient')) {
 64         //    $authClient = Yii::$app->session->get('authClient');
 65         //    $this->handleAuthClientRegistration($authClient, $registration);
 66         //} else {
 67         //    Yii::$app->session->setFlash('error', 'Registration failed.');
 68         //    return $this->redirect(['/user/auth/login']);
 69         //}
```

## 表示項目変更方法
DBの`profile_field`テーブルに、プロフィール情報の項目が登録されており、
`show_at_registration`が`1`のもののみ、新規ユーザー登録画面に表示される。

### 例：Genderを新規ユーザー登録画面に表示させる
Genderはデフォルトで非表示になっている。
該当のレコードは以下のようになっている。

```sql
mysql> select * from profile_field where internal_name = 'gender'\G;
*************************** 1. row ***************************
                       id: 4
profile_field_category_id: 1
                module_id: NULL
         field_type_class: humhub\modules\user\models\fieldtype\Select
        field_type_config: {"options":"male=>Male\nfemale=>Female\ncustom=>Custom","fieldTypes":[]}
            internal_name: gender
                    title: Gender
              description: NULL
               sort_order: 300
                 required: NULL
     show_at_registration: NULL
                 editable: 1
                  visible: 1
               created_at: 2018-02-04 15:02:46
               created_by: NULL
               updated_at: 2018-02-04 15:02:46
               updated_by: NULL
           ldap_attribute: NULL
     translation_category: NULL
                is_system: 1
               searchable: 1
1 row in set (0.00 sec)
```

Genderを表示させたい場合は、以下のように`show_at_registration`を`1`に変更することで対応可能。

```sql
mysql> update profile_field set show_at_registration = 1 where internal_name = 'gender';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
```

これでGenderが表示されるようになった。

<img width="500" alt="Screen Shot 2018-02-12 at 17.47.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/105936/165edd99-c0d3-01b6-8438-6b0243694d8f.png">

さらに、必須項目、編集不可にする場合は、以下で実現できる。

```sql
mysql> update
    profile_field
set
    show_at_registration = 1,
    required = 1,
    editable = 0
where
    internal_name = 'gender'
;
```

## ソースを追ってみる
簡単なメモですが、ソースを追ってみると、以下の箇所が関係しているようです。

### protected/humhub/modules/user/models/forms/Registration.php

| メソッド |
| --- |
| setFormDefinition() |

このメソッドで、表示項目がセットされる

```php:Registration.php
 93     protected function setFormDefinition()
 94     {
 95         $this->definition = [];
 96         $this->definition['elements'] = [];
 97         $this->definition['elements']['User'] = $this->getUserFormDefinition();
 98         $this->definition['elements']['GroupUser'] = $this->getGroupFormDefinition();
 99         if ($this->enablePasswordForm) {
100             $this->definition['elements']['Password'] = $this->getPasswordFormDefinition();
101         }
102         $this->definition['elements']['Profile'] = array_merge(array('type' => 'form'), $this->getProfile()->getFormDefinition());
103         $this->definition['buttons'] = array(
104             'save' => array(
105                 'type' => 'submit',
106                 'class' => 'btn btn-primary',
107                 'label' => Yii::t('UserModule.controllers_AuthController', 'Create account'),
108             ),
109         );
110     }
208                 // Dont allow editing of ldap syned fields - will be overwritten on next ldap sync.
209                 if (in_array($profileField->internal_name, $syncAttributes)) {
210                     $profileField->editable = false;
211                 }
212
213                 $fieldDefinition = $profileField->fieldType->getFieldFormDefinition();
214                 $category['elements'] = array_merge($category['elements'], $fieldDefinition);
215
216                 $profileField->fieldType->loadDefaults($this);
217             }
218
219             $definition['elements']['category_' . $profileFieldCategory->id] = $category;
220         }
221
222         return $definition;
223     }
```

### protected/humhub/modules/user/models/Profile.php

| メソッド |
| --- |
| getFormDefinition() |

186行目の`$safeAttributes`に、表示項目の定義が入っている

```php
176     public function getFormDefinition()
177     {
178         $definition = array();
179         $definition['elements'] = array();
180
181         $syncAttributes = [];
182         if ($this->user !== null) {
183             $syncAttributes = \humhub\modules\user\authclient\AuthClientHelpers::getSyncAttributesByUser($this->user);
184         }
185
186         $safeAttributes = $this->safeAttributes();
187
188         foreach (ProfileFieldCategory::find()->orderBy('sort_order')->all() as $profileFieldCategory) {
189
190             $category = array(
191                 'type' => 'form',
192                 'title' => Yii::t($profileFieldCategory->getTranslationCategory(), $profileFieldCategory->title),
193                 'elements' => array(),
194             );
195
196             foreach (ProfileField::find()->orderBy('sort_order')->where(['profile_field_category_id' => $profileFieldCategory->id])->all() as $profileField) {
197                 /** @var ProfileField $profileField */
198                 $profileField->editable = true;
199
200                 if (!in_array($profileField->internal_name, $safeAttributes)) {
201                     if ($profileField->visible && $this->scenario != 'registration') {
202                         $profileField->editable = false;
203                     } else {
204                         continue;
205                     }
206                 }
207
```

### protected/vendor/yiisoft/yii2/base/Model.php

| メソッド |
| --- |
| safeAttributes() |

protected/humhub/modules/user/models/Profile.phpの
`getFormDefinition()`で呼ばれる`safeAttributes`は、このメソッドで作られる

```php
 748     public function safeAttributes()
 749     {
 750         $scenario = $this->getScenario();
 751         $scenarios = $this->scenarios();
 752         if (!isset($scenarios[$scenario])) {
 753             return [];
 754         }
 755         $attributes = [];
 756         foreach ($scenarios[$scenario] as $attribute) {
 757             if ($attribute[0] !== '!' && !in_array('!' . $attribute, $scenarios[$scenario])) {
 758                 $attributes[] = $attribute;
 759             }
 760         }
 761
 762         return $attributes;
 763     }
```

### protected/vendor/yiisoft/yii2/base/Model.php

| メソッド |
| --- |
| scenarios() |

このメソッドで、表示項目をグループ化する`scenarios`が作られる

```php
 184     public function scenarios()
 185     {
 186         $scenarios = [self::SCENARIO_DEFAULT => []];
 187         foreach ($this->getValidators() as $validator) {
 188             foreach ($validator->on as $scenario) {
 189                 $scenarios[$scenario] = [];
 190             }
 191             foreach ($validator->except as $scenario) {
 192                 $scenarios[$scenario] = [];
 193             }
 194         }
 195         $names = array_keys($scenarios);
 196
 197         foreach ($this->getValidators() as $validator) {
 198             if (empty($validator->on) && empty($validator->except)) {
 199                 foreach ($names as $name) {
 200                     foreach ($validator->attributes as $attribute) {
 201                         $scenarios[$name][$attribute] = true;
 202                     }
 203                 }
 204             } elseif (empty($validator->on)) {
 205                 foreach ($names as $name) {
 206                     if (!in_array($name, $validator->except, true)) {
 207                         foreach ($validator->attributes as $attribute) {
 208                             $scenarios[$name][$attribute] = true;
 209                         }
 210                     }
 211                 }
 212             } else {
 213                 foreach ($validator->on as $name) {
 214                     foreach ($validator->attributes as $attribute) {
 215                         $scenarios[$name][$attribute] = true;
 216                     }
 217                 }
 218             }
 219         }
 220
 221         foreach ($scenarios as $scenario => $attributes) {
 222             if (!empty($attributes)) {
 223                 $scenarios[$scenario] = array_keys($attributes);
 224             }
 225         }
 226
 227         return $scenarios;
 228     }
```

### protected/humhub/modules/user/models/Profile.php

| メソッド |
| --- |
| scenarios() |

以下のメソッドで、`Profile`の`scenarios`がセットされる
101行目で、新規ユーザー登録画面に表示される項目がセットされる

```php
 79     public function scenarios()
 80     {
 81         $scenarios = parent::scenarios();
 82         $scenarios['editAdmin'] = [];
 83         $scenarios['registration'] = [];
 84         $scenarios['editProfile'] = [];
 85
 86         // Get synced attributes if user is set
 87         $syncAttributes = [];
 88         if ($this->user !== null) {
 89             $syncAttributes = \humhub\modules\user\authclient\AuthClientHelpers::getSyncAttributesByUser($this->user);
 90         }
 91
 92         foreach (ProfileField::find()->all() as $profileField) {
 93             // Some fields consist of multiple field definitions (e.g. Birthday)
 94             foreach ($profileField->fieldType->getFieldFormDefinition() as $fieldName => $definition) {
 95                 $scenarios['editAdmin'][] = $fieldName;
 96
 97                 if ($profileField->editable && !in_array($profileField->internal_name, $syncAttributes)) {
 98                     $scenarios['editProfile'][] = $fieldName;
 99                 }
100
101                 if ($profileField->show_at_registration) {
102                     $scenarios['registration'][] = $fieldName;
103                 }
104             }
105         }
106
107         return $scenarios;
108     }
```

以上