diff --git a/.editorconfig b/.editorconfig
index f7e1f865b..95f41c378 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -4,23 +4,15 @@ root = true
[*]
charset = utf-8
indent_style = space
-indent_size = 4
+indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
ij_any_keep_indents_on_empty_lines = false
-# quote_type = single
-
-[*.ts]
-ij_typescript_force_quote_style = true
-ij_typescript_use_double_quotes = false
-# ij_typescript_imports_wrap = split_into_lines
-ij_typescript_spaces_within_imports = true
-ij_typescript_spaces_within_object_type_braces = true
-ij_typescript_spaces_within_object_literal_braces = true
+quote_type = single
-[*.{html,json,twig}]
-indent_size = 2
+# [*.php]
+# indent_size = 4
[*.md]
max_line_length = off
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 58fa96f6c..8dc242a5b 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -26,12 +26,23 @@ Please delete options that are not relevant.
**Test Configuration**:
- - MyAAC Version: (latest: 0.8.15)
+ - MyAAC Version: (latest: 0.8.16)
- Browser:
+ - [ ] Chrome
+ - [ ] Firefox
+ - [ ] Opera
+ - [ ] Safari
+ - [ ] Edge
+ - [ ] Other
- Operating System:
+ - [ ] Windows
+ - [ ] Ubuntu
+ - [ ] MacOS
+ - [ ] Other
## Checklist
+ - [ ] I've created separated branch from main updated
- [ ] My code follows the style guidelines of this project
- [ ] I followed project rules, best practices, and code indentation
- [ ] I have performed a self-review of my own code
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 1a6a87f13..0824a62b8 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -9,7 +9,7 @@
- any: ['install/**/*']
"Area: Plugins":
- - any: ['plugins/**/*']
+ - any: ['plugins/**/*', 'payments/**/*']
"Area: System":
- any: ['system/**/*']
@@ -20,3 +20,5 @@
"Area: Tools":
- any: ['tools/**/*']
+"Area: Workflow":
+ - any: ['./github/**/*']
diff --git a/.github/workflows/phplint.yml b/.github/workflows/phplint.yml
index a920166b2..c8ce1fa73 100644
--- a/.github/workflows/phplint.yml
+++ b/.github/workflows/phplint.yml
@@ -1,16 +1,13 @@
name: PHP Linting
on:
- pull_request:
- branches: [master]
- push:
- branches: [master]
-
+ pull_request:
+ types: [ opened, synchronize, reopened, ready_for_review ]
jobs:
- phplint:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - uses: overtrue/phplint@3.4.0
- with:
- path: .
- options: --exclude="system/libs/polyfill-mbstring/bootstrap80.php"
+ phplint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@main
+ - uses: overtrue/phplint@3.4.0
+ with:
+ path: .
+ options: --exclude="system/libs/polyfill-mbstring/bootstrap80.php"
diff --git a/.github/workflows/prettier-php.yml b/.github/workflows/prettier-php.yml
new file mode 100644
index 000000000..ac0f5a269
--- /dev/null
+++ b/.github/workflows/prettier-php.yml
@@ -0,0 +1,62 @@
+---
+#---
+#name: Format Prettier
+#on:
+# pull_request:
+# types: [ opened, synchronize, reopened, ready_for_review ]
+# merge_group:
+# push:
+# paths:
+# - "/**"
+#jobs:
+# cancel-runs:
+# if: github.event_name == 'pull_request' && github.ref != 'refs/heads/main'
+# runs-on: ubuntu-latest
+# steps:
+# - name: Cancel Previous Runs
+# uses: styfle/cancel-workflow-action@0.9.1
+# with:
+# access_token: ${{ github.token }}
+#
+# prettier:
+# runs-on: ubuntu-latest
+# steps:
+# - name: Set up Git
+# if: ${{ github.ref != 'refs/heads/main' }}
+# run: |
+# git config --global user.email "github-actions[bot]@users.noreply.github.com"
+# git config --global user.name "GitHub Actions"
+#
+# - name: Actions checkout
+# if: ${{ github.ref != 'refs/heads/main' }}
+# uses: actions/checkout@v3
+# with:
+# repository: ${{ github.event.pull_request.head.repo.full_name }}
+# ref: ${{ github.event.pull_request.head.ref }}
+# token: ${{ secrets.GITHUB_TOKEN }}
+#
+# - name: Setup Node.js
+# uses: actions/setup-node@v3
+# with:
+# node-version: 18
+#
+# - name: Install Dependencies
+# run: npm install --save-dev prettier @prettier/plugin-php husky
+#
+# - name: Run prettier code format
+# run: npm run prettier:format
+#
+#
+## npx prettier --write $(git diff --name-only --diff-filter d | grep -E '\.(php|css|js)$' | xargs) --plugin=@prettier/plugin-php
+#
+# # npx prettier --write "**/workflow_test/*.{php,css,js}" --plugin=@prettier/plugin-php
+#
+# - name: Run add and commit
+# if: ${{ github.ref != 'refs/heads/main' }}
+# uses: EndBug/add-and-commit@v9
+# with:
+# author_name: GitHub Actions
+# author_email: github-actions[bot]@users.noreply.github.com
+# message: "PHP code format - (prettier)"
+# env:
+# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.husky/_/.gitignore b/.husky/_/.gitignore
new file mode 100644
index 000000000..f59ec20aa
--- /dev/null
+++ b/.husky/_/.gitignore
@@ -0,0 +1 @@
+*
\ No newline at end of file
diff --git a/.husky/_/husky.sh b/.husky/_/husky.sh
new file mode 100644
index 000000000..cec959a6b
--- /dev/null
+++ b/.husky/_/husky.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env sh
+if [ -z "$husky_skip_init" ]; then
+ debug () {
+ if [ "$HUSKY_DEBUG" = "1" ]; then
+ echo "husky (debug) - $1"
+ fi
+ }
+
+ readonly hook_name="$(basename -- "$0")"
+ debug "starting $hook_name..."
+
+ if [ "$HUSKY" = "0" ]; then
+ debug "HUSKY env variable is set to 0, skipping hook"
+ exit 0
+ fi
+
+ if [ -f ~/.huskyrc ]; then
+ debug "sourcing ~/.huskyrc"
+ . ~/.huskyrc
+ fi
+
+ readonly husky_skip_init=1
+ export husky_skip_init
+ sh -e "$0" "$@"
+ exitCode="$?"
+
+ if [ $exitCode != 0 ]; then
+ echo "husky - $hook_name hook exited with code $exitCode (error)"
+ fi
+
+ if [ $exitCode = 127 ]; then
+ echo "husky - command not found in PATH=$PATH"
+ fi
+
+ exit $exitCode
+fi
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 000000000..cf0c46b93
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+npx --no-install lint-staged
diff --git a/.prettierignore.txt b/.prettierignore.txt
new file mode 100644
index 000000000..aca70b982
--- /dev/null
+++ b/.prettierignore.txt
@@ -0,0 +1,15 @@
+# Ignore system libs
+
+plugins/
+system/libs/PagSeguroLibrary
+system/libs/phpmailer
+system/libs/polyfill-mbstring
+system/libs/pot
+system/libs/semver
+system/libs/Twig
+system/cache
+system/php_sessions
+node_modules
+
+# Files
+login.php
diff --git a/.prettierrc.txt b/.prettierrc.txt
new file mode 100644
index 000000000..b36f6ee80
--- /dev/null
+++ b/.prettierrc.txt
@@ -0,0 +1,43 @@
+{
+ "overrides": [
+ {
+ "files": [
+ "*.php"
+ ],
+ "options": {
+ "parser": "php",
+ "singleQuote": true,
+ "semi": false,
+ "tabWidth": 2,
+ "printWidth": 100,
+ "trailingComma": "none"
+ }
+ },
+ {
+ "files": [
+ "*.css"
+ ],
+ "options": {
+ "arrowParens": "always",
+ "bracketSameLine": false,
+ "bracketSpacing": true,
+ "semi": true,
+ "experimentalTernaries": false,
+ "singleQuote": false,
+ "jsxSingleQuote": false,
+ "quoteProps": "as-needed",
+ "trailingComma": "all",
+ "singleAttributePerLine": false,
+ "htmlWhitespaceSensitivity": "css",
+ "vueIndentScriptAndStyle": false,
+ "proseWrap": "preserve",
+ "insertPragma": true,
+ "requirePragma": false,
+ "tabWidth": 2,
+ "useTabs": false,
+ "embeddedLanguageFormatting": "auto",
+ "printWidth": 80
+ }
+ }
+ ]
+}
diff --git a/login.php b/login.php
index 1ae3483cc..04b108f89 100644
--- a/login.php
+++ b/login.php
@@ -1,36 +1,36 @@
getAttribute('startdate') : $table1->getAttribute('enddate');
- return date_create("{$date}")->format('U');
- } else {
- foreach ($table1 as $attr) {
- if ($attr) {
- return $attr->getAttribute($table2);
- }
- }
+ if ($table1) {
+ if ($date) {
+ $date = ($table2) ? $table1->getAttribute('startdate') : $table1->getAttribute('enddate');
+ return date_create("{$date}")->format('U');
+ } else {
+ foreach ($table1 as $attr) {
+ if ($attr) {
+ return $attr->getAttribute($table2);
}
+ }
}
+ }
}
$request = file_get_contents('php://input');
@@ -38,129 +38,124 @@ function parseEvent($table1, $date, $table2)
$action = $result->type ?? '';
switch ($action) {
- case 'cacheinfo':
- $playersonline = $db->query("select count(*) from `players_online`")->fetchAll();
- die(json_encode([
- 'playersonline' => (intval($playersonline[0][0])),
- 'twitchstreams' => 0,
- 'twitchviewer' => 0,
- 'gamingyoutubestreams' => 0,
- 'gamingyoutubeviewer' => 0
- ]));
- break;
-
- case 'eventschedule':
- $eventlist = [];
- $file_path = config('server_path') . 'data/XML/events.xml';
- if (!file_exists($file_path)) {
- die(json_encode([]));
- }
- $xml = new DOMDocument;
- $xml->load($file_path);
- $tableevent = $xml->getElementsByTagName('event');
-
- foreach ($tableevent as $event) {
- if ($event) {
- $eventlist[] = [
- 'colorlight' => parseEvent($event->getElementsByTagName('colors'), false, 'colorlight'),
- 'colordark' => parseEvent($event->getElementsByTagName('colors'), false, 'colordark'),
- 'description' => parseEvent($event->getElementsByTagName('description'), false, 'description'),
- 'displaypriority' => intval(parseEvent($event->getElementsByTagName('details'), false, 'displaypriority')),
- 'enddate' => intval(parseEvent($event, true, false)),
- 'isseasonal' => getBoolean(intval(parseEvent($event->getElementsByTagName('details'), false, 'isseasonal'))),
- 'name' => $event->getAttribute('name'),
- 'startdate' => intval(parseEvent($event, true, true)),
- 'specialevent' => intval(parseEvent($event->getElementsByTagName('details'), false, 'specialevent'))
- ];
- }
- }
- die(json_encode(['eventlist' => $eventlist, 'lastupdatetimestamp' => time()]));
- break;
-
- case 'boostedcreature':
- $creatureBoost = $db->query("select * from " . $db->tableName('boosted_creature'))->fetchAll();
- $bossBoost = $db->query("select * from " . $db->tableName('boosted_boss'))->fetchAll();
- die(json_encode([
- 'boostedcreature' => true,
- 'creatureraceid' => intval($creatureBoost[0]['raceid']),
- 'bossraceid' => intval($bossBoost[0]['raceid'])
- ]));
- break;
-
- case 'login':
-
- $ip = configLua('ip');
- $port = configLua('gameProtocolPort');
-
- // default world info
- $world = [
- 'id' => 0,
- 'name' => configLua('serverName'),
- 'externaladdress' => $ip,
- 'externaladdressprotected' => $ip,
- 'externaladdressunprotected' => $ip,
- 'externalport' => $port,
- 'externalportprotected' => $port,
- 'externalportunprotected' => $port,
- 'previewstate' => 0,
- 'location' => 'BRA', // BRA, EUR, USA
- 'anticheatprotection' => false,
- 'pvptype' => array_search(configLua('worldType'), ['pvp', 'no-pvp', 'pvp-enforced']),
- 'istournamentworld' => false,
- 'restrictedstore' => false,
- 'currenttournamentphase' => 2
+ case 'cacheinfo':
+ $playersonline = $db->query("SELECT count(*) FROM `players_online`")->fetchAll();
+ die(json_encode([
+ 'playersonline' => (intval($playersonline[0][0])),
+ 'twitchstreams' => 0,
+ 'twitchviewer' => 0,
+ 'gamingyoutubestreams' => 0,
+ 'gamingyoutubeviewer' => 0
+ ]));
+
+ case 'eventschedule':
+ $eventlist = [];
+ $file_path = config('server_path') . 'data/XML/events.xml';
+ if (!file_exists($file_path)) {
+ die(json_encode([]));
+ }
+ $xml = new DOMDocument;
+ $xml->load($file_path);
+ $tableevent = $xml->getElementsByTagName('event');
+
+ foreach ($tableevent as $event) {
+ if ($event) {
+ $eventlist[] = [
+ 'colorlight' => parseEvent($event->getElementsByTagName('colors'), false, 'colorlight'),
+ 'colordark' => parseEvent($event->getElementsByTagName('colors'), false, 'colordark'),
+ 'description' => parseEvent($event->getElementsByTagName('description'), false, 'description'),
+ 'displaypriority' => intval(parseEvent($event->getElementsByTagName('details'), false, 'displaypriority')),
+ 'enddate' => intval(parseEvent($event, true, false)),
+ 'isseasonal' => getBoolean(intval(parseEvent($event->getElementsByTagName('details'), false, 'isseasonal'))),
+ 'name' => $event->getAttribute('name'),
+ 'startdate' => intval(parseEvent($event, true, true)),
+ 'specialevent' => intval(parseEvent($event->getElementsByTagName('details'), false, 'specialevent'))
];
+ }
+ }
+ die(json_encode(['eventlist' => $eventlist, 'lastupdatetimestamp' => time()]));
+
+ case 'boostedcreature':
+ $creatureBoost = $db->query("SELECT * FROM " . $db->tableName('boosted_creature'))->fetchAll();
+ $bossBoost = $db->query("SELECT * FROM " . $db->tableName('boosted_boss'))->fetchAll();
+ die(json_encode([
+ 'boostedcreature' => true,
+ 'creatureraceid' => intval($creatureBoost[0]['raceid']),
+ 'bossraceid' => intval($bossBoost[0]['raceid'])
+ ]));
+
+ case 'login':
+ $ip = configLua('ip');
+ $port = configLua('gameProtocolPort');
+
+ // default world info
+ $world = [
+ 'id' => 0,
+ 'name' => configLua('serverName'),
+ 'externaladdress' => $ip,
+ 'externaladdressprotected' => $ip,
+ 'externaladdressunprotected' => $ip,
+ 'externalport' => $port,
+ 'externalportprotected' => $port,
+ 'externalportunprotected' => $port,
+ 'previewstate' => 0,
+ 'location' => 'BRA', // BRA, EUR, USA
+ 'anticheatprotection' => false,
+ 'pvptype' => array_search(configLua('worldType'), ['pvp', 'no-pvp', 'pvp-enforced']),
+ 'istournamentworld' => false,
+ 'restrictedstore' => false,
+ 'currenttournamentphase' => 2
+ ];
- $account = new OTS_Account();
- $account->findByEmail($result->email);
- $config_salt_enabled = fieldExist('salt', 'accounts');
- $current_password = encrypt(($config_salt_enabled ? $account->getCustomField('salt') : '') . $result->password);
+ $account = new OTS_Account();
+ $account->findByEmail($result->email);
+ $config_salt_enabled = fieldExist('salt', 'accounts');
+ $current_password = encrypt(($config_salt_enabled ? $account->getCustomField('salt') : '') . $result->password);
- if (!$account->isLoaded() || $account->getPassword() != $current_password) {
- sendError('Email or password is not correct.');
- }
+ if (!$account->isLoaded() || $account->getPassword() != $current_password) {
+ sendError('Email or password is not correct.');
+ }
- // common columns
- $columns = 'name, level, sex, vocation, looktype, lookhead, lookbody, looklegs, lookfeet, lookaddons, lastlogin, isreward, istutorial, ismain, hidden';
- $players = $db->query("select {$columns} from players where account_id = {$account->getId()} AND deletion = 0");
- $characters = [];
- if ($players && $players->rowCount() > 0) {
- $players = $players->fetchAll();
- foreach ($players as $player) {
- $characters[] = createChar($config, $player);
- }
- }
+ // common columns
+ $columns = 'name, level, sex, vocation, looktype, lookhead, lookbody, looklegs, lookfeet, lookaddons, lastlogin, isreward, istutorial, ismain, hidden';
+ $players = $db->query("SELECT {$columns} FROM players WHERE account_id = {$account->getId()} AND deletion = 0");
+ $characters = [];
+ if ($players && $players->rowCount() > 0) {
+ $players = $players->fetchAll();
+ foreach ($players as $player) {
+ $characters[] = createChar($config, $player);
+ }
+ }
- $query = $db->query("select `premdays`, `lastday` from `accounts` where `id` = {$account->getId()}");
- $premU = 0;
- if ($query->rowCount() > 0) {
- $premU = checkPremium($db, $query->fetch(), $account);
- } else {
- sendError("Error while fetching your account data. Please contact admin.");
- }
+ $query = $db->query("SELECT `premdays`, `lastday` FROM `accounts` WHERE `id` = {$account->getId()}");
+ $premU = 0;
+ if ($query->rowCount() > 0) {
+ $premU = checkPremium($db, $query->fetch(), $account);
+ } else {
+ sendError("Error while fetching your account data. Please contact admin.");
+ }
- $worlds = [$world];
- $playdata = compact('worlds', 'characters');
- $session = [
- 'sessionkey' => "$result->email\n$result->password",
- 'lastlogintime' => $account ? $account->getLastLogin() : 0,
- 'ispremium' => $account->isPremium(),
- 'premiumuntil' => $premU,
- 'status' => 'active', // active, frozen or suspended
- 'returnernotification' => false,
- 'showrewardnews' => true,
- 'isreturner' => true,
- 'fpstracking' => false,
- 'optiontracking' => false,
- 'tournamentticketpurchasestate' => 0,
- 'emailcoderequest' => false
- ];
- die(json_encode(compact('session', 'playdata')));
- break;
+ $worlds = [$world];
+ $playdata = compact('worlds', 'characters');
+ $session = [
+ 'sessionkey' => "$result->email\n$result->password",
+ 'lastlogintime' => $account ? $account->getLastLogin() : 0,
+ 'ispremium' => $account->isPremium(),
+ 'premiumuntil' => $premU,
+ 'status' => 'active', // active, frozen or suspended
+ 'returnernotification' => false,
+ 'showrewardnews' => true,
+ 'isreturner' => true,
+ 'fpstracking' => false,
+ 'optiontracking' => false,
+ 'tournamentticketpurchasestate' => 0,
+ 'emailcoderequest' => false
+ ];
+ die(json_encode(compact('session', 'playdata')));
- default:
- sendError("Unrecognized event {$action}.");
- break;
+ default:
+ sendError("Unrecognized event {$action}.");
+ break;
}
/**
@@ -171,25 +166,25 @@ function parseEvent($table1, $date, $table2)
*/
function createChar($config, $player)
{
- return [
- 'worldid' => 0,
- 'name' => $player['name'],
- 'ismale' => intval($player['sex']) === 1,
- 'tutorial' => (bool)$player['istutorial'],
- 'level' => intval($player['level']),
- 'vocation' => $config['vocations'][$player['vocation']],
- 'outfitid' => intval($player['looktype']),
- 'headcolor' => intval($player['lookhead']),
- 'torsocolor' => intval($player['lookbody']),
- 'legscolor' => intval($player['looklegs']),
- 'detailcolor' => intval($player['lookfeet']),
- 'addonsflags' => intval($player['lookaddons']),
- 'ishidden' => (bool)$player['hidden'],
- 'istournamentparticipant' => false,
- 'ismaincharacter' => (bool)($player['ismain']),
- 'dailyrewardstate' => intval($player['isreward']),
- 'remainingdailytournamentplaytime' => 0
- ];
+ return [
+ 'worldid' => 0,
+ 'name' => $player['name'],
+ 'ismale' => intval($player['sex']) === 1,
+ 'tutorial' => (bool)$player['istutorial'],
+ 'level' => intval($player['level']),
+ 'vocation' => $config['vocations'][$player['vocation']],
+ 'outfitid' => intval($player['looktype']),
+ 'headcolor' => intval($player['lookhead']),
+ 'torsocolor' => intval($player['lookbody']),
+ 'legscolor' => intval($player['looklegs']),
+ 'detailcolor' => intval($player['lookfeet']),
+ 'addonsflags' => intval($player['lookaddons']),
+ 'ishidden' => (bool)$player['hidden'],
+ 'istournamentparticipant' => false,
+ 'ismaincharacter' => (bool)($player['ismain']),
+ 'dailyrewardstate' => intval($player['isreward']),
+ 'remainingdailytournamentplaytime' => 0
+ ];
}
/**
@@ -201,27 +196,27 @@ function createChar($config, $player)
*/
function checkPremium($db, $query, $account)
{
- $lastDay = (int)$query['lastday'];
- $timeNow = time();
-
- if ($lastDay < $timeNow) {
- $premDays = 0;
- $lastDay = 0;
- } else if ($lastDay == 0) {
- $premDays = 0;
+ $lastDay = (int)$query['lastday'];
+ $timeNow = time();
+
+ if ($lastDay < $timeNow) {
+ $premDays = 0;
+ $lastDay = 0;
+ } else if ($lastDay == 0) {
+ $premDays = 0;
+ } else {
+ $daysLeft = (int)(($lastDay - $timeNow) / 86400);
+ $timeLeft = (int)(($lastDay - $timeNow) % 86400);
+ if ($daysLeft > 0) {
+ $premDays = $daysLeft;
+ } else if ($timeLeft > 0) {
+ $premDays = 1;
} else {
- $daysLeft = (int)(($lastDay - $timeNow) / 86400);
- $timeLeft = (int)(($lastDay - $timeNow) % 86400);
- if ($daysLeft > 0) {
- $premDays = $daysLeft;
- } else if ($timeLeft > 0) {
- $premDays = 1;
- } else {
- $premDays = 0;
- $lastDay = 0;
- }
+ $premDays = 0;
+ $lastDay = 0;
}
+ }
- $db->query("update `accounts` set `premdays` = {$premDays}, `lastday` = {$lastDay} where `id` = {$account->getId()}");
- return $lastDay;
+ $db->query("UPDATE `accounts` SET `premdays` = {$premDays}, `lastday` = {$lastDay} WHERE `id` = {$account->getId()}");
+ return $lastDay;
}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 000000000..ff419a0f3
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1029 @@
+{
+ "name": "myaac_fork",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "devDependencies": {
+ "@prettier/plugin-php": "^0.22.2",
+ "git-format-staged": "^3.1.1",
+ "husky": "^8.0.0",
+ "lint-staged": "^15.2.2",
+ "prettier": "3.2.5",
+ "pretty-quick": "^4.0.0"
+ }
+ },
+ "node_modules/@prettier/plugin-php": {
+ "version": "0.22.2",
+ "resolved": "https://registry.npmjs.org/@prettier/plugin-php/-/plugin-php-0.22.2.tgz",
+ "integrity": "sha512-md0+7tNbsP0oy+wIP3KZZc6fzx1k1jtWaMjOy/gM8yU9f2BDYEi+iHOc/UNPihYvPI28zFTbjvlhH4QXQjQwNg==",
+ "dev": true,
+ "dependencies": {
+ "linguist-languages": "^7.27.0",
+ "php-parser": "^3.1.5"
+ },
+ "peerDependencies": {
+ "prettier": "^3.0.0"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz",
+ "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+ "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+ "dev": true,
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
+ "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
+ "dev": true,
+ "dependencies": {
+ "restore-cursor": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-truncate": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
+ "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
+ "dev": true,
+ "dependencies": {
+ "slice-ansi": "^5.0.0",
+ "string-width": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/colorette": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
+ "dev": true
+ },
+ "node_modules/commander": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
+ "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==",
+ "dev": true
+ },
+ "node_modules/eventemitter3": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
+ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
+ "dev": true
+ },
+ "node_modules/execa": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+ "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^8.0.1",
+ "human-signals": "^5.0.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz",
+ "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+ "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/git-format-staged": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/git-format-staged/-/git-format-staged-3.1.1.tgz",
+ "integrity": "sha512-P749fkktaiAchFZKR7bgdvruzhvbcIDr1uRBrS9/Wdimb7wH1Twchz9gOixj8tUaHVMuXY/ckDojfOwV6AxgPA==",
+ "dev": true,
+ "bin": {
+ "git-format-staged": "git-format-staged"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+ "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16.17.0"
+ }
+ },
+ "node_modules/husky": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz",
+ "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==",
+ "dev": true,
+ "bin": {
+ "husky": "lib/bin.js"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/typicode"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
+ "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
+ "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/lilconfig": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz",
+ "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/linguist-languages": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/linguist-languages/-/linguist-languages-7.27.0.tgz",
+ "integrity": "sha512-Wzx/22c5Jsv2ag+uKy+ITanGA5hzvBZngrNGDXLTC7ZjGM6FLCYGgomauTkxNJeP9of353OM0pWqngYA180xgw==",
+ "dev": true
+ },
+ "node_modules/lint-staged": {
+ "version": "15.2.2",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz",
+ "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "5.3.0",
+ "commander": "11.1.0",
+ "debug": "4.3.4",
+ "execa": "8.0.1",
+ "lilconfig": "3.0.0",
+ "listr2": "8.0.1",
+ "micromatch": "4.0.5",
+ "pidtree": "0.6.0",
+ "string-argv": "0.3.2",
+ "yaml": "2.3.4"
+ },
+ "bin": {
+ "lint-staged": "bin/lint-staged.js"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/lint-staged"
+ }
+ },
+ "node_modules/listr2": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz",
+ "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==",
+ "dev": true,
+ "dependencies": {
+ "cli-truncate": "^4.0.0",
+ "colorette": "^2.0.20",
+ "eventemitter3": "^5.0.1",
+ "log-update": "^6.0.0",
+ "rfdc": "^1.3.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz",
+ "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==",
+ "dev": true,
+ "dependencies": {
+ "ansi-escapes": "^6.2.0",
+ "cli-cursor": "^4.0.0",
+ "slice-ansi": "^7.0.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/is-fullwidth-code-point": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz",
+ "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==",
+ "dev": true,
+ "dependencies": {
+ "get-east-asian-width": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/slice-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz",
+ "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "is-fullwidth-code-point": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/npm-run-path": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
+ "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/php-parser": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/php-parser/-/php-parser-3.1.5.tgz",
+ "integrity": "sha512-jEY2DcbgCm5aclzBdfW86GM6VEIWcSlhTBSHN1qhJguVePlYe28GhwS0yoeLYXpM2K8y6wzLwrbq814n2PHSoQ==",
+ "dev": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pidtree": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
+ "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
+ "dev": true,
+ "bin": {
+ "pidtree": "bin/pidtree.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
+ "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/pretty-quick": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-4.0.0.tgz",
+ "integrity": "sha512-M+2MmeufXb/M7Xw3Afh1gxcYpj+sK0AxEfnfF958ktFeAyi5MsKY5brymVURQLgPLV1QaF5P4pb2oFJ54H3yzQ==",
+ "dev": true,
+ "dependencies": {
+ "execa": "^5.1.1",
+ "find-up": "^5.0.0",
+ "ignore": "^5.3.0",
+ "mri": "^1.2.0",
+ "picocolors": "^1.0.0",
+ "picomatch": "^3.0.1",
+ "tslib": "^2.6.2"
+ },
+ "bin": {
+ "pretty-quick": "lib/cli.mjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "prettier": "^3.0.0"
+ }
+ },
+ "node_modules/pretty-quick/node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/pretty-quick/node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pretty-quick/node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/pretty-quick/node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pretty-quick/node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pretty-quick/node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pretty-quick/node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pretty-quick/node_modules/picomatch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
+ "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pretty-quick/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
+ "node_modules/pretty-quick/node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
+ "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
+ "dev": true,
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/restore-cursor/node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/restore-cursor/node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/restore-cursor/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
+ "node_modules/rfdc": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz",
+ "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==",
+ "dev": true
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/slice-ansi": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
+ "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.0.0",
+ "is-fullwidth-code-point": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/string-argv": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
+ "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.6.19"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz",
+ "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
+ "dev": true
+ },
+ "node_modules/type-fest": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
+ "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
+ "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/yaml": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
+ "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..d63f08276
--- /dev/null
+++ b/package.json
@@ -0,0 +1,24 @@
+{
+ "scripts": {
+ "prettier:format": "prettier --write \"**/{*.php,*.html}\" --plugin=@prettier/plugin-php",
+ "prettier:styles": "prettier --write \"**/*.css\"",
+ "prepare": "husky install"
+ },
+ "devDependencies": {
+ "@prettier/plugin-php": "^0.22.2",
+ "git-format-staged": "^3.1.1",
+ "husky": "^8.0.0",
+ "lint-staged": "^15.2.2",
+ "prettier": "3.2.5",
+ "pretty-quick": "^4.0.0"
+ },
+ "lint-staged": {
+ "**/*.{php,html}": "npm run prettier:format",
+ "*.css": "npm run prettier:styles"
+ },
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ }
+}
diff --git a/test/config.php b/test/config.php
new file mode 100644
index 000000000..2fc8b52a8
--- /dev/null
+++ b/test/config.php
@@ -0,0 +1,388 @@
+
+ * @author OpenTibiaBR
+ * @copyright 2023 MyAAC
+ * @link https://github.com/opentibiabr/myaac
+ */
+
+$config = array(
+ // directories & files
+ 'server_path' => '', // path to the server directory (same directory where config file is located)
+
+ /**
+ * Environment Setting
+ *
+ * if you use this script on your live server - set to 'prod' (production)
+ * if you want to test and debug the script locally, or develop plugins, set to 'dev' (development)
+ * WARNING: on 'dev' cache is disabled, so site will be significantly slower !!!
+ * WARNING2: on 'dev' all PHP errors/warnings are displayed
+ * Recommended: 'prod' cause of speed (page load time is better)
+ */
+ 'env' => 'prod', // 'prod' for production and 'dev' for development
+
+ 'template' => 'tibiacom', // template used by website (kathrine, tibiacom)
+ 'template_allow_change' => false, // allow users to choose their own template while browsing website?
+
+ 'vocations_amount' => 4, // how much basic vocations your server got (without promotion)
+
+ // what client version are you using on this OT?
+ // used for the Downloads page and some templates as well
+ 'client' => 1321, // 1321 = client 13.21
+
+ 'session_prefix' => 'myaac_', // must be unique for every site on your server
+ 'friendly_urls' => false, // mod_rewrite is required for this, it makes links looks more elegant to eye, and also are SEO friendly (example: https://localhost/guilds/Testing instead of https://localhost?subtopic=guilds&name=Testing). Remember to rename .htaccess.dist to .htaccess
+ 'gzip_output' => false, // gzip page content before sending it to the browser, uses less bandwidth but more cpu cycles
+
+ // gesior backward support (templates & pages)
+ // allows using gesior templates and pages with myaac
+ // might bring some performance when disabled
+ 'backward_support' => true,
+
+ // head options (html)
+ 'meta_description' => 'Tibia is a free massive multiplayer online role playing game (MMORPG).', // description of the site
+ 'meta_keywords' => 'free online game, free multiplayer game, ots, open tibia server', // keywords list separated by commas
+ 'title_separator' => ' - ',
+
+ // footer
+ 'footer' => ''/*'
Your Server © 2016. All rights reserved.'*/,
+
+ 'language' => 'en', // default language (currently only 'en' available)
+ 'language_allow_change' => false,
+
+ 'visitors_counter' => true,
+ 'visitors_counter_ttl' => 10, // how long visitor will be marked as online (in minutes)
+ 'views_counter' => true,
+
+ // cache system. by default file cache is used
+ 'cache_engine' => 'auto', // apc, apcu, eaccelerator, xcache, file, auto, or blank to disable.
+ 'cache_prefix' => 'myaac_', // have to be unique if running more MyAAC instances on the same server (except file system cache)
+
+ // database details (leave blank for auto detect from config.lua)
+ 'database_host' => '',
+ 'database_port' => '', // leave blank to default 3306
+ 'database_user' => '',
+ 'database_password' => '',
+ 'database_name' => '',
+ 'database_log' => false, // should database queries be logged and and saved into system/logs/database.log?
+ 'database_socket' => '', // set if you want to connect to database through socket (example: /var/run/mysqld/mysqld.sock)
+ 'database_persistent' => false, // use database permanent connection (like server), may speed up your site
+
+ // multiworld system (only TFS 0.3)
+ 'multiworld' => false, // use multiworld system?
+ 'worlds' => array( // list of worlds
+ //'1' => 'Your World Name',
+ //'2' => 'Your Second World Name'
+ ),
+
+ // images
+ 'outfit_images_url' => './outfit/animoutfit.php', // set to animoutfit.php for animated outfit
+ 'item_images_url' => 'images/items/', // set to images/items if you host your own items in images folder
+
+ // account
+ 'account_management' => true, // disable if you're using other method to manage users (fe. tfs account manager)
+ 'account_login_by_email' => true, // use email instead of Account Name like in latest Tibia
+ 'account_login_by_email_fallback' => false, // allow also additionally login by Account Name/Number (for users that might forget their email)
+ 'account_create_auto_login' => false, // auto login after creating account?
+ 'account_create_character_create' => true, // allow directly to create character on create account page?
+ 'account_mail_verify' => false, // force users to confirm their email addresses when registering account
+ 'account_mail_confirmed_reward' => [ // reward users for confirming their E-Mails
+ // account_mail_verify needs to be enabled too
+ 'premium_days' => 0,
+ 'coins_transferable' => 0,
+ 'coins' => 0,
+ 'message' => 'You received %d %s for confirming your E-Mail address.' // example: You received 20 coins for confirming your E-Mail address.
+ ],
+ 'account_mail_unique' => true, // email addresses cannot be duplicated? (one account = one email)
+ 'account_premium_days' => 0, // default premium days on new account
+ 'account_premium_coins' => 0, // default coins on new account
+ 'account_welcome_mail' => false, // send welcome email when user registers
+ 'account_welcome_mail_show_pass' => false, // send password in welcome email
+ 'account_mail_change' => 2, // how many days user need to change email to account - block hackers
+ 'account_country' => true, // user will be able to set country of origin when registering account, this information will be viewable in others places aswell
+ 'account_country_recognize' => true, // should country of user be automatically recognized by his IP? This makes an external API call to http://ipinfo.io
+
+ 'account_change_coin_type' => 'coins', // which coin you want to use, coins or coins_transferable to buy changes at site
+ 'account_change_character_name' => false, // can user change their character name for coins?
+ 'account_change_character_name_coins' => 250, // cost of name change
+ 'account_change_character_sex' => false, // can user change their character sex for coins?
+ 'account_change_character_sex_coins' => 150, // cost of sex change
+ 'account_change_character_main' => true, // can user change their main character for coins?
+ 'account_change_character_main_coins' => 250, // cost of main change
+ 'characters_per_account' => 10, // max. number of characters per account
+ 'account_update_info_on_register' => true, // let player update your 'Public Information' when register at first time only
+
+ // recovery key
+ 'recovery_key_length' => 15, // length of recovery key code
+ 'account_show_rk' => false,
+ 'generate_new_reckey' => true, // let player generate new recovery key, he will receive e-mail with new rec key (not display on page, hacker can't generate rec key)
+ 'generate_new_reckey_price' => 250, // coins price for new recovery key
+
+ // mail
+ 'mail_enabled' => false, // is aac maker configured to send e-mails?
+ 'mail_address' => 'no-reply@your-server.org', // server e-mail address (from:)
+ 'mail_admin' => 'your-address@your-server.org', // admin email address, where mails from contact form will be sent
+ 'mail_signature' => array( // signature that will be included at the end of every message sent using _mail function
+ 'plain' => ""/*"--\nMy Server,\nhttp://www.myserver.com"*/,
+ 'html' => ''/*'
My Server,\nmyserver.com'*/
+ ),
+ 'smtp_enabled' => false, // send by smtp or mail function (set false if use mail function, set to true if you use GMail or Microsoft Outlook)
+ 'smtp_host' => '', // mail host. smtp.gmail.com for GMail / smtp-mail.outlook.com for Microsoft Outlook
+ 'smtp_port' => 25, // 25 (default) / 465 (ssl, GMail) / 587 (tls, Microsoft Outlook)
+ 'smtp_auth' => true, // need authorization?
+ 'smtp_user' => 'admin@example.org', // here your email username
+ 'smtp_pass' => '',
+ 'smtp_secure' => '', // What kind of encryption to use on the SMTP connection. Options: '', 'ssl' (GMail) or 'tls' (Microsoft Outlook)
+ 'smtp_debug' => false, // set true to debug (you will see more info in error.log)
+
+ // reCAPTCHA (prevent spam bots)
+ 'recaptcha_enabled' => false, // enable recaptcha verification code
+ 'recaptcha_site_key' => '', // get your own site and secret keys at https://www.google.com/recaptcha
+ 'recaptcha_secret_key' => '',
+ 'recaptcha_theme' => 'light', // light, dark
+
+ // e-mail senders
+ 'send_mail_when_change_password' => true, // send e-mail with new password when change password to account
+ 'send_mail_when_generate_reckey' => true, // send e-mail with rec key (key is displayed on page anyway when generate)
+
+ // genders (aka sex)
+ 'genders' => array(
+ 0 => 'Female',
+ 1 => 'Male'
+ ),
+
+ // vocations
+ 'vocations' => array(
+ 0 => 'None',
+ 1 => 'Sorcerer',
+ 2 => 'Druid',
+ 3 => 'Paladin',
+ 4 => 'Knight',
+ 5 => 'Master Sorcerer',
+ 6 => 'Elder Druid',
+ 7 => 'Royal Paladin',
+ 8 => 'Elite Knight',
+ ),
+
+ // new character config
+ 'character_samples' => array( // vocations, format: ID_of_vocation => 'Name of Character to copy'
+ //0 => 'Rook Sample',
+ 1 => 'Sorcerer Sample',
+ 2 => 'Druid Sample',
+ 3 => 'Paladin Sample',
+ 4 => 'Knight Sample'
+ ),
+
+ 'use_character_sample_skills' => false,
+
+ // it must show limited number of players after using search in character page
+ 'characters_search_limit' => 15,
+
+ // town list used when creating character
+ // won't be displayed if there is only one item (rookgaard for example)
+ 'character_towns' => array(1),
+
+ // characters length
+ // This is the minimum and the maximum length that a player can create a character. It is highly recommend the maximum length to be 21.
+ 'character_name_min_length' => 4,
+ 'character_name_max_length' => 21,
+
+ // list of towns
+ // if you use TFS 1.3 with support for 'towns' table in database, then you can ignore this - it will be configured automatically (generated from your .OTBM map)
+ 'towns' => array(
+ 0 => 'No Town',
+ 1 => 'Tutorial City',
+ 5 => 'AbDendriel',
+ 6 => 'Carlin',
+ 8 => 'Thais',
+ 9 => 'Venore',
+ 10 => 'Ankrahmun',
+ 11 => 'Edron',
+ 12 => 'Farmine',
+ 13 => 'Darashia',
+ 14 => 'Liberty Bay',
+ 15 => 'Port Hope',
+ 16 => 'Svargrond',
+ 17 => 'Yalahar',
+ 20 => 'Rathleton'
+ ),
+
+ // guilds
+ 'guild_management' => true, // enable guild management system on the site?
+ 'guild_need_level' => 100, // min. level to form a guild
+ 'guild_need_premium' => true, // require premium account to form a guild?
+ 'guild_image_size_kb' => 80, // maximum size of the guild logo image in KB (kilobytes)
+ 'guild_description_chars_limit' => 1000, // limit of guild description
+ 'guild_description_lines_limit' => 6, // limit of lines, if description has more lines it will be showed as long text, without 'enters'
+ 'guild_motd_chars_limit' => 150, // limit of MOTD (message of the day) that is shown later in the game on the guild channel
+
+ // online page
+ 'online_record' => true, // display players record?
+ 'online_vocations' => false, // display vocation statistics?
+ 'online_vocations_images' => false, // display vocation images?
+ 'online_skulls' => true, // display skull images
+ 'online_outfit' => true,
+ 'online_afk' => false,
+
+ // support list page
+ 'team_style' => 2, // 1/2 (1 - normal table, 2 - in boxes, grouped by group id)
+ 'team_display_status' => true,
+ 'team_display_lastlogin' => true,
+ 'team_display_world' => false,
+ 'team_display_outfit' => true,
+
+ // bans page
+ 'bans_limit' => 50,
+ 'bans_display_all' => true, // should all bans be displayed? (sorted page by page)
+
+ // highscores page
+ 'highscores_vocation_box' => true, // show 'Choose a vocation' box on the highscores (allowing peoples to sort highscores by vocation)?
+ 'highscores_vocation' => true, // show player vocation under his nickname?
+ 'highscores_frags' => false, // show 'Frags' tab (best fraggers on the server)? Only 0.3
+ 'highscores_balance' => false, // show 'Balance' tab (richest players on the server)
+ 'highscores_outfit' => true, // show player outfit?
+ 'highscores_country_box' => false, // doesnt work yet! (not implemented)
+ 'highscores_groups_hidden' => 3, // this group id and higher won't be shown on the highscores
+ 'highscores_ids_hidden' => array(0), // this ids of players will be hidden on the highscores (should be ids of samples)
+ 'highscores_length' => 100, // how many records per page on highscores
+
+ // characters page
+ 'characters' => array( // what things to display on character view page (true/false in each option)
+ 'level' => true,
+ 'experience' => true,
+ 'magic_level' => true,
+ 'balance' => true,
+ 'marriage_info' => true, // only 0.3
+ 'outfit' => true,
+ 'creation_date' => true,
+ 'quests' => true,
+ 'skills' => true,
+ 'equipment' => true,
+ 'frags' => true,
+ 'deleted' => false, // should deleted characters from same account be still listed on the list of characters? When enabled it will show that character is "[DELETED]"
+ ),
+ 'quests' => array( // Canary Storages
+ 'Demon Helmet' => 40077, // Storage.Quest.U6_4.DemonHelmet.Rewards.DemonHelmet
+ 'Annihilator' => 10102,
+ 'Pits Of Inferno' => 52003, // Storage.PitsOfInferno.WeaponReward
+ 'Inquisition' => 51127, // Storage.TheInquisition.Reward
+ 'Demon Oak' => 51700,// Maybe 51700
+ 'SoulWar Quest' => 47223, // Storage.Quest.U12_40.SoulWar.QuestReward
+ 'Yalahar Quest' => 51249, // Storage.InServiceofYalahar.DoorToReward
+ //'Some Quest' => 123,
+ //'Some Quest Two' => 456,
+ ), // quests list (displayed in character view), name => storage
+
+ 'achievements_base' => 300000,
+
+ 'signature_enabled' => false,
+ 'signature_type' => 'tibian', // signature engine to use: tibian, mango, gesior
+ 'signature_cache_time' => 5, // how long to store cached file (in minutes), default 5 minutes
+ 'signature_browser_cache' => 60, // how long to cache by browser (in minutes), default 1 hour
+
+ 'allow_menu_animated' => true, // allow menu with animated gifs
+
+ // news page
+ 'news_limit' => 5, // limit of news on the latest news page
+ 'news_ticker_limit' => 5, // limit of news in tickers (mini news) (0 to disable)
+ 'news_date_format' => 'j.n.Y', // check php manual date() function for more info about this
+ 'news_author' => false, // show author of the news
+
+ // banner home
+ 'banner_status' => false,
+ 'banner_image' => '500x660.png', // templates->tibiacom->images->carousel
+ 'banner_link' => 'www.instagram.com',
+
+ // status bar
+ 'status_bar' => true,
+ 'client_link' => 'https://github.com/dudantas/tibia-client/releases/tag/13.21.13839', // link to download tibia client
+ 'discord_link' => 'https://discord.com/invite/gvTj5sh9Mp', // link to join discord channel
+ 'whatsapp_link' => '5511912345678', // wa.me/5511912345678
+ 'instagram_link' => 'profile', // www.instagram.com/profile
+ 'facebook_link' => 'page', // www.facebook.com/page
+ 'collapse_status' => true,
+
+ // events
+ 'events_xml' => 'data/xml/events.xml',
+
+ // slide
+ 'carousel_status' => true,
+ 'carousel' => array(
+ 'carousel_1' => 'runemaster_small.jpg',
+ 'carousel_2' => 'merrygarb_small.jpg',
+ 'carousel_3' => 'mothcape_small.jpg',
+ ),
+
+ // load page
+ 'pace_load' => true, // load page top bar
+ 'pace_theme' => 'flat-top', // big-counter, bounce, center-atom, center-circle, center-radar, center-simple, corner-indicator, fill-left, flash, flat-top, loading-bar, max-osx, material, minimal
+ 'pace_color' => 'white', // black, blue, green, orange, pink, purple, red, silver, white, yellow
+
+ // char bazaar
+ 'bazaar_create' => 50, // price to create auction
+ 'bazaar_tax' => 12, // tax to bid
+ 'bazaar_bid' => 50, // price to bid
+ 'bazaar_accountid' => 1, // account id to move auction character
+
+ // gifts/shop system
+ 'gifts_system' => true,
+
+ // support/system
+ 'bug_report' => true, // this configurable has no effect, its always enabled
+
+ // forum
+ 'forum' => 'site', // link to the server forum, set to "site" if you want to use build in forum system, otherwise leave empty if you aren't going to use any forum
+ 'forum_level_required' => 0, // level required to post, 0 to disable
+ 'forum_post_interval' => 30, // in seconds
+ 'forum_posts_per_page' => 20,
+ 'forum_threads_per_page' => 20,
+ // uncomment to force use table for forum
+ //'forum_table_prefix' => 'z_', // what forum mysql table to use, z_ (for gesior old forum) or myaac_ (for myaac)
+
+ // last kills
+ 'last_kills_limit' => 50, // max. number of deaths shown on the last kills page
+
+ // status, took automatically from config file if empty
+ 'status_enabled' => true, // you can disable status checking by settings this to "false"
+ 'status_ip' => '',
+ 'status_port' => '',
+ 'status_timeout' => 2, // how long to wait for the initial response from the server (default: 2 seconds)
+
+ // how often to connect to server and update status (default: every minute)
+ // if your status timeout in config.lua is bigger, that it will be used instead
+ // when server is offline, it will be checked every time web refreshes, ignoring this variable
+ 'status_interval' => 60,
+
+ // admin panel
+ 'admin_panel_modules' => 'lastlogin,coinstransferable,coins,donates',
+
+ // other
+ 'email_lai_sec_interval' => 60, // time in seconds between e-mails to one account from lost account interface, block spam
+ 'google_analytics_id' => '', // e.g.: UA-XXXXXXX-X
+ 'experiencetable_columns' => 4, // how many columns to display in experience table page. * experiencetable_rows, 5 = 500 (will show up to 500 level)
+ 'experiencetable_rows' => 500, // till how many levels in one column
+ 'date_timezone' => 'America/Sao_Paulo', // more info at http://php.net/manual/en/timezones.php
+ 'footer_show_load_time' => true, // display load time of the page in the footer
+
+ 'npc' => [],
+
+ // character name blocked
+ 'character_name_blocked' => [
+ 'prefix' => [],
+ 'names' => [],
+ 'words' => [],
+ ],
+
+ 'enablePagseguroLocal' => false, // set true to enable donate and boxes page on localhost.
+);
diff --git a/test/login.php b/test/login.php
new file mode 100644
index 000000000..04b108f89
--- /dev/null
+++ b/test/login.php
@@ -0,0 +1,222 @@
+getAttribute('startdate') : $table1->getAttribute('enddate');
+ return date_create("{$date}")->format('U');
+ } else {
+ foreach ($table1 as $attr) {
+ if ($attr) {
+ return $attr->getAttribute($table2);
+ }
+ }
+ }
+ }
+}
+
+$request = file_get_contents('php://input');
+$result = json_decode($request);
+$action = $result->type ?? '';
+
+switch ($action) {
+ case 'cacheinfo':
+ $playersonline = $db->query("SELECT count(*) FROM `players_online`")->fetchAll();
+ die(json_encode([
+ 'playersonline' => (intval($playersonline[0][0])),
+ 'twitchstreams' => 0,
+ 'twitchviewer' => 0,
+ 'gamingyoutubestreams' => 0,
+ 'gamingyoutubeviewer' => 0
+ ]));
+
+ case 'eventschedule':
+ $eventlist = [];
+ $file_path = config('server_path') . 'data/XML/events.xml';
+ if (!file_exists($file_path)) {
+ die(json_encode([]));
+ }
+ $xml = new DOMDocument;
+ $xml->load($file_path);
+ $tableevent = $xml->getElementsByTagName('event');
+
+ foreach ($tableevent as $event) {
+ if ($event) {
+ $eventlist[] = [
+ 'colorlight' => parseEvent($event->getElementsByTagName('colors'), false, 'colorlight'),
+ 'colordark' => parseEvent($event->getElementsByTagName('colors'), false, 'colordark'),
+ 'description' => parseEvent($event->getElementsByTagName('description'), false, 'description'),
+ 'displaypriority' => intval(parseEvent($event->getElementsByTagName('details'), false, 'displaypriority')),
+ 'enddate' => intval(parseEvent($event, true, false)),
+ 'isseasonal' => getBoolean(intval(parseEvent($event->getElementsByTagName('details'), false, 'isseasonal'))),
+ 'name' => $event->getAttribute('name'),
+ 'startdate' => intval(parseEvent($event, true, true)),
+ 'specialevent' => intval(parseEvent($event->getElementsByTagName('details'), false, 'specialevent'))
+ ];
+ }
+ }
+ die(json_encode(['eventlist' => $eventlist, 'lastupdatetimestamp' => time()]));
+
+ case 'boostedcreature':
+ $creatureBoost = $db->query("SELECT * FROM " . $db->tableName('boosted_creature'))->fetchAll();
+ $bossBoost = $db->query("SELECT * FROM " . $db->tableName('boosted_boss'))->fetchAll();
+ die(json_encode([
+ 'boostedcreature' => true,
+ 'creatureraceid' => intval($creatureBoost[0]['raceid']),
+ 'bossraceid' => intval($bossBoost[0]['raceid'])
+ ]));
+
+ case 'login':
+ $ip = configLua('ip');
+ $port = configLua('gameProtocolPort');
+
+ // default world info
+ $world = [
+ 'id' => 0,
+ 'name' => configLua('serverName'),
+ 'externaladdress' => $ip,
+ 'externaladdressprotected' => $ip,
+ 'externaladdressunprotected' => $ip,
+ 'externalport' => $port,
+ 'externalportprotected' => $port,
+ 'externalportunprotected' => $port,
+ 'previewstate' => 0,
+ 'location' => 'BRA', // BRA, EUR, USA
+ 'anticheatprotection' => false,
+ 'pvptype' => array_search(configLua('worldType'), ['pvp', 'no-pvp', 'pvp-enforced']),
+ 'istournamentworld' => false,
+ 'restrictedstore' => false,
+ 'currenttournamentphase' => 2
+ ];
+
+ $account = new OTS_Account();
+ $account->findByEmail($result->email);
+ $config_salt_enabled = fieldExist('salt', 'accounts');
+ $current_password = encrypt(($config_salt_enabled ? $account->getCustomField('salt') : '') . $result->password);
+
+ if (!$account->isLoaded() || $account->getPassword() != $current_password) {
+ sendError('Email or password is not correct.');
+ }
+
+ // common columns
+ $columns = 'name, level, sex, vocation, looktype, lookhead, lookbody, looklegs, lookfeet, lookaddons, lastlogin, isreward, istutorial, ismain, hidden';
+ $players = $db->query("SELECT {$columns} FROM players WHERE account_id = {$account->getId()} AND deletion = 0");
+ $characters = [];
+ if ($players && $players->rowCount() > 0) {
+ $players = $players->fetchAll();
+ foreach ($players as $player) {
+ $characters[] = createChar($config, $player);
+ }
+ }
+
+ $query = $db->query("SELECT `premdays`, `lastday` FROM `accounts` WHERE `id` = {$account->getId()}");
+ $premU = 0;
+ if ($query->rowCount() > 0) {
+ $premU = checkPremium($db, $query->fetch(), $account);
+ } else {
+ sendError("Error while fetching your account data. Please contact admin.");
+ }
+
+ $worlds = [$world];
+ $playdata = compact('worlds', 'characters');
+ $session = [
+ 'sessionkey' => "$result->email\n$result->password",
+ 'lastlogintime' => $account ? $account->getLastLogin() : 0,
+ 'ispremium' => $account->isPremium(),
+ 'premiumuntil' => $premU,
+ 'status' => 'active', // active, frozen or suspended
+ 'returnernotification' => false,
+ 'showrewardnews' => true,
+ 'isreturner' => true,
+ 'fpstracking' => false,
+ 'optiontracking' => false,
+ 'tournamentticketpurchasestate' => 0,
+ 'emailcoderequest' => false
+ ];
+ die(json_encode(compact('session', 'playdata')));
+
+ default:
+ sendError("Unrecognized event {$action}.");
+ break;
+}
+
+/**
+ * Function to create char in list
+ * @param $config
+ * @param $player
+ * @return array
+ */
+function createChar($config, $player)
+{
+ return [
+ 'worldid' => 0,
+ 'name' => $player['name'],
+ 'ismale' => intval($player['sex']) === 1,
+ 'tutorial' => (bool)$player['istutorial'],
+ 'level' => intval($player['level']),
+ 'vocation' => $config['vocations'][$player['vocation']],
+ 'outfitid' => intval($player['looktype']),
+ 'headcolor' => intval($player['lookhead']),
+ 'torsocolor' => intval($player['lookbody']),
+ 'legscolor' => intval($player['looklegs']),
+ 'detailcolor' => intval($player['lookfeet']),
+ 'addonsflags' => intval($player['lookaddons']),
+ 'ishidden' => (bool)$player['hidden'],
+ 'istournamentparticipant' => false,
+ 'ismaincharacter' => (bool)($player['ismain']),
+ 'dailyrewardstate' => intval($player['isreward']),
+ 'remainingdailytournamentplaytime' => 0
+ ];
+}
+
+/**
+ * Function to check account has premium time and update days
+ * @param $db
+ * @param $query
+ * @param $account
+ * @return float|int
+ */
+function checkPremium($db, $query, $account)
+{
+ $lastDay = (int)$query['lastday'];
+ $timeNow = time();
+
+ if ($lastDay < $timeNow) {
+ $premDays = 0;
+ $lastDay = 0;
+ } else if ($lastDay == 0) {
+ $premDays = 0;
+ } else {
+ $daysLeft = (int)(($lastDay - $timeNow) / 86400);
+ $timeLeft = (int)(($lastDay - $timeNow) % 86400);
+ if ($daysLeft > 0) {
+ $premDays = $daysLeft;
+ } else if ($timeLeft > 0) {
+ $premDays = 1;
+ } else {
+ $premDays = 0;
+ $lastDay = 0;
+ }
+ }
+
+ $db->query("UPDATE `accounts` SET `premdays` = {$premDays}, `lastday` = {$lastDay} WHERE `id` = {$account->getId()}");
+ return $lastDay;
+}
diff --git a/test/style.css b/test/style.css
new file mode 100644
index 000000000..4565c1285
--- /dev/null
+++ b/test/style.css
@@ -0,0 +1,68 @@
+.slidecontainer {
+ width: 100%;
+}
+
+.slider {
+ -webkit-appearance: none;
+ width: 100%;
+
+ outline: none;
+ opacity: 0.7;
+ -webkit-transition: 0.2s;
+ transition: opacity 0.2s;
+}
+
+.slider:hover {
+ opacity: 1;
+}
+
+.slider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 15px;
+ height: 25px;
+ background: #3c8dbc;
+ cursor: pointer;
+}
+
+.slider::-moz-range-thumb {
+ width: 25px;
+ height: 25px;
+ background: #3c8dbc;
+ cursor: pointer;
+}
+
+td.details-control {
+ text-align: center;
+ color: forestgreen;
+ cursor: pointer;
+}
+
+tr.shown td.details-control {
+ text-align: center;
+ color: red;
+}
+
+.table {
+ --bs-table-striped-color: #b8c7d0;
+}
+a {
+ text-decoration: none;
+}
+.skin-blue .main-header .navbar {
+ padding-bottom: 0px;
+ padding-top: 0px;
+}
+tr {
+ color: #b8c7d0;
+}
+.nav-tabs .nav-link {
+ color: #fff;
+ border-bottom: 1px solid #fff;
+ background: #202634;
+ border-radius: 5px 5px 0px 0px;
+}
+ul.nav.nav-tabs.nav-justified.control-sidebar-tabs {
+ background: #202634;
+ padding: 20px;
+}