mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-24 20:17:24 +08:00
Merge remote-tracking branch 'origin/php8' into php8
This commit is contained in:
+1
-1
@@ -40,7 +40,7 @@ Welcome to participate in internationalization work, click [here](https://github
|
|||||||
- Section H&R
|
- Section H&R
|
||||||
- TGBot
|
- TGBot
|
||||||
## System Requirements
|
## System Requirements
|
||||||
- PHP: 8.2|8.3|8.4, must have extensions: bcmath, ctype, curl, fileinfo, json, mbstring, openssl, pdo_mysql, tokenizer, xml, mysqli, gd, redis, pcntl, sockets, posix, gmp, zend opcache, zip, intl, pdo_sqlite, sqlite3
|
- PHP: 8.2|8.3|8.4|8.5, must have extensions: bcmath, ctype, curl, fileinfo, json, mbstring, openssl, pdo_mysql, tokenizer, xml, mysqli, gd, redis, pcntl, sockets, posix, gmp, zend opcache, zip, intl, pdo_sqlite, sqlite3
|
||||||
- Mysql: 5.7 latest version or above
|
- Mysql: 5.7 latest version or above
|
||||||
- Redis:4.0.0 or above
|
- Redis:4.0.0 or above
|
||||||
- Others: supervisor, rsync
|
- Others: supervisor, rsync
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
- TGBot
|
- TGBot
|
||||||
|
|
||||||
## 系统要求
|
## 系统要求
|
||||||
- PHP: 8.2|8.3|8.4,必须扩展:bcmath, ctype, curl, fileinfo, json, mbstring, openssl, pdo_mysql, tokenizer, xml, mysqli, gd, redis, pcntl, sockets, posix, gmp, zend opcache, zip, intl, pdo_sqlite, sqlite3
|
- PHP: 8.2|8.3|8.4|8.5,必须扩展:bcmath, ctype, curl, fileinfo, json, mbstring, openssl, pdo_mysql, tokenizer, xml, mysqli, gd, redis, pcntl, sockets, posix, gmp, zend opcache, zip, intl, pdo_sqlite, sqlite3
|
||||||
- Mysql: 5.7 最新版或以上版本
|
- Mysql: 5.7 最新版或以上版本
|
||||||
- Redis:4.0.0 或以上版本
|
- Redis:4.0.0 或以上版本
|
||||||
- 其他:supervisor, rsync
|
- 其他:supervisor, rsync
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\TorrentCustomFields\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\TorrentCustomFields\TorrentCustomFieldResource;
|
||||||
|
use Filament\Actions\CreateAction;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListTorrentCustomFields extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = TorrentCustomFieldResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\TorrentCustomFields\Schemas;
|
||||||
|
|
||||||
|
use Filament\Forms\Components\Checkbox;
|
||||||
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Forms\Components\Textarea;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
|
use Nexus\Field\Field;
|
||||||
|
|
||||||
|
class TorrentCustomFieldForm
|
||||||
|
{
|
||||||
|
public static function configure(Schema $schema): Schema
|
||||||
|
{
|
||||||
|
return $schema
|
||||||
|
->components([
|
||||||
|
TextInput::make('name')
|
||||||
|
->label(__('label.field.name'))
|
||||||
|
->helperText(__('label.field.name_help'))
|
||||||
|
->alphaDash()
|
||||||
|
->required(),
|
||||||
|
TextInput::make('label')
|
||||||
|
->label(__('label.field.field_label'))
|
||||||
|
->required(),
|
||||||
|
Select::make('type')
|
||||||
|
->options((new Field())->getTypeRadioOptions())
|
||||||
|
->label(__('label.field.type'))
|
||||||
|
->required(),
|
||||||
|
Checkbox::make('required')
|
||||||
|
->label(__('label.field.required')),
|
||||||
|
Textarea::make('help')
|
||||||
|
->label(__('label.field.help'))
|
||||||
|
->rows(3),
|
||||||
|
Textarea::make('options')
|
||||||
|
->label(__('label.field.options'))
|
||||||
|
->rows(3)
|
||||||
|
->hiddenJs("\$get('type') !== 'radio' && \$get('type') !== 'checkbox' && \$get('type') !== 'select'")
|
||||||
|
->helperText(__('label.field.options_help')),
|
||||||
|
Checkbox::make('is_single_row')
|
||||||
|
->label(__('label.field.is_single_row')),
|
||||||
|
TextInput::make('priority')
|
||||||
|
->label(__('label.priority'))
|
||||||
|
->numeric(),
|
||||||
|
Textarea::make('display')
|
||||||
|
->label(__('label.field.display'))
|
||||||
|
->rows(3)
|
||||||
|
->helperText(__('label.search_box.custom_fields_display_help')),
|
||||||
|
|
||||||
|
])
|
||||||
|
->columns(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\TorrentCustomFields\Tables;
|
||||||
|
|
||||||
|
use Filament\Actions\BulkActionGroup;
|
||||||
|
use Filament\Actions\DeleteAction;
|
||||||
|
use Filament\Actions\DeleteBulkAction;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Tables\Columns\IconColumn;
|
||||||
|
use Filament\Tables\Columns\TextColumn;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class TorrentCustomFieldsTable
|
||||||
|
{
|
||||||
|
public static function configure(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
TextColumn::make('id'),
|
||||||
|
TextColumn::make('name')->label(__('label.field.name')),
|
||||||
|
TextColumn::make('label')->label(__('label.field.field_label')),
|
||||||
|
TextColumn::make('type')->label(__('label.field.type')),
|
||||||
|
IconColumn::make('required')->boolean()->label(__('label.field.required')),
|
||||||
|
IconColumn::make('is_single_row')->boolean()->label(__('label.field.is_single_row')),
|
||||||
|
TextColumn::make('priority')->label(__('label.priority')),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->recordActions([
|
||||||
|
EditAction::make(),
|
||||||
|
DeleteAction::make(),
|
||||||
|
])
|
||||||
|
->toolbarActions([
|
||||||
|
BulkActionGroup::make([
|
||||||
|
DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\TorrentCustomFields;
|
||||||
|
|
||||||
|
use App\Filament\Resources\TorrentCustomFields\Pages\ListTorrentCustomFields;
|
||||||
|
use App\Filament\Resources\TorrentCustomFields\Schemas\TorrentCustomFieldForm;
|
||||||
|
use App\Filament\Resources\TorrentCustomFields\Tables\TorrentCustomFieldsTable;
|
||||||
|
use App\Models\TorrentCustomField;
|
||||||
|
use BackedEnum;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
|
use Filament\Support\Icons\Heroicon;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use UnitEnum;
|
||||||
|
|
||||||
|
class TorrentCustomFieldResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = TorrentCustomField::class;
|
||||||
|
|
||||||
|
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||||
|
|
||||||
|
protected static string|null|UnitEnum $navigationGroup = 'Section';
|
||||||
|
|
||||||
|
protected static ?int $navigationSort = 12;
|
||||||
|
|
||||||
|
public static function getNavigationLabel(): string
|
||||||
|
{
|
||||||
|
return __('label.field.label');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getBreadcrumb(): string
|
||||||
|
{
|
||||||
|
return self::getNavigationLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function form(Schema $schema): Schema
|
||||||
|
{
|
||||||
|
return TorrentCustomFieldForm::configure($schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return TorrentCustomFieldsTable::configure($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => ListTorrentCustomFields::route('/'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\User;
|
||||||
|
|
||||||
|
use App\Filament\Resources\User\UserPasskeyResource\Pages;
|
||||||
|
use App\Models\Passkey;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
class UserPasskeyResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = Passkey::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'heroicon-o-key';
|
||||||
|
|
||||||
|
protected static ?string $navigationGroup = 'User';
|
||||||
|
|
||||||
|
protected static ?int $navigationSort = 12;
|
||||||
|
|
||||||
|
public static function getNavigationLabel(): string
|
||||||
|
{
|
||||||
|
return __('passkey.passkey');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getBreadcrumb(): string
|
||||||
|
{
|
||||||
|
return self::getNavigationLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
//
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('id')->sortable()
|
||||||
|
,
|
||||||
|
Tables\Columns\TextColumn::make('user_id')
|
||||||
|
->formatStateUsing(fn($state) => username_for_admin($state))
|
||||||
|
->label(__('label.username'))
|
||||||
|
,
|
||||||
|
Tables\Columns\TextColumn::make('AAGUID')
|
||||||
|
->label("AAGUID")
|
||||||
|
,
|
||||||
|
Tables\Columns\TextColumn::make('credential_id')
|
||||||
|
->label(__('passkey.fields.credential_id'))
|
||||||
|
,
|
||||||
|
Tables\Columns\TextColumn::make('counter')
|
||||||
|
->label(__('passkey.fields.counter'))
|
||||||
|
,
|
||||||
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
|
->label(__('label.created_at'))
|
||||||
|
,
|
||||||
|
])
|
||||||
|
->defaultSort('id', 'desc')
|
||||||
|
->filters([
|
||||||
|
Tables\Filters\Filter::make('user_id')
|
||||||
|
->form([
|
||||||
|
Forms\Components\TextInput::make('uid')
|
||||||
|
->label(__('label.username'))
|
||||||
|
->placeholder('UID')
|
||||||
|
,
|
||||||
|
])->query(function (Builder $query, array $data) {
|
||||||
|
return $query->when($data['uid'], fn(Builder $query, $value) => $query->where("user_id", $value));
|
||||||
|
})
|
||||||
|
,
|
||||||
|
Tables\Filters\Filter::make('credential_id')
|
||||||
|
->form([
|
||||||
|
Forms\Components\TextInput::make('credential_id')
|
||||||
|
->label(__('passkey.fields.credential_id'))
|
||||||
|
->placeholder('Credential ID')
|
||||||
|
,
|
||||||
|
])->query(function (Builder $query, array $data) {
|
||||||
|
return $query->when($data['credential_id'], fn(Builder $query, $value) => $query->where("credential_id", $value));
|
||||||
|
})
|
||||||
|
,
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\DeleteAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ManageUserPasskey::route('/'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\User\UserPasskeyResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\PageListSingle;
|
||||||
|
use App\Filament\Resources\User\UserPasskeyResource;
|
||||||
|
|
||||||
|
class ManageUserPasskey extends PageListSingle
|
||||||
|
{
|
||||||
|
protected static string $resource = UserPasskeyResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,7 +47,7 @@ class CalculateUserSeedBonus implements ShouldQueue
|
|||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
|
|
||||||
public $timeout = 3600;
|
public $timeout = 120;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取任务时,应该通过的中间件。
|
* 获取任务时,应该通过的中间件。
|
||||||
@@ -72,6 +72,7 @@ class CalculateUserSeedBonus implements ShouldQueue
|
|||||||
$this->requestId, $this->beginUid, $this->endUid, $this->idStr, $this->idRedisKey
|
$this->requestId, $this->beginUid, $this->endUid, $this->idStr, $this->idRedisKey
|
||||||
);
|
);
|
||||||
do_log("$logPrefix, job start ...");
|
do_log("$logPrefix, job start ...");
|
||||||
|
|
||||||
$haremAdditionFactor = Setting::get('bonus.harem_addition');
|
$haremAdditionFactor = Setting::get('bonus.harem_addition');
|
||||||
$officialAdditionFactor = Setting::get('bonus.official_addition');
|
$officialAdditionFactor = Setting::get('bonus.official_addition');
|
||||||
$donortimes_bonus = Setting::get('bonus.donortimes');
|
$donortimes_bonus = Setting::get('bonus.donortimes');
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
class Passkey extends NexusModel
|
||||||
|
{
|
||||||
|
protected $table = 'user_passkeys';
|
||||||
|
|
||||||
|
public $timestamps = true;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'id', 'user_id', 'AAGUID', 'credential_id', 'public_key', 'counter',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'user_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function AAGUID() {
|
||||||
|
$guid = $this->AAGUID;
|
||||||
|
return sprintf(
|
||||||
|
'%s-%s-%s-%s-%s',
|
||||||
|
substr($guid, 0, 8),
|
||||||
|
substr($guid, 8, 4),
|
||||||
|
substr($guid, 12, 4),
|
||||||
|
substr($guid, 16, 4),
|
||||||
|
substr($guid, 20, 12)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ use App\Models\SecondIcon;
|
|||||||
use App\Models\Source;
|
use App\Models\Source;
|
||||||
use App\Models\Standard;
|
use App\Models\Standard;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
|
use App\Models\TorrentCustomField;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Policies\CodecPolicy;
|
use App\Policies\CodecPolicy;
|
||||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||||
@@ -34,6 +35,7 @@ class AuthServiceProvider extends ServiceProvider
|
|||||||
Category::class => CodecPolicy::class,
|
Category::class => CodecPolicy::class,
|
||||||
Icon::class => CodecPolicy::class,
|
Icon::class => CodecPolicy::class,
|
||||||
SecondIcon::class => CodecPolicy::class,
|
SecondIcon::class => CodecPolicy::class,
|
||||||
|
TorrentCustomField::class => CodecPolicy::class,
|
||||||
|
|
||||||
Codec::class => CodecPolicy::class,
|
Codec::class => CodecPolicy::class,
|
||||||
AudioCodec::class => CodecPolicy::class,
|
AudioCodec::class => CodecPolicy::class,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class CleanupRepository extends BaseRepository
|
|||||||
|
|
||||||
private static int $oneTaskSeconds = 0;
|
private static int $oneTaskSeconds = 0;
|
||||||
|
|
||||||
private static int $scanSize = 1000;
|
private static int $scanSize = 500;
|
||||||
|
|
||||||
public static function recordBatch(\Redis $redis, $uid, $torrentId)
|
public static function recordBatch(\Redis $redis, $uid, $torrentId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,252 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Models\Passkey;
|
||||||
|
use Exception;
|
||||||
|
use lbuchs\WebAuthn\WebAuthn;
|
||||||
|
use Nexus\Database\NexusDB;
|
||||||
|
use Nexus\Nexus;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class UserPasskeyRepository extends BaseRepository
|
||||||
|
{
|
||||||
|
|
||||||
|
public static function createWebAuthn()
|
||||||
|
{
|
||||||
|
$formats = ['android-key', 'android-safetynet', 'apple', 'fido-u2f', 'packed', 'tpm', 'none'];
|
||||||
|
return new WebAuthn(get_setting('basic.SITENAME'), nexus()->getRequestHost(), $formats);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getCreateArgs($userId, $userName)
|
||||||
|
{
|
||||||
|
$WebAuthn = self::createWebAuthn();
|
||||||
|
|
||||||
|
$passkey = Passkey::query()->where('user_id', '=', $userId)->get();
|
||||||
|
|
||||||
|
$credentialIds = array_map(function ($item) {
|
||||||
|
return hex2bin($item['credential_id']);
|
||||||
|
}, $passkey->toArray());
|
||||||
|
|
||||||
|
$createArgs = $WebAuthn->getCreateArgs(bin2hex($userId), $userName, $userName, 60 * 4, true, 'preferred', null, $credentialIds);
|
||||||
|
|
||||||
|
NexusDB::cache_put("{$userId}_passkey_challenge", $WebAuthn->getChallenge(), 60 * 4);
|
||||||
|
|
||||||
|
return $createArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function processCreate($userId, $clientDataJSON, $attestationObject)
|
||||||
|
{
|
||||||
|
$WebAuthn = self::createWebAuthn();
|
||||||
|
|
||||||
|
$clientDataJSON = !empty($clientDataJSON) ? base64_decode($clientDataJSON) : null;
|
||||||
|
$attestationObject = !empty($attestationObject) ? base64_decode($attestationObject) : null;
|
||||||
|
$challenge = NexusDB::cache_get("{$userId}_passkey_challenge") ?? null;
|
||||||
|
|
||||||
|
$data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $challenge, false, true, false);
|
||||||
|
|
||||||
|
self::insertUserPasskey(
|
||||||
|
$userId,
|
||||||
|
bin2hex($data->AAGUID),
|
||||||
|
bin2hex($data->credentialId),
|
||||||
|
$data->credentialPublicKey,
|
||||||
|
$data->signatureCounter
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getGetArgs()
|
||||||
|
{
|
||||||
|
$WebAuthn = self::createWebAuthn();
|
||||||
|
|
||||||
|
$getArgs = $WebAuthn->getGetArgs(null, 60 * 4, true, true, true, true, true, 'preferred');
|
||||||
|
|
||||||
|
return $getArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function insertUserPasskey($userId, $AAGUID, $credentialId, $publicKey, $counter)
|
||||||
|
{
|
||||||
|
$params = [
|
||||||
|
'user_id' => $userId,
|
||||||
|
'AAGUID' => $AAGUID,
|
||||||
|
'credential_id' => $credentialId,
|
||||||
|
'public_key' => $publicKey,
|
||||||
|
'counter' => $counter,
|
||||||
|
];
|
||||||
|
Passkey::query()->create($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function processGet($challenge, $id, $clientDataJSON, $authenticatorData, $signature, $userHandle)
|
||||||
|
{
|
||||||
|
$WebAuthn = self::createWebAuthn();
|
||||||
|
|
||||||
|
$clientDataJSON = !empty($clientDataJSON) ? base64_decode($clientDataJSON) : null;
|
||||||
|
$id = !empty($id) ? base64_decode($id) : null;
|
||||||
|
$authenticatorData = !empty($authenticatorData) ? base64_decode($authenticatorData) : null;
|
||||||
|
$signature = !empty($signature) ? base64_decode($signature) : null;
|
||||||
|
$userHandle = !empty($userHandle) ? base64_decode($userHandle) : null;
|
||||||
|
$challenge = !empty($challenge) ? base64_decode($challenge) : null;
|
||||||
|
|
||||||
|
$passkey = Passkey::query()->where('credential_id', '=', bin2hex($id))->first();
|
||||||
|
$credentialPublicKey = $passkey->public_key;
|
||||||
|
|
||||||
|
if ($credentialPublicKey === null) {
|
||||||
|
throw new RuntimeException(nexus_trans('passkey.passkey_unknown'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($userHandle !== bin2hex($passkey->user_id)) {
|
||||||
|
throw new RuntimeException(nexus_trans('passkey.passkey_invalid'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $credentialPublicKey, $challenge, null, 'preferred');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new RuntimeException(nexus_trans('passkey.passkey_error') . "\n" . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $passkey->user;
|
||||||
|
if (!$user) {
|
||||||
|
throw new RuntimeException(nexus_trans('passkey.passkey_user_not_found'));
|
||||||
|
}
|
||||||
|
$user->checkIsNormal();
|
||||||
|
|
||||||
|
$ip = getip();
|
||||||
|
$userRep = new UserRepository();
|
||||||
|
$userRep->saveLoginLog($user->id, $ip, 'Web', true);
|
||||||
|
|
||||||
|
logincookie($user->id, $user->auth_key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function delete($userId, $credentialId)
|
||||||
|
{
|
||||||
|
return Passkey::query()->where('user_id', '=', $userId)->where('credential_id', '=', $credentialId)->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getList($userId)
|
||||||
|
{
|
||||||
|
return Passkey::query()->where('user_id', '=', $userId)->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAaguids()
|
||||||
|
{
|
||||||
|
return NexusDB::remember("aaguids", 60 * 60 * 24 * 14, function () {
|
||||||
|
return json_decode(file_get_contents("https://raw.githubusercontent.com/passkeydeveloper/passkey-authenticator-aaguids/refs/heads/main/combined_aaguid.json"), true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static $passkeyvg = 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20216%20216%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%2F%3E%3Cpath%20style%3D%22fill%3Anone%22%20d%3D%22M0%200h216v216H0z%22%2F%3E%3Cpath%20d%3D%22M172.32%2096.79c0%2013.78-8.48%2025.5-20.29%2029.78l7.14%2011.83-10.57%2013%2010.57%2012.71-17.04%2022.87-12.01-12.82V125.7c-10.68-4.85-18.15-15.97-18.15-28.91%200-17.4%2013.51-31.51%2030.18-31.51%2016.66%200%2030.17%2014.11%2030.17%2031.51m-30.18%204.82c4.02%200%207.28-3.4%207.28-7.6s-3.26-7.61-7.28-7.61-7.28%203.4-7.28%207.61c-.01%204.2%203.26%207.6%207.28%207.6%22%20style%3D%22fill-rule%3Aevenodd%3Bclip-rule%3Aevenodd%3Bfill%3A%23353535%22%2F%3E%3Cpath%20d%3D%22M172.41%2096.88c0%2013.62-8.25%2025.23-19.83%2029.67l6.58%2011.84-9.73%2013%209.73%2012.71-17.03%2023.05v-85.54c4.02%200%207.28-3.41%207.28-7.6%200-4.2-3.26-7.61-7.28-7.61V65.28c16.73%200%2030.28%2014.15%2030.28%2031.6m-52.17%2034.55c-9.75-8-16.3-20.3-17.2-34.27H50.8c-10.96%200-19.84%209.01-19.84%2020.13v25.17c0%205.56%204.44%2010.07%209.92%2010.07h69.44c5.48%200%209.92-4.51%209.92-10.07z%22%20style%3D%22fill-rule%3Aevenodd%3Bclip-rule%3Aevenodd%22%2F%3E%3Cpath%20d%3D%22M73.16%2091.13c-2.42-.46-4.82-.89-7.11-1.86-8.65-3.63-13.69-10.32-15.32-19.77-1.12-6.47-.59-12.87%202.03-18.92%203.72-8.6%2010.39-13.26%2019.15-14.84%205.24-.94%2010.46-.73%2015.5%201.15%207.59%202.82%2012.68%208.26%2015.03%2016.24%202.38%208.05%202.03%2016.1-1.56%2023.72-3.72%207.96-10.21%2012.23-18.42%2013.9-.68.14-1.37.27-2.05.41-2.41-.03-4.83-.03-7.25-.03%22%20style%3D%22fill%3A%23141313%22%2F%3E%3C%2Fsvg%3E';
|
||||||
|
|
||||||
|
public static function renderLogin()
|
||||||
|
{
|
||||||
|
printf('<p id="passkey_box"><button type="button" id="passkey_login"><img style="width:32px" src="%s" alt="%s"><br>%s</button></p>', self::$passkeyvg, nexus_trans('passkey.passkey'), nexus_trans('passkey.passkey'));
|
||||||
|
?>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
if (Passkey.conditionalSupported()) {
|
||||||
|
Passkey.isCMA().then(async (isCMA) => {
|
||||||
|
if (isCMA) startPasskeyLogin(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.getElementById('passkey_login').addEventListener('click', () => {
|
||||||
|
if (!Passkey.supported()) {
|
||||||
|
layer.alert('<?php echo nexus_trans('passkey.passkey_not_supported'); ?>');
|
||||||
|
} else {
|
||||||
|
startPasskeyLogin(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const startPasskeyLogin = (conditional) => {
|
||||||
|
Passkey.checkRegistration(conditional, () => {
|
||||||
|
layer.load(2, {shade: 0.3});
|
||||||
|
}).then(() => {
|
||||||
|
if (location.search) {
|
||||||
|
const searchParams = new URLSearchParams(location.search);
|
||||||
|
location.href = searchParams.get('returnto') || '/index.php';
|
||||||
|
} else {
|
||||||
|
location.href = '/index.php';
|
||||||
|
}
|
||||||
|
}).catch((e) => {
|
||||||
|
if (e.name === 'NotAllowedError' || e.name === 'AbortError') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
layer.alert(e.message);
|
||||||
|
}).finally(() => {
|
||||||
|
layer.closeAll('loading');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function renderList($id)
|
||||||
|
{
|
||||||
|
$passkeys = self::getList($id);
|
||||||
|
printf('<button type="button" id="passkey_create">%s</button><br>%s', nexus_trans('passkey.passkey_create'), nexus_trans('passkey.passkey_desc'));
|
||||||
|
?>
|
||||||
|
<table>
|
||||||
|
<?php
|
||||||
|
if (empty($passkeys)) {
|
||||||
|
printf('<tr><td>%s</td></tr>', nexus_trans('passkey.passkey_empty'));
|
||||||
|
} else {
|
||||||
|
$AAGUIDS = self::getAaguids();
|
||||||
|
foreach ($passkeys as $passkey) {
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div style="display:flex;align-items:center;padding:4px">
|
||||||
|
<?php
|
||||||
|
$meta = $AAGUIDS[$passkey->AAGUID()];
|
||||||
|
if (isset($meta)) {
|
||||||
|
printf('<img style="width: 32px" src="%s" alt="%s" /><div style="margin-right:4px"><b>%s</b> (%s)', $meta['icon_dark'], $meta['name'], $meta['name'], $passkey->credential_id);
|
||||||
|
} else {
|
||||||
|
printf('<img style="width: 32px" src="%s" alt="%s" /><div style="margin-right:4px"><b>%s</b>', self::$passkeyvg, $passkey->credential_id, $passkey->credential_id);
|
||||||
|
}
|
||||||
|
printf('<br><b>%s</b>%s</div>', nexus_trans('passkey.passkey_created_at'), gettime($passkey->created_at));
|
||||||
|
printf('<button type="button" style="margin-left:auto" data-passkey-id="%s">%s</button>', $passkey->credential_id, nexus_trans('passkey.passkey_delete'))
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
} ?>
|
||||||
|
</table>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
document.getElementById('passkey_create').addEventListener('click', () => {
|
||||||
|
if (!Passkey.supported()) {
|
||||||
|
layer.alert('<?php echo nexus_trans('passkey.passkey_not_supported'); ?>');
|
||||||
|
} else {
|
||||||
|
layer.load(2, {shade: 0.3});
|
||||||
|
Passkey.createRegistration().then(() => {
|
||||||
|
location.reload();
|
||||||
|
}).catch((e) => {
|
||||||
|
layer.alert(e.message);
|
||||||
|
}).finally(() => {
|
||||||
|
layer.closeAll('loading');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
document.querySelectorAll('button[data-passkey-id]').forEach((button) => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const credentialId = button.getAttribute('data-passkey-id');
|
||||||
|
layer.confirm('<?php echo nexus_trans('passkey.passkey_delete_confirm'); ?>', {}, function () {
|
||||||
|
layer.load(2, {shade: 0.3});
|
||||||
|
Passkey.deleteRegistration(credentialId).then(() => {
|
||||||
|
location.reload();
|
||||||
|
}).catch((e) => {
|
||||||
|
layer.alert(e.message);
|
||||||
|
}).finally(() => {
|
||||||
|
layer.closeAll('loading');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
Nexus::js('js/passkey.js', 'footer', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -19,7 +19,7 @@
|
|||||||
"files": []
|
"files": []
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.2 <8.5",
|
"php": ">=8.2 <8.6",
|
||||||
"ext-bcmath": "*",
|
"ext-bcmath": "*",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
"ext-gd": "*",
|
"ext-gd": "*",
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
"laravel/passport": "^12.0",
|
"laravel/passport": "^12.0",
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.0",
|
||||||
"laravel/tinker": "^2.5",
|
"laravel/tinker": "^2.5",
|
||||||
|
"lbuchs/webauthn": "^2.2",
|
||||||
"league/flysystem-sftp-v3": "^3.0",
|
"league/flysystem-sftp-v3": "^3.0",
|
||||||
"meilisearch/meilisearch-php": "^1.0",
|
"meilisearch/meilisearch-php": "^1.0",
|
||||||
"orangehill/iseed": "^3.0",
|
"orangehill/iseed": "^3.0",
|
||||||
|
|||||||
Generated
+768
-697
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -191,7 +191,7 @@ return [
|
|||||||
'maxJobs' => 0,
|
'maxJobs' => 0,
|
||||||
'memory' => 128,
|
'memory' => 128,
|
||||||
'tries' => 1,
|
'tries' => 1,
|
||||||
'timeout' => 600,
|
'timeout' => 120,
|
||||||
'nice' => 0,
|
'nice' => 0,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
+1
-1
@@ -66,7 +66,7 @@ return [
|
|||||||
'driver' => 'redis',
|
'driver' => 'redis',
|
||||||
'connection' => 'default',
|
'connection' => 'default',
|
||||||
'queue' => env('REDIS_QUEUE', 'nexus_queue'),
|
'queue' => env('REDIS_QUEUE', 'nexus_queue'),
|
||||||
'retry_after' => 90,
|
'retry_after' => 150,
|
||||||
'block_for' => null,
|
'block_for' => null,
|
||||||
'after_commit' => true,
|
'after_commit' => true,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (Schema::hasTable('user_passkeys')) return;
|
||||||
|
Schema::create('user_passkeys', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedMediumInteger('user_id');
|
||||||
|
$table->text('AAGUID')->nullable();
|
||||||
|
$table->text('credential_id');
|
||||||
|
$table->text('public_key');
|
||||||
|
$table->unsignedInteger('counter')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('user_passkeys');
|
||||||
|
}
|
||||||
|
};
|
||||||
+5
-27
@@ -160,7 +160,7 @@ function print_attachment($dlkey, $enableimage = true, $imageresizer = true)
|
|||||||
}
|
}
|
||||||
do_log(sprintf("driver: %s, location: %s, url: %s", $driver, $row['location'], $url));
|
do_log(sprintf("driver: %s, location: %s, url: %s", $driver, $row['location'], $url));
|
||||||
if($imageresizer == true)
|
if($imageresizer == true)
|
||||||
$onclick = " onclick=\"Previewurl('".$url."')\"";
|
$onclick = " data-zoomable data-zoom-src=\"".$url."\"";
|
||||||
else $onclick = "";
|
else $onclick = "";
|
||||||
$return = "<img id=\"attach".$id."\" style=\"max-width: 700px\" alt=\"".htmlspecialchars($row['filename'])."\" src=\"".$url."\"". $onclick . " onmouseover=\"domTT_activate(this, event, 'content', '".htmlspecialchars("<strong>".nexus_trans('attachment.size')."</strong>: ".mksize($row['filesize'])."<br />".gettime($row['added']))."', 'styleClass', 'attach', 'x', findPosition(this)[0], 'y', findPosition(this)[1]-58);\" />";
|
$return = "<img id=\"attach".$id."\" style=\"max-width: 700px\" alt=\"".htmlspecialchars($row['filename'])."\" src=\"".$url."\"". $onclick . " onmouseover=\"domTT_activate(this, event, 'content', '".htmlspecialchars("<strong>".nexus_trans('attachment.size')."</strong>: ".mksize($row['filesize'])."<br />".gettime($row['added']))."', 'styleClass', 'attach', 'x', findPosition(this)[0], 'y', findPosition(this)[1]-58);\" />";
|
||||||
}
|
}
|
||||||
@@ -240,7 +240,7 @@ function formatImg($src, $enableImageResizer, $image_max_width, $image_max_heigh
|
|||||||
}
|
}
|
||||||
return addTempCode("<img style=\"max-width: 100%\" id=\"$imgId\" alt=\"image\" src=\"$src\"" .
|
return addTempCode("<img style=\"max-width: 100%\" id=\"$imgId\" alt=\"image\" src=\"$src\"" .
|
||||||
($enableImageResizer ?
|
($enableImageResizer ?
|
||||||
" onload=\"Scale(this, $image_max_width, $image_max_height);\" onclick=\"Preview(this);\" " : "") .
|
" onload=\"Scale(this, $image_max_width, $image_max_height);\" data-zoomable " : "") .
|
||||||
" onerror=\"handleImageError(this, '$src');\" />");
|
" onerror=\"handleImageError(this, '$src');\" />");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,28 +409,6 @@ function format_comment($text, $strip_html = true, $xssclean = false, $newtab =
|
|||||||
$s = preg_replace("/\[img=([^\<\r\n\"']+?)\]/i", '', $s, -1);
|
$s = preg_replace("/\[img=([^\<\r\n\"']+?)\]/i", '', $s, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// [flash,500,400]http://www/image.swf[/flash]
|
|
||||||
if (strpos($s,"[flash") !== false) { //flash is not often used. Better check if it exist before hand
|
|
||||||
if ($enableflash) {
|
|
||||||
// $s = preg_replace("/\[flash(\,([1-9][0-9]*)\,([1-9][0-9]*))?\]((http|ftp):\/\/[^\s'\"<>]+(\.(swf)))\[\/flash\]/ei", "formatFlash('\\4', '\\2', '\\3')", $s);
|
|
||||||
$s = preg_replace_callback("/\[flash(\,([1-9][0-9]*)\,([1-9][0-9]*))?\]((http|ftp):\/\/[^\s'\"<>]+(\.(swf)))\[\/flash\]/i", function ($matches) {
|
|
||||||
return formatFlash($matches[4], $matches[2], $matches[3]);
|
|
||||||
}, $s);
|
|
||||||
} else {
|
|
||||||
$s = preg_replace("/\[flash(\,([1-9][0-9]*)\,([1-9][0-9]*))?\]((http|ftp):\/\/[^\s'\"<>]+(\.(swf)))\[\/flash\]/i", '', $s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//[flv,320,240]http://www/a.flv[/flv]
|
|
||||||
if (strpos($s,"[flv") !== false) { //flv is not often used. Better check if it exist before hand
|
|
||||||
if ($enableflash) {
|
|
||||||
// $s = preg_replace("/\[flv(\,([1-9][0-9]*)\,([1-9][0-9]*))?\]((http|ftp):\/\/[^\s'\"<>]+(\.(flv)))\[\/flv\]/ei", "formatFlv('\\4', '\\2', '\\3')", $s);
|
|
||||||
$s = preg_replace_callback("/\[flv(\,([1-9][0-9]*)\,([1-9][0-9]*))?\]((http|ftp):\/\/[^\s'\"<>]+(\.(flv)))\[\/flv\]/i", function ($matches) {
|
|
||||||
return formatFlv($matches[4], $matches[2], $matches[3]);
|
|
||||||
}, $s);
|
|
||||||
} else {
|
|
||||||
$s = preg_replace("/\[flv(\,([1-9][0-9]*)\,([1-9][0-9]*))?\]((http|ftp):\/\/[^\s'\"<>]+(\.(flv)))\[\/flv\]/i", '', $s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//[youtube,560,315]https://www.youtube.com/watch?v=DWDL3VTCcCg&ab_channel=ESPNMMA[/youtube]
|
//[youtube,560,315]https://www.youtube.com/watch?v=DWDL3VTCcCg&ab_channel=ESPNMMA[/youtube]
|
||||||
if (str_contains($s, '[youtube') && str_contains($s, 'v=')) {
|
if (str_contains($s, '[youtube') && str_contains($s, 'v=')) {
|
||||||
$s = preg_replace_callback("/\[youtube(\,([1-9][0-9]*)\,([1-9][0-9]*))?\]((http|https):\/\/[^\s'\"<>]+)\[\/youtube\]/i", function ($matches) {
|
$s = preg_replace_callback("/\[youtube(\,([1-9][0-9]*)\,([1-9][0-9]*))?\]((http|https):\/\/[^\s'\"<>]+)\[\/youtube\]/i", function ($matches) {
|
||||||
@@ -2316,7 +2294,7 @@ function validfilename($name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function validemail($email) {
|
function validemail($email) {
|
||||||
return preg_match('/^[\w.-]+@([\w.-]+\.)+[a-z]{2,6}$/is', $email);
|
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validlang($langid) {
|
function validlang($langid) {
|
||||||
@@ -2584,7 +2562,6 @@ $cssupdatedate=($cssupdatedate ? "?".htmlspecialchars($cssupdatedate) : "");
|
|||||||
<link rel="stylesheet" href="<?php echo get_forum_pic_folder()."/forumsprites.css".$cssupdatedate?>" type="text/css" />
|
<link rel="stylesheet" href="<?php echo get_forum_pic_folder()."/forumsprites.css".$cssupdatedate?>" type="text/css" />
|
||||||
<link rel="stylesheet" href="<?php echo $css_uri."theme.css".$cssupdatedate?>" type="text/css" />
|
<link rel="stylesheet" href="<?php echo $css_uri."theme.css".$cssupdatedate?>" type="text/css" />
|
||||||
<link rel="stylesheet" href="<?php echo $css_uri."DomTT.css".$cssupdatedate?>" type="text/css" />
|
<link rel="stylesheet" href="<?php echo $css_uri."DomTT.css".$cssupdatedate?>" type="text/css" />
|
||||||
<link rel="stylesheet" href="styles/curtain_imageresizer.css<?php echo $cssupdatedate?>" type="text/css" />
|
|
||||||
<link rel="stylesheet" href="styles/nexus.css<?php echo $cssupdatedate?>" type="text/css" />
|
<link rel="stylesheet" href="styles/nexus.css<?php echo $cssupdatedate?>" type="text/css" />
|
||||||
<?php
|
<?php
|
||||||
if ($CURUSER){
|
if ($CURUSER){
|
||||||
@@ -3058,7 +3035,6 @@ function stdfoot() {
|
|||||||
print("</ul>");
|
print("</ul>");
|
||||||
print("</div>");
|
print("</div>");
|
||||||
}
|
}
|
||||||
print ("<div style=\"display: none;\" id=\"lightbox\" class=\"lightbox\"></div><div style=\"display: none;\" id=\"curtain\" class=\"curtain\"></div>");
|
|
||||||
if ($add_key_shortcut != "")
|
if ($add_key_shortcut != "")
|
||||||
print($add_key_shortcut);
|
print($add_key_shortcut);
|
||||||
print("</div>");
|
print("</div>");
|
||||||
@@ -3071,10 +3047,12 @@ function stdfoot() {
|
|||||||
}
|
}
|
||||||
$js = <<<JS
|
$js = <<<JS
|
||||||
<script type="application/javascript" src="js/nexus.js"></script>
|
<script type="application/javascript" src="js/nexus.js"></script>
|
||||||
|
<script type="application/javascript" src="js/medium-zoom.min.js"></script>
|
||||||
<script type="application/javascript" src="vendor/jquery-goup-1.1.3/jquery.goup.min.js"></script>
|
<script type="application/javascript" src="vendor/jquery-goup-1.1.3/jquery.goup.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
jQuery(document).ready(function(){
|
jQuery(document).ready(function(){
|
||||||
jQuery.goup()
|
jQuery.goup()
|
||||||
|
mediumZoom('[data-zoomable]')
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
JS;
|
JS;
|
||||||
|
|||||||
@@ -1136,6 +1136,7 @@ function clear_user_cache($uid, $passkey = '')
|
|||||||
\Nexus\Database\NexusDB::cache_del("direct_permissions:$uid");
|
\Nexus\Database\NexusDB::cache_del("direct_permissions:$uid");
|
||||||
if ($passkey) {
|
if ($passkey) {
|
||||||
\Nexus\Database\NexusDB::cache_del('user_passkey_'.$passkey.'_content');//announce.php
|
\Nexus\Database\NexusDB::cache_del('user_passkey_'.$passkey.'_content');//announce.php
|
||||||
|
\Nexus\Database\NexusDB::cache_del('user_passkey_'.$passkey.'_rss');//torrentrss.php
|
||||||
}
|
}
|
||||||
$userInfo = \App\Models\User::query()->find($uid, \App\Models\User::$commonFields);
|
$userInfo = \App\Models\User::query()->find($uid, \App\Models\User::$commonFields);
|
||||||
if ($userInfo) {
|
if ($userInfo) {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ $lang_invite = array
|
|||||||
'harem_addition' => '后宫加成',
|
'harem_addition' => '后宫加成',
|
||||||
'signup_link_help' => '右键复制',
|
'signup_link_help' => '右键复制',
|
||||||
'signup_link' => '注册链接',
|
'signup_link' => '注册链接',
|
||||||
|
'text_uploaded_count' => '上传种子数量',
|
||||||
'text_seed_torrent_count' => '当前做种数',
|
'text_seed_torrent_count' => '当前做种数',
|
||||||
'text_seed_torrent_size' => '当前做种体积',
|
'text_seed_torrent_size' => '当前做种体积',
|
||||||
'text_seed_torrent_bonus_per_hour' => '当前纯做种时魔',
|
'text_seed_torrent_bonus_per_hour' => '当前纯做种时魔',
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ Best Regards,
|
|||||||
'harem_addition' => 'Harem addition',
|
'harem_addition' => 'Harem addition',
|
||||||
'signup_link_help' => 'Right click to copy',
|
'signup_link_help' => 'Right click to copy',
|
||||||
'signup_link' => 'Signup link',
|
'signup_link' => 'Signup link',
|
||||||
|
'text_uploaded_count' => 'Uploaded counts',
|
||||||
'text_seed_torrent_count' => 'Seeding counts',
|
'text_seed_torrent_count' => 'Seeding counts',
|
||||||
'text_seed_torrent_size' => 'Seeding size',
|
'text_seed_torrent_size' => 'Seeding size',
|
||||||
'text_seed_torrent_bonus_per_hour' => 'Seeding bonus per hour',
|
'text_seed_torrent_bonus_per_hour' => 'Seeding bonus per hour',
|
||||||
|
|||||||
+9
-10
@@ -55,14 +55,13 @@ class Field
|
|||||||
|
|
||||||
public function getTypeHuman($type)
|
public function getTypeHuman($type)
|
||||||
{
|
{
|
||||||
global $lang_fields;
|
|
||||||
$map = [
|
$map = [
|
||||||
self::TYPE_TEXT => $lang_fields['field_type_text'],
|
self::TYPE_TEXT => nexus_trans('field.type.text'),
|
||||||
self::TYPE_TEXTAREA => $lang_fields['field_type_textarea'],
|
self::TYPE_TEXTAREA => nexus_trans('field.type.textarea'),
|
||||||
self::TYPE_RADIO => $lang_fields['field_type_radio'],
|
self::TYPE_RADIO => nexus_trans('field.type.radio'),
|
||||||
self::TYPE_CHECKBOX => $lang_fields['field_type_checkbox'],
|
self::TYPE_CHECKBOX => nexus_trans('field.type.checkbox'),
|
||||||
self::TYPE_SELECT => $lang_fields['field_type_select'],
|
self::TYPE_SELECT => nexus_trans('field.type.select'),
|
||||||
self::TYPE_IMAGE => $lang_fields['field_type_image'],
|
self::TYPE_IMAGE => nexus_trans('field.type.image'),
|
||||||
];
|
];
|
||||||
return $map[$type] ?? '';
|
return $map[$type] ?? '';
|
||||||
}
|
}
|
||||||
@@ -439,7 +438,7 @@ JS;
|
|||||||
$customFieldDisplay = $field['display'];
|
$customFieldDisplay = $field['display'];
|
||||||
$customFieldDisplay = str_replace("<%{$field['name']}.label%>", $field['label'], $customFieldDisplay);
|
$customFieldDisplay = str_replace("<%{$field['name']}.label%>", $field['label'], $customFieldDisplay);
|
||||||
$customFieldDisplay = str_replace("<%{$field['name']}.value%>", $contentNotFormatted, $customFieldDisplay);
|
$customFieldDisplay = str_replace("<%{$field['name']}.value%>", $contentNotFormatted, $customFieldDisplay);
|
||||||
$rowByRowHtml .= tr($field['label'], format_comment($customFieldDisplay, false), 1);
|
$rowByRowHtml .= tr($field['label'], format_comment($customFieldDisplay), 1);
|
||||||
} else {
|
} else {
|
||||||
$contentFormatted = $this->formatCustomFieldValue($field, true);
|
$contentFormatted = $this->formatCustomFieldValue($field, true);
|
||||||
$rowByRowHtml .= tr($field['label'], $contentFormatted, 1);
|
$rowByRowHtml .= tr($field['label'], $contentFormatted, 1);
|
||||||
@@ -463,13 +462,13 @@ JS;
|
|||||||
switch ($customFieldWithValue['type']) {
|
switch ($customFieldWithValue['type']) {
|
||||||
case self::TYPE_TEXT:
|
case self::TYPE_TEXT:
|
||||||
case self::TYPE_TEXTAREA:
|
case self::TYPE_TEXTAREA:
|
||||||
$result .= $doFormatComment ? format_comment($fieldValue, false) : $fieldValue;
|
$result .= $doFormatComment ? format_comment($fieldValue) : $fieldValue;
|
||||||
break;
|
break;
|
||||||
case self::TYPE_IMAGE:
|
case self::TYPE_IMAGE:
|
||||||
if (substr($fieldValue, 0, 4) == 'http') {
|
if (substr($fieldValue, 0, 4) == 'http') {
|
||||||
$result .= $doFormatComment ? formatImg($fieldValue, true, 700, 0, "attach{$customFieldWithValue['id']}") : $fieldValue;
|
$result .= $doFormatComment ? formatImg($fieldValue, true, 700, 0, "attach{$customFieldWithValue['id']}") : $fieldValue;
|
||||||
} else {
|
} else {
|
||||||
$result .= $doFormatComment ? format_comment($fieldValue, false) : $fieldValue;
|
$result .= $doFormatComment ? format_comment($fieldValue) : $fieldValue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case self::TYPE_RADIO:
|
case self::TYPE_RADIO:
|
||||||
|
|||||||
+15
-3
@@ -351,9 +351,17 @@ HTML;
|
|||||||
|
|
||||||
public function isIyuu(array $bodyArr): bool
|
public function isIyuu(array $bodyArr): bool
|
||||||
{
|
{
|
||||||
return false;
|
$version = (string)($bodyArr['version'] ?? '');
|
||||||
//Not support, due to change frequently
|
switch ($version) {
|
||||||
// return isset($bodyArr['ret']) && $bodyArr['ret'] == 200;
|
case '2.0.0':
|
||||||
|
return isset($bodyArr['ret'])
|
||||||
|
&& intval($bodyArr['ret']) === 200
|
||||||
|
&& isset($bodyArr['data']['format'])
|
||||||
|
&& is_string($bodyArr['data']['format'])
|
||||||
|
&& $bodyArr['data']['format'] !== '';
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function listRatings(array $ptGenData, string $imdbLink, string $desc = ''): array
|
public function listRatings(array $ptGenData, string $imdbLink, string $desc = ''): array
|
||||||
@@ -575,6 +583,10 @@ HTML;
|
|||||||
do_log("$log, site: $site can not be updated: " . $exception->getMessage(), 'error');
|
do_log("$log, site: $site can not be updated: " . $exception->getMessage(), 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (empty($ptGenInfo)) {
|
||||||
|
do_log("$log, no pt gen info updated");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
$siteIdAndRating = $this->listRatings($ptGenInfo, $torrent->url, $extra->descr);
|
$siteIdAndRating = $this->listRatings($ptGenInfo, $torrent->url, $extra->descr);
|
||||||
foreach ($siteIdAndRating as $key => $value) {
|
foreach ($siteIdAndRating as $key => $value) {
|
||||||
if (!isset($ptGenInfo[$key]['data']) || !is_array($ptGenInfo[$key]['data'])) {
|
if (!isset($ptGenInfo[$key]['data']) || !is_array($ptGenInfo[$key]['data'])) {
|
||||||
|
|||||||
+46
-1
@@ -1,11 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
require "../include/bittorrent.php";
|
require "../include/bittorrent.php";
|
||||||
dbconn();
|
dbconn();
|
||||||
loggedinorreturn();
|
|
||||||
|
|
||||||
$action = $_POST['action'] ?? '';
|
$action = $_POST['action'] ?? '';
|
||||||
$params = $_POST['params'] ?? [];
|
$params = $_POST['params'] ?? [];
|
||||||
|
|
||||||
|
if ($action != 'getPasskeyGetArgs' && $action != 'processPasskeyGet') {
|
||||||
|
loggedinorreturn();
|
||||||
|
}
|
||||||
|
|
||||||
class AjaxInterface{
|
class AjaxInterface{
|
||||||
|
|
||||||
public static function toggleUserMedalStatus($params)
|
public static function toggleUserMedalStatus($params)
|
||||||
@@ -179,6 +182,48 @@ class AjaxInterface{
|
|||||||
$user->tokens()->where('id', $params['id'])->delete();
|
$user->tokens()->where('id', $params['id'])->delete();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getPasskeyCreateArgs($params)
|
||||||
|
{
|
||||||
|
global $CURUSER;
|
||||||
|
$rep = new \App\Repositories\UserPasskeyRepository();
|
||||||
|
return $rep->getCreateArgs($CURUSER['id'], $CURUSER['username']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function processPasskeyCreate($params)
|
||||||
|
{
|
||||||
|
global $CURUSER;
|
||||||
|
$rep = new \App\Repositories\UserPasskeyRepository();
|
||||||
|
return $rep->processCreate($CURUSER['id'], $params['clientDataJSON'], $params['attestationObject']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function deletePasskey($params)
|
||||||
|
{
|
||||||
|
global $CURUSER;
|
||||||
|
$rep = new \App\Repositories\UserPasskeyRepository();
|
||||||
|
return $rep->delete($CURUSER['id'], $params['credentialId']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPasskeyList($params)
|
||||||
|
{
|
||||||
|
global $CURUSER;
|
||||||
|
$rep = new \App\Repositories\UserPasskeyRepository();
|
||||||
|
return $rep->getList($CURUSER['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPasskeyGetArgs($params)
|
||||||
|
{
|
||||||
|
global $CURUSER;
|
||||||
|
$rep = new \App\Repositories\UserPasskeyRepository();
|
||||||
|
return $rep->getGetArgs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function processPasskeyGet($params)
|
||||||
|
{
|
||||||
|
global $CURUSER;
|
||||||
|
$rep = new \App\Repositories\UserPasskeyRepository();
|
||||||
|
return $rep->processGet($params['challenge'], $params['id'], $params['clientDataJSON'], $params['authenticatorData'], $params['signature'], $params['userHandle']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$class = 'AjaxInterface';
|
$class = 'AjaxInterface';
|
||||||
|
|||||||
+10
-44
@@ -13,7 +13,7 @@ foreach (array("passkey","info_hash","peer_id","event") as $x)
|
|||||||
$GLOBALS[$x] = $_GET[$x];
|
$GLOBALS[$x] = $_GET[$x];
|
||||||
}
|
}
|
||||||
// get integer type port, downloaded, uploaded, left from client
|
// get integer type port, downloaded, uploaded, left from client
|
||||||
foreach (array("port","downloaded","uploaded","left","compact","no_peer_id") as $x)
|
foreach (array("port","downloaded","uploaded","left") as $x)
|
||||||
{
|
{
|
||||||
$GLOBALS[$x] = intval($_GET[$x] ?? 0);
|
$GLOBALS[$x] = intval($_GET[$x] ?? 0);
|
||||||
}
|
}
|
||||||
@@ -172,7 +172,7 @@ elseif ($az['showclienterror'] == 'yes'){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check torrent based on info_hash
|
// check torrent based on info_hash
|
||||||
$checkTorrentSql = "SELECT torrents.id, size, owner, sp_state, seeders, leechers, UNIX_TIMESTAMP(added) AS ts, added, banned, hr, approval_status, price, categories.mode FROM torrents left join categories on torrents.category = categories.id WHERE " . hash_where("info_hash", $info_hash);
|
$checkTorrentSql = "SELECT torrents.id, size, owner, sp_state, seeders, leechers, times_completed, UNIX_TIMESTAMP(added) AS ts, added, banned, hr, approval_status, price, categories.mode FROM torrents left join categories on torrents.category = categories.id WHERE " . hash_where("info_hash", $info_hash);
|
||||||
if (!$torrent = $Cache->get_value('torrent_hash_'.$info_hash.'_content')){
|
if (!$torrent = $Cache->get_value('torrent_hash_'.$info_hash.'_content')){
|
||||||
$res = sql_query($checkTorrentSql);
|
$res = sql_query($checkTorrentSql);
|
||||||
$torrent = mysql_fetch_array($res);
|
$torrent = mysql_fetch_array($res);
|
||||||
@@ -251,13 +251,11 @@ $rep_dict = [
|
|||||||
"min interval" => (int)$announce_wait,
|
"min interval" => (int)$announce_wait,
|
||||||
"complete" => (int)$torrent["seeders"],
|
"complete" => (int)$torrent["seeders"],
|
||||||
"incomplete" => (int)$torrent["leechers"],
|
"incomplete" => (int)$torrent["leechers"],
|
||||||
"peers" => [], // By default it is a array object, only when `&compact=1` then it should be a string
|
"downloaded" => (int)$torrent["times_completed"],
|
||||||
|
"peers" => '',
|
||||||
|
"peers6" => '',
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($compact == 1) {
|
|
||||||
$rep_dict['peers'] = ''; // Change `peers` from array to string
|
|
||||||
$rep_dict['peers6'] = ''; // If peer use IPv6 address , we should add packed string in `peers6`
|
|
||||||
}
|
|
||||||
$GLOBALS['rep_dict'] = $rep_dict;
|
$GLOBALS['rep_dict'] = $rep_dict;
|
||||||
if ($isReAnnounce) {
|
if ($isReAnnounce) {
|
||||||
do_log("$log, [YES_RE_ANNOUNCE]");
|
do_log("$log, [YES_RE_ANNOUNCE]");
|
||||||
@@ -280,43 +278,11 @@ if (isset($event) && $event == "stopped") {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($compact == 1) {
|
if (!empty($row['ipv4'])) {
|
||||||
// $peerField = filter_var($row['ip'],FILTER_VALIDATE_IP,FILTER_FLAG_IPV6) ? 'peers6' : 'peers';
|
$rep_dict['peers'] .= inet_pton($row["ipv4"]) . pack("n", $row["port"]);
|
||||||
// $rep_dict[$peerField] .= inet_pton($row["ip"]) . pack("n", $row["port"]);
|
}
|
||||||
if (!empty($row['ipv4'])) {
|
if (!empty($row['ipv6'])) {
|
||||||
$rep_dict['peers'] .= inet_pton($row["ipv4"]) . pack("n", $row["port"]);
|
$rep_dict['peers6'] .= inet_pton($row["ipv6"]) . pack("n", $row["port"]);
|
||||||
}
|
|
||||||
if (!empty($row['ipv6'])) {
|
|
||||||
$rep_dict['peers6'] .= inet_pton($row["ipv6"]) . pack("n", $row["port"]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// $peer = [
|
|
||||||
// 'ip' => $row["ip"],
|
|
||||||
// 'port' => (int) $row["port"]
|
|
||||||
// ];
|
|
||||||
//
|
|
||||||
// if ($no_peer_id == 1) {
|
|
||||||
// $peer['peer id'] = $row["peer_id"];
|
|
||||||
// }
|
|
||||||
// $rep_dict['peers'][] = $peer;
|
|
||||||
if (!empty($row['ipv4'])) {
|
|
||||||
$peer = [
|
|
||||||
'peer_id' => $row['peer_id'],
|
|
||||||
'ip' => $row['ipv4'],
|
|
||||||
'port' => (int)$row['port'],
|
|
||||||
];
|
|
||||||
if ($no_peer_id) unset($peer['peer_id']);
|
|
||||||
$rep_dict['peers'][] = $peer;
|
|
||||||
}
|
|
||||||
if (!empty($row['ipv6'])) {
|
|
||||||
$peer = [
|
|
||||||
'peer_id' => $row['peer_id'],
|
|
||||||
'ip' => $row['ipv6'],
|
|
||||||
'port' => (int)$row['port'],
|
|
||||||
];
|
|
||||||
if ($no_peer_id) unset($peer['peer_id']);
|
|
||||||
$rep_dict['peers'][] = $peer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ if ($action == 'view') {
|
|||||||
begin_main_frame();
|
begin_main_frame();
|
||||||
echo $field->buildFieldForm();
|
echo $field->buildFieldForm();
|
||||||
} elseif ($action == 'submit') {
|
} elseif ($action == 'submit') {
|
||||||
|
die("This method is deprecated! This method is no longer available in 1.10, it does not save data correctly, please go to the management system!");
|
||||||
try {
|
try {
|
||||||
$result = $field->save($_REQUEST);
|
$result = $field->save($_REQUEST);
|
||||||
nexus_redirect('fields.php?action=view');
|
nexus_redirect('fields.php?action=view');
|
||||||
|
|||||||
+7
-5
@@ -100,14 +100,14 @@ if ($type == 'new'){
|
|||||||
} else {
|
} else {
|
||||||
inviteMenu($menuSelected);
|
inviteMenu($menuSelected);
|
||||||
if ($menuSelected == 'invitee') {
|
if ($menuSelected == 'invitee') {
|
||||||
$whereStr = "invited_by = " . sqlesc($id);
|
$whereStr = "u.invited_by = " . sqlesc($id);
|
||||||
if (!empty($_GET['status'])) {
|
if (!empty($_GET['status'])) {
|
||||||
$whereStr .= " and status = " . sqlesc($_GET['status']);
|
$whereStr .= " and u.status = " . sqlesc($_GET['status']);
|
||||||
}
|
}
|
||||||
if (!empty($_GET['enabled'])) {
|
if (!empty($_GET['enabled'])) {
|
||||||
$whereStr .= " and enabled = " . sqlesc($_GET['enabled']);
|
$whereStr .= " and u.enabled = " . sqlesc($_GET['enabled']);
|
||||||
}
|
}
|
||||||
$rel = sql_query("SELECT COUNT(*) FROM users WHERE $whereStr") or sqlerr(__FILE__, __LINE__);
|
$rel = sql_query("SELECT COUNT(*) FROM users u WHERE $whereStr") or sqlerr(__FILE__, __LINE__);
|
||||||
$arro = mysql_fetch_row($rel);
|
$arro = mysql_fetch_row($rel);
|
||||||
$number = $arro[0];
|
$number = $arro[0];
|
||||||
$textSelectOnePlease = nexus_trans('nexus.select_one_please');
|
$textSelectOnePlease = nexus_trans('nexus.select_one_please');
|
||||||
@@ -164,13 +164,14 @@ JS;
|
|||||||
} else {
|
} else {
|
||||||
list($pagertop, $pagerbottom, $limit) = pager($pageSize, $number, "?id=$id&menu=$menuSelected&");
|
list($pagertop, $pagerbottom, $limit) = pager($pageSize, $number, "?id=$id&menu=$menuSelected&");
|
||||||
$haremAdditionFactor = (float)get_setting('bonus.harem_addition');
|
$haremAdditionFactor = (float)get_setting('bonus.harem_addition');
|
||||||
$ret = sql_query("SELECT id, username, email, uploaded, downloaded, status, warned, enabled, donor, email, seed_points_per_hour, seeding_torrent_count, seeding_torrent_size, last_announce_at FROM users WHERE $whereStr $limit") or sqlerr();
|
$ret = sql_query("SELECT u.id, u.username, u.email, u.uploaded, u.downloaded, u.status, u.warned, u.enabled, u.donor, u.email, u.seed_points_per_hour, u.seeding_torrent_count, u.seeding_torrent_size, u.last_announce_at, COUNT(t.id) AS torrent_count FROM users u LEFT JOIN torrents t ON t.owner = u.id WHERE $whereStr GROUP BY u.id $limit") or sqlerr();
|
||||||
$num = mysql_num_rows($ret);
|
$num = mysql_num_rows($ret);
|
||||||
|
|
||||||
print("<tr>
|
print("<tr>
|
||||||
<td class=colhead><b>".$lang_invite['text_username']."</b></td>
|
<td class=colhead><b>".$lang_invite['text_username']."</b></td>
|
||||||
<td class=colhead><b>".$lang_invite['text_email']."</b></td>
|
<td class=colhead><b>".$lang_invite['text_email']."</b></td>
|
||||||
<td class=colhead><b>".$lang_invite['text_enabled']."</b></td>
|
<td class=colhead><b>".$lang_invite['text_enabled']."</b></td>
|
||||||
|
<td class=colhead><b>".$lang_invite['text_uploaded_count']."</b></td>
|
||||||
<td class=colhead><b>".$lang_invite['text_uploaded']."</b></td>
|
<td class=colhead><b>".$lang_invite['text_uploaded']."</b></td>
|
||||||
<td class=colhead><b>".$lang_invite['text_downloaded']."</b></td>
|
<td class=colhead><b>".$lang_invite['text_downloaded']."</b></td>
|
||||||
<td class=colhead><b>".$lang_invite['text_ratio']."</b></td>
|
<td class=colhead><b>".$lang_invite['text_ratio']."</b></td>
|
||||||
@@ -212,6 +213,7 @@ JS;
|
|||||||
<td class=rowfollow>".get_username($arr['id'])."</td>
|
<td class=rowfollow>".get_username($arr['id'])."</td>
|
||||||
<td class=rowfollow>".$arr['email']."</td>
|
<td class=rowfollow>".$arr['email']."</td>
|
||||||
<td class=rowfollow>".$arr['enabled']."</td>
|
<td class=rowfollow>".$arr['enabled']."</td>
|
||||||
|
<td class=rowfollow>" . $arr['torrent_count'] . "</td>
|
||||||
<td class=rowfollow>" . mksize($arr['uploaded']) . "</td>
|
<td class=rowfollow>" . mksize($arr['uploaded']) . "</td>
|
||||||
<td class=rowfollow>" . mksize($arr['downloaded']) . "</td>
|
<td class=rowfollow>" . mksize($arr['downloaded']) . "</td>
|
||||||
<td class=rowfollow>".$ratio."</td>
|
<td class=rowfollow>".$ratio."</td>
|
||||||
|
|||||||
Vendored
+2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -65,7 +65,7 @@ jQuery(document).ready(function () {
|
|||||||
let position = getPosition(e, imgPosition)
|
let position = getPosition(e, imgPosition)
|
||||||
let src = imgEle.attr("src")
|
let src = imgEle.attr("src")
|
||||||
if (src) {
|
if (src) {
|
||||||
previewEle.attr("src", src).css(position).fadeIn("fast");
|
previewEle.stop(true, true).attr("src", src).css(position).fadeIn("fast");
|
||||||
}
|
}
|
||||||
}).on("mouseout", selector, function (e) {
|
}).on("mouseout", selector, function (e) {
|
||||||
// previewEle.remove()
|
// previewEle.remove()
|
||||||
|
|||||||
Vendored
+169
@@ -0,0 +1,169 @@
|
|||||||
|
const Passkey = (() => {
|
||||||
|
const apiUrl = '/ajax.php';
|
||||||
|
|
||||||
|
const supported = () => {
|
||||||
|
return window.PublicKeyCredential;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionalSupported = () => {
|
||||||
|
return supported() && PublicKeyCredential.isConditionalMediationAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCMA = async () => {
|
||||||
|
return await PublicKeyCredential.isConditionalMediationAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCreateArgs = async () => {
|
||||||
|
const getArgsParams = new URLSearchParams();
|
||||||
|
getArgsParams.set('action', 'getPasskeyCreateArgs');
|
||||||
|
|
||||||
|
const response = await fetch(apiUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
body: getArgsParams,
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.ret !== 0) {
|
||||||
|
throw new Error(data.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createArgs = data.data;
|
||||||
|
recursiveBase64StrToArrayBuffer(createArgs);
|
||||||
|
return createArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createRegistration = async () => {
|
||||||
|
const createArgs = await getCreateArgs();
|
||||||
|
|
||||||
|
const cred = await navigator.credentials.create(createArgs);
|
||||||
|
|
||||||
|
const processCreateParams = new URLSearchParams();
|
||||||
|
processCreateParams.set('action', 'processPasskeyCreate');
|
||||||
|
processCreateParams.set('params[transports]', cred.response.getTransports ? cred.response.getTransports() : null)
|
||||||
|
processCreateParams.set('params[clientDataJSON]', cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null);
|
||||||
|
processCreateParams.set('params[attestationObject]', cred.response.attestationObject ? arrayBufferToBase64(cred.response.attestationObject) : null);
|
||||||
|
|
||||||
|
const response = await fetch(apiUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
body: processCreateParams,
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.ret !== 0) {
|
||||||
|
throw new Error(data.msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getGetArgs = async () => {
|
||||||
|
const getArgsParams = new URLSearchParams();
|
||||||
|
getArgsParams.set('action', 'getPasskeyGetArgs');
|
||||||
|
|
||||||
|
const response = await fetch(apiUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
body: getArgsParams,
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.ret !== 0) {
|
||||||
|
throw new Error(data.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getArgs = data.data;
|
||||||
|
recursiveBase64StrToArrayBuffer(getArgs);
|
||||||
|
return getArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
let abortController;
|
||||||
|
|
||||||
|
const checkRegistration = async (conditional, showLoading) => {
|
||||||
|
if (abortController) {
|
||||||
|
abortController.abort()
|
||||||
|
abortController = null;
|
||||||
|
}
|
||||||
|
if (!conditional) showLoading();
|
||||||
|
const getArgs = await getGetArgs();
|
||||||
|
if (conditional) {
|
||||||
|
abortController = new AbortController();
|
||||||
|
getArgs.signal = abortController.signal;
|
||||||
|
getArgs.mediation = 'conditional';
|
||||||
|
}
|
||||||
|
|
||||||
|
const cred = await navigator.credentials.get(getArgs);
|
||||||
|
|
||||||
|
if (conditional) showLoading();
|
||||||
|
|
||||||
|
const processGetParams = new URLSearchParams();
|
||||||
|
processGetParams.set('action', 'processPasskeyGet');
|
||||||
|
processGetParams.set('params[challenge]', arrayBufferToBase64(getArgs['publicKey']['challenge']));
|
||||||
|
processGetParams.set('params[id]', cred.rawId ? arrayBufferToBase64(cred.rawId) : null);
|
||||||
|
processGetParams.set('params[clientDataJSON]', cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null);
|
||||||
|
processGetParams.set('params[authenticatorData]', cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null);
|
||||||
|
processGetParams.set('params[signature]', cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null);
|
||||||
|
processGetParams.set('params[userHandle]', cred.response.userHandle ? arrayBufferToBase64(cred.response.userHandle) : null);
|
||||||
|
|
||||||
|
const response = await fetch(apiUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
body: processGetParams,
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.ret !== 0) {
|
||||||
|
throw new Error(data.msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteRegistration = async (credentialId) => {
|
||||||
|
const deleteParams = new URLSearchParams();
|
||||||
|
deleteParams.set('action', 'deletePasskey');
|
||||||
|
deleteParams.set('params[credentialId]', credentialId);
|
||||||
|
|
||||||
|
const response = await fetch(apiUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
body: deleteParams,
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.ret !== 0) {
|
||||||
|
throw new Error(data.msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const recursiveBase64StrToArrayBuffer = (obj) => {
|
||||||
|
let prefix = '=?BINARY?B?';
|
||||||
|
let suffix = '?=';
|
||||||
|
if (typeof obj === 'object') {
|
||||||
|
for (let key in obj) {
|
||||||
|
if (typeof obj[key] === 'string') {
|
||||||
|
let str = obj[key];
|
||||||
|
if (str.substring(0, prefix.length) === prefix && str.substring(str.length - suffix.length) === suffix) {
|
||||||
|
str = str.substring(prefix.length, str.length - suffix.length);
|
||||||
|
|
||||||
|
let binary_string = window.atob(str);
|
||||||
|
let len = binary_string.length;
|
||||||
|
let bytes = new Uint8Array(len);
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
bytes[i] = binary_string.charCodeAt(i);
|
||||||
|
}
|
||||||
|
obj[key] = bytes.buffer;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
recursiveBase64StrToArrayBuffer(obj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrayBufferToBase64 = (buffer) => {
|
||||||
|
let binary = '';
|
||||||
|
let bytes = new Uint8Array(buffer);
|
||||||
|
let len = bytes.byteLength;
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
binary += String.fromCharCode(bytes[i]);
|
||||||
|
}
|
||||||
|
return window.btoa(binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
supported: supported,
|
||||||
|
conditionalSupported: conditionalSupported,
|
||||||
|
createRegistration: createRegistration,
|
||||||
|
checkRegistration: checkRegistration,
|
||||||
|
deleteRegistration: deleteRegistration,
|
||||||
|
isCMA: isCMA,
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -92,6 +92,7 @@ if (isset($returnto)) {
|
|||||||
if ($useChallengeResponseAuthentication) {
|
if ($useChallengeResponseAuthentication) {
|
||||||
print('<input type="hidden" name="response" />');
|
print('<input type="hidden" name="response" />');
|
||||||
}
|
}
|
||||||
|
\App\Repositories\UserPasskeyRepository::renderLogin();
|
||||||
?>
|
?>
|
||||||
</form>
|
</form>
|
||||||
<?php
|
<?php
|
||||||
@@ -134,4 +135,5 @@ print("</td></tr></table></form></td></tr></table>");
|
|||||||
?>
|
?>
|
||||||
<?php
|
<?php
|
||||||
render_password_challenge_js("login-form", "username", "password");
|
render_password_challenge_js("login-form", "username", "password");
|
||||||
|
\Nexus\Nexus::js('js/passkey.js', 'footer', true);
|
||||||
stdfoot();
|
stdfoot();
|
||||||
|
|||||||
+14
-9
@@ -113,7 +113,7 @@ if (isset($_GET['new_offer']) && $_GET["new_offer"]){
|
|||||||
'sender' => $CURUSER['id'],
|
'sender' => $CURUSER['id'],
|
||||||
'subject' => nexus_trans('offer.msg_new_offer_subject'),
|
'subject' => nexus_trans('offer.msg_new_offer_subject'),
|
||||||
'msg' => nexus_trans('offer.msg_new_offer_msg', [
|
'msg' => nexus_trans('offer.msg_new_offer_msg', [
|
||||||
'username' => "[url=userdetails.php?id={$CURUSER['id']}]{$CURUSER['username']}[/url]",
|
'username' => "[url=userdetails.php?id={$CURUSER['id']}]{$CURUSER['username']}[/url]",
|
||||||
'offername' => "[url=offers.php?id={$id}&off_details=1]{$name}[/url]"]),
|
'offername' => "[url=offers.php?id={$id}&off_details=1]{$name}[/url]"]),
|
||||||
'added' => now(),
|
'added' => now(),
|
||||||
]);
|
]);
|
||||||
@@ -147,6 +147,9 @@ if (isset($_GET['off_details']) && $_GET["off_details"]){
|
|||||||
|
|
||||||
$res = sql_query("SELECT * FROM offers WHERE id = $id") or sqlerr(__FILE__,__LINE__);
|
$res = sql_query("SELECT * FROM offers WHERE id = $id") or sqlerr(__FILE__,__LINE__);
|
||||||
$num = mysql_fetch_array($res);
|
$num = mysql_fetch_array($res);
|
||||||
|
if (!$num) {
|
||||||
|
bark($lang_offers['text_nothing_found']);
|
||||||
|
}
|
||||||
|
|
||||||
$s = $num["name"];
|
$s = $num["name"];
|
||||||
|
|
||||||
@@ -270,7 +273,7 @@ if (isset($_GET["allow_offer"]) && $_GET["allow_offer"]) {
|
|||||||
$subject = nexus_trans("offer.msg_your_offer_allowed", [], $locale);
|
$subject = nexus_trans("offer.msg_your_offer_allowed", [], $locale);
|
||||||
$allowedtime = date("Y-m-d H:i:s");
|
$allowedtime = date("Y-m-d H:i:s");
|
||||||
//sql_query("INSERT INTO messages (sender, receiver, added, msg, subject) VALUES(0, {$arr['userid']}, '" . $allowedtime . "', " . sqlesc($msg) . ", ".sqlesc($subject).")") or sqlerr(__FILE__, __LINE__);
|
//sql_query("INSERT INTO messages (sender, receiver, added, msg, subject) VALUES(0, {$arr['userid']}, '" . $allowedtime . "', " . sqlesc($msg) . ", ".sqlesc($subject).")") or sqlerr(__FILE__, __LINE__);
|
||||||
|
|
||||||
\App\Models\Message::add([
|
\App\Models\Message::add([
|
||||||
'sender' => 0,
|
'sender' => 0,
|
||||||
'receiver' => $arr['userid'],
|
'receiver' => $arr['userid'],
|
||||||
@@ -278,7 +281,7 @@ if (isset($_GET["allow_offer"]) && $_GET["allow_offer"]) {
|
|||||||
'subject' => $subject,
|
'subject' => $subject,
|
||||||
'added' => $allowedtime,
|
'added' => $allowedtime,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
sql_query ("UPDATE offers SET allowed = 'allowed', allowedtime = '".$allowedtime."' WHERE id = $offid") or sqlerr(__FILE__,__LINE__);
|
sql_query ("UPDATE offers SET allowed = 'allowed', allowedtime = '".$allowedtime."' WHERE id = $offid") or sqlerr(__FILE__,__LINE__);
|
||||||
|
|
||||||
write_log("{$CURUSER['username']} allowed offer {$arr['name']}",'normal');
|
write_log("{$CURUSER['username']} allowed offer {$arr['name']}",'normal');
|
||||||
@@ -329,7 +332,7 @@ if (isset($_GET["finish_offer"]) && $_GET["finish_offer"]) {
|
|||||||
}
|
}
|
||||||
//===use this line if you DO HAVE subject in your PM system
|
//===use this line if you DO HAVE subject in your PM system
|
||||||
$subject = nexus_trans("offer.msg_your_offer", [], $locale).$arr['name'].nexus_trans("offer.msg_voted_on", [], $locale);
|
$subject = nexus_trans("offer.msg_your_offer", [], $locale).$arr['name'].nexus_trans("offer.msg_voted_on", [], $locale);
|
||||||
|
|
||||||
\App\Models\Message::add([
|
\App\Models\Message::add([
|
||||||
'sender' => 0,
|
'sender' => 0,
|
||||||
'subject' => $subject,
|
'subject' => $subject,
|
||||||
@@ -337,7 +340,7 @@ if (isset($_GET["finish_offer"]) && $_GET["finish_offer"]) {
|
|||||||
'added' => $finishvotetime,
|
'added' => $finishvotetime,
|
||||||
'msg' => $msg,
|
'msg' => $msg,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
//===use this line if you DO NOT subject in your PM system
|
//===use this line if you DO NOT subject in your PM system
|
||||||
//sql_query("INSERT INTO messages (sender, receiver, added, msg) VALUES(0, $arr['userid'], '" . date("Y-m-d H:i:s") . "', " . sqlesc($msg) . ")") or sqlerr(__FILE__, __LINE__);
|
//sql_query("INSERT INTO messages (sender, receiver, added, msg) VALUES(0, $arr['userid'], '" . date("Y-m-d H:i:s") . "', " . sqlesc($msg) . ")") or sqlerr(__FILE__, __LINE__);
|
||||||
write_log("{$CURUSER['username']} closed poll {$arr['name']}",'normal');
|
write_log("{$CURUSER['username']} closed poll {$arr['name']}",'normal');
|
||||||
@@ -508,10 +511,12 @@ if (isset($_GET["vote"]) && $_GET["vote"]){
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sql_query("UPDATE offers SET $vote = $vote + 1 WHERE id=".sqlesc($offerid)) or sqlerr(__FILE__,__LINE__);
|
|
||||||
|
|
||||||
$res = sql_query("SELECT users.username, offers.userid, offers.name FROM offers LEFT JOIN users ON offers.userid = users.id WHERE offers.id = ".sqlesc($offerid)) or sqlerr(__FILE__,__LINE__);
|
$res = sql_query("SELECT users.username, offers.userid, offers.name FROM offers LEFT JOIN users ON offers.userid = users.id WHERE offers.id = ".sqlesc($offerid)) or sqlerr(__FILE__,__LINE__);
|
||||||
$arr = mysql_fetch_assoc($res);
|
$arr = mysql_fetch_assoc($res);
|
||||||
|
if (!$arr) {
|
||||||
|
bark($lang_offers['text_nothing_found']);
|
||||||
|
}
|
||||||
|
sql_query("UPDATE offers SET $vote = $vote + 1 WHERE id=".sqlesc($offerid)) or sqlerr(__FILE__,__LINE__);
|
||||||
$locale = get_user_locale($arr['userid']);
|
$locale = get_user_locale($arr['userid']);
|
||||||
|
|
||||||
$rs = sql_query("SELECT yeah, against, allowed FROM offers WHERE id=".sqlesc($offerid)) or sqlerr(__FILE__,__LINE__);
|
$rs = sql_query("SELECT yeah, against, allowed FROM offers WHERE id=".sqlesc($offerid)) or sqlerr(__FILE__,__LINE__);
|
||||||
@@ -530,7 +535,7 @@ if (isset($_GET["vote"]) && $_GET["vote"]){
|
|||||||
sql_query("UPDATE offers SET allowed='allowed', allowedtime=".sqlesc($finishtime)." WHERE id=".sqlesc($offerid)) or sqlerr(__FILE__,__LINE__);
|
sql_query("UPDATE offers SET allowed='allowed', allowedtime=".sqlesc($finishtime)." WHERE id=".sqlesc($offerid)) or sqlerr(__FILE__,__LINE__);
|
||||||
$msg = nexus_trans("offer.msg_offer_voted_on", [], $locale)."[b][url=". get_protocol_prefix() . $BASEURL."/offers.php?id=$offerid&off_details=1]" . $arr['name'] . "[/url][/b].". nexus_trans("offer.msg_find_offer_option", [], $locale).$timeoutnote;
|
$msg = nexus_trans("offer.msg_offer_voted_on", [], $locale)."[b][url=". get_protocol_prefix() . $BASEURL."/offers.php?id=$offerid&off_details=1]" . $arr['name'] . "[/url][/b].". nexus_trans("offer.msg_find_offer_option", [], $locale).$timeoutnote;
|
||||||
$subject = nexus_trans("offer.msg_your_offer_allowed", [], $locale);
|
$subject = nexus_trans("offer.msg_your_offer_allowed", [], $locale);
|
||||||
|
|
||||||
\App\Models\Message::add([
|
\App\Models\Message::add([
|
||||||
'sender' => 0,
|
'sender' => 0,
|
||||||
'receiver' => $arr['userid'],
|
'receiver' => $arr['userid'],
|
||||||
@@ -556,7 +561,7 @@ if (isset($_GET["vote"]) && $_GET["vote"]){
|
|||||||
'added' => now(),
|
'added' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
write_log("System denied offer {$arr['name']}",'normal');
|
write_log("System denied offer {$arr['name']}",'normal');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,38 +228,6 @@ insert_tag(
|
|||||||
""
|
""
|
||||||
);
|
);
|
||||||
|
|
||||||
insert_tag(
|
|
||||||
$lang_tags['text_flash'],
|
|
||||||
$lang_tags['text_flash_description'],
|
|
||||||
$lang_tags['text_flash_syntax'],
|
|
||||||
sprintf($lang_tags['text_flash_example'], getSchemeAndHttpHost()),
|
|
||||||
""
|
|
||||||
);
|
|
||||||
|
|
||||||
insert_tag(
|
|
||||||
$lang_tags['text_flash_two'],
|
|
||||||
$lang_tags['text_flash_two_description'],
|
|
||||||
$lang_tags['text_flash_two_syntax'],
|
|
||||||
sprintf($lang_tags['text_flash_two_example'], getSchemeAndHttpHost()),
|
|
||||||
""
|
|
||||||
);
|
|
||||||
|
|
||||||
insert_tag(
|
|
||||||
$lang_tags['text_flv_one'],
|
|
||||||
$lang_tags['text_flv_one_description'],
|
|
||||||
$lang_tags['text_flv_one_syntax'],
|
|
||||||
sprintf($lang_tags['text_flv_one_example'], getSchemeAndHttpHost()),
|
|
||||||
""
|
|
||||||
);
|
|
||||||
|
|
||||||
insert_tag(
|
|
||||||
$lang_tags['text_flv_two'],
|
|
||||||
$lang_tags['text_flv_two_description'],
|
|
||||||
$lang_tags['text_flv_two_syntax'],
|
|
||||||
sprintf($lang_tags['text_flv_two_example'], getSchemeAndHttpHost()),
|
|
||||||
""
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
insert_tag(
|
insert_tag(
|
||||||
$lang_tags['text_youtube'],
|
$lang_tags['text_youtube'],
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ if ($cacheData && nexus_env('APP_ENV') != 'local') {
|
|||||||
header ("Content-type: text/xml");
|
header ("Content-type: text/xml");
|
||||||
die($cacheData);
|
die($cacheData);
|
||||||
}
|
}
|
||||||
dbconn();
|
dbconn(doLogin: false);
|
||||||
function hex_esc($matches) {
|
function hex_esc($matches) {
|
||||||
return sprintf("%02x", ord($matches[0]));
|
return sprintf("%02x", ord($matches[0]));
|
||||||
}
|
}
|
||||||
@@ -19,8 +19,10 @@ $dllink = false;
|
|||||||
|
|
||||||
$where = "";
|
$where = "";
|
||||||
if ($passkey){
|
if ($passkey){
|
||||||
$res = sql_query("SELECT id, enabled, parked, passkey FROM users WHERE passkey=". sqlesc($passkey)." LIMIT 1");
|
$user = \Nexus\Database\NexusDB::remember('user_passkey_'.$passkey.'_rss', 3600, function () use ($passkey) {
|
||||||
$user = mysql_fetch_array($res);
|
$res = sql_query("SELECT id, enabled, parked, passkey FROM users WHERE passkey=". sqlesc($passkey)." LIMIT 1");
|
||||||
|
return mysql_fetch_array($res);
|
||||||
|
});
|
||||||
if (!$user)
|
if (!$user)
|
||||||
die("invalid passkey");
|
die("invalid passkey");
|
||||||
elseif ($user['enabled'] == 'no' || $user['parked'] == 'yes')
|
elseif ($user['enabled'] == 'no' || $user['parked'] == 'yes')
|
||||||
|
|||||||
@@ -936,6 +936,9 @@ EOD;
|
|||||||
$twoStepY .= '</div>';
|
$twoStepY .= '</div>';
|
||||||
tr_small($lang_usercp['row_two_step_secret'], $twoStepY, 1);
|
tr_small($lang_usercp['row_two_step_secret'], $twoStepY, 1);
|
||||||
}
|
}
|
||||||
|
printf('<tr><td class="rowhead" valign="top" align="right">%s</td><td class="rowfollow" valign="top" align="left">', nexus_trans('passkey.passkey'));
|
||||||
|
\App\Repositories\UserPasskeyRepository::renderList($CURUSER['id']);
|
||||||
|
printf('</td></tr>');
|
||||||
|
|
||||||
if ($disableemailchange != 'no' && $smtptype != 'none') //system-wide setting
|
if ($disableemailchange != 'no' && $smtptype != 'none') //system-wide setting
|
||||||
tr_small($lang_usercp['row_email_address'], "<input type=\"text\" name=\"email\" style=\"width: 200px\" value=\"" . htmlspecialchars($CURUSER["email"]) . "\" /> <br /><font class=small>".$lang_usercp['text_email_address_note']."</font>", 1);
|
tr_small($lang_usercp['row_email_address'], "<input type=\"text\" name=\"email\" style=\"width: 200px\" value=\"" . htmlspecialchars($CURUSER["email"]) . "\" /> <br /><font class=small>".$lang_usercp['text_email_address_note']."</font>", 1);
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ if (count($_GET) > 0 && !$_GET['h'])
|
|||||||
if (strpos($email,'*') === False && strpos($email,'?') === False
|
if (strpos($email,'*') === False && strpos($email,'?') === False
|
||||||
&& strpos($email,'%') === False)
|
&& strpos($email,'%') === False)
|
||||||
{
|
{
|
||||||
if (validemail($email) !== 1)
|
if (!validemail($email))
|
||||||
{
|
{
|
||||||
stdmsg("Error", "Bad email.");
|
stdmsg("Error", "Bad email.");
|
||||||
stdfoot();
|
stdfoot();
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => [
|
||||||
|
'text' => 'Short text',
|
||||||
|
'textarea' => 'Long text',
|
||||||
|
'radio' => 'Horizontal single select',
|
||||||
|
'checkbox' => 'Horizontal multiple select',
|
||||||
|
'select' => 'Vertical single select',
|
||||||
|
'image' => 'Image',
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -456,4 +456,18 @@ Note: In 1.8, the 'searchbox_name' part can be omitted, i.e. the rule is 'pic/ca
|
|||||||
'select_section' => 'Selections',
|
'select_section' => 'Selections',
|
||||||
'select_section_help' => "If a selection is not defined, all options from the selection are allowed for the rule. At least one selection should be defined.",
|
'select_section_help' => "If a selection is not defined, all options from the selection are allowed for the rule. At least one selection should be defined.",
|
||||||
],
|
],
|
||||||
|
'field' => [
|
||||||
|
'label' => 'Custom field',
|
||||||
|
'name' => 'Name',
|
||||||
|
'name_help' => 'Only allow digit, alphabet, underline',
|
||||||
|
'field_label' => 'Display label',
|
||||||
|
'type' => 'Type',
|
||||||
|
'required' => 'Required',
|
||||||
|
'mod_only' => 'Mod only',
|
||||||
|
'help' => 'Help text',
|
||||||
|
'options' => 'Options',
|
||||||
|
'options_help' => 'Required when type is radio, checkbox, select. One line, one option, format: value|display text',
|
||||||
|
'is_single_row' => 'Display on a single row',
|
||||||
|
'display' => 'Custom display',
|
||||||
|
]
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'passkey_title' => 'Passkey',
|
||||||
|
'passkey' => 'Passkey',
|
||||||
|
'passkey_desc' => 'Passkey are a secure and convenient way to authenticate without the need for passwords. They can be used across multiple devices.',
|
||||||
|
'passkey_create' => 'Create Passkey',
|
||||||
|
'passkey_empty' => 'No passkey found.',
|
||||||
|
'passkey_created_at' => 'Created at:',
|
||||||
|
'passkey_delete_confirm' => 'Are you sure you want to delete this passkey? This action cannot be undone.',
|
||||||
|
'passkey_delete' => 'Delete',
|
||||||
|
'passkey_unknown' => 'An error occurred while processing your request.',
|
||||||
|
'passkey_invalid' => 'Invalid passkey data.',
|
||||||
|
'passkey_error' => 'An error occurred while processing your request. Please try again later.',
|
||||||
|
'passkey_user_not_found' => 'User not found.',
|
||||||
|
'passkey_not_supported' => 'Your browser does not support passkey. Please use a modern browser to create and manage your passkey.',
|
||||||
|
'fields' => [
|
||||||
|
'credential_id' => 'Credential ID',
|
||||||
|
'counter' => 'Counter',
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => [
|
||||||
|
'text' => '短文本',
|
||||||
|
'textarea' => '长文本',
|
||||||
|
'radio' => '横向单选',
|
||||||
|
'checkbox' => '横向多选',
|
||||||
|
'select' => '下拉单选',
|
||||||
|
'image' => '图片',
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -498,4 +498,18 @@ return [
|
|||||||
'select_section' => '选择',
|
'select_section' => '选择',
|
||||||
'select_section_help' => "如果某个选择未指定,其所有选项都符合此规则。必须至少指定一个选择。",
|
'select_section_help' => "如果某个选择未指定,其所有选项都符合此规则。必须至少指定一个选择。",
|
||||||
],
|
],
|
||||||
|
'field' => [
|
||||||
|
'label' => '自定义字段',
|
||||||
|
'name' => '名称',
|
||||||
|
'name_help' => '仅允许数字、字母、下划线',
|
||||||
|
'field_label' => '显示标签',
|
||||||
|
'type' => '类型',
|
||||||
|
'required' => '不能为空',
|
||||||
|
'mod_only' => '仅管理组可见',
|
||||||
|
'help' => '辅助说明',
|
||||||
|
'options' => '选项',
|
||||||
|
'options_help' => '类型为单选、多选、下拉时必填,一行一个,格式:选项值|选项描述文本',
|
||||||
|
'is_single_row' => '展示时单独一行',
|
||||||
|
'display' => '自定义展示',
|
||||||
|
]
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'passkey_title' => ' 通 行 密 钥 ',
|
||||||
|
'passkey' => '通行密钥',
|
||||||
|
'passkey_desc' => '通行密钥是一种安全、方便的身份验证方式,无需输入密码。通行密钥可在多台设备上使用。',
|
||||||
|
'passkey_create' => '创建通行密钥',
|
||||||
|
'passkey_empty' => '没有通行密钥。',
|
||||||
|
'passkey_created_at' => '创建于:',
|
||||||
|
'passkey_delete_confirm' => '您确实要删除此通行密钥吗?此操作无法撤消。',
|
||||||
|
'passkey_delete' => '删除',
|
||||||
|
'passkey_unknown' => '处理您的请求时出错。',
|
||||||
|
'passkey_invalid' => '通行密钥数据无效。',
|
||||||
|
'passkey_error' => '处理您的请求时出错。请稍后重试。',
|
||||||
|
'passkey_user_not_found' => '未找到用户。',
|
||||||
|
'passkey_not_supported' => '您的浏览器不支持通行密钥。请使用现代浏览器创建和管理您的通行密钥。',
|
||||||
|
'fields' => [
|
||||||
|
'credential_id' => '凭据 ID',
|
||||||
|
'counter' => '计数',
|
||||||
|
],
|
||||||
|
];
|
||||||
Reference in New Issue
Block a user