Просмотр исходного кода

Merge remote-tracking branch 'origin/master' into feat/growi-bot

Yuki Takei 5 лет назад
Родитель
Сommit
208aafbb75

+ 15 - 2
CHANGES.md

@@ -1,11 +1,24 @@
 # CHANGES
 # CHANGES
 
 
-## v4.2.14-RC
+## v4.2.15-EC
 
 
+* Support: Update libs
+    * eslint-config-weseek
+
+## v4.2.14
+
+* Feature: Add an option to restrict publishing email property for new users
+* Improvement: Invite modal in admin page without email server settings
+* Improvement: Global notification settings in admin page without email server settings
+* Fix: Can create pages on the share route
+    * Introduced by v4.2.8
+* Fix: Pages restrected by group are excluded for recurrence operation
+    * Introduced by v4.2.8
+* Fix: Rename and duplicate to descendants path does not work correctly
+    * Introduced by v4.2.8
 * Support: Update libs
 * Support: Update libs
     * bunyan
     * bunyan
     * browser-bunyan
     * browser-bunyan
-    * eslint-config-weseek
 
 
 ## v4.2.13
 ## v4.2.13
 
 

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "growi",
   "name": "growi",
-  "version": "4.2.14-RC",
+  "version": "4.2.15-RC",
   "description": "Team collaboration software using markdown",
   "description": "Team collaboration software using markdown",
   "tags": [
   "tags": [
     "wiki",
     "wiki",

+ 9 - 3
resource/locales/en_US/admin/admin.json

@@ -1,4 +1,5 @@
 {
 {
+  "mailer_setup_required":"<a href='/admin/app'>Email settings</a> are required to send.",
   "admin_top": {
   "admin_top": {
     "management_wiki": "Management Wiki",
     "management_wiki": "Management Wiki",
     "system_information": "System information",
     "system_information": "System information",
@@ -22,6 +23,7 @@
     "confidential_name": "Confidential name",
     "confidential_name": "Confidential name",
     "confidential_example": "ex): internal use only",
     "confidential_example": "ex): internal use only",
     "default_language": "Default language for new users",
     "default_language": "Default language for new users",
+    "default_mail_visibility": "Disclose e-mail for new users",
     "file_uploading": "File uploading",
     "file_uploading": "File uploading",
     "enable_files_except_image": "Enabling this option will allow upload of any file type. Without this option, only image file upload is supported.",
     "enable_files_except_image": "Enabling this option will allow upload of any file type. Without this option, only image file upload is supported.",
     "attach_enable": "You can attach files other than image files if you enable this option.",
     "attach_enable": "You can attach files other than image files if you enable this option.",
@@ -273,16 +275,20 @@
     }
     }
   },
   },
   "user_management": {
   "user_management": {
-    "invite_users": "Invite new users",
+    "invite_users": "Temporarily issue a new user",
     "click_twice_same_checkbox": "You should check at least one checkbox.",
     "click_twice_same_checkbox": "You should check at least one checkbox.",
     "invite_modal": {
     "invite_modal": {
-      "emails": "Emails (Possible to invite multiple people with new lines)",
+      "emails": "Emails (Possible to issue multiple people with new lines)",
+      "description1":"Temporarily issue new users by email addresses.",
+      "description2":"A temporary password will be generated for the first login.",
       "invite_thru_email": "Send invitation email",
       "invite_thru_email": "Send invitation email",
+      "mail_setting_link":"<i class='icon-settings mr-2'></i><a href='/admin/app'>Email settings</a>",
       "valid_email": "Valid email address is required",
       "valid_email": "Valid email address is required",
       "temporary_password": "The created user has a temporary password",
       "temporary_password": "The created user has a temporary password",
       "send_new_password": "Please send the new password to the user.",
       "send_new_password": "Please send the new password to the user.",
       "send_temporary_password": "Be sure to copy the temporary password ON THIS SCREEN and send it to the user.",
       "send_temporary_password": "Be sure to copy the temporary password ON THIS SCREEN and send it to the user.",
-      "existing_email": "The following emails already exist"
+      "existing_email": "The following emails already exist",
+      "issue": "Issue"
     },
     },
     "user_table": {
     "user_table": {
       "administrator": "Administrator",
       "administrator": "Administrator",

+ 10 - 4
resource/locales/ja_JP/admin/admin.json

@@ -1,4 +1,5 @@
 {
 {
+  "mailer_setup_required":"送信するには <a href='/admin/app'>メールの設定</a> が必要です。",
   "admin_top": {
   "admin_top": {
     "management_wiki": "Wiki管理",
     "management_wiki": "Wiki管理",
     "system_information": "システム情報",
     "system_information": "システム情報",
@@ -22,6 +23,7 @@
     "confidential_name": "コンフィデンシャル表示",
     "confidential_name": "コンフィデンシャル表示",
     "confidential_example": "例: 社外秘",
     "confidential_example": "例: 社外秘",
     "default_language": "新規ユーザーのデフォルト設定言語",
     "default_language": "新規ユーザーのデフォルト設定言語",
+    "default_mail_visibility": "新規ユーザーの初期メール公開設定",
     "file_uploading": "ファイルアップロード",
     "file_uploading": "ファイルアップロード",
     "enable_files_except_image": "画像以外のファイルアップロードを許可",
     "enable_files_except_image": "画像以外のファイルアップロードを許可",
     "attach_enable": "許可をしている場合、画像以外のファイルをページに添付可能になります。",
     "attach_enable": "許可をしている場合、画像以外のファイルをページに添付可能になります。",
@@ -271,16 +273,20 @@
     }
     }
   },
   },
   "user_management": {
   "user_management": {
-    "invite_users": "新規ユーザーの招待",
+    "invite_users": "新規ユーザーの仮発行",
     "click_twice_same_checkbox": "少なくとも一つはチェックしてください。",
     "click_twice_same_checkbox": "少なくとも一つはチェックしてください。",
     "invite_modal": {
     "invite_modal": {
-      "emails": "メールアドレス (複数行入力で複数人招待可能)",
-      "invite_thru_email": "招待をメールで送信",
+      "emails": "メールアドレス (複数行入力で複数人発行可能)",
+      "description1":"メールアドレスを使用して新規ユーザーを仮発行します。",
+      "description2":"初回のログイン時に使用する仮パスワードが生成されます。",
+      "invite_thru_email": "招待メールを送信する",
+      "mail_setting_link":"<i class='icon-settings mr-2'></i><a href='/admin/app'>メールの設定</a>",
       "valid_email": "メールアドレスを入力してください。",
       "valid_email": "メールアドレスを入力してください。",
       "temporary_password": "作成したユーザーは仮パスワードが設定されています。",
       "temporary_password": "作成したユーザーは仮パスワードが設定されています。",
       "send_new_password": "新規発行したパスワードを、対象ユーザーへ連絡してください。",
       "send_new_password": "新規発行したパスワードを、対象ユーザーへ連絡してください。",
       "send_temporary_password": "招待メールを送っていない場合、この画面で必ず仮パスワードをコピーし、招待者へ連絡してください。",
       "send_temporary_password": "招待メールを送っていない場合、この画面で必ず仮パスワードをコピーし、招待者へ連絡してください。",
-      "existing_email": "以下のEmailはすでに存在しています。"
+      "existing_email": "以下のEmailはすでに存在しています。",
+      "issue": "発行"
     },
     },
     "user_table": {
     "user_table": {
       "administrator": "管理者",
       "administrator": "管理者",

+ 9 - 3
resource/locales/zh_CN/admin/admin.json

@@ -1,4 +1,5 @@
 {
 {
+  "mailer_setup_required":"<a href='/admin/app'>Email settings</a> are required to send.",
 	"admin_top": {
 	"admin_top": {
 		"management_wiki": "管理Wiki",
 		"management_wiki": "管理Wiki",
 		"system_information": "系统信息",
 		"system_information": "系统信息",
@@ -22,6 +23,7 @@
 		"confidential_name": "内部名称",
 		"confidential_name": "内部名称",
 		"confidential_example": "ex):仅供内部使用",
 		"confidential_example": "ex):仅供内部使用",
 		"default_language": "新用户的默认语言",
 		"default_language": "新用户的默认语言",
+		"default_mail_visibility": "新用户的默认电子邮件可见性",
 		"file_uploading": "文件上传",
 		"file_uploading": "文件上传",
 		"enable_files_except_image": "启用此选项将允许上传任何文件类型。如果没有此选项,则仅支持图像文件上载。",
 		"enable_files_except_image": "启用此选项将允许上传任何文件类型。如果没有此选项,则仅支持图像文件上载。",
 		"attach_enable": "如果启用此选项,则可以附加图像文件以外的文件。",
 		"attach_enable": "如果启用此选项,则可以附加图像文件以外的文件。",
@@ -281,16 +283,20 @@
     }
     }
   },
   },
 	"user_management": {
 	"user_management": {
-		"invite_users": "邀请新用户",
+		"invite_users": "临时发布新用户",
 		"click_twice_same_checkbox": "您应该至少选中一个复选框。",
 		"click_twice_same_checkbox": "您应该至少选中一个复选框。",
 		"invite_modal": {
 		"invite_modal": {
 			"emails": "电子邮件",
 			"emails": "电子邮件",
-			"invite_thru_email": "发送邀请电子邮件",
+      "description1":"通过电子邮件地址临时发布新用户。",
+      "description2":"将为首次登录生成一个临时密码。",
+      "mail_setting_link":"<i class='icon-settings mr-2'></i><a href='/admin/app'>Email settings</a>",
 			"valid_email": "需要有效的电子邮件地址",
 			"valid_email": "需要有效的电子邮件地址",
+			"invite_thru_email": "发送邀请电子邮件",
 			"temporary_password": "创建的用户具有临时密码",
 			"temporary_password": "创建的用户具有临时密码",
 			"send_new_password": "请将新密码发送给用户。",
 			"send_new_password": "请将新密码发送给用户。",
 			"send_temporary_password": "请确保复制此屏幕上的临时密码并将其发送给用户。",
 			"send_temporary_password": "请确保复制此屏幕上的临时密码并将其发送给用户。",
-			"existing_email": "以下电子邮件已存在"
+			"existing_email": "以下电子邮件已存在",
+      "issue": "Issue"
 		},
 		},
 		"user_table": {
 		"user_table": {
 			"administrator": "管理员",
 			"administrator": "管理员",

+ 35 - 0
src/client/js/components/Admin/App/AppSetting.jsx

@@ -103,6 +103,41 @@ class AppSetting extends React.Component {
           </div>
           </div>
         </div>
         </div>
 
 
+        <div className="row form-group mb-5">
+          <label
+            className="text-left text-md-right col-md-3 col-form-label"
+          >
+            {t('admin:app_setting.default_mail_visibility')}
+          </label>
+          <div className="col-md-6 py-2">
+
+            <div className="custom-control custom-radio custom-control-inline">
+              <input
+                type="radio"
+                id="radio-email-show"
+                className="custom-control-input"
+                name="mailVisibility"
+                checked={adminAppContainer.state.isEmailPublishedForNewUser === true}
+                onChange={() => { adminAppContainer.changeIsEmailPublishedForNewUserShow(true) }}
+              />
+              <label className="custom-control-label" htmlFor="radio-email-show">{t('Show')}</label>
+            </div>
+
+            <div className="custom-control custom-radio custom-control-inline">
+              <input
+                type="radio"
+                id="radio-email-hide"
+                className="custom-control-input"
+                name="mailVisibility"
+                checked={adminAppContainer.state.isEmailPublishedForNewUser === false}
+                onChange={() => { adminAppContainer.changeIsEmailPublishedForNewUserShow(false) }}
+              />
+              <label className="custom-control-label" htmlFor="radio-email-hide">{t('Hide')}</label>
+            </div>
+
+          </div>
+        </div>
+
         <div className="row form-group mb-5">
         <div className="row form-group mb-5">
           <label
           <label
             className="text-left text-md-right col-md-3 col-form-label"
             className="text-left text-md-right col-md-3 col-form-label"

+ 6 - 1
src/client/js/components/Admin/Notification/ManageGlobalNotification.jsx

@@ -96,7 +96,9 @@ class ManageGlobalNotification extends React.Component {
 
 
 
 
   render() {
   render() {
-    const { t } = this.props;
+    const { t, appContainer } = this.props;
+    const { isMailerSetup } = appContainer.config;
+
     return (
     return (
       <React.Fragment>
       <React.Fragment>
 
 
@@ -179,7 +181,10 @@ class ManageGlobalNotification extends React.Component {
                     />
                     />
 
 
                   </div>
                   </div>
+
                   <p className="p-2">
                   <p className="p-2">
+                    {/* eslint-disable-next-line react/no-danger */}
+                    {!isMailerSetup && <span className="form-text text-muted" dangerouslySetInnerHTML={{ __html: t('admin:mailer_setup_required') }} />}
                     <b>Hint: </b>
                     <b>Hint: </b>
                     <a href="https://ifttt.com/create" target="blank">{t('notification_setting.email.ifttt_link')}
                     <a href="https://ifttt.com/create" target="blank">{t('notification_setting.email.ifttt_link')}
                       <i className="icon-share-alt" />
                       <i className="icon-share-alt" />

+ 25 - 6
src/client/js/components/Admin/Users/UserInviteModal.jsx

@@ -46,7 +46,12 @@ class UserInviteModal extends React.Component {
 
 
     return (
     return (
       <>
       <>
-        <label> {t('admin:user_management.invite_modal.emails')}</label>
+        <label>{t('admin:user_management.invite_modal.emails')}</label>
+        <p>
+          {t('admin:user_management.invite_modal.description1')}
+          <br />
+          {t('admin:user_management.invite_modal.description2')}
+        </p>
         <textarea
         <textarea
           className="form-control"
           className="form-control"
           placeholder="e.g.&#13;&#10;user1@growi.org&#13;&#10;user2@growi.org"
           placeholder="e.g.&#13;&#10;user1@growi.org&#13;&#10;user2@growi.org"
@@ -74,15 +79,29 @@ class UserInviteModal extends React.Component {
   }
   }
 
 
   renderModalFooter() {
   renderModalFooter() {
-    const { t } = this.props;
+    const { t, appContainer } = this.props;
+    const { isMailerSetup } = appContainer.config;
 
 
     return (
     return (
       <>
       <>
         <div className="col text-left custom-control custom-checkbox custom-checkbox-info text-left" onChange={this.handleCheckBox}>
         <div className="col text-left custom-control custom-checkbox custom-checkbox-info text-left" onChange={this.handleCheckBox}>
-          <input type="checkbox" id="sendEmail" className="custom-control-input" name="sendEmail" defaultChecked={this.state.sendEmail} />
+          <input
+            type="checkbox"
+            id="sendEmail"
+            className="custom-control-input"
+            name="sendEmail"
+            defaultChecked={this.state.sendEmail}
+            disabled={!isMailerSetup}
+          />
           <label className="custom-control-label" htmlFor="sendEmail">
           <label className="custom-control-label" htmlFor="sendEmail">
             {t('admin:user_management.invite_modal.invite_thru_email')}
             {t('admin:user_management.invite_modal.invite_thru_email')}
           </label>
           </label>
+          {isMailerSetup
+            // eslint-disable-next-line react/no-danger
+            ? <p className="form-text text-muted" dangerouslySetInnerHTML={{ __html: t('admin:user_management.invite_modal.mail_setting_link') }} />
+            // eslint-disable-next-line react/no-danger
+            : <p className="form-text text-muted" dangerouslySetInnerHTML={{ __html: t('admin:mailer_setup_required') }} />
+          }
         </div>
         </div>
         <div>
         <div>
           <button
           <button
@@ -90,7 +109,7 @@ class UserInviteModal extends React.Component {
             className="btn btn-outline-secondary mr-2"
             className="btn btn-outline-secondary mr-2"
             onClick={this.onToggleModal}
             onClick={this.onToggleModal}
           >
           >
-            Cancel
+            {t('Cancel')}
           </button>
           </button>
 
 
           <button
           <button
@@ -99,7 +118,7 @@ class UserInviteModal extends React.Component {
             onClick={this.handleSubmit}
             onClick={this.handleSubmit}
             disabled={!this.validEmail()}
             disabled={!this.validEmail()}
           >
           >
-            Invite
+            {t('admin:user_management.invite_modal.issue')}
           </button>
           </button>
         </div>
         </div>
       </>
       </>
@@ -119,7 +138,7 @@ class UserInviteModal extends React.Component {
           className="btn btn-outline-secondary"
           className="btn btn-outline-secondary"
           onClick={this.onToggleModal}
           onClick={this.onToggleModal}
         >
         >
-          Close
+          {t('Close')}
         </button>
         </button>
       </>
       </>
     );
     );

+ 10 - 0
src/client/js/services/AdminAppContainer.js

@@ -19,6 +19,7 @@ export default class AdminAppContainer extends Container {
       title: this.dummyTitle,
       title: this.dummyTitle,
       confidential: '',
       confidential: '',
       globalLang: '',
       globalLang: '',
+      isEmailPublishedForNewUser: true,
       fileUpload: '',
       fileUpload: '',
 
 
       siteUrl: '',
       siteUrl: '',
@@ -78,6 +79,7 @@ export default class AdminAppContainer extends Container {
       title: appSettingsParams.title,
       title: appSettingsParams.title,
       confidential: appSettingsParams.confidential,
       confidential: appSettingsParams.confidential,
       globalLang: appSettingsParams.globalLang,
       globalLang: appSettingsParams.globalLang,
+      isEmailPublishedForNewUser: appSettingsParams.isEmailPublishedForNewUser,
       fileUpload: appSettingsParams.fileUpload,
       fileUpload: appSettingsParams.fileUpload,
       siteUrl: appSettingsParams.siteUrl,
       siteUrl: appSettingsParams.siteUrl,
       envSiteUrl: appSettingsParams.envSiteUrl,
       envSiteUrl: appSettingsParams.envSiteUrl,
@@ -144,6 +146,13 @@ export default class AdminAppContainer extends Container {
     this.setState({ globalLang });
     this.setState({ globalLang });
   }
   }
 
 
+  /**
+   * Change isEmailPublishedForNewUser
+   */
+  changeIsEmailPublishedForNewUserShow(isEmailPublishedForNewUser) {
+    this.setState({ isEmailPublishedForNewUser });
+  }
+
   /**
   /**
    * Change fileUpload
    * Change fileUpload
    */
    */
@@ -309,6 +318,7 @@ export default class AdminAppContainer extends Container {
       title: this.state.title,
       title: this.state.title,
       confidential: this.state.confidential,
       confidential: this.state.confidential,
       globalLang: this.state.globalLang,
       globalLang: this.state.globalLang,
+      isEmailPublishedForNewUser: this.state.isEmailPublishedForNewUser,
       fileUpload: this.state.fileUpload,
       fileUpload: this.state.fileUpload,
     });
     });
     const { appSettingParams } = response.data;
     const { appSettingParams } = response.data;

+ 1 - 0
src/server/models/config.js

@@ -229,6 +229,7 @@ module.exports = function(crowi) {
       isAclEnabled: crowi.aclService.isAclEnabled(),
       isAclEnabled: crowi.aclService.isAclEnabled(),
       isSearchServiceConfigured: crowi.searchService.isConfigured,
       isSearchServiceConfigured: crowi.searchService.isConfigured,
       isSearchServiceReachable: crowi.searchService.isReachable,
       isSearchServiceReachable: crowi.searchService.isReachable,
+      isMailerSetup: crowi.mailService.isMailerSetup,
       globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
       globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
     };
     };
 
 

+ 5 - 0
src/server/models/user.js

@@ -268,6 +268,7 @@ module.exports = function(crowi) {
     this.name = name;
     this.name = name;
     this.username = username;
     this.username = username;
     this.status = STATUS_ACTIVE;
     this.status = STATUS_ACTIVE;
+    this.isEmailPublished = crowi.configManager.getConfig('crowi', 'customize:isEmailPublishedForNewUser');
 
 
     this.save((err, userData) => {
     this.save((err, userData) => {
       userEvent.emit('activated', userData);
       userEvent.emit('activated', userData);
@@ -651,6 +652,10 @@ module.exports = function(crowi) {
     }
     }
 
 
     const configManager = crowi.configManager;
     const configManager = crowi.configManager;
+
+    // Default email show/hide is up to the administrator
+    newUser.isEmailPublished = configManager.getConfig('crowi', 'customize:isEmailPublishedForNewUser');
+
     const globalLang = configManager.getConfig('crowi', 'app:globalLang');
     const globalLang = configManager.getConfig('crowi', 'app:globalLang');
     if (globalLang != null) {
     if (globalLang != null) {
       newUser.lang = globalLang;
       newUser.lang = globalLang;

+ 7 - 0
src/server/routes/apiv3/app-settings.js

@@ -38,6 +38,9 @@ const ErrorV3 = require('../../models/vo/error-apiv3');
  *          globalLang:
  *          globalLang:
  *            type: string
  *            type: string
  *            description: language set when create user
  *            description: language set when create user
+ *          isEmailPublishedForNewUser:
+ *            type: boolean
+ *            description: default email show/hide setting when create user
  *          fileUpload:
  *          fileUpload:
  *            type: boolean
  *            type: boolean
  *            description: enable upload file except image file
  *            description: enable upload file except image file
@@ -154,6 +157,7 @@ module.exports = (crowi) => {
       body('title').trim(),
       body('title').trim(),
       body('confidential'),
       body('confidential'),
       body('globalLang').isIn(listLocaleIds()),
       body('globalLang').isIn(listLocaleIds()),
+      body('isEmailPublishedForNewUser').isBoolean(),
       body('fileUpload').isBoolean(),
       body('fileUpload').isBoolean(),
     ],
     ],
     siteUrlSetting: [
     siteUrlSetting: [
@@ -219,6 +223,7 @@ module.exports = (crowi) => {
       title: crowi.configManager.getConfig('crowi', 'app:title'),
       title: crowi.configManager.getConfig('crowi', 'app:title'),
       confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
       confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
       globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
       globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
+      isEmailPublishedForNewUser: crowi.configManager.getConfig('crowi', 'customize:isEmailPublishedForNewUser'),
       fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
       fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
       siteUrl: crowi.configManager.getConfig('crowi', 'app:siteUrl'),
       siteUrl: crowi.configManager.getConfig('crowi', 'app:siteUrl'),
       envSiteUrl: crowi.configManager.getConfigFromEnvVars('crowi', 'app:siteUrl'),
       envSiteUrl: crowi.configManager.getConfigFromEnvVars('crowi', 'app:siteUrl'),
@@ -289,6 +294,7 @@ module.exports = (crowi) => {
       'app:title': req.body.title,
       'app:title': req.body.title,
       'app:confidential': req.body.confidential,
       'app:confidential': req.body.confidential,
       'app:globalLang': req.body.globalLang,
       'app:globalLang': req.body.globalLang,
+      'customize:isEmailPublishedForNewUser': req.body.isEmailPublishedForNewUser,
       'app:fileUpload': req.body.fileUpload,
       'app:fileUpload': req.body.fileUpload,
     };
     };
 
 
@@ -298,6 +304,7 @@ module.exports = (crowi) => {
         title: crowi.configManager.getConfig('crowi', 'app:title'),
         title: crowi.configManager.getConfig('crowi', 'app:title'),
         confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
         confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
         globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
         globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
+        isEmailPublishedForNewUser: crowi.configManager.getConfig('crowi', 'customize:isEmailPublishedForNewUser'),
         fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
         fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
       };
       };
       return res.apiv3({ appSettingParams });
       return res.apiv3({ appSettingParams });

+ 6 - 0
src/server/service/config-loader.js

@@ -422,6 +422,12 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
     type:    TYPES.STRING,
     type:    TYPES.STRING,
     default: null,
     default: null,
   },
   },
+  DEFAULT_EMAIL_PUBLISHED: {
+    ns:      'crowi',
+    key:     'customize:isEmailPublishedForNewUser',
+    type:    TYPES.BOOLEAN,
+    default: true,
+  },
 };
 };
 
 
 class ConfigLoader {
 class ConfigLoader {