Migration complete

This commit is contained in:
2026-06-23 19:24:37 +02:00
parent 0436a99a7c
commit 39adc2171c
25 changed files with 517 additions and 25 deletions

View File

@@ -0,0 +1,79 @@
<?php
namespace RomhackPlaza\Master\Api\Controller;
use RomhackPlaza\Master\Helper\Migration;
use XF\Api\Controller\AbstractController;
use XF\Api\ControllerPlugin\UserPlugin;
use XF\Entity\User;
use XF\Entity\UserAuth;
use XF\Mvc\ParameterBag;
use XF\Mvc\Reply\AbstractReply;
use XF\Repository\UserRepository;
class MigrateController extends AbstractController
{
protected function preDispatchController($action, ParameterBag $params)
{
$rhpz_enable_migration = \XF::options()->rhpz_enable_migration;
if( !$rhpz_enable_migration ){
$this->error("Migration not enabled");
}
}
public function actionPostUser(): AbstractReply
{
$input = $this->filter([
'username' => 'str',
'email' => 'str',
'profile[about]' => 'str',
'profile[website]' => 'str',
'register_date' => 'uint',
'user_group_id' => 'uint',
'xf_user_id' => 'uint',
'avatar_path' => 'str',
'banner_path' => 'str',
'source_real_password' => 'str',
'wp_password' => 'str',
'xf_scheme_class' => 'str',
'xf_password_data' => 'str',
]);
$this->assertAdminPermission('user');
$this->assertRequiredApiInput(['username','email','user_group_id']);
/** @var UserRepository $userRepository */
$userRepository = \XF::repository(UserRepository::class);
$user = $userRepository->setupBaseUser(null);
$userPlugin = $this->plugin(UserPlugin::class);
$userPlugin->userSaveProcessAdmin($user)->run();
/** @var UserAuth $authUser */
$authUser = $user->getExistingRelation('Auth');
if( $input['source_real_password'] === 'wp' ){
$authUser->scheme_class = 'RomhackPlaza\Master:WordPress';
$authUser->data = ['hash' => $input['wp_password']];
$authUser->save();
} elseif( $input['source_real_password'] === 'xf' ){
$authUser->scheme_class = $input['xf_scheme_class'];
$authUser->data = @unserialize($input['xf_password_data']);
$authUser->save();
}
if( $input['avatar_path'] !== "" ){
Migration::setAvatarFromPath($input['avatar_path'], $user);
}
if( $input['banner_path'] !== "" ){
Migration::setBannerFromPath($input['banner_path'], $user);
}
if( $input['register_date'] != 0 ){
$user->register_date = $input['register_date'];
$user->save();
}
return $this->apiSuccess(['success' => true, 'user_id' => $user->user_id, 'password_set' => true ]);
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace RomhackPlaza\Master\Authentication;
use XF\Authentication\AbstractAuth;
class WordPress extends AbstractAuth
{
private const ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
public function generate($password)
{
return false;
}
public function authenticate($userId, $password)
{
if (!is_string($password) || $password === '' || empty($this->data) ) {
return false;
}
return $this->verifyPassword($password, $this->data['hash']);
}
private function verifyPassword($password, $storedHash): bool
{
if( str_starts_with($storedHash, '$wp') ){
$passwordToVerify = base64_encode( hash_hmac( 'sha384', $password, 'wp-sha384', true ) );
return password_verify( $passwordToVerify, substr($storedHash, 3) );
}
if (str_starts_with($storedHash, '$2y$') || str_starts_with($storedHash, '$2a$') || str_starts_with($storedHash, '$2b$')) {
return password_verify($password, $storedHash);
}
if (strlen($storedHash) === 34 && (str_starts_with($storedHash, '$P$') || str_starts_with($storedHash, '$H$'))) {
return $this->verifyPhpass($password, $storedHash);
}
if (strlen($storedHash) <= 32) {
return hash_equals($storedHash, md5($password));
}
return false;
}
private function verifyPhpass(string $password, string $storedHash): bool
{
$itoa64 = self::ITOA64;
$countLog2 = strpos($itoa64, $storedHash[3]);
if ($countLog2 < 7 || $countLog2 > 30) return false;
$count = 1 << $countLog2;
$salt = substr($storedHash, 4, 8);
if (strlen($salt) !== 8) return false;
$hash = md5($salt . $password, true);
do {
$hash = md5($hash . $password, true);
} while (--$count);
$output = substr($storedHash, 0, 12) . self::encode64($hash, $itoa64);
return hash_equals($storedHash, $output);
}
private function encode64(string $input, string $itoa64): string
{
$output = '';
$i = 0;
$count = strlen($input);
do {
$value = ord($input[$i++]);
$output .= $itoa64[$value & 0x3f];
if ($i < $count) $value |= ord($input[$i]) << 8;
$output .= $itoa64[($value >> 6) & 0x3f];
if ($i++ >= $count) break;
if ($i < $count) $value |= ord($input[$i]) << 16;
$output .= $itoa64[($value >> 12) & 0x3f];
if ($i++ >= $count) break;
$output .= $itoa64[($value >> 18) & 0x3f];
} while ($i < $count);
return $output;
}
public function getAuthenticationName()
{
return 'RomhackPlaza\Master:WordPress';
}
}

86
Helper/Migration.php Normal file
View File

@@ -0,0 +1,86 @@
<?php
namespace RomhackPlaza\Master\Helper;
use XF\Container;
use XF\Entity\User;
use XF\Entity\UserAuth;
use XF\Service\User\AvatarService;
use XF\Service\User\ProfileBannerService;
class Migration
{
public static function oldXfDb(): ?\XF\Db\Mysqli\Adapter {
$container = \XF::app()->container();
if( !isset( $container['oldXfDb'] ) ){
$container['oldXfDb'] = function(Container $c){
$config = \XF::config('oldxfdb');
if( !$config )
return null;
return new \XF\Db\Mysqli\Adapter($config, true);
};
}
return $container['oldXfDb'];
}
public static function uniqueUsername(string $username): string
{
$u = $username;
$i = 1;
while(\XF::em()->findOne('XF:User', ['username' => $u])){
$u = $username . '_' . $i++;
}
return $u;
}
public static function copyUserPassword(int $xf_user_id, User $user): bool
{
$oldDb = self::oldXfDb();
if( !$oldDb ){
return false;
}
$row = $oldDb->fetchRow(
'SELECT scheme_class, data FROM xf_user_authenticate WHERE user_id = ?',
[$xf_user_id]
);
if( !$row ){
return false;
}
$user->Auth->scheme_class = $row['scheme_class'];
$user->Auth->data = $row['data'];
return $user->Auth->save();
}
public static function setAvatarFromPath(string $avatar_path, User $user): bool
{
if(!is_readable($avatar_path))
return false;
$avatarService = \XF::service(AvatarService::class, $user);
if (!$avatarService->setImage($avatar_path))
return false;
return $avatarService->updateAvatar();
}
public static function setBannerFromPath(mixed $banner_path, User $user)
{
if( !is_readable($banner_path) )
return false;
$bannerService = \XF::service(ProfileBannerService::class, $user);
if( !$bannerService->setImage($banner_path) )
return false;
return $bannerService->updateBanner();
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace RomhackPlaza\Master\Import\Importer;
use RomhackPlaza\Master\Helper\Laravel;
use XF\Import\StepState;
use XFI\Import\Importer\XenForo23;
class RHPZForums extends XenForo23
{
public static function getListInfo(): array
{
return [
'target' => 'XenForo',
'source' => 'RHPZ Forums (XF)'
];
}
public function stepUserGroups(StepState $state)
{
$map = $this->getMigrationSetting('old_xf_group_to_xf_group');
foreach( $map as $oldId => $newId ){
$this->log('user_group', $oldId, $newId);
$state->imported++;
}
return $state->complete();
}
public function stepUserFields(StepState $state)
{
return $state->complete();
}
public function stepUsers(StepState $state, array $stepConfig, $maxTime)
{
$rows = $this->sourceDb->fetchAll("SELECT user_id FROM xf_user ORDER BY user_id" );
foreach ( $rows as $row ) {
$oldId = $row['user_id'];
$newId = $this->getNewUserId($oldId);
if( $newId ){
$this->log('user', $oldId, $newId);
$state->imported++;
}
}
return $state->complete();
}
public function stepAvatars(StepState $state, array $stepConfig, $maxTime)
{
return $state->complete();
}
public function stepNodes(StepState $state)
{
$map = $this->getMigrationSetting('old_xf_node_to_new_xf_node');
foreach( $map as $oldId => $newId ){
$this->log('node', $oldId, $newId);
$state->imported++;
}
return $state->complete();
}
public function stepNodePermissions(StepState $state)
{
return $state->complete();
}
public function stepModerators(StepState $state)
{
return $state->complete();
}
private function getMigrationSetting(string $key): mixed
{
$db = Laravel::db();
if( !$db )
return null;
$row = $db->fetchOne("SELECT value FROM migration_settings WHERE `key` = ? ", [$key]);
return $row ? json_decode( $row, true ) : [];
}
private function getNewUserId(int $oldId): ?int
{
$db = Laravel::db();
if( !$db )
return null;
$row = $db->fetchOne("SELECT user_id FROM migration_user_plan WHERE xf_user_id = ?", [$oldId]);
return $row ? (int) $row : null;
}
}

View File

@@ -2,8 +2,24 @@
namespace RomhackPlaza\Master;
use XF\Container;
use XF\Import\Manager;
use XF\Mvc\Renderer\AbstractRenderer;
use XF\Mvc\Reply\AbstractReply;
use XF\SubContainer\Import;
class Listener
{
public static function importImporterClasses(Import $container, Container $parentContainer, array &$importers)
{
if( \XF::isAddOnActive('XFI') )
$importers = array_merge(
$importers,
Manager::getImporterShortNamesForType('RomhackPlaza/Master')
);
}
public static function criteriaUser(string $rule, array $data, \XF\Entity\User $user, bool &$returnValue ){
switch( $rule ){
case 'rhpz_entry_count':
@@ -20,4 +36,38 @@ class Listener
break;
}
}
/**
* Credits to Kirby for the guest part.
* @link https://xenforo.com/community/resources/style-variation-default.9504/
*
* @param \XF\App $app
* @param array $params
* @param AbstractReply $reply
* @param AbstractRenderer $renderer
*
* @return void
* @throws \XF\PrintableException
*/
public static function checkStyleVariation( \XF\App $app, array &$params, AbstractReply $reply, AbstractRenderer $renderer )
{
$user = \XF::visitor();
if( $user->user_id !== 0 ){ // Logged in.
if( $user->style_variation !== 'default' && $user->style_variation !== 'alternate' ){
$user->style_variation = 'default';
$user->save();
}
} else {
$cookie = $app->request()->getCookie('style_variation');
if( $cookie !== 'default' && $cookie !== 'alternate' ){
$app->response()->setCookie('style_variation', 'default', 86400 * 365);
}
if( $user->style_variation === '' ){
$style = $app->templater()->getStyle();
if( $style->isVariationsEnabled() ){
$user->setAsSaved('style_variation', 'default');
}
}
}
}
}

View File

@@ -1,5 +1,11 @@
{
"app_pub_render_page_c6149e27cfdc21a20c8047e0e5cd5503.json": {
"hash": "95a8be583623f99ff47eef23a658e459"
},
"criteria_user_0cfa5d0143e6477a6bc20b11e255e850.json": {
"hash": "f29b40ef7a5b420b212848744fd9b7db"
},
"import_importer_classes_64d8f27f58ba02af52958c1d4f94f812.json": {
"hash": "2ff08f9036890eb77d7bc1c7fc02fc4c"
}
}

View File

@@ -0,0 +1,9 @@
{
"event_id": "app_pub_render_page",
"execute_order": 10,
"callback_class": "RomhackPlaza\\Master\\Listener",
"callback_method": "checkStyleVariation",
"active": true,
"hint": "",
"description": ""
}

View File

@@ -0,0 +1,9 @@
{
"event_id": "import_importer_classes",
"execute_order": 15,
"callback_class": "RomhackPlaza\\Master\\Listener",
"callback_method": "importImporterClasses",
"active": true,
"hint": "",
"description": ""
}

View File

@@ -41,11 +41,8 @@
"pages.json": {
"hash": "8d75bf99b2fb6528f84433f4519d4da3"
},
"rom_checker.json": {
"hash": "c9349db6120520e82d02b793a30e67ce"
},
"rom_hasher.json": {
"hash": "e14613a1bd0f1e1fadc084a009801570"
"hash": "f322c39dd3ea03343ab626a1da796c55"
},
"rom_patcher.json": {
"hash": "dff75392eea1fdffdaec193913d5d8a0"

View File

@@ -1,13 +0,0 @@
{
"parent_navigation_id": "tools",
"display_order": 300,
"navigation_type_id": "basic",
"type_config": {
"link": "{$xf.options.homePageUrl}/tools/rom-checker",
"display_condition": "",
"extra_attributes": {
"icon": "check"
}
},
"enabled": true
}

View File

@@ -3,7 +3,7 @@
"display_order": 200,
"navigation_type_id": "basic",
"type_config": {
"link": "{$xf.options.homePageUrl}/tools/rom-hasher",
"link": "{$xf.options.homePageUrl}/hash",
"display_condition": "",
"extra_attributes": {
"icon": "hash"

View File

@@ -13,6 +13,7 @@ namespace XF;
/**
* @property non-negative-int|null $rhpz_club_node_id Club Parent Node ID
* @property string|null $rhpz_delete_account_master_key Delete account Master key
* @property bool|null $rhpz_enable_migration Enable Migration functions
*/
class Options
{

View File

@@ -4,5 +4,8 @@
},
"rhpz_delete_account_master_key.json": {
"hash": "d649af68f404568ba3c06cc0a7121649"
},
"rhpz_enable_migration.json": {
"hash": "6a3f9a6223e06f02636e434bae26cc93"
}
}

View File

@@ -0,0 +1,13 @@
{
"edit_format": "onoff",
"edit_format_params": "",
"data_type": "boolean",
"sub_options": [],
"validation_class": "",
"validation_method": "",
"advanced": true,
"default_value": "0",
"relations": {
"romhackplaza": 500
}
}

View File

@@ -23,6 +23,12 @@
"version_string": "1.0.0",
"hash": "15d96c48f857b975c051d7c98b13a385"
},
"entries.txt": {
"global_cache": false,
"version_id": 1000000,
"version_string": "1.0.0",
"hash": "bc14b0a9c516310fc195a778937cc0a0"
},
"nav.about.txt": {
"global_cache": false,
"version_id": 1000000,
@@ -107,12 +113,6 @@
"version_string": "1.0.0",
"hash": "453aceb005ceaf54a47da15fee8b2a26"
},
"nav.rom_checker.txt": {
"global_cache": false,
"version_id": 1000000,
"version_string": "1.0.0",
"hash": "f5e8c49f0487c68231a8804df2379cc5"
},
"nav.rom_hasher.txt": {
"global_cache": false,
"version_id": 1000000,
@@ -155,6 +155,12 @@
"version_string": "1.0.0",
"hash": "8b275759f2215b3b242de29110f05a4c"
},
"option.rhpz_enable_migration.txt": {
"global_cache": false,
"version_id": 1000000,
"version_string": "1.0.0",
"hash": "5c647f124187d520df70e6c1610fb041"
},
"option_explain.rhpz_club_node_id.txt": {
"global_cache": false,
"version_id": 1000000,
@@ -167,6 +173,12 @@
"version_string": "1.0.0",
"hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"option_explain.rhpz_enable_migration.txt": {
"global_cache": false,
"version_id": 1000000,
"version_string": "1.0.0",
"hash": "d98b161589c31532e68fe26daa70dc32"
},
"option_group.romhackplaza.txt": {
"global_cache": false,
"version_id": 1000000,

View File

@@ -0,0 +1 @@
Entries

View File

@@ -1 +0,0 @@
ROM Checker

View File

@@ -0,0 +1 @@
Enable Migration functions

View File

@@ -0,0 +1 @@
Enable migration endpoints and other things.

View File

@@ -1,4 +1,7 @@
{
"api_migrate_.json": {
"hash": "df6fcab95396a546b48a38eb9561b68f"
},
"api_romhackplaza_entry_.json": {
"hash": "5f1609f559980b44af09fd85c3b34a30"
},

View File

@@ -0,0 +1,11 @@
{
"route_type": "api",
"route_prefix": "migrate",
"sub_name": "",
"format": "",
"build_class": "",
"build_method": "",
"controller": "RomhackPlaza\\Master:Migrate",
"context": "",
"action_prefix": ""
}

View File

@@ -22,5 +22,14 @@
},
"public/rhpz_forum_overview_wrapper_search_button.json": {
"hash": "af109f16aca8a12c4f8496fc4431f4b8"
},
"public/rhpz_member_macros_member_stat_pairs_entry_count.json": {
"hash": "3d744e57f81abeb7f2d8291404f1e5b3"
},
"public/rhpz_member_view_add_entry_tab.json": {
"hash": "35151043dca0be7987a72ee8b1cffc6f"
},
"public/rhpz_member_view_add_entry_tabpanel.json": {
"hash": "5fcc29178d06fc924bdb604734cc9c59"
}
}

View File

@@ -0,0 +1,9 @@
{
"template": "member_macros",
"description": "Add RHPZ Entry count on member profile page",
"execution_order": 10,
"enabled": true,
"action": "str_replace",
"find": "<!--[XF:stat_pairs:above_reactions]-->",
"replace": "<dl class=\"pairs pairs--rows pairs--rows--centered\">\n\t<dt>{{ phrase('entries') }}</dt>\n\t<dd>\n\t\t{$user.rhpz_entry_count|number}\n\t</dd>\n</dl>"
}

View File

@@ -0,0 +1,9 @@
{
"template": "member_view",
"description": "Add entry tab on members page",
"execution_order": 10,
"enabled": true,
"action": "str_replace",
"find": "<!--[XF:tabs:after_recent_content]-->",
"replace": "<a href=\"{{ $xf.options.homePageUrl }}/database?userId={$user.user_id}\"\n class=\"tabs-tab\"\n id=\"entries\">{{ phrase('entries') }}</a>\n$0"
}

View File

@@ -0,0 +1,9 @@
{
"template": "member_view",
"description": "Add Entry tab panel",
"execution_order": 10,
"enabled": true,
"action": "str_replace",
"find": "<!--[XF:tab_panes:after_recent_content]-->",
"replace": "<li role=\"tabpanel\" aria-labelledby=\"entries\">\n\t<a href=\"{{$xf.options.homePageUrl}}/database?userId={$user.user_id}\" class=\"button\">Go to this page</a>\n</li>\n$0"
}