* @copyright 2020 Copyright XEHub Corp. * @license http://www.gnu.org/licenses/lgpl-3.0-standalone.html LGPL * @link https://xpressengine.io */ namespace Xpressengine\Migrations; use Illuminate\Database\Schema\Blueprint; use DB; use Schema; use XeLang; use Xpressengine\Support\Migration; use Xpressengine\User\Models\User; use Xpressengine\User\UserRegisterHandler; /** * Class UserMigration * * @category Migrations * @package Xpressengine\Migrations * @author XE Developers * @copyright 2020 Copyright XEHub Corp. * @license http://www.gnu.org/licenses/lgpl-3.0-standalone.html LGPL * @link https://xpressengine.io */ class UserMigration extends Migration { /** * Run when install the application. * * @return void */ public function install() { Schema::create('user', function (Blueprint $table) { $table->engine = "InnoDB"; $table->string('id', 36)->comment('user ID'); $table->string('display_name', 255)->comment('display name.'); $table->string('email', 255)->nullable()->comment('email'); $table->string('login_id', 255)->unique()->comment('id'); $table->string('password', 255)->nullable()->comment('password'); $table->string('rating', 15)->default('user')->comment('user rating. guest/user/manager/super'); $table->string('status', 20)->comment('account status. activated/deactivated'); $table->text('introduction')->default(null)->nullable()->comment('user introduction'); $table->string('profile_image_id', 36)->nullable()->comment('profile image file ID'); $table->string('remember_token', 255)->nullable()->comment('token for keep login'); $table->timestamp('login_at')->nullable()->comment('login date'); $table->timestamp('created_at')->nullable()->index()->comment('created date'); $table->timestamp('updated_at')->nullable()->index()->comment('updated date'); $table->timestamp('password_updated_at')->nullable()->comment('password updated date'); $table->primary('id'); }); Schema::create('user_group', function (Blueprint $table) { $table->engine = "InnoDB"; $table->string('id', 36)->comment('group ID'); $table->string('name')->comment('group name'); $table->string('description', 1000)->comment('group description'); $table->integer('order')->default(0)->index()->comment('order number'); $table->timestamp('created_at')->nullable()->index()->comment('created date'); $table->timestamp('updated_at')->nullable()->comment('updated date'); $table->primary('id'); }); Schema::create('user_group_user', function (Blueprint $table) { // user IDs included in the use group $table->engine = "InnoDB"; $table->increments('id')->comment('ID'); $table->string('group_id', 36)->comment('group ID'); $table->string('user_id', 36)->comment('user ID'); $table->timestamp('created_at')->nullable()->comment('created date'); $table->unique(['group_id', 'user_id']); $table->index('group_id'); $table->index('user_id'); }); Schema::create('user_account', function (Blueprint $table) { // user account. Login via account information provided by other providers. As like OAuth. $table->engine = "InnoDB"; $table->string('id', 36)->comment('ID'); $table->string('user_id')->comment('user ID'); $table->string('account_id')->comment('account Id'); $table->string('email')->nullable()->comment('email'); $table->char('provider', 20)->comment('OAuth provider. naver/twitter/facebook/...'); $table->string('token', 500)->comment('token'); $table->string('token_secret', 500)->comment('token secret'); $table->timestamp('created_at')->nullable()->comment('created date'); $table->timestamp('updated_at')->nullable()->comment('updated date'); $table->primary('id'); $table->unique(['provider', 'account_id']); }); Schema::create('user_email', function (Blueprint $table) { $table->engine = "InnoDB"; $table->increments('id')->comment('ID'); $table->string('user_id', 36)->comment('user ID'); $table->string('address')->comment('email address'); $table->timestamp('created_at')->nullable()->index()->comment('created date'); $table->timestamp('updated_at')->nullable()->comment('updated date'); $table->index('user_id'); $table->index('address'); }); Schema::create('user_pending_email', function (Blueprint $table) { // email confirm $table->engine = "InnoDB"; $table->increments('id')->comment('ID'); $table->string('user_id', 36)->comment('user ID'); $table->string('address')->comment('email address'); $table->string('confirmation_code')->nullable()->comment('confirmation code'); $table->timestamp('created_at')->nullable()->index()->comment('created date'); $table->timestamp('updated_at')->nullable()->comment('updated date'); $table->index('user_id'); $table->index('address'); }); Schema::create('user_password_resets', function (Blueprint $table) { // find account password $table->engine = "InnoDB"; $table->increments('id')->comment('ID'); $table->string('email')->index()->comment('email address'); $table->string('token')->index()->comment('token'); $table->timestamp('created_at')->nullable()->comment('created date'); }); Schema::create('user_register_token', function (Blueprint $table) { // find account password $table->engine = "InnoDB"; $table->string('id', 36)->comment('user ID'); $table->string('guard', 100)->comment('the guard creating token'); $table->text('data')->comment('token data'); $table->timestamp('created_at')->nullable()->comment('created date'); }); Schema::create('user_terms', function (Blueprint $table) { $table->string('id', 36); $table->string('title'); $table->string('content')->nullable(); $table->string('description')->nullable(); $table->integer('order')->default(0); $table->boolean('is_enabled')->default(false); $table->boolean('is_require')->default(true); $table->primary('id'); $table->engine = "InnoDB"; }); Schema::create('user_login_log', function (Blueprint $table) { $table->engine = "InnoDB"; $table->bigIncrements('id')->comment('row id'); $table->string('user_id', 36)->comment('user ID'); $table->string('user_agent')->comment('user agent'); $table->string('ip', 15)->comment('ip'); $table->timestamp('created_at')->nullable()->comment('created date'); $table->index('user_id'); }); $this->createUserTermAgreeTable(); } /** * Run after installation. * * @return void */ public function installed() { DB::table('config')->insert([ ['name' => 'user', 'vars' => '[]'], ['name' => 'user.common', 'vars' => '{"useCaptcha":false,"webmasterName":"webmaster","webmasterEmail":"webmaster@domain.com"}'], ['name' => 'user.register', 'vars' => '{"secureLevel":"low","joinable":true,"register_process":"activated","term_agree_type":"pre","display_name_unique":false,"use_display_name":true,"password_rules":"min:6|alpha|numeric|special_char"}'], ['name' => 'toggleMenu@user', 'vars' => '{"activate":["user\/toggleMenu\/xpressengine@profile","user\/toggleMenu\/xpressengine@manage"]}'] ]); } /** * Run after service activation. * * @return void */ public function init() { $registerConfig = app('xe.config')->get('user.register'); // add default user groups $joinGroup = app('xe.user')->groups()->create( [ 'name' => '정회원', 'description' => 'default user group' ] ); app('xe.user')->groups()->create( [ 'name' => '준회원', 'description' => 'sub user group' ] ); $registerConfig->set('joinGroup', $joinGroup->id); $displayNameCaption = XeLang::genUserKey(); foreach (XeLang::getLocales() as $locale) { $value = "닉네임"; if ($locale != 'ko') { $value = "Nickname"; } XeLang::save($displayNameCaption, $locale, $value); } $registerConfig->set('display_name_caption', $displayNameCaption); app('xe.config')->modify($registerConfig); // set admin's group auth()->user()->joinGroups($joinGroup); } /** * check updated * * @param null $installedVersion installed version * * @return bool */ public function checkUpdated($installedVersion = null) { if ($this->checkNeedMergeConfig() == true) { return false; } if ($this->checkExistRegisterConfig() == false) { return false; } if ($this->checkNeedAddTermTableColumns() === false) { return false; } if ($this->checkExistUserTermAgreeTable() === false) { return false; } if ($this->checkExistUserLoginIdColumn() === false) { return false; } return true; } /** * run update * * @param null $installedVersion installed version * * @return void */ public function update($installedVersion = null) { if ($this->checkNeedMergeConfig() == true) { $this->mergeConfig(); } if ($this->checkExistRegisterConfig() == false) { $this->divideRegisterConfig(); } if ($this->checkNeedAddTermTableColumns() === false) { $this->addTermTableColumns(); $this->updateOldTermsRequire(); } if ($this->checkExistUserTermAgreeTable() === false) { $this->createUserTermAgreeTable(); } //TODO 실행 조건 추가 $this->deleteDisplayNameUnique(); if ($this->checkExistUserLoginIdColumn() === false) { $this->createUserLoginIdColumn(); $this->migrationLoginIdColumn(); $this->setLoginIdColumnUnique(); } } /** * check need user setting merge(common, join) * * @return bool */ private function checkNeedMergeConfig() { return app('xe.config')->get('user.join') !== null; } /** * run user setting merge(common, join) * * @return void */ private function mergeConfig() { $commonConfig = app('xe.config')->get('user.common'); $joinConfig = app('xe.config')->get('user.join'); $commonConfigAttribute = $commonConfig->getPureAll(); $joinConfigAttribute = $joinConfig->getPureAll(); foreach ($joinConfigAttribute as $name => $value) { $commonConfigAttribute[$name] = $value; } app('xe.config')->put('user.common', $commonConfigAttribute); app('xe.config')->remove($joinConfig); } /** * check exist register config * * @return bool */ private function checkExistRegisterConfig() { return app('xe.config')->get('user.register') !== null; } /** * divide from user.common to user.register * * @return void */ private function divideRegisterConfig() { $commonConfig = app('xe.config')->get('user.common'); $originalCommonConfigAttribute = $commonConfig->getPureAll(); $commonConfigAttribute = ['useCaptcha', 'webmasterName', 'webmasterEmail']; $registerConfigAttribute = array_diff_key($originalCommonConfigAttribute, array_flip($commonConfigAttribute)); $newCommonConfigAttribute = []; foreach ($commonConfigAttribute as $config) { if (isset($originalCommonConfigAttribute[$config]) === true) { $newCommonConfigAttribute[$config] = $originalCommonConfigAttribute[$config]; } } if (isset($registerConfigAttribute['guard_forced']) && $registerConfigAttribute['guard_forced'] === true) { $registerConfigAttribute['register_process'] = User::STATUS_PENDING_EMAIL; } else { $registerConfigAttribute['register_process'] = User::STATUS_ACTIVATED; } unset($registerConfigAttribute['guard_forced']); $registerConfigAttribute['term_agree_type'] = UserRegisterHandler::TERM_AGREE_PRE; $registerConfigAttribute['display_name_unique'] = true; $registerConfigAttribute['use_display_name'] = true; $registerConfigAttribute['dynamic_fields'] = array_keys(app('xe.dynamicField')->gets('user')); $displayNameCaption = XeLang::genUserKey(); foreach (XeLang::getLocales() as $locale) { $value = "닉네임"; if ($locale != 'ko') { $value = "Nickname"; } XeLang::save($displayNameCaption, $locale, $value); } $registerConfigAttribute['display_name_caption'] = $displayNameCaption; $passwordRuleLevel = app('config')->get('xe.user.password.default'); $registerConfigAttribute['password_rules'] = app('config')->get("xe.user.password.levels.{$passwordRuleLevel}"); if (isset($registerConfigAttribute['secureLevel']) === true) { unset($registerConfigAttribute['secureLevel']); } app('xe.config')->put('user.common', $newCommonConfigAttribute); app('xe.config')->set('user.register', $registerConfigAttribute); } /** * Check need term table update * * @return bool */ private function checkNeedAddTermTableColumns() { return Schema::hasColumn('user_terms', 'is_require') && Schema::hasColumn('user_terms', 'description'); } /** * Add term table description, is_require columns * * @return void */ private function addTermTableColumns() { Schema::table('user_terms', function (Blueprint $table) { $table->string('description')->after('title')->nullable(); $table->boolean('is_require')->default(true)->nullable(); }); } /** * Old term update require * * @return void */ private function updateOldTermsRequire() { \DB::table('user_terms')->update(['is_require' => true]); } /** * Check exist user term agree table * * @return bool */ private function checkExistUserTermAgreeTable() { return Schema::hasTable('user_term_agrees'); } /** * Create user term agree table * * @return void */ private function createUserTermAgreeTable() { Schema::create('user_term_agrees', function (Blueprint $table) { $table->string('id', 36); $table->string('user_id', 36); $table->string('term_id', 36); $table->softDeletes(); $table->timestamps(); $table->unique(['user_id', 'term_id']); $table->index('user_id'); $table->index('term_id'); }); } /** * display_name field unique 삭제 * * @return void */ private function deleteDisplayNameUnique() { try { Schema::table('user', function (Blueprint $table) { $table->dropUnique('user_display_name_unique'); }); } catch (\Exception $e) { } } /** * User 테이블에 login_id 컬럼이 존재 여부 확인 * * @return bool */ private function checkExistUserLoginIdColumn() { return Schema::hasColumn('user', 'login_id'); } /** * User 테이블에 login_id 컬럼 생성 * * @return void */ private function createUserLoginIdColumn() { Schema::table('user', function (Blueprint $table) { $table->string('login_id')->after('email'); }); } /** * user email에서 계정을 login_id로 update * 중복된 login_id는 확인해서 login_id 뒤에 index를 붙임 * * @return void */ private function migrationLoginIdColumn() { \DB::table('user')->update(['login_id' => \DB::raw("REPLACE(SUBSTRING_INDEX(email, '@', 1), '.', '')")]); $duplicateEmails = \DB::table('user')->select('login_id') ->groupBy('login_id')->havingRaw('count(login_id) > 1')->get(); foreach ($duplicateEmails as $duplicateEmail) { $duplicateUsers = User::where('login_id', $duplicateEmail->login_id)->orderBy('created_at')->get(); foreach ($duplicateUsers as $index => $duplicateUser) { $duplicateUser->login_id .= ($index + 1); $duplicateUser->save(); } } } /** * login_id 업데이트 후 unique 지정 * * @return void */ private function setLoginIdColumnUnique() { Schema::table('user', function (Blueprint $table) { $table->unique('login_id'); }); } }