This commit is contained in:
Daniil Gentili 2019-10-31 12:45:19 +01:00
parent f0e616aade
commit 75e84f87f9
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
3 changed files with 303 additions and 96 deletions

View File

@ -16,9 +16,6 @@
* *
* @link https://docs.madelineproto.xyz MadelineProto documentation * @link https://docs.madelineproto.xyz MadelineProto documentation
*/ */
/*
* Logger class
*/
namespace danog\MadelineProto; namespace danog\MadelineProto;
@ -27,6 +24,9 @@ use Amp\Failure;
use function Amp\ByteStream\getStderr; use function Amp\ByteStream\getStderr;
use function Amp\ByteStream\getStdout; use function Amp\ByteStream\getStdout;
/**
* Logger class
*/
class Logger class Logger
{ {
use Tools; use Tools;
@ -36,14 +36,54 @@ class Logger
const SET = ['bold' => 1, 'dim' => 2, 'underlined' => 3, 'blink' => 4, 'reverse' => 5, 'hidden' => 6]; const SET = ['bold' => 1, 'dim' => 2, 'underlined' => 3, 'blink' => 4, 'reverse' => 5, 'hidden' => 6];
const RESET = ['all' => 0, 'bold' => 21, 'dim' => 22, 'underlined' => 24, 'blink' => 25, 'reverse' => 26, 'hidden' => 28]; const RESET = ['all' => 0, 'bold' => 21, 'dim' => 22, 'underlined' => 24, 'blink' => 25, 'reverse' => 26, 'hidden' => 28];
/**
* Logging mode
*
* @var integer
*/
public $mode = 0; public $mode = 0;
/**
* Optional logger parameter
*
* @var mixed
*/
public $optional = null; public $optional = null;
/**
* Logger prefix
*
* @var string
*/
public $prefix = ''; public $prefix = '';
public $level = 3; /**
* Logging level
*
* @var integer
*/
public $level = self::NOTICE;
/**
* Logging colors
*
* @var array
*/
public $colors = []; public $colors = [];
/**
* Newline
*
* @var string
*/
public $newline = "\n"; public $newline = "\n";
/**
* Default logger instance
*
* @var self
*/
public static $default; public static $default;
/**
* Whether the AGPL notice was printed
*
* @var boolean
*/
public static $printed = false; public static $printed = false;
const ULTRA_VERBOSE = 5; const ULTRA_VERBOSE = 5;
@ -53,31 +93,17 @@ class Logger
const ERROR = 1; const ERROR = 1;
const FATAL_ERROR = 0; const FATAL_ERROR = 0;
const NO_LOGGER = 0; const NO_LOGGER = 0;
const DEFAULT_LOGGER = 1; const DEFAULT_LOGGER = 1;
const FILE_LOGGER = 2; const FILE_LOGGER = 2;
const ECHO_LOGGER = 3; const ECHO_LOGGER = 3;
const CALLABLE_LOGGER = 4; const CALLABLE_LOGGER = 4;
/**
* Construct global logger.
*
* @param [type] $mode
* @param [type] $optional
* @param string $prefix
* @param [type] $level
* @param [type] $max_size
* @return void
*/
public static function constructor($mode, $optional = null, $prefix = '', $level = self::NOTICE, $max_size = 100 * 1024 * 1024)
{
self::$default = new self($mode, $optional, $prefix, $level, $max_size);
}
/** /**
* Construct global static logger from MadelineProto settings. * Construct global static logger from MadelineProto settings.
* *
* @param array $settings * @param array $settings Settings array
*
* @return void * @return void
*/ */
public static function constructorFromSettings(array $settings) public static function constructorFromSettings(array $settings)
@ -90,8 +116,9 @@ class Logger
/** /**
* Get logger from MadelineProto settings. * Get logger from MadelineProto settings.
* *
* @param array $settings * @param array $settings Settings array
* @param string $prefix Optional prefix * @param string $prefix Optional prefix for log messages
*
* @return self * @return self
*/ */
public static function getLoggerFromSettings(array $settings, string $prefix = ''): self public static function getLoggerFromSettings(array $settings, string $prefix = ''): self
@ -127,7 +154,35 @@ class Logger
return $logger; return $logger;
} }
public function __construct($mode, $optional = null, $prefix = '', $level = self::NOTICE, $max_size = 100 * 1024 * 1024)
/**
* Construct global logger.
*
* @param int $mode One of the logger constants
* @param mixed $optional Optional parameter for logger
* @param string $prefix Prefix for log messages
* @param int $level Default logging level
* @param int $max_size Maximum size for logfile
*
* @return void
*/
public static function constructor(int $mode, $optional = null, string $prefix = '', int $level = self::NOTICE, int $max_size = 100 * 1024 * 1024)
{
self::$default = new self($mode, $optional, $prefix, $level, $max_size);
}
/**
* Construct global logger.
*
* @param int $mode One of the logger constants
* @param mixed $optional Optional parameter for logger
* @param string $prefix Prefix for log messages
* @param int $level Default logging level
* @param int $max_size Maximum size for logfile
*
* @return void
*/
public function __construct(int $mode, $optional = null, string $prefix = '', int $level = self::NOTICE, int $max_size = 100 * 1024 * 1024)
{ {
if ($mode === null) { if ($mode === null) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['no_mode_specified']); throw new Exception(\danog\MadelineProto\Lang::$current_lang['no_mode_specified']);
@ -176,7 +231,15 @@ class Logger
} }
} }
public static function log($param, $level = self::NOTICE) /**
* Log a message
*
* @param mixed $param Message
* @param int $level Logging level
*
* @return void
*/
public static function log($param, int $level = self::NOTICE)
{ {
if (!\is_null(self::$default)) { if (!\is_null(self::$default)) {
self::$default->logger($param, $level, \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php')); self::$default->logger($param, $level, \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php'));
@ -185,7 +248,16 @@ class Logger
} }
} }
public function logger($param, $level = self::NOTICE, $file = null) /**
* Log a message
*
* @param mixed $param Message to log
* @param int $level Logging level
* @param string $file File that originated the message
*
* @return void
*/
public function logger($param, int $level = self::NOTICE, string $file = '')
{ {
if ($level > $this->level || $this->mode === 0) { if ($level > $this->level || $this->mode === 0) {
return false; return false;
@ -213,7 +285,7 @@ class Logger
} elseif (!\is_string($param)) { } elseif (!\is_string($param)) {
$param = \json_encode($param, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); $param = \json_encode($param, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
} }
if ($file === null) { if (empty($file)) {
$file = \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php'); $file = \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php');
} }
$param = \str_pad($file.$prefix.': ', 16 + \strlen($prefix))."\t".$param; $param = \str_pad($file.$prefix.': ', 16 + \strlen($prefix))."\t".$param;
@ -234,9 +306,4 @@ class Logger
break; break;
} }
} }
public function __destruct()
{
//\danog\MadelineProto\Tools::wait($this->stdout->write(''));
}
} }

View File

@ -19,6 +19,8 @@
namespace danog\MadelineProto; namespace danog\MadelineProto;
use Amp\Artax\Client;
use Amp\Dns\Resolver;
use Amp\Loop; use Amp\Loop;
use danog\MadelineProto\Async\AsyncConstruct; use danog\MadelineProto\Async\AsyncConstruct;
use danog\MadelineProto\Loop\Generic\PeriodicLoop; use danog\MadelineProto\Loop\Generic\PeriodicLoop;
@ -410,6 +412,13 @@ class MTProto extends AsyncConstruct implements TLCallback
*/ */
public $datacenter; public $datacenter;
/**
* Logger instance
*
* @var Logger
*/
public $logger;
/** /**
* Constructor function. * Constructor function.
* *
@ -512,7 +521,7 @@ class MTProto extends AsyncConstruct implements TLCallback
* *
* @return void * @return void
*/ */
private function cleanup() public function cleanup()
{ {
$this->referenceDatabase = new ReferenceDatabase($this); $this->referenceDatabase = new ReferenceDatabase($this);
$callbacks = [$this, $this->referenceDatabase]; $callbacks = [$this, $this->referenceDatabase];
@ -523,7 +532,16 @@ class MTProto extends AsyncConstruct implements TLCallback
return $this; return $this;
} }
public function logger($param, $level = Logger::NOTICE, $file = null) /**
* Logger
*
* @param string $param Parameter
* @param int $level Logging level
* @param string $file File where the message originated
*
* @return void
*/
public function logger($param, int $level = Logger::NOTICE, string $file = '')
{ {
if ($file === null) { if ($file === null) {
$file = \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php'); $file = \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php');
@ -533,37 +551,37 @@ class MTProto extends AsyncConstruct implements TLCallback
} }
/** /**
* Whether this is altervista * Get async HTTP client
* *
* @return boolean * @return \Amp\Artax\Client
*/ */
public function isAltervista(): bool public function getHTTPClient(): Client
{
return Magic::$altervista;
}
public function isInitingAuthorization()
{
return $this->initing_authorization;
}
public function getHTTPClient()
{ {
return $this->datacenter->getHTTPClient(); return $this->datacenter->getHTTPClient();
} }
public function getDNSClient() /**
* Get async DNS client
*
* @return \Amp\Dns\Resolver
*/
public function getDNSClient(): Resolver
{ {
return $this->datacenter->getDNSClient(); return $this->datacenter->getDNSClient();
} }
public function fileGetContents($url): \Generator /**
* Get contents of remote file asynchronously
*
* @param string $url URL
*
* @return \Generator<string>
*/
public function fileGetContents(string $url): \Generator
{ {
return $this->datacenter->fileGetContents($url); return $this->datacenter->fileGetContents($url);
} }
public function testing(callable $a, ?string $b = null, $c = null, $d = 2, $e = self::METHOD_BEFORE_CALLBACK): ?string
{
}
/** /**
* Get all datacenter connections. * Get all datacenter connections.
* *
@ -588,6 +606,11 @@ class MTProto extends AsyncConstruct implements TLCallback
return true; return true;
} }
/**
* Prompt serialization of instance
*
* @return void
*/
public function serialize() public function serialize()
{ {
if ($this->wrapper instanceof API && isset($this->wrapper->session) && !\is_null($this->wrapper->session) && !$this->asyncInitPromise) { if ($this->wrapper instanceof API && isset($this->wrapper->session) && !\is_null($this->wrapper->session) && !$this->asyncInitPromise) {
@ -595,7 +618,12 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->wrapper->serialize($this->wrapper->session); $this->wrapper->serialize($this->wrapper->session);
} }
} }
public function startLoops() /**
* Start all internal loops
*
* @return void
*/
private function startLoops()
{ {
if (!$this->callCheckerLoop) { if (!$this->callCheckerLoop) {
$this->callCheckerLoop = new PeriodicLoop($this, [$this, 'checkCalls'], 'call check', 10); $this->callCheckerLoop = new PeriodicLoop($this, [$this, 'checkCalls'], 'call check', 10);
@ -619,7 +647,12 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->configLoop->start(); $this->configLoop->start();
$this->checkTosLoop->start(); $this->checkTosLoop->start();
} }
public function stopLoops() /**
* Stop all internal loops
*
* @return void
*/
private function stopLoops()
{ {
if ($this->callCheckerLoop) { if ($this->callCheckerLoop) {
$this->callCheckerLoop->signal(true); $this->callCheckerLoop->signal(true);
@ -642,6 +675,10 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->checkTosLoop = null; $this->checkTosLoop = null;
} }
} }
/**
* Wakeup function
*/
public function __wakeup() public function __wakeup()
{ {
$backtrace = \debug_backtrace(0, 3); $backtrace = \debug_backtrace(0, 3);
@ -649,7 +686,14 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->setInitPromise($this->__wakeup_async($backtrace)); $this->setInitPromise($this->__wakeup_async($backtrace));
} }
public function __wakeup_async($backtrace) /**
* Async wakeup function
*
* @param array $backtrace Stack trace
*
* @return \Generator
*/
public function __wakeup_async(array $backtrace): \Generator
{ {
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']); \set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
$this->setupLogger(); $this->setupLogger();
@ -834,6 +878,9 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->updaters[false]->start(); $this->updaters[false]->start();
} }
/**
* Destructor
*/
public function __destruct() public function __destruct()
{ {
$this->stopLoops(); $this->stopLoops();
@ -859,7 +906,15 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->logger("Successfully destroyed MadelineProto"); $this->logger("Successfully destroyed MadelineProto");
} }
public static function getSettings($settings, $previousSettings = []): array /**
* Get correct settings array for the latest version
*
* @param array $settings Current settings array
* @param array $previousSettings Previous settings array
*
* @return array
*/
public static function getSettings(array $settings, array $previousSettings = []): array
{ {
Magic::classExists(); Magic::classExists();
if (isset($previousSettings['connection_settings']['default_dc'])) { if (isset($previousSettings['connection_settings']['default_dc'])) {
@ -1186,7 +1241,14 @@ class MTProto extends AsyncConstruct implements TLCallback
} }
return $settings; return $settings;
} }
public function parseSettings($settings) /**
* Parse and store settings
*
* @param array $settings Settings
*
* @return void
*/
public function parseSettings(array $settings)
{ {
$settings = self::getSettings($settings, $this->settings); $settings = self::getSettings($settings, $this->settings);
if ($settings['app_info'] === null) { if ($settings['app_info'] === null) {
@ -1200,6 +1262,11 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->setupLogger(); $this->setupLogger();
} }
/**
* Setup logger
*
* @return void
*/
public function setupLogger() public function setupLogger()
{ {
$this->logger = Logger::getLoggerFromSettings($this->settings, isset($this->authorization['user']) ? isset($this->authorization['user']['username']) ? $this->authorization['user']['username'] : $this->authorization['user']['id'] : ''); $this->logger = Logger::getLoggerFromSettings($this->settings, isset($this->authorization['user']) ? isset($this->authorization['user']['username']) ? $this->authorization['user']['username'] : $this->authorization['user']['id'] : '');
@ -1240,7 +1307,19 @@ class MTProto extends AsyncConstruct implements TLCallback
return $this->datacenter->isHttp($datacenter); return $this->datacenter->isHttp($datacenter);
} }
// Connects to all datacenters and if necessary creates authorization keys, binds them and writes client info
public function isInitingAuthorization()
{
return $this->initing_authorization;
}
/**
* Connects to all datacenters and if necessary creates authorization keys, binds them and writes client info
*
* @param boolean $reconnectAll Whether to reconnect to all DCs
*
* @return \Generator
*/
public function connectToAllDcs(bool $reconnectAll = true): \Generator public function connectToAllDcs(bool $reconnectAll = true): \Generator
{ {
$this->channels_state->get(false); $this->channels_state->get(false);
@ -1275,6 +1354,11 @@ class MTProto extends AsyncConstruct implements TLCallback
yield $this->getPhoneConfig(); yield $this->getPhoneConfig();
} }
/**
* Clean up MadelineProto session after logout
*
* @return void
*/
public function resetSession() public function resetSession()
{ {
if (isset($this->seqUpdater)) { if (isset($this->seqUpdater)) {
@ -1316,6 +1400,11 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->dialog_params = ['_' => 'MadelineProto.dialogParams', 'limit' => 0, 'offset_date' => 0, 'offset_id' => 0, 'offset_peer' => ['_' => 'inputPeerEmpty'], 'count' => 0]; $this->dialog_params = ['_' => 'MadelineProto.dialogParams', 'limit' => 0, 'offset_date' => 0, 'offset_id' => 0, 'offset_peer' => ['_' => 'inputPeerEmpty'], 'count' => 0];
$this->full_chats = []; $this->full_chats = [];
} }
/**
* Reset the update state and fetch all updates from the beginning
*
* @return void
*/
public function resetUpdateState() public function resetUpdateState()
{ {
if (isset($this->seqUpdater)) { if (isset($this->seqUpdater)) {
@ -1343,6 +1432,13 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->startUpdateSystem(); $this->startUpdateSystem();
} }
/**
* Start the update system
*
* @param boolean $anyway Force start update system?
*
* @return void
*/
public function startUpdateSystem($anyway = false) public function startUpdateSystem($anyway = false)
{ {
if ($this->asyncInitPromise && !$anyway) { if ($this->asyncInitPromise && !$anyway) {
@ -1382,6 +1478,13 @@ class MTProto extends AsyncConstruct implements TLCallback
} }
} }
/**
* Store shared phone config
*
* @param mixed $watcherId Watcher ID
*
* @return void
*/
public function getPhoneConfig($watcherId = null) public function getPhoneConfig($watcherId = null)
{ {
if ($this->authorized === self::LOGGED_IN && \class_exists(VoIPServerConfigInternal::class) && !$this->authorization['user']['bot'] && $this->datacenter->getDataCenterConnection($this->settings['connection_settings']['default_dc'])->hasTempAuthKey()) { if ($this->authorized === self::LOGGED_IN && \class_exists(VoIPServerConfigInternal::class) && !$this->authorization['user']['bot'] && $this->datacenter->getDataCenterConnection($this->settings['connection_settings']['default_dc'])->hasTempAuthKey()) {
@ -1392,13 +1495,15 @@ class MTProto extends AsyncConstruct implements TLCallback
} }
} }
/**
public function getCdnConfig($datacenter) * Store RSA keys for CDN datacenters
{ *
/* * @param string $datacenter DC ID
* *********************************************************************** *
* Fetch RSA keys for CDN datacenters * @return \Generator
*/ */
public function getCdnConfig(string $datacenter): \Generator
{
try { try {
foreach ((yield $this->methodCallAsyncRead('help.getCdnConfig', [], ['datacenter' => $datacenter]))['public_keys'] as $curkey) { foreach ((yield $this->methodCallAsyncRead('help.getCdnConfig', [], ['datacenter' => $datacenter]))['public_keys'] as $curkey) {
$tempkey = new \danog\MadelineProto\RSA($curkey['public_key']); $tempkey = new \danog\MadelineProto\RSA($curkey['public_key']);
@ -1409,12 +1514,25 @@ class MTProto extends AsyncConstruct implements TLCallback
} }
} }
public function getCachedConfig() /**
* Get cached server-side config
*
* @return array
*/
public function getCachedConfig(): array
{ {
return $this->config; return $this->config;
} }
public function getConfig($config = [], $options = []) /**
* Get cached (or eventually re-fetch) server-side config
*
* @param array $config Current config
* @param array $options Options for method call
*
* @return \Generator
*/
public function getConfig(array $config = [], array $options = []): \Generator
{ {
if ($this->config['expires'] > \time()) { if ($this->config['expires'] > \time()) {
return $this->config; return $this->config;
@ -1425,7 +1543,12 @@ class MTProto extends AsyncConstruct implements TLCallback
return $this->config; return $this->config;
} }
public function parseConfig() /**
* Parse cached config
*
* @return \Generator
*/
private function parseConfig(): \Generator
{ {
if (isset($this->config['dc_options'])) { if (isset($this->config['dc_options'])) {
$options = $this->config['dc_options']; $options = $this->config['dc_options'];
@ -1436,7 +1559,14 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->logger->logger($this->config, Logger::NOTICE); $this->logger->logger($this->config, Logger::NOTICE);
} }
public function parseDcOptions($dc_options) /**
* Parse DC options from config
*
* @param array $dc_options DC options
*
* @return \Generator
*/
private function parseDcOptions(array $dc_options): \Generator
{ {
foreach ($dc_options as $dc) { foreach ($dc_options as $dc) {
$test = $this->config['test_mode'] ? 'test' : 'main'; $test = $this->config['test_mode'] ? 'test' : 'main';

View File

@ -223,7 +223,7 @@ trait Tools
} }
/** /**
* Convert value to unsigned base256 int * Convert value to unsigned base256 int.
* *
* @param int $value Value * @param int $value Value
* *
@ -242,7 +242,7 @@ trait Tools
} }
/** /**
* Convert double to binary version * Convert double to binary version.
* *
* @param double $value Value to convert * @param double $value Value to convert
* *
@ -259,7 +259,7 @@ trait Tools
} }
/** /**
* Unpack binary double * Unpack binary double.
* *
* @param string $value Value to unpack * @param string $value Value to unpack
* *
@ -275,7 +275,7 @@ trait Tools
} }
/** /**
* Synchronously wait for a promise|generator * Synchronously wait for a promise|generator.
* *
* @param \Generator|Promise $promise The promise to wait for * @param \Generator|Promise $promise The promise to wait for
* @param boolean $ignoreSignal Whether to ignore shutdown signals * @param boolean $ignoreSignal Whether to ignore shutdown signals
@ -382,7 +382,7 @@ trait Tools
} }
/** /**
* Create an artificial timeout for any \Generator or Promise * Create an artificial timeout for any \Generator or Promise.
* *
* @param \Generator|Promise $promise * @param \Generator|Promise $promise
* @param integer $timeout * @param integer $timeout
@ -395,7 +395,7 @@ trait Tools
} }
/** /**
* Convert generator, promise or any other value to a promise * Convert generator, promise or any other value to a promise.
* *
* @param \Generator|Promise|mixed $promise * @param \Generator|Promise|mixed $promise
* *
@ -413,7 +413,7 @@ trait Tools
} }
/** /**
* Call promise in background * Call promise in background.
* *
* @param \Generator|Promise $promise Promise to resolve * @param \Generator|Promise $promise Promise to resolve
* @param ?\Generator|Promise $actual Promise to resolve instead of $promise * @param ?\Generator|Promise $actual Promise to resolve instead of $promise
@ -454,7 +454,7 @@ trait Tools
} }
/** /**
* Call promise in background, deferring execution * Call promise in background, deferring execution.
* *
* @param \Generator|Promise $promise Promise to resolve * @param \Generator|Promise $promise Promise to resolve
* *
@ -466,7 +466,7 @@ trait Tools
} }
/** /**
* Rethrow error catched in strand * Rethrow error catched in strand.
* *
* @param \Throwable $e Exception * @param \Throwable $e Exception
* @param string $file File where the strand started * @param string $file File where the strand started
@ -499,7 +499,7 @@ trait Tools
} }
/** /**
* Call promise $b after promise $a * Call promise $b after promise $a.
* *
* @param \Generator|Promise $a Promise A * @param \Generator|Promise $a Promise A
* @param \Generator|Promise $b Promise B * @param \Generator|Promise $b Promise B
@ -513,9 +513,9 @@ trait Tools
$a->onResolve(static function ($e, $res) use ($b, $deferred) { $a->onResolve(static function ($e, $res) use ($b, $deferred) {
if ($e) { if ($e) {
if (isset($this)) { if (isset($this)) {
$this->rethrow($e, $file); $this->rethrow($e);
} else { } else {
self::rethrow($e, $file); self::rethrow($e);
} }
return; return;
} }
@ -523,9 +523,9 @@ trait Tools
$b->onResolve(function ($e, $res) use ($deferred) { $b->onResolve(function ($e, $res) use ($deferred) {
if ($e) { if ($e) {
if (isset($this)) { if (isset($this)) {
$this->rethrow($e, $file); $this->rethrow($e);
} else { } else {
self::rethrow($e, $file); self::rethrow($e);
} }
return; return;
} }
@ -809,4 +809,14 @@ trait Tools
{ {
return \str_replace('_', '\\_', $hwat); return \str_replace('_', '\\_', $hwat);
} }
/**
* Whether this is altervista.
*
* @return boolean
*/
public function isAltervista(): bool
{
return Magic::$altervista;
}
} }