mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch '1.8.x' into feat-mongodb
This commit is contained in:
@@ -134,4 +134,5 @@ _APP_PROJECT_REGIONS=default
|
||||
_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000
|
||||
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
|
||||
_APP_TRUSTED_HEADERS=x-forwarded-for
|
||||
_APP_POOL_ADAPTER=stack
|
||||
_APP_POOL_ADAPTER=stack
|
||||
_APP_WORKER_SCREENSHOTS_ROUTER=http://appwrite
|
||||
|
||||
+1
-5
@@ -30,9 +30,6 @@ use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Broker\Pool as BrokerPool;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Span\Exporter;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\Span\Storage;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter\None as NoTelemetry;
|
||||
|
||||
@@ -340,6 +337,5 @@ $cli
|
||||
$cli->shutdown()->action(fn () => Timer::clearAll());
|
||||
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
Span::setStorage(new Storage\Coroutine());
|
||||
Span::addExporter(new Exporter\Stdout());
|
||||
require_once __DIR__ . '/init/span.php';
|
||||
run($cli->run(...));
|
||||
|
||||
@@ -52,6 +52,7 @@ use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Log\User;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Service;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Text;
|
||||
@@ -1245,17 +1246,7 @@ Http::error()
|
||||
$trace = $error->getTrace();
|
||||
|
||||
if (php_sapi_name() === 'cli') {
|
||||
Console::error('[Error] Timestamp: ' . date('c', time()));
|
||||
|
||||
if ($route) {
|
||||
Console::error('[Error] Method: ' . $route->getMethod());
|
||||
Console::error('[Error] URL: ' . $route->getPath());
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
Console::error('[Error] Message: ' . $message);
|
||||
Console::error('[Error] File: ' . $file);
|
||||
Console::error('[Error] Line: ' . $line);
|
||||
Span::error($error);
|
||||
}
|
||||
|
||||
switch ($class) {
|
||||
|
||||
+39
-21
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
require_once __DIR__ . '/init/span.php';
|
||||
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
@@ -31,6 +32,7 @@ use Utopia\Http\Http;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Log\User;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\System\System;
|
||||
|
||||
const DOMAIN_SYNC_TIMER = 30; // 30 seconds
|
||||
@@ -167,7 +169,6 @@ $http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) use (&$fil
|
||||
$files = new Files();
|
||||
$files->load(__DIR__ . '/../public');
|
||||
}
|
||||
Console::success('Worker ' . ++$workerId . ' started successfully');
|
||||
});
|
||||
|
||||
$http->on(Constant::EVENT_WORKER_STOP, function ($server, $workerId) {
|
||||
@@ -207,7 +208,8 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
|
||||
}
|
||||
}
|
||||
|
||||
Console::success("[Setup] - $dbName database init started...");
|
||||
Span::init("database.setup");
|
||||
Span::add('database.name', $dbName);
|
||||
|
||||
$attempts = 0;
|
||||
while (true) {
|
||||
@@ -218,6 +220,7 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
|
||||
break; // exit loop on success
|
||||
} catch (\Exception $e) {
|
||||
if ($e instanceof DuplicateException) {
|
||||
Span::add('database.exists', true);
|
||||
Console::info(" └── Skip: metadata table already exists");
|
||||
break;
|
||||
}
|
||||
@@ -232,6 +235,7 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
|
||||
}
|
||||
|
||||
// Process collections
|
||||
$collectionsCreated = 0;
|
||||
foreach ($collections as $key => $collection) {
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
continue;
|
||||
@@ -241,8 +245,6 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::info(" └── Creating collection: {$collection['$id']}...");
|
||||
|
||||
$attributes = array_map(fn ($attr) => new Document([
|
||||
'$id' => ID::custom($attr['$id']),
|
||||
'type' => $attr['type'],
|
||||
@@ -264,14 +266,19 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
|
||||
]), $collection['indexes']);
|
||||
|
||||
$database->createCollection($key, $attributes, $indexes);
|
||||
$collectionsCreated++;
|
||||
}
|
||||
|
||||
Span::add('database.collections_created', $collectionsCreated);
|
||||
|
||||
if ($extraSetup) {
|
||||
$extraSetup($database);
|
||||
}
|
||||
|
||||
Span::current()?->finish();
|
||||
}
|
||||
|
||||
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
|
||||
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $totalWorkers, $register) {
|
||||
$app = new Http('UTC');
|
||||
|
||||
go(function () use ($register, $app) {
|
||||
@@ -296,7 +303,6 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
}
|
||||
|
||||
if ($dbForPlatform->getDocument('buckets', 'default')->isEmpty()) {
|
||||
Console::info(" └── Creating default bucket...");
|
||||
$dbForPlatform->createDocument('buckets', new Document([
|
||||
'$id' => ID::custom('default'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
@@ -319,7 +325,6 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
|
||||
$bucket = $dbForPlatform->getDocument('buckets', 'default');
|
||||
|
||||
Console::info(" └── Creating files collection for default bucket...");
|
||||
$files = $collections['buckets']['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
throw new Exception('Files collection is not configured.');
|
||||
@@ -349,7 +354,6 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
}
|
||||
|
||||
if ($authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
|
||||
Console::info(" └── Creating screenshots bucket...");
|
||||
$authorization->skip(fn () => $dbForPlatform->createDocument('buckets', new Document([
|
||||
'$id' => ID::custom('screenshots'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
@@ -367,7 +371,6 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
|
||||
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
|
||||
|
||||
Console::info(" └── Creating files collection for screenshots bucket...");
|
||||
$files = $collections['buckets']['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
throw new Exception('Files collection is not configured.');
|
||||
@@ -405,6 +408,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
$cache = $app->getResource('cache');
|
||||
|
||||
foreach ($sharedTablesV2 as $hostname) {
|
||||
Span::init('database.setup');
|
||||
Span::add('database.hostname', $hostname);
|
||||
|
||||
$adapter = new DatabasePool($pools->get($hostname));
|
||||
$dbForProject = (new Database($adapter, $cache))
|
||||
->setDatabase('appwrite')
|
||||
@@ -422,6 +428,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
$dbForProject->create();
|
||||
break; // exit loop on success
|
||||
} catch (DuplicateException) {
|
||||
Span::add('database.exists', true);
|
||||
Console::success('[Setup] - Skip: metadata table already exists');
|
||||
break;
|
||||
} catch (\Throwable $e) {
|
||||
@@ -439,6 +446,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
$audit->setup();
|
||||
}
|
||||
|
||||
$collectionsCreated = 0;
|
||||
foreach ($projectCollections as $key => $collection) {
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
continue;
|
||||
@@ -450,17 +458,21 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
$attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']);
|
||||
$indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']);
|
||||
|
||||
Console::success('[Setup] - Creating project collection: ' . $collection['$id'] . '...');
|
||||
|
||||
$dbForProject->createCollection($key, $attributes, $indexes);
|
||||
$collectionsCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
Console::success('[Setup] - Server database init completed...');
|
||||
Span::add('database.collections_created', $collectionsCreated);
|
||||
Span::current()?->finish();
|
||||
}
|
||||
});
|
||||
|
||||
Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)');
|
||||
Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}");
|
||||
Span::init('http.server.start');
|
||||
Span::add('server.workers', $totalWorkers);
|
||||
Span::add('server.payload_size', $payloadSize);
|
||||
Span::add('server.master_pid', $http->master_pid);
|
||||
Span::add('server.manager_pid', $http->manager_pid);
|
||||
Span::current()?->finish();
|
||||
|
||||
// Start the task that starts fetching custom domains
|
||||
$http->task([], 0);
|
||||
@@ -473,12 +485,16 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
});
|
||||
|
||||
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register, &$files) {
|
||||
Span::init('http.request');
|
||||
|
||||
Http::setResource('swooleRequest', fn () => $swooleRequest);
|
||||
Http::setResource('swooleResponse', fn () => $swooleResponse);
|
||||
|
||||
$request = new Request($swooleRequest);
|
||||
$response = new Response($swooleResponse);
|
||||
|
||||
Span::add('http.method', $request->getMethod());
|
||||
|
||||
if ($files instanceof Files && $files->isFileLoaded($request->getURI())) {
|
||||
$time = (60 * 60 * 24 * 45); // 45 days cache
|
||||
|
||||
@@ -507,7 +523,12 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
$authorization->addRole(Role::any()->toString());
|
||||
|
||||
$app->run($request, $response);
|
||||
|
||||
$route = $app->getRoute();
|
||||
Span::add('http.path', $route?->getPath() ?? 'unknown');
|
||||
} catch (\Throwable $th) {
|
||||
Span::error($th);
|
||||
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$logger = $app->getResource("logger");
|
||||
@@ -570,12 +591,6 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
}
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: ' . get_class($th));
|
||||
Console::error('[Error] Message: ' . $th->getMessage());
|
||||
Console::error('[Error] File: ' . $th->getFile());
|
||||
Console::error('[Error] Line: ' . $th->getLine());
|
||||
Console::error('[Error] Trace: ' . $th->getTraceAsString());
|
||||
|
||||
$swooleResponse->setStatusCode(500);
|
||||
|
||||
$output = ((Http::isDevelopment())) ? [
|
||||
@@ -592,6 +607,9 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
];
|
||||
|
||||
$swooleResponse->end(\json_encode($output));
|
||||
} finally {
|
||||
Span::add('http.response.code', $response->getStatusCode());
|
||||
Span::current()?->finish();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Utopia\Span\Exporter;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\Span\Storage;
|
||||
|
||||
Span::setStorage(new Storage\Coroutine());
|
||||
Span::addExporter(new Exporter\Pretty());
|
||||
+1
-5
@@ -45,17 +45,13 @@ use Utopia\Queue\Message;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Server;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Span\Exporter;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\Span\Storage;
|
||||
use Utopia\Storage\Device\Telemetry as TelemetryDevice;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
use Utopia\Telemetry\Adapter\None as NoTelemetry;
|
||||
|
||||
Runtime::enableCoroutine();
|
||||
Span::setStorage(new Storage\Coroutine());
|
||||
Span::addExporter(new Exporter\Stdout());
|
||||
require_once __DIR__ . '/init/span.php';
|
||||
|
||||
global $register;
|
||||
Server::setResource('register', fn () => $register);
|
||||
|
||||
+2
-1
@@ -59,7 +59,7 @@
|
||||
"utopia-php/detector": "0.2.*",
|
||||
"utopia-php/domains": "1.*",
|
||||
"utopia-php/emails": "0.6.*",
|
||||
"utopia-php/dns": "1.5.*",
|
||||
"utopia-php/dns": "1.6.*",
|
||||
"utopia-php/dsn": "0.2.1",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
"utopia-php/fetch": "0.5.*",
|
||||
@@ -70,6 +70,7 @@
|
||||
"utopia-php/migration": "1.5.*",
|
||||
"utopia-php/platform": "0.7.*",
|
||||
"utopia-php/pools": "1.*",
|
||||
"utopia-php/span": "1.1.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/queue": "0.15.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
|
||||
Generated
+15
-14
@@ -3948,22 +3948,22 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/dns",
|
||||
"version": "1.5.4",
|
||||
"version": "1.6.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/dns.git",
|
||||
"reference": "ee831a6f2ceb28babb042ea65539c26ea4530bf6"
|
||||
"reference": "98c70520213a41e2fe1867e5b110273c06bf1cab"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/dns/zipball/ee831a6f2ceb28babb042ea65539c26ea4530bf6",
|
||||
"reference": "ee831a6f2ceb28babb042ea65539c26ea4530bf6",
|
||||
"url": "https://api.github.com/repos/utopia-php/dns/zipball/98c70520213a41e2fe1867e5b110273c06bf1cab",
|
||||
"reference": "98c70520213a41e2fe1867e5b110273c06bf1cab",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.3",
|
||||
"utopia-php/domains": "1.0.*",
|
||||
"utopia-php/span": "1.0.*",
|
||||
"utopia-php/span": "1.1.*",
|
||||
"utopia-php/telemetry": "*",
|
||||
"utopia-php/validators": "0.*"
|
||||
},
|
||||
@@ -3999,9 +3999,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/dns/issues",
|
||||
"source": "https://github.com/utopia-php/dns/tree/1.5.4"
|
||||
"source": "https://github.com/utopia-php/dns/tree/1.6.2"
|
||||
},
|
||||
"time": "2026-02-02T10:40:38+00:00"
|
||||
"time": "2026-02-13T12:29:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
@@ -4909,25 +4909,26 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/span",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/span.git",
|
||||
"reference": "f2f6c499ded3a776e8019902e83d140ff0f89693"
|
||||
"reference": "49d04aa588a2cdbbc9381ee7a1c129469e0f905c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/span/zipball/f2f6c499ded3a776e8019902e83d140ff0f89693",
|
||||
"reference": "f2f6c499ded3a776e8019902e83d140ff0f89693",
|
||||
"url": "https://api.github.com/repos/utopia-php/span/zipball/49d04aa588a2cdbbc9381ee7a1c129469e0f905c",
|
||||
"reference": "49d04aa588a2cdbbc9381ee7a1c129469e0f905c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1"
|
||||
"php": ">=8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.0",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"rector/rector": "^2.3",
|
||||
"swoole/ide-helper": "^5.0"
|
||||
},
|
||||
"suggest": {
|
||||
@@ -4946,9 +4947,9 @@
|
||||
"description": "Simple span tracing library for PHP with coroutine support",
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/span/issues",
|
||||
"source": "https://github.com/utopia-php/span/tree/1.0.0"
|
||||
"source": "https://github.com/utopia-php/span/tree/1.1.4"
|
||||
},
|
||||
"time": "2026-01-12T20:05:10+00:00"
|
||||
"time": "2026-02-13T10:58:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/storage",
|
||||
|
||||
@@ -572,6 +572,7 @@ services:
|
||||
environment:
|
||||
# Specific
|
||||
- _APP_BROWSER_HOST
|
||||
- _APP_WORKER_SCREENSHOTS_ROUTER
|
||||
# Basic
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
|
||||
@@ -301,7 +301,7 @@ class Create extends Base
|
||||
|
||||
if ($async) {
|
||||
if (is_null($scheduledAt)) {
|
||||
if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check
|
||||
if ($project->getId() != '6862e6a6000cce69f9da') {
|
||||
$execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
$queueForFunctions
|
||||
@@ -344,7 +344,7 @@ class Create extends Base
|
||||
->setAttribute('scheduleInternalId', $schedule->getSequence())
|
||||
->setAttribute('scheduledAt', $scheduledAt);
|
||||
|
||||
if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check
|
||||
if ($project->getId() != '6862e6a6000cce69f9da') {
|
||||
$execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
}
|
||||
@@ -505,7 +505,7 @@ class Create extends Base
|
||||
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
|
||||
;
|
||||
|
||||
if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check
|
||||
if ($project->getId() != '6862e6a6000cce69f9da') {
|
||||
$execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,15 +111,16 @@ class Screenshots extends Action
|
||||
throw new \Exception('Bucket not found');
|
||||
}
|
||||
|
||||
$routerHost = System::getEnv('_APP_WORKER_SCREENSHOTS_ROUTER', 'http://appwrite');
|
||||
$configs = [
|
||||
'screenshotLight' => [
|
||||
'headers' => [ 'x-appwrite-hostname' => $rule->getAttribute('domain') ],
|
||||
'url' => 'http://appwrite/?appwrite-preview=1&appwrite-theme=light',
|
||||
'url' => $routerHost . '/?appwrite-preview=1&appwrite-theme=light',
|
||||
'theme' => 'light'
|
||||
],
|
||||
'screenshotDark' => [
|
||||
'headers' => [ 'x-appwrite-hostname' => $rule->getAttribute('domain') ],
|
||||
'url' => 'http://appwrite/?appwrite-preview=1&appwrite-theme=dark',
|
||||
'url' => $routerHost . '/?appwrite-preview=1&appwrite-theme=dark',
|
||||
'theme' => 'dark'
|
||||
],
|
||||
];
|
||||
|
||||
@@ -7,7 +7,6 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Executions extends Action
|
||||
{
|
||||
@@ -45,7 +44,8 @@ class Executions extends Action
|
||||
throw new Exception('Missing execution');
|
||||
}
|
||||
|
||||
if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check
|
||||
$project = new Document($payload['project'] ?? []);
|
||||
if ($project->getId() != '6862e6a6000cce69f9da') {
|
||||
$dbForProject->upsertDocument('executions', $execution);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user