connection = $connection; } /** * @return Connection */ protected function getConnection(): Connection { return $this->connection; } /** * @param string $language * @return void */ public function addTranslations(string $language): void { $filepath = __DIR__ . '/translation/' . $language . '.yaml'; $yml = Yaml::parseFile($filepath); $translations = array_shift($yml); foreach ($translations as $translation) { $sourceObj = new TransUnit($translation['target'], $translation['unitId']); $this->saveTranslationRecord($translation['group'], $sourceObj, $language); } } /** * @param string $groupName * @param TransUnit $source * @param string $language * @return void */ private function saveTranslationRecord(string $groupName, TransUnit $source, string $language): void { $groupId = $this->getLangStringHelper()->getGroupId($groupName); $langStringId = $this->getLangStringHelper()->getLangStringIdByUnitIdAndGroup($source->getUnitId(), $groupId); if ($langStringId == null) { throw new Exception( 'Cannot add a translation to a non existent lang string: ' . $source->getUnitId() ); } $langId = $this->getLanguageId($language); $existTranslation = $this->getTranslationRecord($langStringId, $langId); if ($existTranslation != null) { // TODO hanldle customized translations } else { $insetQuery = $this->createQueryBuilder(); $insetQuery->insert('ohrm_i18n_translate') ->values( [ 'lang_string_id' => ':langStringId', 'language_id' => ':langId', 'value' => ':target', ] ) ->setParameter('langStringId', $langStringId) ->setParameter('langId', $langId) ->setParameter('target', $source->getTarget()) ->executeQuery(); } } /** * @return LangStringHelper */ private function getLangStringHelper(): LangStringHelper { if (is_null($this->langStringHelper)) { $this->langStringHelper = new LangStringHelper($this->getConnection()); } return $this->langStringHelper; } /** * @param string $langCode * @return int * @throws Exception */ private function getLanguageId(string $langCode): int { $searchQuery = $this->createQueryBuilder(); $searchQuery->select('language.id') ->from('ohrm_i18n_language', 'language') ->where('language.code = :langCode') ->setParameter('langCode', $langCode); return $searchQuery->executeQuery()->fetchOne(); } /** * @return QueryBuilder */ protected function createQueryBuilder(): QueryBuilder { return $this->getConnection()->createQueryBuilder(); } /** * @param array $langStringId * @param int $langId * @return string * @throws Exception */ private function getTranslationRecord(int $langStringId, int $langId): string { $searchQuery = $this->createQueryBuilder(); $searchQuery->select('translate.id') ->from('ohrm_i18n_translate', 'translate') ->where('translate.language_id = :langCode') ->andWhere('translate.lang_string_id = :langStringId') ->setParameter('langCode', $langId) ->setParameter('langStringId', $langStringId); return $searchQuery->executeQuery()->fetchOne(); } } class Migration extends AbstractMigration { protected ?LangStringHelper $langStringHelper = null; protected ?TranslationHelper $translationHelper = null; /** * @inheritDoc */ public function up(): void { $groups = ['admin', 'auth', 'general']; foreach ($groups as $group) { $this->getLangStringHelper()->insertOrUpdateLangStrings($group); } $this->getConnection()->createQueryBuilder() ->insert('ohrm_module') ->values( [ 'name' => ':name', 'status' => ':status', 'display_name' => ':display_name' ] ) ->setParameter('name', "auth") ->setParameter('status', 1) ->setParameter('display_name', 'Auth') ->executeQuery(); $this->getConnection()->createQueryBuilder() ->insert('ohrm_module') ->values( [ 'name' => ':name', 'status' => ':status', 'display_name' => ':display_name' ] ) ->setParameter('name', "mobile") ->setParameter('status', 1) ->setParameter('display_name', 'Mobile') ->executeQuery(); $this->getConfigHelper()->setConfigValue('auth.password_policy.min_password_length', '8'); $this->getConfigHelper()->setConfigValue('auth.password_policy.min_uppercase_letters', '1'); $this->getConfigHelper()->setConfigValue('auth.password_policy.min_lowercase_letters', '1'); $this->getConfigHelper()->setConfigValue('auth.password_policy.min_numbers_in_password', '1'); $this->getConfigHelper()->setConfigValue('auth.password_policy.min_special_characters', '1'); $this->getConfigHelper()->setConfigValue('auth.password_policy.default_required_password_strength', 'strong'); $this->getConfigHelper()->setConfigValue('auth.password_policy.is_spaces_allowed', 'false'); $this->getDataGroupHelper()->insertApiPermissions(__DIR__ . '/permission/api.yaml'); $this->changePermissionForAttendanceConfigurationAPI(); $this->changePermissionForTimeConfigPeriodAPI(); $this->changePermissionForEmployeeWorkShiftAPI(); $this->getSchemaHelper()->createTable('ohrm_enforce_password') ->addColumn('id', Types::INTEGER, ['Autoincrement' => true]) ->addColumn('user_id', Types::INTEGER, ['Notnull' => true]) ->addColumn('enforce_request_date', Types::DATETIME_MUTABLE, ['Notnull' => false]) ->addColumn('reset_code', Types::STRING, ['Notnull' => true]) ->addColumn('expired', Types::BOOLEAN, ['Notnull' => true, 'Default' => 0]) ->setPrimaryKey(['id']) ->create(); $resetCode = new Index( 'reset_code', ['reset_code'] ); $this->getSchemaManager()->createIndex($resetCode, 'ohrm_enforce_password'); $foreignKeyConstraint = new ForeignKeyConstraint( ['user_id'], 'ohrm_user', ['id'], 'enforcePasswordUser', ['onDelete' => 'NO ACTION'] ); $this->getSchemaHelper()->addForeignKey('ohrm_enforce_password', $foreignKeyConstraint); $this->modifyDefaultRequiredPasswordStrength(); $this->modifyDefaultPasswordEnforcement(); $this->createOAuth2Tables(); // https://github.com/orangehrm/orangehrm/issues/1622 $this->getSchemaHelper()->addOrChangeColumns('ohrm_migration_log', [ 'php_version' => ['Type' => Type::getType(Types::STRING), 'Length' => 255], ]); $langCodes = [ 'zh_Hans_CN' ]; foreach ($langCodes as $langCode) { $this->getTranslationHelper()->addTranslations($langCode); } $this->updateLangStringVersion($this->getVersion()); } /** * @inheritDoc */ public function getVersion(): string { return '5.4.0'; } public function getLangStringHelper(): LangStringHelper { if (is_null($this->langStringHelper)) { $this->langStringHelper = new LangStringHelper( $this->getConnection() ); } return $this->langStringHelper; } public function getTranslationHelper(): TranslationHelper { if (is_null($this->translationHelper)) { $this->translationHelper = new TranslationHelper( $this->getConnection() ); } return $this->translationHelper; } private function updateLangStringVersion(string $version): void { $qb = $this->createQueryBuilder() ->update('ohrm_i18n_lang_string', 'lang_string') ->set('lang_string.version', ':version') ->setParameter('version', $version); $qb->andWhere($qb->expr()->isNull('lang_string.version')) ->executeStatement(); } private function modifyDefaultRequiredPasswordStrength(): void { $value = $this->getConfigHelper()->getConfigValue('authentication.default_required_password_strength'); if ( $value === "veryWeak" || $value === "weak" || $value === "better" || $value === "medium" || $value === "strong" || $value === "strongest" ) { if ($value === "medium") { $value = "better"; } $this->getConfigHelper()->setConfigValue('auth.password_policy.default_required_password_strength', $value); } $this->getConfigHelper()->deleteConfigValue('authentication.default_required_password_strength'); } private function modifyDefaultPasswordEnforcement(): void { $value = $this->getConfigHelper()->getConfigValue('authentication.enforce_password_strength'); if ($value !== 'on') { $value = 'off'; } $this->getConfigHelper()->setConfigValue('auth.password_policy.enforce_password_strength', $value); $this->getConfigHelper()->deleteConfigValue('authentication.enforce_password_strength'); } private function createOAuth2Tables(): void { $this->getSchemaHelper()->createTable('ohrm_oauth2_client') ->addColumn('id', Types::BIGINT, ['Autoincrement' => true]) ->addColumn('name', Types::STRING, ['Length' => 255]) ->addColumn('client_id', Types::STRING, ['Length' => 255]) ->addColumn('client_secret', Types::STRING, ['Length' => 255, 'Notnull' => false]) ->addColumn('redirect_uri', Types::STRING, ['Length' => 2000]) ->addColumn('is_confidential', Types::BOOLEAN) ->addColumn('enabled', Types::BOOLEAN) ->addUniqueIndex(['client_id'], 'idx_client_id') ->setPrimaryKey(['id']) ->create(); $this->getSchemaHelper()->createTable('ohrm_oauth2_authorization_code') ->addColumn('id', Types::BIGINT, ['Autoincrement' => true]) ->addColumn('authorization_code', Types::STRING, ['Length' => 255]) ->addColumn('client_id', Types::BIGINT) ->addColumn('user_id', Types::INTEGER) ->addColumn('redirect_uri', Types::STRING, ['Length' => 2000]) ->addColumn('expiry_date_time_utc', Types::DATETIME_IMMUTABLE) ->addColumn('revoked', Types::BOOLEAN) ->addUniqueIndex(['authorization_code'], 'idx_authorization_code') ->setPrimaryKey(['id']) ->create(); $foreignKeyConstraintClientId = new ForeignKeyConstraint( ['client_id'], 'ohrm_oauth2_client', ['id'], 'auth_code_client_id', ['onDelete' => 'CASCADE'] ); $this->getSchemaHelper()->addForeignKey('ohrm_oauth2_authorization_code', $foreignKeyConstraintClientId); $this->getSchemaHelper()->createTable('ohrm_oauth2_access_token') ->addColumn('id', Types::BIGINT, ['Autoincrement' => true]) ->addColumn('access_token', Types::STRING, ['Length' => 255]) ->addColumn('client_id', Types::BIGINT) ->addColumn('user_id', Types::INTEGER) ->addColumn('expiry_date_time_utc', Types::DATETIME_IMMUTABLE) ->addColumn('revoked', Types::BOOLEAN) ->addUniqueIndex(['access_token'], 'idx_access_token') ->setPrimaryKey(['id']) ->create(); $foreignKeyAccessTokenClientId = new ForeignKeyConstraint( ['client_id'], 'ohrm_oauth2_client', ['id'], 'access_token_client_id', ['onDelete' => 'CASCADE'] ); $this->getSchemaHelper()->addForeignKey('ohrm_oauth2_access_token', $foreignKeyAccessTokenClientId); $this->getSchemaHelper()->createTable('ohrm_oauth2_refresh_token') ->addColumn('id', Types::BIGINT, ['Autoincrement' => true]) ->addColumn('refresh_token', Types::STRING, ['Length' => 255]) ->addColumn('access_token', Types::STRING, ['Length' => 255]) ->addColumn('expiry_date_time_utc', Types::DATETIME_IMMUTABLE) ->addColumn('revoked', Types::BOOLEAN) ->addUniqueIndex(['refresh_token'], 'idx_refresh_token') ->setPrimaryKey(['id']) ->create(); $foreignKeyAccessToken = new ForeignKeyConstraint( ['access_token'], 'ohrm_oauth2_access_token', ['access_token'], 'oauth2_access_token', ['onDelete' => 'CASCADE'] ); $this->getSchemaHelper()->addForeignKey('ohrm_oauth2_refresh_token', $foreignKeyAccessToken); $this->getConnection()->createQueryBuilder() ->insert('ohrm_oauth2_client') ->values( [ 'name' => ':name', 'client_id' => ':client_id', 'client_secret' => ':client_secret', 'redirect_uri' => ':redirect_uri', 'is_confidential' => ':is_confidential', 'enabled' => ':enabled', ] ) ->setParameter('name', "OrangeHRM Mobile App") ->setParameter('client_id', 'orangehrm_mobile_app') ->setParameter('client_secret', null) ->setParameter('redirect_uri', 'com.orangehrm.opensource://oauthredirect') ->setParameter('is_confidential', false, Types::BOOLEAN) ->setParameter('enabled', true, Types::BOOLEAN) ->executeQuery(); $encryptionKey = base64_encode(random_bytes(32)); $this->getConfigHelper()->setConfigValue('oauth.encryption_key', $encryptionKey); $encryptionKey = base64_encode(random_bytes(32)); $this->getConfigHelper()->setConfigValue('oauth.token_encryption_key', $encryptionKey); // see https://php.net/manual/en/dateinterval.construct.php for TTL duration $this->getConfigHelper()->setConfigValue('oauth.auth_code_ttl', 'PT5M'); // 5 minutes $this->getConfigHelper()->setConfigValue('oauth.refresh_token_ttl', 'P1M'); // 1 month $this->getConfigHelper()->setConfigValue('oauth.access_token_ttl', 'PT30M'); // 30 minutes } private function changePermissionForAttendanceConfigurationAPI(): void { $this->getDataGroupHelper()->addDataGroupPermissions( 'apiv2_attendance_configuration', 'ESS', true, false, false, false, false ); } private function changePermissionForTimeConfigPeriodAPI(): void { $this->getDataGroupHelper()->addDataGroupPermissions( 'apiv2_time_time_sheet_config', 'ESS', true, false, false, false, false ); } private function changePermissionForEmployeeWorkShiftAPI(): void { $this->getDataGroupHelper()->addDataGroupPermissions( 'apiv2_pim_employee_work_shift', 'ESS', true, false, false, false, false ); } }