Init project
commit
129ee5ac3d
@ -0,0 +1,2 @@
|
||||
<?php
|
||||
$specialPageAliases = [];
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use Isekai\UserPoints\Service\IsekaiUserPointsFactory;
|
||||
use Isekai\UserPoints\Service\IsekaiUserDailySignFactory;
|
||||
|
||||
return [
|
||||
'IsekaiUserPoints' => static function ( MediaWikiServices $services ) {
|
||||
return new IsekaiUserPointsFactory( $services );
|
||||
},
|
||||
'IsekaiUserDailySign' => static function ( MediaWikiServices $services ) {
|
||||
return new IsekaiUserDailySignFactory( $services );
|
||||
},
|
||||
];
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "hyperzlib/isekai-user-points",
|
||||
"type": "library",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Lex Lim",
|
||||
"email": "hyperzlib@outlook.com"
|
||||
}
|
||||
],
|
||||
"require": {}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
{
|
||||
"name": "Isekai User Points",
|
||||
"namemsg": "isekai-userpoints",
|
||||
"author": "Hyperzlib",
|
||||
"version": "1.0.0",
|
||||
"url": "https://git.isekai.cn/Isekai-Project/mediawiki-extension-IsekaiUserPoints",
|
||||
"descriptionmsg": "isekai-userpoints-desc",
|
||||
"license-name": "GPL-2.0-or-later",
|
||||
"type": "api",
|
||||
"requires": {
|
||||
"MediaWiki": ">= 1.35.0"
|
||||
},
|
||||
"MessagesDirs": {
|
||||
"IsekaiUserPoints": [
|
||||
"i18n"
|
||||
]
|
||||
},
|
||||
"ExtensionMessagesFiles": {
|
||||
"IsekaiUserPointsAlias": "IsekaiUserPoints.alias.php"
|
||||
},
|
||||
"AutoloadNamespaces": {
|
||||
"Isekai\\UserPoints\\": "includes"
|
||||
},
|
||||
"Hooks": {
|
||||
"LoadExtensionSchemaUpdates": "Isekai\\UserPoints\\Hooks::onLoadExtensionSchemaUpdates",
|
||||
"BeforePageDisplay": "Isekai\\UserPoints\\Hooks::onBeforePageDisplay",
|
||||
"GetPreferences": "Isekai\\UserPoints\\Hooks::onGetPreferences"
|
||||
},
|
||||
"APIModules": {
|
||||
"userdailysign": "Isekai\\UserPoints\\Api\\ApiUserDailySign"
|
||||
},
|
||||
"APIPropModules": {
|
||||
"userpoints": "Isekai\\UserPoints\\Api\\ApiQueryUserPoints",
|
||||
"pointinfo": "Isekai\\UserPoints\\Api\\ApiQueryPointInfo"
|
||||
},
|
||||
"APIListModules": {
|
||||
"userspoints": "Isekai\\UserPoints\\Api\\ApiQueryUsersPoints"
|
||||
},
|
||||
"ServiceWiringFiles": [
|
||||
"IsekaiUserPoints.services.php"
|
||||
],
|
||||
"GroupPermissions": {
|
||||
"bureaucrat": {
|
||||
"queryuserpoints": true,
|
||||
"edituserpoints": true
|
||||
},
|
||||
"suppress": {
|
||||
"queryuserpoints": true,
|
||||
"edituserpoints": true
|
||||
},
|
||||
"sysop": {
|
||||
"queryuserpoints": true,
|
||||
"edituserpoints": true
|
||||
}
|
||||
},
|
||||
"GrantPermissions": {
|
||||
"userpointsmanager": {
|
||||
"queryuserpoints": true,
|
||||
"edituserpoints": true
|
||||
}
|
||||
},
|
||||
"ResourceModules": {
|
||||
"ext.isekai.userpoints.base": {
|
||||
"styles": ["ext.isekai.userpoints.base.less"]
|
||||
},
|
||||
"ext.isekai.userpoints.dailysign": {
|
||||
"scripts": ["ext.isekai.userpoints.dailysign.js"],
|
||||
"messages": [
|
||||
"comma-separator",
|
||||
"isekai-userpoints-point-name-num",
|
||||
"isekai-userpoints-dailysign-notify-title",
|
||||
"isekai-userpoints-dailysign-notify-success"
|
||||
]
|
||||
}
|
||||
},
|
||||
"ResourceFileModulePaths": {
|
||||
"localBasePath": "modules",
|
||||
"remoteExtPath": "IsekaiUserPoints/modules"
|
||||
},
|
||||
"config": {
|
||||
"IsekaiUserPointConfig": {
|
||||
"value": {
|
||||
"exp": {
|
||||
"name": "Exp.",
|
||||
"namemsg": "isekai-userpoints-point-name-exp",
|
||||
"icon": {
|
||||
"normal": {
|
||||
"image": "https://static.isekai.dev/isekaiwiki/isekaiwiki-exp.png"
|
||||
},
|
||||
"invert": {
|
||||
"image": "https://static.isekai.dev/isekaiwiki/isekaiwiki-exp-invert.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"IsekaiUserPointShowOnUserPerferences": {
|
||||
"value": true
|
||||
},
|
||||
"IsekaiUserDailySignConfig": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"manifest_version": 2
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
{
|
||||
"isekai-userpoints": "异世界百科 用户积分",
|
||||
"isekai-userpoints-desc": "存储用户积分,为其他应用提供积分功能",
|
||||
|
||||
"isekai-userpoints-point-name-num": "$2 $3 $1",
|
||||
|
||||
"isekai-userpoints-service-system": "积分系统",
|
||||
"isekai-userpoints-action-system-point-expired": "积分过期",
|
||||
|
||||
"apihelp-query+userpoints-summary": "获取当前登录用户的积分",
|
||||
|
||||
"apihelp-query+userspoints-summary": "获取用户积分",
|
||||
"apihelp-query+userspoints-param-users": "要获取信息的用户列表。",
|
||||
"apihelp-query+userspoints-param-userids": "要获得信息的用户ID列表。",
|
||||
|
||||
"apierror-isekaiuserpoints-notfound": "未找到积分信息",
|
||||
|
||||
"isekai-userpoints-point-name-exp": "经验值",
|
||||
"isekai-userpoints-point-name-kamakano": "星砂",
|
||||
|
||||
"isekai-userpoints-source-system": "积分系统",
|
||||
|
||||
"apihelp-userdailysign-summary": "用户签到",
|
||||
"apiwarn-isekai-userpoints-already-signed-today": "今天已经签到过了",
|
||||
|
||||
"isekai-userpoints-dailysign-notify-title": "每日签到",
|
||||
"isekai-userpoints-dailysign-notify-success": "每日登录网站,获得了$1"
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
namespace Isekai\UserPoints\Api;
|
||||
|
||||
use ApiQueryBase;
|
||||
use ApiQuery;
|
||||
use Config;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use WANObjectCache;
|
||||
|
||||
use Isekai\UserPoints\Utils;
|
||||
|
||||
class ApiQueryPointInfo extends ApiQueryBase {
|
||||
private const CACHE_VERSION = 2;
|
||||
|
||||
private const PREFIX = 'pi';
|
||||
|
||||
private $params;
|
||||
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var WANObjectCache
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @param ApiQuery $query API query module object
|
||||
* @param string $moduleName Name of this query module
|
||||
*/
|
||||
public function __construct($query, $moduleName) {
|
||||
parent::__construct($query, $moduleName, self::PREFIX);
|
||||
$this->config = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'textextracts' );
|
||||
$this->cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ApiUsageException
|
||||
*/
|
||||
public function execute() {
|
||||
$pointConfig = $this->config->get('IsekaiUserPointConfig');
|
||||
$pointTypes = array_keys($pointConfig);
|
||||
|
||||
$pointInfos = [];
|
||||
foreach ($pointTypes as $pointType) {
|
||||
$pointInfos[$pointType] = [
|
||||
'name' => Utils::getPointName($pointType),
|
||||
'icon' => Utils::getPointIcon($pointType),
|
||||
];
|
||||
}
|
||||
|
||||
$result = $this->getResult();
|
||||
$result->addValue(
|
||||
[ 'query', $this->getModuleName() ],
|
||||
'pointinfo',
|
||||
$pointInfos
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Ignored parameters
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheMode($params) {
|
||||
return 'public';
|
||||
}
|
||||
|
||||
public function getAllowedParams() {
|
||||
return [
|
||||
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
namespace Isekai\UserPoints\Api;
|
||||
|
||||
use ApiQueryBase;
|
||||
use ApiQuery;
|
||||
use Config;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use WANObjectCache;
|
||||
use Isekai\UserPoints\Service\IsekaiUserPointsFactory;
|
||||
use Isekai\UserPoints\Utils;
|
||||
use stdClass;
|
||||
|
||||
class ApiQueryUserPoints extends ApiQueryBase {
|
||||
private const CACHE_VERSION = 2;
|
||||
|
||||
private const PREFIX = 'up';
|
||||
|
||||
private $params;
|
||||
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var WANObjectCache
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @param ApiQuery $query API query module object
|
||||
* @param string $moduleName Name of this query module
|
||||
*/
|
||||
public function __construct($query, $moduleName) {
|
||||
parent::__construct($query, $moduleName, self::PREFIX);
|
||||
$this->config = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'textextracts' );
|
||||
$this->cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ApiUsageException
|
||||
*/
|
||||
public function execute() {
|
||||
$user = $this->getUser();
|
||||
if (!$user->isRegistered()) {
|
||||
$this->dieWithError('apierror-mustbeloggedin-generic', 'login-required');
|
||||
return false;
|
||||
}
|
||||
|
||||
$pointConfig = $this->config->get('IsekaiUserPointConfig');
|
||||
$pointTypes = array_keys($pointConfig);
|
||||
|
||||
/** @var IsekaiUserPointsFactory */
|
||||
$userPointsFactory = MediaWikiServices::getInstance()->getService('IsekaiUserPoints');
|
||||
|
||||
/** @var array<IsekaiUserPoints> */
|
||||
$userPoints = [];
|
||||
foreach ($pointTypes as $pointType) {
|
||||
$userPoint = $userPointsFactory->newFromUser($user, $pointType);
|
||||
if ($userPoint) {
|
||||
$userPoints[$pointType] = $userPointsFactory->newFromUser($user, $pointType);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($userPoints) === 0) {
|
||||
$this->dieWithError('apierror-isekaiuserpoints-notfound', 'not-found');
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = $this->getResult();
|
||||
foreach ($userPoints as $pointType => $userPoint) {
|
||||
$pointName = Utils::getPointName($pointType);
|
||||
$pointIcon = Utils::getPointIcon($pointType);
|
||||
$result->addValue(['query', $this->getModuleName()], $pointType, [
|
||||
'points' => $userPoint->points,
|
||||
'timed_points_data' => $userPoint->timedPointsData,
|
||||
'locked_points' => $userPoint->lockedPoints,
|
||||
'locked_points_data' => $userPoint->lockedPointsData,
|
||||
'name' => $pointName,
|
||||
'icon' => $pointIcon,
|
||||
]);
|
||||
}
|
||||
if (empty($userPoints)) {
|
||||
$result->addValue(['query'], $this->getModuleName(), new stdClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Ignored parameters
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheMode($params) {
|
||||
return 'private';
|
||||
}
|
||||
|
||||
public function getAllowedParams() {
|
||||
return [];
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
namespace Isekai\UserPoints\Api;
|
||||
|
||||
use ApiQueryBase;
|
||||
use ApiQuery;
|
||||
use Config;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use WANObjectCache;
|
||||
use Wikimedia\ParamValidator\ParamValidator;
|
||||
use User;
|
||||
|
||||
use Isekai\UserPoints\Service\IsekaiUserPointsFactory;
|
||||
use Isekai\UserPoints\Utils;
|
||||
|
||||
class ApiQueryUsersPoints extends ApiQueryBase {
|
||||
private const CACHE_VERSION = 2;
|
||||
|
||||
private const PREFIX = 'up';
|
||||
|
||||
private $params;
|
||||
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var WANObjectCache
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @param ApiQuery $query API query module object
|
||||
* @param string $moduleName Name of this query module
|
||||
*/
|
||||
public function __construct($query, $moduleName) {
|
||||
parent::__construct($query, $moduleName, self::PREFIX);
|
||||
$this->config = MediaWikiServices::getInstance()->getMainConfig();
|
||||
$this->cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ApiUsageException
|
||||
*/
|
||||
public function execute() {
|
||||
$this->checkUserRightsAny('queryuserpoints');
|
||||
|
||||
$params = $this->extractRequestParams();
|
||||
$this->requireMaxOneParameter( $params, 'userids', 'users' );
|
||||
|
||||
$users = (array)$params['users'];
|
||||
$userids = (array)$params['userids'];
|
||||
|
||||
$services = MediaWikiServices::getInstance();
|
||||
|
||||
$pointConfig = $this->config->get('IsekaiUserPointConfig');
|
||||
$pointTypes = array_keys($pointConfig);
|
||||
|
||||
$pointInfos = [];
|
||||
foreach ($pointTypes as $pointType) {
|
||||
$pointInfos[$pointType] = [
|
||||
'name' => Utils::getPointName($pointType),
|
||||
'icon' => Utils::getPointIcon($pointType),
|
||||
];
|
||||
}
|
||||
|
||||
/** @var IsekaiUserPointsFactory */
|
||||
$userPointsFactory = $services->getService('IsekaiUserPoints');
|
||||
|
||||
$userFactory = $services->getUserFactory();
|
||||
$userInstances = [];
|
||||
foreach ( $users as $u ) {
|
||||
$user = $userFactory->newFromName( $u );
|
||||
$userInstances[] = $user;
|
||||
}
|
||||
foreach ( $userids as $u ) {
|
||||
$user = $userFactory->newFromId( $u );
|
||||
$userInstances[] = $user;
|
||||
}
|
||||
|
||||
$userPointsInstances = $userPointsFactory->newFromUsers($userInstances, $pointTypes);
|
||||
|
||||
$result = $this->getResult();
|
||||
foreach ($userPointsInstances as $userPointsTuple) {
|
||||
list($user, $type, $userPoint) = $userPointsTuple;
|
||||
$result->addValue(
|
||||
[ 'query', $this->getModuleName(), "pointdata", $user->getId() ],
|
||||
$type,
|
||||
[
|
||||
'point_type' => $type,
|
||||
'points' => $userPoint->points,
|
||||
'timed_points_data' => $userPoint->timedPointsData,
|
||||
'locked_points' => $userPoint->lockedPoints,
|
||||
'locked_points_data' => $userPoint->lockedPointsData,
|
||||
]
|
||||
);
|
||||
}
|
||||
$result->addValue(
|
||||
[ 'query', $this->getModuleName() ],
|
||||
'pointinfo',
|
||||
$pointInfos
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Ignored parameters
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheMode($params) {
|
||||
return 'public';
|
||||
}
|
||||
|
||||
public function getAllowedParams() {
|
||||
return [
|
||||
'users' => [
|
||||
ParamValidator::PARAM_ISMULTI => true
|
||||
],
|
||||
'userids' => [
|
||||
ParamValidator::PARAM_ISMULTI => true,
|
||||
ParamValidator::PARAM_TYPE => 'integer'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
namespace Isekai\UserPoints\Api;
|
||||
|
||||
use ApiBase;
|
||||
use Isekai\UserPoints\Service\IsekaiUserPointsFactory;
|
||||
use Isekai\UserPoints\Service\IsekaiUserDailySignFactory;
|
||||
use Isekai\UserPoints\Utils;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
|
||||
class ApiUserDailySign extends ApiBase {
|
||||
public function __construct( $main, $method ) {
|
||||
parent::__construct( $main->getMain(), $method );
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
$services = MediaWikiServices::getInstance();
|
||||
|
||||
$user = $this->getUser();
|
||||
|
||||
if (!$user->isRegistered()) {
|
||||
$this->dieWithError('apierror-mustbeloggedin-generic', 'login-required');
|
||||
}
|
||||
|
||||
/** @var IsekaiUserDailySignFactory */
|
||||
$dailySignFactory = $services->getService('IsekaiUserDailySign');
|
||||
$dailySignService = $dailySignFactory->newFromUser($user);
|
||||
|
||||
$result = $this->getResult();
|
||||
if (date('Y-m-d') === date('Y-m-d', strtotime($dailySignService->lastSignDate))) {
|
||||
$result->addValue(['userdailysign'], 'success', 0);
|
||||
$result->addValue(['userdailysign'], 'point_delta', []);
|
||||
$this->addWarning('isekai-userpoints-already-signed-today', 'already-signed-today');
|
||||
return;
|
||||
}
|
||||
|
||||
$pointDelta = $dailySignService->doSign();
|
||||
|
||||
if (!empty($pointDelta)) {
|
||||
$result->addValue(['userdailysign'], 'success', 1);
|
||||
} else {
|
||||
$result->addValue(['userdailysign'], 'success', 0);
|
||||
}
|
||||
|
||||
$resultPointDelta = [];
|
||||
foreach ($pointDelta as $pointType => $delta) {
|
||||
$pointName = Utils::getPointName($pointType);
|
||||
$pointIcon = Utils::getPointIcon($pointType);
|
||||
|
||||
$resultPointDelta[] = [
|
||||
'point_type' => $pointType,
|
||||
'name' => $pointName,
|
||||
'icon' => $pointIcon,
|
||||
'points' => $delta,
|
||||
];
|
||||
}
|
||||
|
||||
$result->addValue(['userdailysign'], 'point_delta', $resultPointDelta);
|
||||
}
|
||||
|
||||
public function needsToken() {
|
||||
return 'csrf';
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Isekai\UserPoints;
|
||||
|
||||
use DatabaseUpdater;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use OutputPage;
|
||||
use Skin;
|
||||
use User;
|
||||
|
||||
class Hooks {
|
||||
/**
|
||||
* Implements LoadExtensionSchemaUpdates hook.
|
||||
*
|
||||
* @param \DatabaseUpdater $updater
|
||||
*/
|
||||
public static function onLoadExtensionSchemaUpdates($updater) {
|
||||
$dir = dirname(__DIR__) . '/sql/';
|
||||
$type = $updater->getDB()->getType();
|
||||
$updater->addExtensionTable('isekai_user_daily_sign', $dir . $type . '/isekai_user_daily_sign.sql');
|
||||
$updater->addExtensionTable('isekai_user_daily_sign_log', $dir . $type . '/isekai_user_daily_sign_log.sql');
|
||||
$updater->addExtensionTable('isekai_user_points', $dir . $type . '/isekai_user_points.sql');
|
||||
$updater->addExtensionTable('isekai_user_points_log', $dir . $type . '/isekai_user_points_log.sql');
|
||||
}
|
||||
|
||||
public static function onBeforePageDisplay(OutputPage $out, Skin $skin) {
|
||||
$config = MediaWikiServices::getInstance()->getMainConfig();
|
||||
|
||||
$out->addModuleStyles(['ext.isekai.userpoints.base']);
|
||||
|
||||
$dailySignConfig = $config->get('IsekaiUserDailySignConfig');
|
||||
if ($dailySignConfig) {
|
||||
$out->addModules(['ext.isekai.userpoints.dailysign']);
|
||||
}
|
||||
}
|
||||
|
||||
public static function onGetPreferences(User $user, &$preferences) {
|
||||
$config = MediaWikiServices::getInstance()->getMainConfig();
|
||||
if ($config->get('IsekaiUserPointShowOnUserPerferences')) {
|
||||
$userPointConfig = $config->get('IsekaiUserPointConfig');
|
||||
|
||||
/** @var \Isekai\UserPoints\Service\IsekaiUserPointsFactory */
|
||||
$userPointsFactory = MediaWikiServices::getInstance()->getService('IsekaiUserPoints');
|
||||
|
||||
foreach ($userPointConfig as $pointType => $pointConfig) {
|
||||
$userPoints = $userPointsFactory->newFromUser($user, $pointType);
|
||||
|
||||
$icon = Utils::getPointIcon($pointType);
|
||||
$name = Utils::getPointName($pointType);
|
||||
|
||||
$language = MediaWikiServices::getInstance()->getContentLanguage();
|
||||
|
||||
$preferences['isekai-userpoints-' . $pointType] = [
|
||||
'type' => 'text',
|
||||
'label-raw' => $icon . ' ' . $name,
|
||||
'default' => $language->formatNum($userPoints->points),
|
||||
'section' => 'personal/info',
|
||||
'raw' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
<?php
|
||||
namespace Isekai\UserPoints\Service;
|
||||
|
||||
use stdClass;
|
||||
use User;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
|
||||
/**
|
||||
* @property int $user_id
|
||||
* @property string $last_sign_date
|
||||
* @property string|array $sign_days_data
|
||||
* @property int $total_sign_days
|
||||
*/
|
||||
class IsekaiUserDailySignData extends stdClass {}
|
||||
|
||||
/**
|
||||
* @property int $userId
|
||||
* @property string $lastSignDate
|
||||
* @property array $signDaysData
|
||||
* @property int $totalSignDays
|
||||
*/
|
||||
class IsekaiUserDailySign {
|
||||
/** @var MediaWikiServices */
|
||||
private $services;
|
||||
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/** @var array */
|
||||
private $pointConfig;
|
||||
|
||||
/** @var array */
|
||||
private $dailySignConfig;
|
||||
|
||||
private $dataKeysMap = [
|
||||
'userId' => 'user_id',
|
||||
'lastSignDate' => 'last_sign_date',
|
||||
'signDaysData' => 'sign_days_data',
|
||||
'totalSignDays' => 'total_sign_days'
|
||||
];
|
||||
|
||||
/** @var IsekaiUserDailySignData|null */
|
||||
private $mDailySignData = null;
|
||||
|
||||
private $recordExists = false;
|
||||
|
||||
public function __construct(User $user, array $pointConfig, array $dailySignConfig, MediaWikiServices $services) {
|
||||
$this->user = $user;
|
||||
$this->pointConfig = $pointConfig;
|
||||
$this->dailySignConfig = $dailySignConfig;
|
||||
$this->services = $services;
|
||||
}
|
||||
|
||||
private function loadDailySignData() {
|
||||
if (!$this->mDailySignData) {
|
||||
$dbr = $this->services->getDBLoadBalancer()->getConnection(DB_REPLICA);
|
||||
$this->mDailySignData = $dbr->selectRow(
|
||||
'isekai_user_daily_sign',
|
||||
'*',
|
||||
[
|
||||
'user_id' => $this->user->getId()
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
if (!$this->mDailySignData) {
|
||||
$this->initDefaultData();
|
||||
} else {
|
||||
$this->mDailySignData->sign_days_data = json_decode($this->mDailySignData->sign_days_data, true) ?? [];
|
||||
|
||||
$this->recordExists = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function initDefaultData() {
|
||||
$this->mDailySignData = new IsekaiUserDailySignData();
|
||||
$this->mDailySignData->user_id = $this->user->getId();
|
||||
$this->mDailySignData->last_sign_date = '1970-01-01';
|
||||
$this->mDailySignData->sign_days_data = [];
|
||||
$this->mDailySignData->total_sign_days = 0;
|
||||
}
|
||||
|
||||
public function __get($name) {
|
||||
$this->loadDailySignData();
|
||||
if (isset($this->dataKeysMap[$name])) {
|
||||
$key = $this->dataKeysMap[$name];
|
||||
return $this->mDailySignData->$key;
|
||||
} else {
|
||||
throw new \Exception("Invalid property $name");
|
||||
}
|
||||
}
|
||||
|
||||
public function doSign() {
|
||||
$this->loadDailySignData();
|
||||
|
||||
$pointDelta = [];
|
||||
|
||||
$lastSignTime = strtotime($this->mDailySignData->last_sign_date);
|
||||
|
||||
if (date('Y-m-d', time()) == date('Y-m-d', $lastSignTime)) { // already signed in today
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var IsekaiUserPointsFactory */
|
||||
$userPointsFactory = $this->services->getService('IsekaiUserPoints');
|
||||
foreach ($this->dailySignConfig as $pointType => $config) {
|
||||
if (!isset($this->pointConfig[$pointType])) { // point type not exist, ignore
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($config['points'])) { // no points config, ignore
|
||||
continue;
|
||||
}
|
||||
|
||||
$signDays = $this->mDailySignData->sign_days_data[$pointType] ?? 0;
|
||||
|
||||
$continuous_reset = $config['continuous_reset'] ?? false;
|
||||
$expireTime = $config['expire_time'] ?? -1;
|
||||
$points = $config['points'];
|
||||
|
||||
if (!is_array($points)) {
|
||||
$points = [ $points ];
|
||||
}
|
||||
|
||||
switch ($continuous_reset) {
|
||||
case 'day':
|
||||
if (date('Y-m-d', strtotime('-1 day')) != date('Y-m-d', $lastSignTime)) {
|
||||
$signDays = 0;
|
||||
}
|
||||
case 'week':
|
||||
if (date('Y-W', time()) != date('Y-W', $lastSignTime)) {
|
||||
$signDays = 0;
|
||||
}
|
||||
break;
|
||||
case 'month':
|
||||
if (date('Y-m', time()) != date('Y-m', $lastSignTime)) {
|
||||
$signDays = 0;
|
||||
}
|
||||
break;
|
||||
case 'year':
|
||||
if (date('Y', time()) != date('Y', $lastSignTime)) {
|
||||
$signDays = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$pointsIndex = min($signDays, count($points) - 1);
|
||||
$pointsAdd = $points[$pointsIndex];
|
||||
|
||||
$pointsService = $userPointsFactory->newFromUser($this->user, $pointType);
|
||||
$pointsService->addPoints($pointsAdd, $expireTime, 'daily_sign', 'sign_in');
|
||||
|
||||
$pointDelta[$pointType] = $pointsAdd;
|
||||
|
||||
$this->mDailySignData->sign_days_data[$pointType] = $signDays + 1;
|
||||
}
|
||||
|
||||
$this->mDailySignData->last_sign_date = date('Y-m-d H:i:s', time());
|
||||
$this->mDailySignData->total_sign_days ++;
|
||||
|
||||
$this->save();
|
||||
$this->saveSignLog($pointDelta);
|
||||
|
||||
return $pointDelta;
|
||||
}
|
||||
|
||||
private function save() {
|
||||
$dbw = $this->services->getDBLoadBalancer()->getConnection(DB_PRIMARY);
|
||||
if ($this->recordExists) {
|
||||
$dbw->update(
|
||||
'isekai_user_daily_sign',
|
||||
[
|
||||
'last_sign_date' => $this->mDailySignData->last_sign_date,
|
||||
'sign_days_data' => json_encode($this->mDailySignData->sign_days_data),
|
||||
'total_sign_days' => $this->mDailySignData->total_sign_days,
|
||||
],
|
||||
[
|
||||
'user_id' => $this->user->getId()
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
} else {
|
||||
$dbw->insert(
|
||||
'isekai_user_daily_sign',
|
||||
[
|
||||
'user_id' => $this->mDailySignData->user_id,
|
||||
'last_sign_date' => $this->mDailySignData->last_sign_date,
|
||||
'sign_days_data' => json_encode($this->mDailySignData->sign_days_data),
|
||||
'total_sign_days' => $this->mDailySignData->total_sign_days,
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
$this->recordExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
private function saveSignLog($pointDelta) {
|
||||
$dbr = $this->services->getDBLoadBalancer()->getConnection(DB_REPLICA);
|
||||
$dbw = $this->services->getDBLoadBalancer()->getConnection(DB_PRIMARY);
|
||||
|
||||
$currentYear = intval(date('Y', time()));
|
||||
$currentMonth = intval(date('m', time()));
|
||||
$currentDate = intval(date('d', time()));
|
||||
|
||||
$logData = $dbr->selectRow(
|
||||
'isekai_user_daily_sign_log',
|
||||
'*',
|
||||
[
|
||||
'user_id' => $this->user->getId(),
|
||||
'year' => $currentYear,
|
||||
'month' => $currentMonth
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
if ($logData) {
|
||||
$signLog = json_decode($logData->sign_log, true);
|
||||
if (!is_array($signLog)) {
|
||||
$signLog = [];
|
||||
}
|
||||
$signLog[] = [
|
||||
'date' => $currentDate,
|
||||
'delta' => $pointDelta
|
||||
];
|
||||
$logData->sign_log = json_encode($signLog);
|
||||
$dbw->update(
|
||||
'isekai_user_daily_sign_log',
|
||||
[
|
||||
'sign_log' => $logData->sign_log
|
||||
],
|
||||
[
|
||||
'user_id' => $this->user->getId(),
|
||||
'year' => $currentYear,
|
||||
'month' => $currentMonth
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
} else {
|
||||
$signLog = [[
|
||||
'date' => $currentDate,
|
||||
'delta' => $pointDelta
|
||||
]];
|
||||
$dbw->insert(
|
||||
'isekai_user_daily_sign_log',
|
||||
[
|
||||
'user_id' => $this->user->getId(),
|
||||
'year' => $currentYear,
|
||||
'month' => $currentMonth,
|
||||
'sign_log' => json_encode($signLog)
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace Isekai\UserPoints\Service;
|
||||
|
||||
use Config;
|
||||
use User;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
|
||||
class IsekaiUserDailySignFactory {
|
||||
/** @var MediaWikiServices */
|
||||
private $services;
|
||||
|
||||
/** @var \Config */
|
||||
private $config;
|
||||
|
||||
/** @var array */
|
||||
private $pointConfig;
|
||||
|
||||
/** @var array */
|
||||
private $dailySignConfig;
|
||||
|
||||
private static $instances;
|
||||
|
||||
public function __construct(MediaWikiServices $services) {
|
||||
$this->services = $services;
|
||||
$this->config = $services->getMainConfig();
|
||||
$this->pointConfig = $this->config->get('IsekaiUserPointConfig');
|
||||
$this->dailySignConfig = $this->config->get('IsekaiUserDailySignConfig');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @return IsekaiUserDailySign|null
|
||||
*/
|
||||
public function newFromUser(User $user) {
|
||||
if (!$this->dailySignConfig) {
|
||||
throw new \Exception('$wgIsekaiUserDailySignConfig is not configured');
|
||||
}
|
||||
|
||||
if (!$user->isRegistered()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$userId = $user->getId();
|
||||
$cacheKey = $userId;
|
||||
if (!isset(self::$instances[$cacheKey])) {
|
||||
self::$instances[$cacheKey] = new IsekaiUserDailySign($user, $this->pointConfig, $this->dailySignConfig, $this->services);
|
||||
}
|
||||
return self::$instances[$cacheKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
* @return IsekaiUserDailySign|null
|
||||
*/
|
||||
public function newFromUserId($userId) {
|
||||
$user = $this->services->getUserFactory()->newFromId($userId);
|
||||
if (!$user) {
|
||||
return null;
|
||||
}
|
||||
return $this->newFromUser($user);
|
||||
}
|
||||
}
|
@ -0,0 +1,441 @@
|
||||
<?php
|
||||
namespace Isekai\UserPoints\Service;
|
||||
|
||||
use User;
|
||||
use stdClass;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
|
||||
/**
|
||||
* @property int $user_id
|
||||
* @property string $type
|
||||
* @property int $points
|
||||
* @property string|array $timed_points_data
|
||||
* @property int $locked_points
|
||||
* @property string|array $locked_points_data
|
||||
* @property int $next_refresh_time
|
||||
*/
|
||||
class IsekaiUserPointsData extends stdClass {}
|
||||
|
||||
/**
|
||||
* @property int $points
|
||||
* @property array $timedPointsData
|
||||
* @property int $lockedPoints
|
||||
* @property array $lockedPointsData
|
||||
* @property int $nextRefreshTime
|
||||
*/
|
||||
class IsekaiUserPoints {
|
||||
public const DEFAULT_TRANSACTION_TIMEOUT = 60 * 10;
|
||||
public const MAX_TRANSACTION_TIMEOUT = 60 * 60;
|
||||
|
||||
/** @var MediaWikiServices */
|
||||
private $services;
|
||||
|
||||
/** @var \Config */
|
||||
private $config;
|
||||
|
||||
/** @var User */
|
||||
public $user;
|
||||
|
||||
/** @var string */
|
||||
public $type;
|
||||
|
||||
/** @var string */
|
||||
public $pointName = '';
|
||||
|
||||
/** @var string */
|
||||
private $rawName = '';
|
||||
|
||||
/** @var IsekaiUserPointsData|null */
|
||||
private $mPointData = null;
|
||||
|
||||
/** @var bool */
|
||||
private $pointRecordExists = false;
|
||||
|
||||
private $pointDataKeysMap = [
|
||||
'points' => 'points',
|
||||
'timedPointsData' => 'timed_points_data',
|
||||
'lockedPoints' => 'locked_points',
|
||||
'lockedPointsData' => 'locked_points_data',
|
||||
'nextRefreshTime' => 'next_refresh_time'
|
||||
];
|
||||
|
||||
public function __construct(User $user, string $type, array $pointConfig, MediaWikiServices $mediaWikiServices = null) {
|
||||
if (!$mediaWikiServices) {
|
||||
$this->services = MediaWikiServices::getInstance();
|
||||
} else {
|
||||
$this->services = $mediaWikiServices;
|
||||
}
|
||||
$this->config = $this->services->getMainConfig();
|
||||
|
||||
$this->user = $user;
|
||||
$this->type = $type;
|
||||
|
||||
if (isset($pointConfig['namemsg'])) {
|
||||
$this->pointName = wfMessage($pointConfig['namemsg'])->text();
|
||||
$this->rawName = $pointConfig['name'] ?? $this->pointName;
|
||||
} elseif (isset($pointConfig['name'])) {
|
||||
$this->pointName = $this->rawName = $pointConfig['name'];
|
||||
}
|
||||
}
|
||||
|
||||
public function setData($pointData) {
|
||||
$this->mPointData = $pointData;
|
||||
}
|
||||
|
||||
private function loadPointData() {
|
||||
if (!$this->mPointData) {
|
||||
$dbr = $this->services->getDBLoadBalancer()->getConnection(DB_REPLICA);
|
||||
$this->mPointData = $dbr->selectRow(
|
||||
'isekai_user_points',
|
||||
'*',
|
||||
[
|
||||
'user_id' => $this->user->getId(),
|
||||
'type' => $this->type
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
if (!$this->mPointData) {
|
||||
$this->initDefaultData();
|
||||
} else {
|
||||
$this->mPointData->timed_points_data = json_decode($this->mPointData->timed_points_data, true) ?? [];
|
||||
$this->mPointData->locked_points_data = json_decode($this->mPointData->locked_points_data, true) ?? [];
|
||||
$this->pointRecordExists = true;
|
||||
$this->removeExpiredPoints();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function initDefaultData() {
|
||||
$this->mPointData = new IsekaiUserPointsData();
|
||||
$this->mPointData->user_id = $this->user->getId();
|
||||
$this->mPointData->type = $this->type;
|
||||
$this->mPointData->points = 0;
|
||||
$this->mPointData->timed_points_data = [];
|
||||
$this->mPointData->locked_points = 0;
|
||||
$this->mPointData->locked_points_data = [];
|
||||
$this->mPointData->next_refresh_time = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove expired points
|
||||
*/
|
||||
private function removeExpiredPoints() {
|
||||
$currentTime = time();
|
||||
if ($this->mPointData->next_refresh_time !== null &&
|
||||
$currentTime > $this->mPointData->next_refresh_time &&
|
||||
is_array($this->mPointData->timed_points_data)) {
|
||||
|
||||
$touched = false;
|
||||
$originalPoints = $this->mPointData->points;
|
||||
foreach ($this->mPointData->timed_points_data as $key => $data) {
|
||||
list($expireTime, $points) = $data;
|
||||
if ($currentTime > $expireTime) {
|
||||
$this->mPointData->points -= $points;
|
||||
unset($this->mPointData->timed_points_data[$key]);
|
||||
$touched = true;
|
||||
}
|
||||
}
|
||||
if ($touched) {
|
||||
// Update next refresh time
|
||||
if (count($this->mPointData->timed_points_data) > 0) {
|
||||
// Sort by expire time
|
||||
usort($this->mPointData->timed_points_data, function($a, $b) {
|
||||
return $a[0] - $b[0];
|
||||
});
|
||||
|
||||
$this->mPointData->next_refresh_time = $this->mPointData->timed_points_data[0][0];
|
||||
} else {
|
||||
$this->mPointData->next_refresh_time = null;
|
||||
}
|
||||
|
||||
$this->save();
|
||||
|
||||
$deltaPoints = $originalPoints - $this->mPointData->points;
|
||||
|
||||
$hookContainer = $this->services->getHookContainer();
|
||||
$hookContainer->run('IsekaiUserPoints::PointsExpired', [$this, $deltaPoints]);
|
||||
|
||||
$this->savePointLog(-$deltaPoints, 'system', 'point-expired');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __get($name) {
|
||||
$this->loadPointData();
|
||||
if (isset($this->pointDataKeysMap[$name])) {
|
||||
$key = $this->pointDataKeysMap[$name];
|
||||
return $this->mPointData->$key;
|
||||
} else {
|
||||
throw new \Exception("Invalid property $name");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user points
|
||||
* @param int $points Points to add
|
||||
* @param int $expireTime Expire time in seconds. -1 for never expire
|
||||
* @param string $service Service name that add points. Message key is `isekai-userpoints-service-$service`.
|
||||
* @param ?string $action Action name that add points. Message key is `isekai-userpoints-action-$service-$action`.
|
||||
*/
|
||||
public function addPoints($points, $expireTime = -1, $service = 'system', $action = null) {
|
||||
$this->loadPointData();
|
||||
|
||||
$hookContainer = MediaWikiServices::getInstance()->getHookContainer();
|
||||
|
||||
$hookContainer->run('IsekaiUserPoints::BeforeAddPoints', [$this, &$points, &$expireTime, &$service, &$action]);
|
||||
|
||||
$this->mPointData->points += $points;
|
||||
|
||||
// Handle expire time
|
||||
$expireTime = intval($expireTime);
|
||||
$resolvedExipreTime = null;
|
||||
if ($expireTime > 0) {
|
||||
// Quantify expire time to 1 day
|
||||
$expireTime = ceil($expireTime / 86400) * 86400;
|
||||
|
||||
$resolvedExipreTime = time() + $expireTime;
|
||||
|
||||
$recordExists = false;
|
||||
foreach ($this->mPointData->timed_points_data as $key => $data) {
|
||||
$oneExpireTime = $data[0];
|
||||
if ($resolvedExipreTime === $oneExpireTime) {
|
||||
$this->mPointData->timed_points_data[$key][1] += $points;
|
||||
$recordExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$recordExists) {
|
||||
$this->mPointData->timed_points_data[] = [$resolvedExipreTime, $points];
|
||||
}
|
||||
if ($this->mPointData->next_refresh_time === null) {
|
||||
$this->mPointData->next_refresh_time = $resolvedExipreTime;
|
||||
}
|
||||
}
|
||||
|
||||
/** @todo Add point log */
|
||||
|
||||
$this->save();
|
||||
|
||||
$this->savePointLog($points, $service, $action, $resolvedExipreTime);
|
||||
$hookContainer->run('IsekaiUserPoints::AfterAddPoints', [$this, $points, $expireTime, $service, $action]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if the user has enough points to consume
|
||||
* @param int $points Points to consume
|
||||
*/
|
||||
public function hasEnoughPoints($points) {
|
||||
$this->loadPointData();
|
||||
return $this->mPointData->points >= $points;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume points
|
||||
* @param int $points Points to consume
|
||||
* @param bool $force Force consume points even if the user has not enough points
|
||||
* @param bool $ignoreSave Ignore save points data after consume points
|
||||
* @param string $service Service name that consume points. Message key is `isekai-userpoints-service-$service`.
|
||||
* @param ?string $action Action name that consume points. Message key is `isekai-userpoints-action-$service-$action`.
|
||||
* @return bool True if the user has enough points to consume
|
||||
*/
|
||||
public function consumePoints($points, $force = false, $ignoreSave = false, $service = 'system', $action = null) {
|
||||
$this->loadPointData();
|
||||
|
||||
$hookContainer = MediaWikiServices::getInstance()->getHookContainer();
|
||||
|
||||
$hookContainer->run('IsekaiUserPoints::BeforeConsumePoints', [$this, &$points, &$service, &$action]);
|
||||
|
||||
if (!$this->hasEnoughPoints($points) && !$force) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->mPointData->points = max(0, $this->mPointData->points - $points);
|
||||
|
||||
if ($this->mPointData->next_refresh_time !== null) { // Update timed points data
|
||||
$consumedPoints = $points;
|
||||
$touched = false;
|
||||
foreach ($this->mPointData->timed_points_data as $key => $data) {
|
||||
list($expireTime, $onePoints) = $data;
|
||||
if ($onePoints <= $consumedPoints) {
|
||||
$consumedPoints -= $onePoints;
|
||||
unset($this->mPointData->timed_points_data[$key]);
|
||||
$touched = true;
|
||||
} else { // All points consumed
|
||||
$this->mPointData->timed_points_data[$key][1] -= $consumedPoints;
|
||||
$consumedPoints = 0;
|
||||
$touched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($touched) {
|
||||
// Update next refresh time
|
||||
if (count($this->mPointData->timed_points_data) > 0) {
|
||||
// Sort by expire time
|
||||
usort($this->mPointData->timed_points_data, function($a, $b) {
|
||||
return $a[0] - $b[0];
|
||||
});
|
||||
$this->mPointData->next_refresh_time = $this->mPointData->timed_points_data[0][0];
|
||||
} else {
|
||||
$this->mPointData->next_refresh_time = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!$ignoreSave) {
|
||||
$this->save();
|
||||
}
|
||||
|
||||
$this->savePointLog(-$points, $service, $action);
|
||||
$hookContainer->run('IsekaiUserPoints::AfterConsumePoints', [$this, $points, $service, $action]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a consume points transaction
|
||||
* @param int $points Points to consume
|
||||
* @param int|null $timeout Transaction timeout in seconds
|
||||
* @param string $service Service name that consume points. Message key is `isekai-userpoints-service-$service`.
|
||||
* @param ?string $action Action name that consume points. Message key is `isekai-userpoints-action-$service-$action`.
|
||||
* @return bool True if the transaction started successfully
|
||||
*/
|
||||
public function startConsumePointsTransaction($points, $timeout = null, $service = 'system', $action = null) {
|
||||
$this->loadPointData();
|
||||
|
||||
$hookContainer = MediaWikiServices::getInstance()->getHookContainer();
|
||||
|
||||
$hookContainer->run('IsekaiUserPoints::BeforeConsumePoints', [$this, &$points, &$service, &$action]);
|
||||
|
||||
if (!$this->hasEnoughPoints($points)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$timeout = min($timeout ?? self::DEFAULT_TRANSACTION_TIMEOUT, self::MAX_TRANSACTION_TIMEOUT);
|
||||
$expireTime = time() + $timeout;
|
||||
|
||||
// Generate random string for transaction id
|
||||
$transactionId = '';
|
||||
do {
|
||||
$transactionId = bin2hex(random_bytes(8));
|
||||
} while (isset($this->mPointData->locked_points_data[$transactionId]));
|
||||
|
||||
$result = $this->consumePoints($points, false, true);
|
||||
if (!$result) return false;
|
||||
|
||||
$this->mPointData->locked_points += $points;
|
||||
$this->mPointData->locked_points_data[$transactionId] = [$expireTime, $points, $service, $action];
|
||||
|
||||
$this->save();
|
||||
|
||||
return $transactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit a consume points transaction
|
||||
* @param string $transactionId Transaction id
|
||||
* @return bool True if the transaction is committed successfully
|
||||
*/
|
||||
public function commitConsumePointsTransaction($transactionId) {
|
||||
if (!isset($this->mPointData->locked_points_data[$transactionId])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($expireTime, $points, $service, $action) = $this->mPointData->locked_points_data[$transactionId];
|
||||
$this->mPointData->locked_points = max(0, $this->mPointData->locked_points - $points);
|
||||
unset($this->mPointData->locked_points_data[$transactionId]);
|
||||
|
||||
$this->save();
|
||||
|
||||
$hookContainer = MediaWikiServices::getInstance()->getHookContainer();
|
||||
|
||||
$this->savePointLog(-$points, $service, $action);
|
||||
$hookContainer->run('IsekaiUserPoints::AfterConsumePoints', [$this, $points, $service, $action]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback a consume points transaction
|
||||
* @param string $transactionId Transaction id
|
||||
* @return bool True if the transaction is rolled back successfully
|
||||
*/
|
||||
public function rollbackConsumePointsTransaction($transactionId) {
|
||||
if (!isset($this->mPointData->locked_points_data[$transactionId])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($expireTime, $points) = $this->mPointData->locked_points_data[$transactionId];
|
||||
$this->mPointData->locked_points = max(0, $this->mPointData->locked_points - $points);
|
||||
$this->mPointData->points += $points;
|
||||
unset($this->mPointData->locked_points_data[$transactionId]);
|
||||
|
||||
$this->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save point data
|
||||
*/
|
||||
private function save() {
|
||||
$dbw = $this->services->getDBLoadBalancer()->getConnection(DB_PRIMARY);
|
||||
if ($this->pointRecordExists) {
|
||||
$dbw->update(
|
||||
'isekai_user_points',
|
||||
[
|
||||
'points' => $this->mPointData->points,
|
||||
'timed_points_data' => json_encode($this->mPointData->timed_points_data),
|
||||
'locked_points' => $this->mPointData->locked_points,
|
||||
'locked_points_data' => json_encode($this->mPointData->locked_points_data),
|
||||
'next_refresh_time' => $this->mPointData->next_refresh_time
|
||||
],
|
||||
[
|
||||
'user_id' => $this->user->getId(),
|
||||
'type' => $this->type,
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
} else {
|
||||
$dbw->insert(
|
||||
'isekai_user_points',
|
||||
[
|
||||
'user_id' => $this->mPointData->user_id,
|
||||
'type' => $this->mPointData->type,
|
||||
'points' => $this->mPointData->points,
|
||||
'timed_points_data' => json_encode($this->mPointData->timed_points_data),
|
||||
'locked_points' => $this->mPointData->locked_points,
|
||||
'locked_points_data' => json_encode($this->mPointData->locked_points_data),
|
||||
'next_refresh_time' => $this->mPointData->next_refresh_time,
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
$this->pointRecordExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
private function savePointLog($points, $service, $action, $expireTime = null) {
|
||||
$dbw = $this->services->getDBLoadBalancer()->getConnection(DB_PRIMARY);
|
||||
|
||||
if ($expireTime) {
|
||||
$expireDate = date('Y-m-d', $expireTime);
|
||||
} else {
|
||||
$expireDate = null;
|
||||
}
|
||||
|
||||
$dbw->insert(
|
||||
'isekai_user_points_log',
|
||||
[
|
||||
'user_id' => $this->user->getId(),
|
||||
'point_type' => $this->type,
|
||||
'points' => $points,
|
||||
'point_expire' => $expireDate,
|
||||
'service' => $service,
|
||||
'action' => $action,
|
||||
'timestamp' => wfTimestampNow()
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
namespace Isekai\UserPoints\Service;
|
||||
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use User;
|
||||
|
||||
class IsekaiUserPointsFactory {
|
||||
/** @var MediaWikiServices */
|
||||
private $services;
|
||||
|
||||
/** @var \Config */
|
||||
private $config;
|
||||
|
||||
/** @var array */
|
||||
private $pointConfig;
|
||||
|
||||
private static $instances;
|
||||
|
||||
public function __construct(MediaWikiServices $services) {
|
||||
$this->services = $services;
|
||||
$this->config = $services->getMainConfig();
|
||||
$this->pointConfig = $this->config->get('IsekaiUserPointConfig');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
* @param string $pointType
|
||||
* @return IsekaiUserPoints|null
|
||||
*/
|
||||
public function newFromUserId(int $userId, string $pointType) {
|
||||
$user = $this->services->getUserFactory()->newFromId($userId);
|
||||
return $this->newFromUser($user, $pointType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @return IsekaiUserPoints|null
|
||||
*/
|
||||
public function newFromUser(User $user, string $pointType) {
|
||||
if (!isset($this->pointConfig[$pointType])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$user->isRegistered()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$userId = $user->getId();
|
||||
$cacheKey = $userId . ':' . $pointType;
|
||||
if (!isset(self::$instances[$cacheKey])) {
|
||||
self::$instances[$cacheKey] = new IsekaiUserPoints($user, $pointType, $this->pointConfig, $this->services);
|
||||
}
|
||||
return self::$instances[$cacheKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @param string $pointType
|
||||
* @param stdClass $data
|
||||
*/
|
||||
public function newFromData(User $user, string $pointType, $data) {
|
||||
$instance = $this->newFromUser($user, $pointType);
|
||||
if ($instance) {
|
||||
$instance->setData($data);
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: User, 1: string, 2: IsekaiUserPoints} (user, instance)
|
||||
*/
|
||||
public function newFromUsers(array $users, array $pointTypes) {
|
||||
$userids = [];
|
||||
$usermap = [];
|
||||
foreach ($users as $user) {
|
||||
$userId = $user->getId();
|
||||
$userids[] = $userId;
|
||||
$usermap[$userId] = $user;
|
||||
}
|
||||
|
||||
$dbr = $this->services->getDBLoadBalancer()->getConnection(DB_REPLICA);
|
||||
$pointDataRows = $dbr->select(
|
||||
'isekai_user_points',
|
||||
'*',
|
||||
[ 'user_id' => $userids, 'point_type' => $pointTypes ],
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
$result = [];
|
||||
$initedUsers = [];
|
||||
foreach ($pointDataRows as $pointData) {
|
||||
$user = $usermap[$pointData->user_id];
|
||||
$pointType = $pointData->type;
|
||||
$instance = $this->newFromUser($user, $pointType);
|
||||
if ($instance) {
|
||||
$instance->setData($pointData);
|
||||
}
|
||||
|
||||
$result[] = [$user, $instance];
|
||||
$initedUsers[] = $user;
|
||||
}
|
||||
foreach ($userids as $userId) {
|
||||
if (!in_array($userId, $initedUsers)) {
|
||||
$user = $usermap[$userId];
|
||||
$pointType = $pointData->type;
|
||||
$instance = $this->newFromUser($user, $pointType);
|
||||
|
||||
$result[] = [$user, $pointType, $instance];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace Isekai\UserPoints;
|
||||
|
||||
use Html;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
|
||||
class Utils {
|
||||
public static function getPointName($pointType) {
|
||||
$pointConfig = MediaWikiServices::getInstance()->getMainConfig()->get('IsekaiUserPointConfig');
|
||||
|
||||
if (!isset($pointConfig[$pointType])) {
|
||||
return '{' . $pointType . '}';
|
||||
} else {
|
||||
$currentConfig = $pointConfig[$pointType];
|
||||
if (isset($currentConfig['namemsg'])) {
|
||||
return wfMessage($currentConfig['namemsg'])->text();
|
||||
} else {
|
||||
return $currentConfig['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function getPointIcon($pointType) {
|
||||
$pointConfig = MediaWikiServices::getInstance()->getMainConfig()->get('IsekaiUserPointConfig');
|
||||
|
||||
if (!isset($pointConfig[$pointType])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$currentConfig = $pointConfig[$pointType];
|
||||
if (!isset($currentConfig['icon']) || !isset($currentConfig['icon']['normal'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$iconDom = [];
|
||||
foreach(['normal', 'invert'] as $iconType) {
|
||||
if (!isset($currentConfig['icon'][$iconType])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$iconConfig = $currentConfig['icon'][$iconType] ?? $currentConfig['icon']['normal'];
|
||||
|
||||
$className = $iconConfig['class'] ?? [];
|
||||
if (is_string($className)) {
|
||||
$className = explode(' ', $className);
|
||||
}
|
||||
|
||||
$className[] = 'isekai-point-icon-' . $iconType;
|
||||
|
||||
if (isset($iconConfig['image'])) {
|
||||
$iconDom[] = Html::element('img', [
|
||||
'src' => $iconConfig['image'],
|
||||
'class' => implode(' ', $className),
|
||||
]);
|
||||
} elseif (isset($iconConfig['html'])) {
|
||||
$iconDom[] = Html::rawElement('span', [
|
||||
'class' => implode(' ', $className),
|
||||
], $iconConfig['html']);
|
||||
} else {
|
||||
$text = $iconConfig['text'] ?? '';
|
||||
$iconDom[] = Html::element('span', [
|
||||
'class' => implode(' ', $className),
|
||||
], $text);
|
||||
}
|
||||
}
|
||||
|
||||
return Html::rawElement('span', [
|
||||
'class' => "isekai-point-icon isekai-point-icon-type-$pointType"
|
||||
], implode('', $iconDom));
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
.isekai-point-icon {
|
||||
> .isekai-point-icon-invert {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
$(function() {
|
||||
const storeKey = 'isekai-userpoints-dailysign-lastSignDate';
|
||||
const lastSignDate = localStorage.getItem(storeKey);
|
||||
const today = new Date().toLocaleDateString();
|
||||
if (lastSignDate !== today) {
|
||||
let mwApi = new mw.Api();
|
||||
mwApi.postWithToken('csrf', {
|
||||
action: 'userdailysign',
|
||||
}).done(function(data) {
|
||||
if (data.userdailysign && data.userdailysign.success) {
|
||||
if (Array.isArray(data.userdailysign.point_delta)) {
|
||||
const pointDelta = data.userdailysign.point_delta;
|
||||
let pointMsgList = [];
|
||||
pointDelta.forEach(function (pointDeltaInfo) {
|
||||
let msg = mw.msg('isekai-userpoints-point-name-num', pointDeltaInfo.name, pointDeltaInfo.icon, pointDeltaInfo.points);
|
||||
pointMsgList.push(msg);
|
||||
});
|
||||
let separator = mw.msg('comma-separator');
|
||||
let pointMsg = pointMsgList.join(separator);
|
||||
|
||||
let notificationMsg = mw.msg('isekai-userpoints-dailysign-notify-success', pointMsg);
|
||||
mw.notify('', {
|
||||
title: mw.msg('isekai-userpoints-dailysign-notify-title'),
|
||||
tag: 'isekai-userpoints-dailysign',
|
||||
id: 'isekai-userpoints-dailysign-notify',
|
||||
});
|
||||
|
||||
function changeNotifyContent() {
|
||||
let notifyDom = document.querySelector('#isekai-userpoints-dailysign-notify');
|
||||
if (notifyDom) {
|
||||
notifyDom.querySelector('.mw-notification-content').innerHTML = notificationMsg;
|
||||
} else {
|
||||
requestAnimationFrame(changeNotifyContent);
|
||||
}
|
||||
}
|
||||
|
||||
changeNotifyContent();
|
||||
}
|
||||
localStorage.setItem(storeKey, today);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
CREATE TABLE /*_*/isekai_user_daily_sign (
|
||||
`user_id` INT UNSIGNED NOT NULL,
|
||||
`last_sign_date` DATE NOT NULL,
|
||||
`sign_days_data` LONGTEXT NOT NULL,
|
||||
`total_sign_days` INT UNSIGNED NOT NULL DEFAULT '0'
|
||||
) /*$wgDBTableOptions*/;
|
||||
ALTER TABLE /*_*/isekai_user_daily_sign ADD PRIMARY KEY (`user_id`);
|
@ -0,0 +1,10 @@
|
||||
CREATE TABLE /*_*/isekai_user_daily_sign_log (
|
||||
`user_id` INT UNSIGNED NOT NULL,
|
||||
`year` INT UNSIGNED NOT NULL,
|
||||
`month` INT UNSIGNED NOT NULL,
|
||||
`sign_log` LONGTEXT NOT NULL
|
||||
) /*$wgDBTableOptions*/;
|
||||
ALTER TABLE /*_*/isekai_user_daily_sign_log ADD PRIMARY KEY (`user_id`);
|
||||
ALTER TABLE /*_*/isekai_user_daily_sign_log ADD INDEX(`user_id`, `year`, `month`);
|
||||
ALTER TABLE /*_*/isekai_user_daily_sign_log ADD INDEX(`year`);
|
||||
ALTER TABLE /*_*/isekai_user_daily_sign_log ADD INDEX(`month`);
|
@ -0,0 +1,14 @@
|
||||
CREATE TABLE /*_*/isekai_user_points (
|
||||
`user_id` INT UNSIGNED NOT NULL,
|
||||
`type` VARCHAR(20) NOT NULL,
|
||||
`points` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
`timed_points_data` LONGTEXT NOT NULL,
|
||||
`locked_points` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
`locked_points_data` LONGTEXT NOT NULL,
|
||||
`next_refresh_time` BIGINT UNSIGNED NULL
|
||||
) /*$wgDBTableOptions*/;
|
||||
ALTER TABLE /*_*/isekai_user_points ADD PRIMARY KEY (`user_id`, `type`);
|
||||
ALTER TABLE /*_*/isekai_user_points ADD INDEX(`user_id`);
|
||||
ALTER TABLE /*_*/isekai_user_points ADD INDEX(`type`);
|
||||
ALTER TABLE /*_*/isekai_user_points ADD INDEX(`points`);
|
||||
ALTER TABLE /*_*/isekai_user_points ADD INDEX(`next_refresh_time`);
|
@ -0,0 +1,15 @@
|
||||
CREATE TABLE /*_*/isekai_user_points_log (
|
||||
`id` INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` INT UNSIGNED NOT NULL,
|
||||
`point_type` VARCHAR(20) NOT NULL,
|
||||
`points` INT NOT NULL DEFAULT '0',
|
||||
`point_expire` DATE NULL,
|
||||
`service` VARCHAR(255) NOT NULL,
|
||||
`action` VARCHAR(255) NOT NULL,
|
||||
`timestamp` BIGINT UNSIGNED NOT NULL
|
||||
) /*$wgDBTableOptions*/;
|
||||
ALTER TABLE /*_*/isekai_user_points_log ADD INDEX(`user_id`, `point_type`);
|
||||
ALTER TABLE /*_*/isekai_user_points_log ADD INDEX(`user_id`);
|
||||
ALTER TABLE /*_*/isekai_user_points_log ADD INDEX(`service`);
|
||||
ALTER TABLE /*_*/isekai_user_points_log ADD INDEX(`action`);
|
||||
ALTER TABLE /*_*/isekai_user_points_log ADD INDEX(`timestamp`);
|
@ -0,0 +1,7 @@
|
||||
CREATE TABLE isekai_user_daily_sign (
|
||||
user_id INT NOT NULL,
|
||||
last_sign_date DATE NOT NULL,
|
||||
sign_days_data JSON NOT NULL,
|
||||
total_sign_days INT NOT NULL DEFAULT 0
|
||||
);
|
||||
ALTER TABLE isekai_user_daily_sign ADD PRIMARY KEY (user_id);
|
@ -0,0 +1,10 @@
|
||||
CREATE TABLE isekai_user_daily_sign_log (
|
||||
user_id INT NOT NULL,
|
||||
year INT NOT NULL,
|
||||
month INT NOT NULL,
|
||||
sign_log JSON NOT NULL
|
||||
);
|
||||
ALTER TABLE isekai_user_daily_sign_log ADD PRIMARY KEY (user_id);
|
||||
ALTER TABLE isekai_user_daily_sign_log ADD INDEX(user_id, year, month);
|
||||
ALTER TABLE isekai_user_daily_sign_log ADD INDEX(year);
|
||||
ALTER TABLE isekai_user_daily_sign_log ADD INDEX(month);
|
@ -0,0 +1,17 @@
|
||||
-- Create table for postgres
|
||||
CREATE TABLE isekai_user_points (
|
||||
user_id INTEGER NOT NULL,
|
||||
type VARCHAR(20) NOT NULL,
|
||||
points INTEGER NOT NULL DEFAULT 0,
|
||||
timed_points_data TEXT NOT NULL,
|
||||
locked_points INTEGER NOT NULL DEFAULT 0,
|
||||
locked_points_data TEXT NOT NULL,
|
||||
next_refresh_time BIGINT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE isekai_user_points ADD PRIMARY KEY (user_id, type);
|
||||
ALTER TABLE isekai_user_points ADD INDEX (user_id);
|
||||
ALTER TABLE isekai_user_points ADD INDEX (type);
|
||||
ALTER TABLE isekai_user_points ADD INDEX (points);
|
||||
ALTER TABLE isekai_user_points ADD INDEX (timed_points_data);
|
||||
ALTER TABLE isekai_user_points ADD INDEX (next_refresh_time);
|
@ -0,0 +1,15 @@
|
||||
CREATE TABLE isekai_user_points_log (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
point_type VARCHAR(20) NOT NULL,
|
||||
points INT NOT NULL DEFAULT 0,
|
||||
point_expire DATE NULL,
|
||||
service VARCHAR(255) NOT NULL,
|
||||
action VARCHAR(255) NOT NULL,
|
||||
timestamp BIGINT NOT NULL
|
||||
);
|
||||
ALTER TABLE isekai_user_points_log ADD INDEX(user_id, point_type);
|
||||
ALTER TABLE isekai_user_points_log ADD INDEX(user_id);
|
||||
ALTER TABLE isekai_user_points_log ADD INDEX(service);
|
||||
ALTER TABLE isekai_user_points_log ADD INDEX(action);
|
||||
ALTER TABLE isekai_user_points_log ADD INDEX(timestamp);
|
Loading…
Reference in New Issue