当前提交
89707e927f
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"root": true,
|
||||
"extends": [
|
||||
"wikimedia/client-es5",
|
||||
"wikimedia/mediawiki"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
*~
|
||||
*.kate-swp
|
||||
.*.swp
|
||||
node_modules/**
|
||||
vendor/**
|
||||
composer.lock
|
||||
/.eslintcache
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
$cfg = require __DIR__ . '/../vendor/mediawiki/mediawiki-phan-config/src/config.php';
|
||||
|
||||
$cfg['directory_list'] = array_merge(
|
||||
$cfg['directory_list'],
|
||||
[
|
||||
'../SocialProfile'
|
||||
]
|
||||
);
|
||||
|
||||
$cfg['exclude_analysis_directory_list'] = array_merge(
|
||||
$cfg['exclude_analysis_directory_list'],
|
||||
[
|
||||
'../SocialProfile'
|
||||
]
|
||||
);
|
||||
|
||||
return $cfg;
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0"?>
|
||||
<ruleset>
|
||||
<rule ref="./vendor/mediawiki/mediawiki-codesniffer/MediaWiki">
|
||||
<exclude name="MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic" />
|
||||
<exclude name="MediaWiki.Commenting.PropertyDocumentation.MissingDocumentationPublic" />
|
||||
<exclude name="MediaWiki.Files.ClassMatchesFilename.NotMatch" />
|
||||
<exclude name="MediaWiki.WhiteSpace.SpaceBeforeSingleLineComment.NewLineComment" />
|
||||
</rule>
|
||||
<file>.</file>
|
||||
<arg name="bootstrap" value="./vendor/mediawiki/mediawiki-codesniffer/utils/bootstrap-ci.php"/>
|
||||
<arg name="extensions" value="php"/>
|
||||
<arg name="encoding" value="UTF-8"/>
|
||||
</ruleset>
|
|
@ -0,0 +1,24 @@
|
|||
/* eslint-env node, es6 */
|
||||
module.exports = function ( grunt ) {
|
||||
grunt.loadNpmTasks( 'grunt-eslint' );
|
||||
grunt.loadNpmTasks( 'grunt-banana-checker' );
|
||||
|
||||
grunt.initConfig( {
|
||||
banana: {
|
||||
all: 'i18n/'
|
||||
},
|
||||
eslint: {
|
||||
options: {
|
||||
cache: true
|
||||
},
|
||||
all: [
|
||||
'**/*.json',
|
||||
'!node_modules/**',
|
||||
'!vendor/**'
|
||||
]
|
||||
}
|
||||
} );
|
||||
|
||||
grunt.registerTask( 'test', [ 'eslint', 'banana' ] );
|
||||
grunt.registerTask( 'default', 'test' );
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"require-dev": {
|
||||
"mediawiki/mediawiki-codesniffer": "37.0.0",
|
||||
"mediawiki/mediawiki-phan-config": "0.11.0",
|
||||
"mediawiki/minus-x": "1.1.1",
|
||||
"php-parallel-lint/php-console-highlighter": "0.5.0",
|
||||
"php-parallel-lint/php-parallel-lint": "1.3.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"parallel-lint . --exclude vendor --exclude node_modules",
|
||||
"minus-x check .",
|
||||
"@phpcs"
|
||||
],
|
||||
"fix": [
|
||||
"minus-x fix .",
|
||||
"phpcbf"
|
||||
],
|
||||
"phan": "phan -d . --long-progress-bar",
|
||||
"seccheck": "seccheck-mwext",
|
||||
"seccheck-fast": "seccheck-fast-mwext",
|
||||
"phpcs": "phpcs -sp --cache"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"name": "New Signup Page",
|
||||
"version": "1.4.0",
|
||||
"author": [
|
||||
"Jack Phoenix",
|
||||
"Qiuwen Baike Contributors",
|
||||
],
|
||||
"license-name": "GPL-2.0-or-later",
|
||||
"url": "https://www.mediawiki.org/wiki/Extension:NewSignupPage",
|
||||
"descriptionmsg": "newsignuppage-desc",
|
||||
"type": "other",
|
||||
"requires": {
|
||||
"MediaWiki": ">= 1.34.0"
|
||||
},
|
||||
"config": {
|
||||
"RegisterTrack": {
|
||||
"value": false,
|
||||
"path": false,
|
||||
"description": "Should we track information about referred users into the user_register_track DB table?",
|
||||
"public": false
|
||||
},
|
||||
"AutoAddFriendOnInvite": {
|
||||
"value": false,
|
||||
"path": false,
|
||||
"description": "When a user signs up via a referral link, add the referring user automatically as the new user's friend (and vice-versa)?",
|
||||
"public": false
|
||||
},
|
||||
"NewSignupPageToSURL": {
|
||||
"value": "",
|
||||
"path": false,
|
||||
"description": "Canonical URL to the site's terms of use page",
|
||||
"public": false
|
||||
},
|
||||
"NewSignupPagePPURL": {
|
||||
"value": "",
|
||||
"path": false,
|
||||
"description": "Canonical URL to the site's privacy policy page",
|
||||
"public": false
|
||||
}
|
||||
},
|
||||
"MessagesDirs": {
|
||||
"NewSignupPage": [
|
||||
"i18n"
|
||||
]
|
||||
},
|
||||
"AutoloadClasses": {
|
||||
"NewSignupPage": "includes/NewSignupPage.class.php",
|
||||
"NewSignupPageAuthenticationRequest": "includes/auth/NewSignupPageAuthenticationRequest.php",
|
||||
"NewSignupPageSecondaryAuthenticationProvider": "includes/auth/NewSignupPageSecondaryAuthenticationProvider.php"
|
||||
},
|
||||
"AuthManagerAutoConfig": {
|
||||
"secondaryauth": {
|
||||
"NewSignupPageSecondaryAuthenticationProvider": {
|
||||
"class": "NewSignupPageSecondaryAuthenticationProvider"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Hooks": {
|
||||
"BeforePageDisplay": "NewSignupPage::onBeforePageDisplay",
|
||||
"LoadExtensionSchemaUpdates": "NewSignupPage::onLoadExtensionSchemaUpdates"
|
||||
},
|
||||
"ResourceFileModulePaths": {
|
||||
"localBasePath": "",
|
||||
"remoteExtPath": "NewSignupPage"
|
||||
},
|
||||
"ResourceModules": {
|
||||
"ext.newsignuppage": {
|
||||
"scripts": "resources/js/NewSignupPage.js",
|
||||
"messages": [
|
||||
"badretype"
|
||||
]
|
||||
}
|
||||
},
|
||||
"AvailableRights": [
|
||||
"bypasstoscheck"
|
||||
],
|
||||
"manifest_version": 2
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"@metadata": {
|
||||
"authors": []
|
||||
},
|
||||
"newsignuppage-desc": "Adds new features to the [[Special:CreateAccount|signup form]]",
|
||||
"newsignuppage-recruited": "recruited [$1 $2]",
|
||||
"newsignuppage-loginform-tos": "I am over 13 years of age and I have read, understood and agree to be bound by the [$1 Terms of Service] and [$2 Privacy Policy]",
|
||||
"newsignuppage-must-accept-tos": "You must accept the site's Terms of Service in order to be able to create an account!",
|
||||
"action-bypasstoscheck": "create new accounts without explicitly accepting the site's Terms of Service",
|
||||
"right-bypasstoscheck": "Create new accounts without explicitly accepting the site's Terms of Service"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"@metadata": {
|
||||
"authors": []
|
||||
},
|
||||
"newsignuppage-desc": "{{desc|name=NewSignupPage|url=https://www.mediawiki.org/wiki/Extension:NewSignupPage}}",
|
||||
"newsignuppage-recruited": "$1 is a URL to the recruited user's user page; $2 is the recruited user's username. This message is displayed on Special:UserActivity and on the output of <siteactivity> tag on wikis where the [[mw:Extension:SocialProfile|SocialProfile extension]] is installed and site administrators have chosen to give out points for recruiting new users; not used on normal wikis",
|
||||
"newsignuppage-loginform-tos": "The text for the new checkbox which is displayed in Special:CreateAccount, right below the \"Remember my login on this computer\" checkbox.\nParameters:\n* $1 is the full URL to the site's terms of service page, configurable via the <code>$wgNewSignupPageToSURL</code> global variable\n* $2 is the full URL to the site's privacy policy page, configurable via the <code>$wgNewSignupPagePPURL</code> global variable",
|
||||
"newsignuppage-must-accept-tos": "Error message displayed to the user in Special:CreateAccount in a fancy red <code><div></code> if they don't check the \"I accept the Terms of Service\" checkbox",
|
||||
"action-bypasstoscheck": "{{doc-action|bypasstoscheck}}",
|
||||
"right-bypasstoscheck": "{{doc-right|bypasstoscheck}}"
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Kly"
|
||||
]
|
||||
},
|
||||
"newsignuppage-desc": "添加新功能到[[Special:CreateAccount|註冊表格]]",
|
||||
"newsignuppage-recruited": "招募[$1 $2]",
|
||||
"newsignuppage-loginform-tos": "我已超過 13 歲,已詳細閱讀過、了解、並同意接受[$1 服務條款]與[$2 隱私權政策]",
|
||||
"newsignuppage-must-accept-tos": "您必須接受網站的服務條款才能夠建立帳號!",
|
||||
"action-bypasstoscheck": "建立新帳號但未明確接受網站的服務條款",
|
||||
"right-bypasstoscheck": "建立新帳號但未明確接受網站的服務條款"
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
/**
|
||||
* NewSignupPage extension for MediaWiki -- enhances the default signup form
|
||||
*
|
||||
* @file
|
||||
* @ingroup Extensions
|
||||
* @author Jack Phoenix
|
||||
* @copyright Copyright © 2008-2020 Jack Phoenix
|
||||
* @license GPL-2.0-or-later
|
||||
*/
|
||||
class NewSignupPage {
|
||||
|
||||
/**
|
||||
* Add the JavaScript file to the page output on the signup page.
|
||||
*
|
||||
* @param OutputPage &$out
|
||||
* @param Skin &$skin
|
||||
*/
|
||||
public static function onBeforePageDisplay( &$out, &$skin ) {
|
||||
$title = $out->getTitle();
|
||||
|
||||
// Only do our magic if we're on the signup page or login page
|
||||
// It's called Special:CreateAccount or Special:UserLogin since AuthManager (MW 1.27+)
|
||||
|
||||
// Warning: Userlogin should be all lowercased!
|
||||
if ( $title->isSpecial( 'CreateAccount' ) || $title->isSpecial( 'Userlogin' ) ) {
|
||||
$out->addModules( ['ext.newsignuppage'] );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the necessary database table when the user runs
|
||||
* maintenance/update.php, the core MediaWiki updater script, provided that
|
||||
* the configuration specifies us to create it.
|
||||
*
|
||||
* @param DatabaseUpdater $updater
|
||||
* @return bool True when we should not do anything
|
||||
*/
|
||||
public static function onLoadExtensionSchemaUpdates( $updater ) {
|
||||
global $wgRegisterTrack;
|
||||
|
||||
$db = $updater->getDB();
|
||||
|
||||
if ( !$db->tableExists( 'user_register_track' ) && !$wgRegisterTrack ) {
|
||||
// Table doesn't exist and shouldn't either -> bail out
|
||||
return true;
|
||||
}
|
||||
|
||||
$dir = __DIR__ . '/../sql';
|
||||
$dbType = $db->getType();
|
||||
$file = $dir . '/user_register_track.sql';
|
||||
if ( $dbType === 'postgres' ) {
|
||||
$file = $dir . '/user_register_track.postgres.sql';
|
||||
}
|
||||
|
||||
$updater->addExtensionTable( 'user_register_track', $file );
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\Auth\AuthenticationRequest;
|
||||
|
||||
/**
|
||||
* @ingroup Auth
|
||||
* @since MediaWiki 1.27
|
||||
* @phan-file-suppress PhanTypeMismatchReturn It appears that phan seems to hate the retval of getFieldInfo()...
|
||||
*/
|
||||
class NewSignupPageAuthenticationRequest extends AuthenticationRequest {
|
||||
public $required = self::REQUIRED; // only ToS check is mandatory
|
||||
|
||||
/**
|
||||
* @var int Email invitation source identifier to be stored in the
|
||||
* user_email_track table
|
||||
* @see /extensions/MiniInvite/includes/UserEmailTrack.class.php for details
|
||||
*/
|
||||
public $from;
|
||||
|
||||
/**
|
||||
* @var string|int Username of the person who referred the user creating an
|
||||
* account to the wiki; used to give out points to the referring user and
|
||||
* also automatically friend them and the new user if that configuration
|
||||
* setting is enabled
|
||||
*/
|
||||
public $referral;
|
||||
|
||||
/**
|
||||
* @var bool Was the "I agree to the terms of service"
|
||||
* checkbox checked? It must be in order for the account creation process
|
||||
* to continue.
|
||||
*/
|
||||
public $wpTermsOfService;
|
||||
|
||||
/** @var WebRequest */
|
||||
public $request;
|
||||
|
||||
/**
|
||||
* @param WebRequest $request
|
||||
*/
|
||||
public function __construct( $request ) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getFieldInfo() {
|
||||
global $wgNewSignupPageToSURL, $wgNewSignupPagePPURL;
|
||||
return [
|
||||
'from' => [
|
||||
'type' => 'hidden',
|
||||
'optional' => true,
|
||||
'value' => $this->request->getInt( 'from' )
|
||||
],
|
||||
'referral' => [
|
||||
'type' => 'hidden',
|
||||
'optional' => true,
|
||||
'value' => $this->request->getVal( 'referral' )
|
||||
],
|
||||
'wpTermsOfService' => [
|
||||
'type' => 'checkbox',
|
||||
'label' => wfMessage(
|
||||
'newsignuppage-loginform-tos',
|
||||
$wgNewSignupPageToSURL,
|
||||
$wgNewSignupPagePPURL
|
||||
)
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function loadFromSubmission( array $data ) {
|
||||
// We always want to use this request, so ignore parent's return value.
|
||||
parent::loadFromSubmission( $data );
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\Auth\AbstractSecondaryAuthenticationProvider;
|
||||
use MediaWiki\Auth\AuthenticationRequest;
|
||||
use MediaWiki\Auth\AuthenticationResponse;
|
||||
use MediaWiki\Auth\AuthManager;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
|
||||
/**
|
||||
* @license GPL-2.0-or-later
|
||||
* @note Uses GPL-licensed code from LoginReg extension (in beginSecondaryAccountCreation())
|
||||
*/
|
||||
class NewSignupPageSecondaryAuthenticationProvider extends AbstractSecondaryAuthenticationProvider {
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct( $params = [] ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort the creation of the new account if the user hasn't checked the
|
||||
* "I agree to the terms of service" checkbox and they aren't allowed to
|
||||
* bypass that check.
|
||||
*
|
||||
* @param User $user
|
||||
* @param User $creator
|
||||
* @param array $reqs
|
||||
* @return StatusValue
|
||||
*/
|
||||
public function testForAccountCreation( $user, $creator, array $reqs ) {
|
||||
$req = AuthenticationRequest::getRequestByClass( $reqs, NewSignupPageAuthenticationRequest::class );
|
||||
if (
|
||||
$req && $req->wpTermsOfService ||
|
||||
$creator->isAllowed( 'bypasstoscheck' )
|
||||
) {
|
||||
return StatusValue::newGood();
|
||||
} else {
|
||||
return StatusValue::newFatal( 'newsignuppage-must-accept-tos' );
|
||||
}
|
||||
}
|
||||
|
||||
public function getAuthenticationRequests( $action, array $options ) {
|
||||
if ( $action === AuthManager::ACTION_CREATE || $action === AuthManager::ACTION_LOGIN ) {
|
||||
return [ new NewSignupPageAuthenticationRequest(
|
||||
$this->manager->getRequest()
|
||||
) ];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function beginSecondaryAuthentication( $user, array $reqs ) {
|
||||
return AuthenticationResponse::newAbstain();
|
||||
}
|
||||
|
||||
public function beginSecondaryAccountCreation( $user, $creator, array $reqs ) {
|
||||
global $wgAutoAddFriendOnInvite, $wgRegisterTrack;
|
||||
|
||||
$req = AuthenticationRequest::getRequestByClass(
|
||||
$reqs, NewSignupPageAuthenticationRequest::class
|
||||
);
|
||||
|
||||
$referral_user = User::newFromName( $req->referral );
|
||||
$user_id_referral = 0;
|
||||
|
||||
if ( $wgAutoAddFriendOnInvite && $referral_user instanceof User ) {
|
||||
$user_id_referral = $referral_user->getId();
|
||||
if ( $user_id_referral ) {
|
||||
// need to create fake request first
|
||||
$rel = new UserRelationship( $referral_user );
|
||||
$request_id = $rel->addRelationshipRequest( $user, 1, '', false );
|
||||
|
||||
// clear the status
|
||||
$rel->updateRelationshipRequestStatus( $request_id, 1 );
|
||||
|
||||
// automatically add relationships
|
||||
$rel = new UserRelationship( $user );
|
||||
$rel->addRelationship( $request_id, true );
|
||||
|
||||
// Update social statistics for both users (so that we don't
|
||||
// show "0 of 0" in the new user's profile when they in fact
|
||||
// do have one friend already!)
|
||||
// @todo FIXME: broken until UserStatsTrack is refactored to support RequestContext
|
||||
// instead of global objects (the global object in incStatField() is _not_
|
||||
// our $user even though by all logic it should be and it was in older versions
|
||||
// of MW)
|
||||
$stats = new UserStatsTrack( $user->getId(), $user->getName() );
|
||||
$stats->updateRelationshipCount( 1 );
|
||||
$stats->incStatField( 'friend' );
|
||||
|
||||
$statsReferringUser = new UserStatsTrack( $user_id_referral, $referral_user->getName() );
|
||||
$statsReferringUser->updateRelationshipCount( 1 );
|
||||
$statsReferringUser->incStatField( 'friend' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $wgRegisterTrack ) {
|
||||
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
||||
$cache->delete( $cache->makeKey( 'users', 'new', '1' ) );
|
||||
|
||||
// How the user registered (via email from friend, just on the site etc.)?
|
||||
$from = $req->from;
|
||||
if ( !$from ) {
|
||||
$from = 0;
|
||||
}
|
||||
|
||||
// Track if the user clicked on email from friend
|
||||
if ( $referral_user instanceof User ) {
|
||||
// Update the social statistics of the referring user (to give
|
||||
// them points, if specified so on the configuration file)
|
||||
$stats = new UserStatsTrack( $referral_user->getId(), $referral_user->getName() );
|
||||
$stats->incStatField( 'referral_complete' );
|
||||
|
||||
// Add a new site activity event that will show up on the output
|
||||
// of <siteactivity /> at least
|
||||
if ( class_exists( 'UserSystemMessage' ) ) {
|
||||
$m = new UserSystemMessage();
|
||||
// Nees to be forContent because addMessage adds this into a
|
||||
// database table - we don't want to display Japanese text
|
||||
// to English users
|
||||
$message = wfMessage(
|
||||
'newsignuppage-recruited',
|
||||
$user->getUserPage()->getFullURL(),
|
||||
$user->getName()
|
||||
)->parse();
|
||||
$m->addMessage(
|
||||
$referral_user,
|
||||
UserSystemMessage::TYPE_RECRUIT,
|
||||
$message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Track registration
|
||||
$dbw = wfGetDB( DB_MASTER );
|
||||
$dbw->insert(
|
||||
'user_register_track',
|
||||
[
|
||||
'ur_actor' => $user->getActorId(),
|
||||
'ur_actor_referral' => ( $referral_user instanceof User ? $referral_user->getActorId() : 0 ),
|
||||
'ur_from' => $from,
|
||||
'ur_date' => $dbw->timestamp( date( 'Y-m-d H:i:s' ) )
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
}
|
||||
|
||||
return AuthenticationResponse::newPass();
|
||||
}
|
||||
|
||||
}
|
文件差异内容过多而无法显示
加载差异
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-config-wikimedia": "0.20.0",
|
||||
"grunt": "1.4.0",
|
||||
"grunt-banana-checker": "0.9.0",
|
||||
"grunt-eslint": "23.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* JavaScript file for performing some interactive and client-side validation of
|
||||
* the password fields on the registration form to avoid some user frustration,
|
||||
* especially on mobile devices.
|
||||
*
|
||||
* @file
|
||||
* @author by Qiuwen Contributors
|
||||
*/
|
||||
// Mandatory check
|
||||
(function () {
|
||||
var a = document.getElementById("wpLoginAttempt") || document.getElementById("wpCreateaccount");
|
||||
a && (a.disabled = !0, document.getElementById("mw-input-wpTermsOfService").addEventListener("change", function () {
|
||||
a.disabled = !0 === document.getElementById("mw-input-wpTermsOfService").checked ? !1 : !0;
|
||||
}));
|
||||
})();
|
|
@ -0,0 +1,12 @@
|
|||
DROP SEQUENCE IF EXISTS user_register_track_ur_id_seq CASCADE;
|
||||
CREATE SEQUENCE user_register_track_ur_id_seq;
|
||||
|
||||
CREATE TABLE user_register_track (
|
||||
ur_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('user_register_track_ur_id_seq'),
|
||||
ur_actor INTEGER NOT NULL,
|
||||
ur_actor_referral INTEGER NOT NULL,
|
||||
ur_from SMALLINT default 0,
|
||||
ur_date TIMESTAMPTZ default NULL
|
||||
);
|
||||
|
||||
ALTER SEQUENCE user_register_track_ur_id_seq OWNED BY user_register_track.ur_id;
|
|
@ -0,0 +1,7 @@
|
|||
CREATE TABLE /*_*/user_register_track (
|
||||
`ur_id` int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`ur_actor` bigint unsigned NOT NULL,
|
||||
`ur_actor_referral` bigint unsigned NOT NULL,
|
||||
`ur_from` int(5) default 0,
|
||||
`ur_date` datetime default NULL
|
||||
) /*$wgDBTableOptions*/;
|
正在加载...
在新工单中引用