Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF.php
<?php

/**
 * Basic setup class and facade into app-specific configurations and the DIC.
 */
class XF
{
   
/**
     * Current printable and encoded versions. These are used for visual output
     * and installation/upgrading.
     *
     * @var string
     * @var integer
     */
   
public static $version = '2.2.8';
    public static
$versionId = 2020870; // abbccde = a.b.c d (alpha: 1, beta: 3, RC: 5, stable: 7, PL: 9) e

   
const XF_API_URL = '';
    const
XF_LICENSE_KEY = '';

    const
API_VERSION = 1;

    protected static
$memoryLimit = null;

   
/**
     * @var \Composer\Autoload\ClassLoader
     */
   
public static $autoLoader = null;

    public static
$debugMode = false;
    public static
$developmentMode = false;
    public static
$time = 0;

    public static
$DS = DIRECTORY_SEPARATOR;

    protected static
$rootDirectory = '.';
    protected static
$sourceDirectory = '.';

    protected static
$app = null;

   
/**
     * @var \XF\Entity\User
     */
   
protected static $visitor = null;

   
/**
     * @var \XF\Entity\User
     */
   
protected static $preRegActionUser = null;

   
/**
     * @var \XF\Language
     */
   
protected static $language = null;

   
/**
     * @var \XF\Entity\ApiKey|null
     */
   
protected static $apiKey = null;

   
/**
     * @var bool
     */
   
protected static $apiBypassPermissions = false;

   
/**
     * Starts the XF framework and standardized the environment.
     */
   
public static function start($rootDirectory)
    {
       
self::$time = time();
       
self::$rootDirectory = $rootDirectory;
       
self::$sourceDirectory = __DIR__;

       
self::standardizeEnvironment();
       
self::startAutoloader();
       
self::startSystem();
    }

   
/**
     * Sets up the PHP environment in the XF-expected way
     */
   
public static function standardizeEnvironment()
    {
       
ignore_user_abort(true);

       
self::setMemoryLimit(128 * 1024 * 1024);

       
error_reporting(E_ALL | E_STRICT & ~8192);

       
date_default_timezone_set('UTC');
       
setlocale(LC_ALL, 'C');

       
// if you really need to load a phar file, you can call stream_wrapper_restore('phar');
       
@stream_wrapper_unregister('phar');

        @
ini_set('output_buffering', false);
        @
ini_set('default_charset', 'UTF-8');

        if (
PHP_VERSION_ID >= 70100)
        {
            @
ini_set('serialize_precision', -1);
        }

        if (
PHP_VERSION_ID >= 70400)
        {
           
// since we catch exceptions, turn this off to aid debugging
           
@ini_set('zend.exception_ignore_args', false);
        }

        if (
PHP_VERSION_ID >= 80100)
        {
           
mysqli_report(MYSQLI_REPORT_OFF);
        }

       
// see http://bugs.php.net/bug.php?id=36514
        // and http://xenforo.com/community/threads/53637/
       
if (!@ini_get('output_handler'))
        {
           
$level = ob_get_level();
            while (
$level)
            {
                @
ob_end_clean();
               
$newLevel = ob_get_level();
                if (
$newLevel >= $level)
                {
                    break;
                }
               
$level = $newLevel;
            }
        }
    }

   
/**
     * Handler for set_error_handler to convert notices, warnings, and other errors
     * into exceptions.
     *
     * @param integer $errorType Type of error (one of the E_* constants)
     * @param string $errorString
     * @param string $file
     * @param integer $line
     *
     * @throws \ErrorException
     */
   
public static function handlePhpError($errorType, $errorString, $file, $line)
    {
        if (
$errorType & error_reporting())
        {
           
$errorString = '[' . \XF\Util\Php::convertErrorCodeToString($errorType) . '] '. $errorString;

           
$trigger = true;

           
$isDevError = ($errorType & E_STRICT
               
|| $errorType & E_DEPRECATED
               
|| $errorType & E_USER_DEPRECATED
           
);

            if (!
self::$debugMode)
            {
               
// production (non-debug) mode
               
if ($isDevError)
                {
                   
// do not log anything for these, they're developer notices
                   
$trigger = false;
                }
                else if (
$errorType & E_NOTICE || $errorType & E_USER_NOTICE)
                {
                   
// minor developer issues, log and let execution continue
                   
$trigger = false;
                   
$e = new \ErrorException($errorString, 0, $errorType, $file, $line);
                   
self::app()->logException($e);
                }
            }
            else
            {
               
// debug mode specific behaviors
               
if ($isDevError && preg_match('#src(/|\\\\)vendor(/|\\\\)#', $file))
                {
                   
// dev/deprecation error in a vendor library, log but let execution continue
                   
$trigger = false;
                   
$e = new \ErrorException($errorString, 0, $errorType, $file, $line);
                   
self::app()->logException($e);
                }
            }

            if (
$trigger)
            {
                throw new \
ErrorException($errorString, 0, $errorType, $file, $line);
            }
        }
    }

   
/**
     * Default exception handler.
     *
     * @param Exception $e
     */
   
public static function handleException($e)
    {
       
$app = self::app();
       
$app->logException($e, true); // exiting so rollback
       
$app->displayFatalExceptionMessage($e);
    }

   
/**
     * @param \Exception|\Throwable $e
     * @param bool $rollback
     * @param string $messagePrefix
     * @param bool $forceLog
     */
   
public static function logException($e, $rollback = false, $messagePrefix = '', $forceLog = false)
    {
       
self::app()->error()->logException($e, $rollback, $messagePrefix, $forceLog);
    }

    public static function
logError($message, $forceLog = false)
    {
       
self::app()->error()->logError($message, $forceLog);
    }

   
/**
     * Try to log fatal errors so that debugging is easier.
     */
   
public static function handleFatalError()
    {
       
$error = @error_get_last();
        if (!
$error)
        {
            return;
        }

        if (empty(
$error['type']) || !($error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR)))
        {
            return;
        }

        try
        {
           
self::app()->logException(
                new
ErrorException("Fatal Error: " . $error['message'], $error['type'], 1, $error['file'], $error['line']),
               
true
           
);
        }
        catch (
Exception $e) {}
    }

   
/**
     * Sets up XF's autoloader
     */
   
public static function startAutoloader()
    {
        if (
self::$autoLoader)
        {
            return;
        }

       
/** @var \Composer\Autoload\ClassLoader $autoLoader */
       
$autoLoader = require(__DIR__ . '/vendor/autoload.php');
       
$autoLoader->register();

       
self::$autoLoader = $autoLoader;
    }

    public static function
registerComposerAutoloadData(string $pathPrefix, array $data, $prepend = false)
    {
       
$composerAutoload = new \XF\ComposerAutoload(self::app(), $pathPrefix);
       
$composerAutoload->checkPaths(false);

        if (
$data['namespaces'])
        {
           
$composerAutoload->autoloadNamespaces($prepend);
        }
        if (
$data['psr4'])
        {
           
$composerAutoload->autoloadPsr4($prepend);
        }
        if (
$data['classmap'])
        {
           
$composerAutoload->autoloadClassmap();
        }
        if (
$data['files'])
        {
           
$composerAutoload->autoloadFiles();
        }
    }

    public static function
registerComposerAutoloadDir($dir, $prepend = false)
    {
        try
        {
           
$composerAutoload = new \XF\ComposerAutoload(self::app(), $dir);
           
$composerAutoload->checkPaths(true);
           
$composerAutoload->autoloadAll($prepend);
        }
        catch (\
Exception $e)
        {
            if (\
XF::$debugMode)
            {
                throw
$e;
            }
            else
            {
               
self::logException($e, true, 'Error registering composer autoload directory: ');
            }
        }
    }

    public static function
startSystem()
    {
       
register_shutdown_function(['XF', 'triggerRunOnce']);

        require(
__DIR__ . '/vendor/dokuwiki/utf8/utf8.php');

       
set_error_handler(['XF', 'handlePhpError']);
       
set_exception_handler(['XF', 'handleException']);
       
register_shutdown_function(['XF', 'handleFatalError']);
    }

    protected static
$runOnce = [];

    public static function
runOnce($key, \Closure $fn)
    {
        if (isset(
self::$runOnce[$key]))
        {
           
// if this key already exists, allow a new function with the
            // same key to replace it and move to the end of the queue.
           
unset(self::$runOnce[$key]);
        }
       
self::$runOnce[$key] = $fn;
    }

    public static function
runLater(\Closure $fn)
    {
       
self::$runOnce[] = $fn;
    }

   
/**
     * Dequeues running of a specific run once action.
     *
     * @param string $key
     *
     * @return bool True if an action was dequeue
     */
   
public static function dequeueRunOnce($key): bool
   
{
        if (isset(
self::$runOnce[$key]))
        {
            unset(
self::$runOnce[$key]);

            return
true;
        }
        else
        {
            return
false;
        }
    }

    public static function
triggerRunOnce($rethrow = false)
    {
       
$i = 0;

        do
        {
            foreach (
self::$runOnce AS $key => $fn)
            {
                unset(
self::$runOnce[$key]);

                try
                {
                   
$fn();
                }
                catch (\
Exception $e)
                {
                   
self::logException($e, true);
                   
// can't know if we have an open transaction from before so have to roll it back

                   
if ($rethrow)
                    {
                        throw
$e;
                    }
                }
            }

           
$i++;
        }
        while (
self::$runOnce && $i < 5);
    }

    public static function
getRootDirectory()
    {
        return
self::$rootDirectory;
    }

    public static function
getSourceDirectory()
    {
        return
self::$sourceDirectory;
    }

    public static function
getAddOnDirectory()
    {
        return \
XF::getSourceDirectory() . self::$DS . 'addons';
    }

   
/**
     * @param int|null $versionId
     * @param string|null $operator
     *
     * @return int|bool
     */
   
public static function isAddOnActive(
       
string $addOnId,
       
$versionId = null,
       
$operator = '>='
   
)
    {
       
$addOns = \XF::app()->container('addon.cache');
        if (!isset(
$addOns[$addOnId]))
        {
            return
false;
        }

       
$activeVersionId = $addOns[$addOnId];
        if (
$versionId === null)
        {
            return
$activeVersionId;
        }

        switch (
$operator)
        {
            case
'>':
                return (
$activeVersionId > $versionId);

            case
'>=':
                return (
$activeVersionId >= $versionId);

            case
'<':
                return (
$activeVersionId < $versionId);

            case
'<=':
                return (
$activeVersionId <= $versionId);
        }

        return
$activeVersionId;
    }

    public static function
getVendorDirectory()
    {
        return \
XF::getSourceDirectory() . self::$DS . 'vendor';
    }

    public static function
updateTime()
    {
       
self::$time = time();
    }

   
/**
     * @param \XF\App $app
     */
   
public static function setApp(\XF\App $app)
    {
        if (
self::$app)
        {
            throw new \
LogicException(
               
'A second app cannot be setup. '
               
. 'Tried to set ' . get_class($app) . ' after setting ' . get_class(self::$app)
            );
        }

       
self::$app = $app;
    }

   
/**
     * @return \XF\App
     */
   
public static function app()
    {
        if (!
self::$app)
        {
            return
self::setupApp('\XF\App');
        }

        return
self::$app;
    }

    public static function
setupApp($appClass, array $setupOptions = [])
    {
       
/** @var \XF\App $app */
       
$app = new $appClass(new \XF\Container());
       
self::setApp($app);
       
$app->setup($setupOptions);

        return
$app;
    }

   
/**
     * Detects if the request URL matches the API path
     *
     * @return bool
     */
   
public static function requestUrlMatchesApi()
    {
       
$baseRequest = new \XF\Http\Request(new \XF\InputFilterer());
        return
boolval(preg_match('#^api(?:/|$)#i', $baseRequest->getRoutePath()));
    }

   
/**
     * Runs the specified application
     *
     * @param string $appClass
     */
   
public static function runApp($appClass)
    {
       
$app = self::setupApp($appClass);

       
ob_start();

       
$response = $app->run();

       
$extraOutput = ob_get_clean();
        if (
strlen($extraOutput))
        {
           
$body = $response->body();
            if (
is_string($body))
            {
                if (
$response->contentType() == 'text/html')
                {
                    if (
strpos($body, '<!--XF:EXTRA_OUTPUT-->') !== false)
                    {
                       
$body = str_replace('<!--XF:EXTRA_OUTPUT-->', $extraOutput . '<!--XF:EXTRA_OUTPUT-->', $body);
                    }
                    else
                    {
                       
$body = preg_replace('#<body[^>]*>#i', "\\0$extraOutput", $body);
                    }
                   
$response->body($body);
                }
                else
                {
                   
$response->body($extraOutput . $body);
                }
            }
        }

        if (\
XF::$debugMode)
        {
           
$app = \XF::app();
           
$container = $app->container();

            if (
$container->isCached('db'))
            {
               
$queryCount = \XF::db()->getQueryCount();
            }
            else
            {
               
$queryCount = null;
            }

           
$debug = [
               
'time' => round(microtime(true) - $app->container('time.granular'), 4),
               
'queries' => $queryCount,
               
'memory' => round(memory_get_peak_usage() / 1024 / 1024, 2)
            ];

           
$response->header('X-XF-Debug-Stats', json_encode($debug));
        }

       
$response->send($app->request());
    }

   
/**
     * @return \XF\Entity\User
     */
   
public static function visitor()
    {
        if (!
self::$visitor)
        {
           
/** @var \XF\Repository\User $userRepo */
           
$userRepo = self::repository('XF:User');
           
self::$visitor = $userRepo->getVisitor(0);
        }

        return
self::$visitor;
    }

    public static function
setVisitor(\XF\Entity\User $user = null)
    {
       
self::$visitor = $user;
    }

   
/**
     * Temporarily take an action with the given user considered to be the visitor
     *
     * @param \XF\Entity\User $user
     * @param Closure $action
     * @param bool $withLanguage If true, the action will be taken with the given user's language
     *
     * @return mixed
     *
     * @throws Exception
     */
   
public static function asVisitor(\XF\Entity\User $user, \Closure $action, bool $withLanguage = false)
    {
       
$oldVisitor = self::$visitor;
       
self::setVisitor($user);

        if (
$withLanguage)
        {
           
$oldLang = self::$language;

           
$newLang = self::app()->userLanguage($user);
           
$newLangeOrigTz = $newLang->getTimeZone();
           
$newLang->setTimeZone($user->timezone);
           
self::setLanguage($newLang);
        }

        try
        {
            return
$action();
        }
        finally
        {
           
self::setVisitor($oldVisitor);

            if (
$withLanguage)
            {
               
$newLang->setTimeZone($newLangeOrigTz);
               
self::$language = $oldLang;
            }
        }
    }

   
/**
     * @return \XF\Entity\User
     */
   
public static function preRegActionUser(): \XF\Entity\User
   
{
        if (!
self::$preRegActionUser)
        {
           
/** @var \XF\Repository\User $userRepo */
           
$userRepo = self::repository('XF:User');
           
self::$preRegActionUser = $userRepo->getPreRegActionUser();
        }

        return
self::$preRegActionUser;
    }

   
/**
     * @param Closure $action
     *
     * @return mixed
     */
   
public static function asPreRegActionUser(\Closure $action)
    {
        return
self::asVisitor(self::preRegActionUser(), $action);
    }

   
/**
     * Helper to make conditional pre-registration user actions syntactically simpler.
     *
     * If the first argument as true, the closure will be run in the context of the pre-reg action user. Otherwise,
     * it will be run in the context of the visitor.
     *
     * @param bool $isNeeded
     * @param Closure $action
     *
     * @return mixed
     */
   
public static function asPreRegActionUserIfNeeded(bool $isNeeded, \Closure $action)
    {
        if (
$isNeeded)
        {
            return
self::asPreRegActionUser($action);
        }
        else
        {
            return
$action();
        }
    }

   
/**
     * Helper to make it easier to determine if a particular action can be performed by a pre-reg action user.
     * This is specifically for things like permission checks and is expected to only return true false.
     *
     * @param Closure $action
     * @return bool
     */
   
public static function canPerformPreRegAction(\Closure $action): bool
   
{
       
$visitor = self::visitor();
        if (!
$visitor->canTriggerPreRegAction())
        {
            return
false;
        }

        return \
XF::asVisitor(\XF::preRegActionUser(), $action);
    }

   
/**
     * @return \XF\Language
     */
   
public static function language()
    {
        if (!
self::$language)
        {
           
self::$language = self::app()->language(0);
        }

        return
self::$language;
    }

    public static function
setLanguage(\XF\Language $language)
    {
       
self::$language = $language;
    }

   
/**
     * @return \XF\Entity\ApiKey
     */
   
public static function apiKey()
    {
        if (!
self::$apiKey)
        {
           
/** @var \XF\Repository\Api $apiRepo */
           
$apiRepo = self::repository('XF:Api');
           
self::$apiKey = $apiRepo->getFallbackApiKey();
        }

        return
self::$apiKey;
    }

    public static function
setApiKey(\XF\Entity\ApiKey $key = null)
    {
       
self::$apiKey = $key;
    }

   
/**
     * True if the API has been set to bypass permissions for the current request.
     * This is only possible if a super user key is being used.
     *
     * @return bool
     */
   
public static function isApiBypassingPermissions()
    {
        return
self::$apiBypassPermissions && self::apiKey()->is_super_user;
    }

   
/**
     * True in most cases, this is just the inverse of isApiBypassingPermissions(), in contexts where
     * the inverted logic is easier to read.
     *
     * @return bool
     */
   
public static function isApiCheckingPermissions()
    {
        return !
self::isApiBypassingPermissions();
    }

    public static function
setApiBypassPermissions($bypass)
    {
       
self::$apiBypassPermissions = $bypass;
    }

   
/**
     * @return bool
     */
   
public static function isPushUsable()
    {
       
$options = self::options();

        if (!isset(
$options->enablePush) || !$options->enablePush)
        {
            return
false;
        }

       
$request = self::app()->request();

        if (
$request->isHostLocal())
        {
            return
true;
        }

        if (
$request->isSecure())
        {
            return
true;
        }

        return
false;
    }

    public static function
phrasedException($name, array $params = [])
    {
        return new \
XF\PrintableException(
           
self::phrase($name, $params)->render(),
           
$name
       
);
    }

    public static function
phrase($name, array $params = [], $allowHtml = true)
    {
        return
self::language()->phrase($name, $params, true, $allowHtml);
    }

    public static function
phraseDeferred($name, array $params = [])
    {
        return
self::language()->phrase($name, $params, false);
    }

    public static function
string(array $parts = [])
    {
        return new \
XF\StringBuilder($parts);
    }

    public static function
config($key = null)
    {
        return
self::app()->config($key);
    }

   
/**
     * @return \XF\Session\Session
     */
   
public static function session()
    {
        return
self::app()->session();
    }

   
/**
     * @return \ArrayObject
     */
   
public static function options()
    {
        return
self::app()->options();
    }

   
/**
     * @return \XF\Mail\Mailer
     */
   
public static function mailer()
    {
        return
self::app()->mailer();
    }

   
/**
     * @return \XF\Db\AbstractAdapter
     */
   
public static function db()
    {
        return
self::app()->db();
    }

   
/**
     * @return \XF\PermissionCache
     */
   
public static function permissionCache()
    {
        return
self::app()->permissionCache();
    }

   
/**
     * @return \XF\Mvc\Entity\Manager
     */
   
public static function em()
    {
        return
self::app()->em();
    }

   
/**
     * @param string $identifier
     *
     * @return \XF\Mvc\Entity\Finder
     */
   
public static function finder($identifier)
    {
        return
self::app()->finder($identifier);
    }

   
/**
     * @param string $identifier
     *
     * @return \XF\Mvc\Entity\Repository
     */
   
public static function repository($identifier)
    {
        return
self::app()->repository($identifier);
    }

   
/**
     * @param string $class
     *
     * @return \XF\Service\AbstractService
     */
   
public static function service($class)
    {
       
$args = func_get_args();
        return
call_user_func_array([self::app(), 'service'], $args);
    }

   
/**
     * @param string $class
     *
     * @return mixed
     */
   
public static function helper($class)
    {
       
$args = func_get_args();
        return
call_user_func_array([self::app(), 'helper'], $args);
    }

   
/**
     * @param array $columns
     * @param array $existingValues
     * @param bool $isUpdating
     *
     * @return \XF\Mvc\Entity\ArrayValidator
     */
   
public static function arrayValidator(
        array
$columns,
        array
$existingValues = [],
       
bool $isUpdating = false
   
): \XF\Mvc\Entity\ArrayValidator
   
{
        return
self::app()->arrayValidator($columns, $existingValues, $isUpdating);
    }

   
/**
     * @return \XF\DataRegistry
     */
   
public static function registry()
    {
        return
self::app()->registry();
    }

   
/**
     * @return \League\Flysystem\MountManager
     */
   
public static function fs()
    {
        return
self::app()->fs();
    }

   
/**
     * @return \XF\Extension
     */
   
public static function extension()
    {
        return
self::app()->extension();
    }

   
/**
     * Fires a code event for an extension point
     *
     * @param string $event
     * @param array $args
     * @param null|string $hint
     *
     * @return bool
     */
   
public static function fire($event, array $args, $hint = null)
    {
        return
self::extension()->fire($event, $args, $hint);
    }

   
/**
     * Gets the callable class name for a dynamically extended class.
     *
     * @param string $class
     * @param null|string $fakeBaseClass
     * @return string
     *
     * @throws Exception
     */
   
public static function extendClass($class, $fakeBaseClass = null)
    {
        return
self::app()->extendClass($class, $fakeBaseClass);
    }

   
/**
     * Sets the memory limit. Will not shrink the limit.
     *
     * @param integer $limit Limit must be given in integer (byte) format.
     *
     * @return bool True if the limit was updated (or already met)
     */
   
public static function setMemoryLimit($limit)
    {
       
$existingLimit = self::getMemoryLimit();
        if (
$existingLimit < 0)
        {
            return
true;
        }

       
$limit = intval($limit);
        if (
$limit == -1 || ($limit > $existingLimit && $existingLimit))
        {
            if (@
ini_set('memory_limit', $limit) === false)
            {
                return
false;
            }

           
self::$memoryLimit = $limit;
        }

        return
true;
    }

    public static function
increaseMemoryLimit($amount)
    {
       
$amount = intval($amount);
        if (
$amount <= 0)
        {
            return
false;
        }

       
$currentLimit = self::getMemoryLimit();
        if (
$currentLimit < 0)
        {
            return
true;
        }

        return
self::setMemoryLimit($currentLimit + $amount);
    }

   
/**
     * Gets the current memory limit.
     *
     * @return int
     */
   
public static function getMemoryLimit()
    {
        if (
self::$memoryLimit === null)
        {
           
$curLimit = @ini_get('memory_limit');
            if (
$curLimit === false)
            {
               
// reading failed, so we have to treat it as unlimited - unlikely to be able to change anyway
               
$curLimitInt = -1;
            }
            else
            {
               
$curLimitInt = intval($curLimit);

                switch (
substr($curLimit, -1))
                {
                    case
'g':
                    case
'G':
                       
$curLimitInt *= 1024;
                   
// fall through

                   
case 'm':
                    case
'M':
                       
$curLimitInt *= 1024;
                   
// fall through

                   
case 'k':
                    case
'K':
                       
$curLimitInt *= 1024;
                }
            }

           
self::$memoryLimit = $curLimitInt;
        }

        return
self::$memoryLimit;
    }

   
/**
     * Attempts to determine the current available amount of memory.
     * If there is no memory limit
     *
     * @return int
     */
   
public static function getAvailableMemory()
    {
       
$limit = self::getMemoryLimit();
        if (
$limit < 0)
        {
            return
PHP_INT_MAX;
        }

       
$used = memory_get_usage();
       
$available = $limit - $used;

        return (
$available < 0 ? 0 : $available);
    }

   
/**
     * Generates a psuedo-random string of the specified length.
     *
     * @param integer $length
     * @param boolean $raw If true, raw binary is returned, otherwise modified base64
     *
     * @return string
     */
   
public static function generateRandomString($length, $raw = false)
    {
        if (
$raw)
        {
            return \
XF\Util\Random::getRandomBytes($length);
        }
        else
        {
            return \
XF\Util\Random::getRandomString($length);
        }
    }

    public static function
stringToClass($string, $formatter, $defaultInfix = null)
    {
       
$parts = explode(':', $string, 3);
        if (
count($parts) == 1)
        {
           
// already a class
           
return $string;
        }

       
$prefix = $parts[0];
        if (isset(
$parts[2]))
        {
           
$infix = $parts[1];
           
$suffix = $parts[2];
        }
        else
        {
           
$infix = $defaultInfix;
           
$suffix = $parts[1];
        }

        return
$defaultInfix === null
           
? sprintf($formatter, $prefix, $suffix)
            :
sprintf($formatter, $prefix, $infix, $suffix);
    }

    public static function
getCopyrightHtml()
    {
        return
'<a href="https://xenforo.com" class="u-concealed" dir="ltr" target="_blank" rel="sponsored noopener">Community platform by XenForo<sup>&reg;</sup> <span class="copyright">&copy; 2010-2021 XenForo Ltd.</span></a>';
    }

    public static function
getCopyrightHtmlAcp()
    {
        return
'<a href="https://xenforo.com" class="u-concealed" dir="ltr" target="_blank" rel="sponsored noopener" data-xf-init="tooltip" title="&copy; 2010-2021 XenForo Ltd.">Community platform by XenForo<sup>&reg;</sup></a>';
    }

    public static function
isPreEscaped($value, $type = 'html')
    {
        if (
$value instanceof \XF\PreEscaped && $value->escapeType == $type)
        {
            return
true;
        }
        else if (
$value instanceof \XF\PreEscapedInterface && $value->getPreEscapeType() == $type)
        {
            return
true;
        }
        else
        {
            return
false;
        }
    }

    public static function
escapeString($value, $type = 'html')
    {
        if (
$type === false)
        {
           
$type = 'raw';
        }
        else if (
$type === true)
        {
           
$type = 'html';
        }

        if (
self::isPreEscaped($value, $type))
        {
            return
strval($value);
        }
        else if (
$type == 'html' && ($value instanceof \XF\Phrase || $value instanceof \XF\Template\Template))
        {
            return
strval($value);
        }
        else if (
is_array($value))
        {
           
trigger_error("Array passed into XF::escapeString unexpectedly.", E_USER_NOTICE);
            return
'Array';
        }

       
$value = strval($value);

        switch (
$type)
        {
            case
'html':
                return
htmlspecialchars($value, ENT_QUOTES, 'utf-8');

            case
'raw':
                return
$value;

            case
'js':
               
$value = strtr($value, [
                   
'\\' => '\\\\',
                   
'"' => '\\"',
                   
"'" => "\\'",
                   
"\r" => '\r',
                   
"\n" => '\n',
                   
'</' => '<\\/',
                ]);
               
$value = preg_replace('/-(?=-)/', '-\\', $value);
                return
$value;

            case
'json':
               
$value = strtr($value, [
                   
'\\' => '\\\\',
                   
'"' => '\\"',
                   
"\t" => '\t',
                   
"\r" => '\r',
                   
"\n" => '\n',
                   
'/' => '\\/',
                   
'<!' => '\u003C!'
               
]);
                return
$value;

            case
'htmljs':
                return \
XF::escapeString(\XF::escapeString($value, 'html'), 'js');

            case
'datauri':
               
$value = strtr($value, [
                   
"\r" => '%0D',
                   
"\n" => '%0A',
                   
'%' => '%25',
                   
'#' => '%23',
                   
'(' => '%28',
                   
')' => '%29',
                   
'<' => '%3C',
                   
'>' => '%3E',
                   
'?' => '%3F',
                   
'[' => '%5B',
                   
']' => '%5D',
                   
'\\' => '%5C',
                   
'^' => '%5E',
                   
'`' => '%60',
                   
'{' => '%7B',
                   
'}' => '%7D',
                   
'|' => '%7C'
               
]);
                return
$value;

            default:
                return
htmlspecialchars($value, ENT_QUOTES, 'utf-8');
        }
    }

   
/**
     * Renders the input to a string with a plain text use case expected.
     *
     * The most significant use case is if you might have a phrase object which defaults to HTML escaping its params.
     * This function will render its parameters without escaping.
     *
     * @param mixed $input
     *
     * @return string
     */
   
public static function renderPlainString($input): string
   
{
        if (
$input instanceof \XF\Phrase)
        {
            return
$input->render('raw');
        }

        return
strval($input);
    }

    public static function
cleanString($string, $trim = true)
    {
        return
self::app()->inputFilterer()->cleanString($string, $trim);
    }

    public static function
cleanArrayStrings(array $input, $trim = true)
    {
        return
self::app()->inputFilterer()->cleanArrayStrings($input, $trim);
    }

    public static function
dump($var)
    {
       
self::app()->debugger()->dump($var);
    }

    public static function
dumpSimple($var, $echo = true)
    {
        return
self::app()->debugger()->dumpSimple($var, $echo);
    }

   
/**
     * @deprecated will be removed in XF 2.3.
     */
   
public static function dumpConsole($var, $type = 'log')
    {
        return
self::app()->debugger()->dumpConsole($var, $type);
    }

    public static function
dumpToFile($var, $logName = null)
    {
        return
self::app()->debugger()->dumpToFile($var, $logName);
    }

    public static function
canonicalizeUrl($uri)
    {
        return
self::convertToAbsoluteUrl($uri, self::options()->boardUrl);
    }

    public static function
convertToAbsoluteUrl($uri, $fullBasePath)
    {
       
$fullBasePath = rtrim($fullBasePath, '/');
       
$baseParts = parse_url($fullBasePath);
        if (!
$baseParts)
        {
            return
$uri;
        }

        if (
$uri == '.')
        {
           
$uri = ''; // current directory
       
}

        if (empty(
$baseParts['scheme']))
        {
           
$baseParts['scheme'] = 'http';
        }

        if (
substr($uri, 0, 2) == '//')
        {
            return
$baseParts['scheme'] . ':' . $uri;
        }
        else if (
substr($uri, 0, 1) == '/')
        {
            if (empty(
$baseParts['host']))
            {
                return
$uri; // really can't guess
           
}

            return
$baseParts['scheme'] . '://' . $baseParts['host']
                . (!empty(
$baseParts['port']) ? ":$baseParts[port]" : '')  . $uri;
        }
        else if (
preg_match('#^[a-z0-9-]+://#i', $uri))
        {
            return
$uri;
        }
        else
        {
            return
$fullBasePath . '/' . $uri;
        }
    }
}