<?php
/**
 * -------------------------------------------------------------------------
 * The HELPER module to provide generic routines for theme templates.
 *
 * -------------------------------------------------------------------------
 *
 * Implemented routines below are:
 *     getUrlScheme
 *     getThemeName
 *     sendStatus200
 *     sendStatus204
 *     sendStatus206
 *     sendStatus301
 *     sendStatus302
 *     sendStatus307
 *     sendStatus308
 *     sendStatus400
 *     sendStatus401
 *     sendStatus403
 *     sendStatus404
 *     sendStatus405
 *     sendStatus406
 *     sendStatus409
 *     sendStatus410
 *     sendStatus429
 *     sendStatus451
 *     sendStatus500
 *     sendStatus501
 *     sendStatus503
 *     sendHeaderHTML
 *     sendHeaderTEXT
 *     sendHeaderXML
 *     sendHeaderCSS
 *     sendHeaderJS
 *     sendHeaderJSON
 *     sendHeaderCSV
 *     sendHeaderEXCEL
 *     sendHeaderFILE
 *     sendHeaderExpires
 *     sendHeaderLastModified
 *     stopIfHead
 *     sendSecurityPolicy
 *     secureByNonce
 *     isEmpty
 *     isNonEmpty
 *     printValue
 *     printDomainUrl
 *     printSiteUrl
 *     printThemeUrl
 *     printPageUrl
 *     printItemField
 *     getFormInput
 *     getFormFile
 *     makeSecureUrl
 *     makeMediaUrl
 *     validateMedia
 *     removeMedia
 *     uploadMedia
 *     dumpVar
 *
 * -------------------------------------------------------------------------
 *
 * @package    MimimiFramework
 * @subpackage Modules
 * @copyright  2022 MiMiMi Community
 *             https://mimimi.software/
 * @license    CC BY 4
 *             https://www.creativecommons.org/licenses/by/4.0
 * -------------------------------------------------------------------------
 */

mimimiInclude('Module.php');
class MimimiHelper extends MimimiModule {
};

/**
 * -------------------------------------------------------------------------
 * Retrieves the requested URL protocol.
 *
 * @public
 * @return  string
 * -------------------------------------------------------------------------
 */

function getUrlScheme () {
    return mimimiServer('SERVER_PROTOCOL', 'HTTP/1.1');
}

/**
 * -------------------------------------------------------------------------
 * Retrieves the active theme name.
 *
 * @public
 * @return  string
 * -------------------------------------------------------------------------
 */

function getThemeName () {
    return MIMIMI_CMS_THEME;
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "200 OK" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus200 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 200 OK', true, 200);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "204 No Content" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus204 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 204 No Content', true, 204);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "206 Partial Content" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus206 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 206 Partial Content', true, 206);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "301 Moved Permanently" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus301 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 301 Moved Permanently', true, 301);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "302 Found" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus302 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 302 Found', true, 302);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "307 Temporary Redirect" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus307 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 307 Temporary Redirect', true, 307);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "308 Permanent Redirect" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus308 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 308 Permanent Redirect', true, 308);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "400 Bad Request" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus400 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 400 Bad Request', true, 400);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "401 Unauthorized" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus401 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 401 Unauthorized', true, 401);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "403 Forbidden" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus403 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 403 Forbidden', true, 403);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "404 Not Found" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus404 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 404 Not Found', true, 404);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "405 Method Not Allowed" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus405 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 405 Method Not Allowed', true, 405);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "406 Not Acceptable" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus406 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 406 Not Acceptable', true, 406);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "409 Conflict" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus409 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 409 Conflict', true, 409);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "410 Gone" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus410 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 410 Gone', true, 410);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "429 Too Many Requests" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus429 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 429 Too Many Requests', true, 429);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "451 Unavailable For Legal Reasons" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus451 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 451 Unavailable For Legal Reasons', true, 451);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "500 Internal Server Error" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus500 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 500 Internal Server Error', true, 500);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "501 Not Implemented" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus501 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 501 Not Implemented', true, 501);
}

/**
 * -------------------------------------------------------------------------
 * Sends status code "503 Service Unavailable" to the user's browser.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendStatus503 () {
    $scheme = getUrlScheme();
    @ header($scheme . ' 503 Service Unavailable', true, 503);
}

/**
 * -------------------------------------------------------------------------
 * Sends content type "Html" to the user's browser.
 *
 * @public
 * @param   bool  $utf8  True if the HTML document is UTF-8 encoded.
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendHeaderHTML ( $utf8 = true ) {
    $charset = $utf8 ? '; charset=UTF-8' : '';
    @ header('Content-Type: text/html' . $charset, true);
}

/**
 * -------------------------------------------------------------------------
 * Sends content type "Text" to the user's browser.
 *
 * @public
 * @param   bool  $utf8  True if the text document is UTF-8 encoded.
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendHeaderTEXT ( $utf8 = true ) {
    $charset = $utf8 ? '; charset=UTF-8' : '';
    @ header('Content-Type: text/plain' . $charset, true);
}

/**
 * -------------------------------------------------------------------------
 * Sends content type "Xml" to the user's browser.
 *
 * @public
 * @param   bool  $utf8  True if the XML document is UTF-8 encoded.
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendHeaderXML ( $utf8 = true ) {
    $charset = $utf8 ? '; charset=UTF-8' : '';
    @ header('Content-Type: text/xml' . $charset, true);
}

/**
 * -------------------------------------------------------------------------
 * Sends content type "Style" to the user's browser.
 *
 * @public
 * @param   bool  $utf8  True if the style file is UTF-8 encoded.
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendHeaderCSS ( $utf8 = true ) {
    $charset = $utf8 ? '; charset=UTF-8' : '';
    @ header('Content-Type: text/css' . $charset, true);
}

/**
 * -------------------------------------------------------------------------
 * Sends content type "Javascript" to the user's browser.
 *
 * @public
 * @param   bool  $utf8  True if javascript file is UTF-8 encoded.
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendHeaderJS ( $utf8 = true ) {
    $charset = $utf8 ? '; charset=UTF-8' : '';
    @ header('Content-Type: application/javascript' . $charset, true);
}

/**
 * -------------------------------------------------------------------------
 * Sends content type "Json" to the user's browser.
 *
 * @public
 * @param   bool  $utf8  True if the JSON file is UTF-8 encoded.
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendHeaderJSON ( $utf8 = true ) {
    $charset = $utf8 ? '; charset=UTF-8' : '';
    @ header('Content-Type: application/json' . $charset, true);
}

/**
 * -------------------------------------------------------------------------
 * Sends content type "Csv" to the user's browser.
 *
 * @public
 * @param   bool  $utf8  True if the CSV file is UTF-8 encoded.
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendHeaderCSV ( $utf8 = true ) {
    $charset = $utf8 ? '; charset=UTF-8' : '';
    @ header('Content-Type: text/csv' . $charset, true);
}

/**
 * -------------------------------------------------------------------------
 * Sends content type "Excel" to the user's browser.
 *
 * @public
 * @param   bool  $utf8  True if the XLS file is UTF-8 encoded.
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendHeaderEXCEL ( $utf8 = true ) {
    $charset = $utf8 ? '; charset=UTF-8' : '';
    @ header('Content-Type: application/vnd.ms-excel' . $charset, true);
}

/**
 * -------------------------------------------------------------------------
 * Sends content type "File" to the user's browser.
 *
 * @public
 * @param   string  $filename  The name that the file will have after downloading.
 * @param   bool    $inlined   True if the file is an attachment (downloadable file),
 *                             False if it is an inline document.
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendHeaderFILE ( $filename, $inlined = false ) {
    $type = $inlined ? 'inline' : 'attachment';
    @ header('Content-Description: File Transfer', true);
    @ header('Content-Disposition: ' . $type . '; filename="' . $filename . '"', true);
    @ header('Content-Transfer-Encoding: binary', true);
}

/**
 * -------------------------------------------------------------------------
 * Sends header "Expires" to the user's browser.
 *
 * @public
 * @param   int  $plus  Time offset (in hours) relative to server time.
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendHeaderExpires ( $plus = 1 ) {
    $time = time() + $plus * 3600;
    @ header('Expires: ' . date('r', $time), true);
    @ header('Cache-Control: public', true);
    @ header('Pragma: public', true);
}

/**
 * -------------------------------------------------------------------------
 * Sends header "Last-Modified" to the user's browser.
 *
 * @public
 * @param   int                           $plus  Time offset (in hours) relative to server time.
 * @param   int|string|array|object|null  $item  INTEGER CASE: The required time value.
 *                                               STRING CASE:  The absolute path to the file used as the measurement object.
 *                                               ARRAY CASE:   The database record with the "modified" or "created" field used as the measurement object.
 *                                               OBJECT CASE:  The database record with the "modified" or "created" property used as the measurement object.
 *                                               If no valid item case is specified, the measurement object is the server time.
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendHeaderLastModified ( $plus = 0, $item = null ) {
    $now = time();
    try {
        $type = gettype($item);
        switch ( $type ) {
            case 'integer':
                $time = $item;
                break;
            case 'string':
                $time = @ filemtime($item);
                break;
            case 'array':
                $time = empty($item['modified'])
                        ? ( empty($item['created'])
                            ? $now
                            : @ strtotime($item['created']) )
                        : @ strtotime($item['modified']);
                break;
            case 'object':
                $time = empty($item->modified)
                        ? ( empty($item->created)
                            ? $now
                            : @ strtotime($item->created) )
                        : @ strtotime($item->modified);
                break;
            case 'boolean':
            case 'double':
            case 'resource':
            case 'resource (closed)':
            case 'NULL':
            case 'unknown type':
            default:
                $time = $now;
        }
    } catch (Exception $e) {
        $time = $now;
    }
    @ header('Last-Modified: ' . date('r', $time + $plus * 3600), true);
}

/**
 * -------------------------------------------------------------------------
 * Exits if the request method is HEAD.
 *
 * @public
 * @return  void
 * -------------------------------------------------------------------------
 */

function stopIfHead () {
    $value = mimimiServer('REQUEST_METHOD');
    if ( $value == 'HEAD' ) {
        mimimiStop('', 0);
    }
}

/**
 * -------------------------------------------------------------------------
 * Sends content security policy to the user's browser.
 *
 * @public
 * @global  string  $nonce  The current NONCE (number used once).
 * @return  void
 * -------------------------------------------------------------------------
 */

function sendSecurityPolicy () {
    global $nonce;
           $nonce = base64_encode(mimimiRandomId(20));
    @ header('X-Frame-Options: DENY');
    $styleUrls      = "'nonce-" . printValue($nonce, false) . "' " .
                      printThemeUrl(false) . 'css/';
    $javascriptUrls = "'nonce-" . printValue($nonce, false) . "' " .
                      printThemeUrl(false) . 'js/';
    $fontUrls       = "'nonce-" . printValue($nonce, false) . "' " .
                      printThemeUrl(false) . 'fonts/';
    $imageUrls      = "'nonce-" . printValue($nonce, false) . "' " .
                      printSiteUrl(false) . 'favicon.ico ' .
                      printThemeUrl(false) . 'images/ ' .
                      printSiteUrl(false) . 'media/';
    $videoUrls      = "'nonce-" . printValue($nonce, false) . "' " .
                      printThemeUrl(false) . 'images/ ' .
                      printSiteUrl(false) . 'media/';
    $pluginUrls     = "'none'";
    $iframeUrls     = "'none'";
    $formUrls       = printSiteUrl(false);
    $otherUrls      = "'none'";
    @ header('Content-Security-Policy: default-src '     . $otherUrls      . '; ' .
                                      'style-src '       . $styleUrls      . '; ' .
                                      'script-src '      . $javascriptUrls . '; ' .
                                      'font-src '        . $fontUrls       . '; ' .
                                      'img-src '         . $imageUrls      . '; ' .
                                      'media-src '       . $videoUrls      . '; ' .
                                      'object-src '      . $pluginUrls     . '; ' .
                                      'form-action '     . $formUrls       . '; ' .
                                      'frame-ancestors ' . $iframeUrls);
}

/**
 * -------------------------------------------------------------------------
 * Prints the NONCE tag attribute.
 *
 * @public
 * @param   bool    $echo   True if sent to the browser,
 *                          False if returned as a result.
 * @global  string  $nonce  Current NONCE (number used once).
 * @return  string
 * -------------------------------------------------------------------------
 */

function secureByNonce ( $echo = true ) {
    global $nonce;
    if (! is_string($nonce)) {
        $nonce = '';
    }
    $value = 'nonce="' . printValue($nonce, false) . '"';
    if ($echo) {
        echo $value;
        return '';
    }
    return $value;
}

/**
 * -------------------------------------------------------------------------
 * Checks if the value is empty.
 *
 * @public
 * @param   string  $value  Value to be checked.
 * @return  bool
 * -------------------------------------------------------------------------
 */

function isEmpty ( $value ) {
    return empty($value) ||
           preg_replace('~(^\s+|\s+$)~u', '', $value) == '';
}

/**
 * -------------------------------------------------------------------------
 * Checks if the value is non-empty.
 *
 * @public
 * @param   string  $value  Value to be checked.
 * @return  bool
 * -------------------------------------------------------------------------
 */

function isNonEmpty ( $value ) {
    return ! isEmpty($value);
}

/**
 * -------------------------------------------------------------------------
 * Safely prints value.
 *
 * @public
 * @param   string  $value  Value to be printed.
 * @param   bool    $echo   True if sent to the browser,
 *                          False if returned as a result.
 * @return  string
 * -------------------------------------------------------------------------
 */

function printValue ( $value, $echo = true ) {
    $value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
    if ($echo) {
        echo $value;
        return '';
    }
    return $value;
}

/**
 * -------------------------------------------------------------------------
 * Prints the URL of the domain (physical root of the website).
 *
 * @public
 * @param   bool    $echo      True if sent to the browser,
 *                             False if returned as a result.
 * @param   bool    $relative  True if the relative path is printed (it's an empty string),
 *                             False if the absolute path is printed.
 * @return  string
 * -------------------------------------------------------------------------
 */

function printDomainUrl ( $echo = true, $relative = false ) {
    if ($relative) return '';
    $domain = mimimiSite(false);
    if ($echo) {
        printValue($domain);
        return '';
    }
    return $domain;
}

/**
 * -------------------------------------------------------------------------
 * Prints the URL of the website root.
 *
 * @public
 * @param   bool    $echo      True if sent to the browser,
 *                             False if returned as a result.
 * @param   bool    $relative  True if the relative path is printed (it's an empty string),
 *                             False if the absolute path is printed.
 * @return  string
 * -------------------------------------------------------------------------
 */

function printSiteUrl ( $echo = true, $relative = false ) {
    if ($relative) return '';
    $root = mimimiRoot(false);
    if ($echo) {
        printDomainUrl();
        printValue($root);
        return '';
    }
    return printDomainUrl(false) .
           $root;
}

/**
 * -------------------------------------------------------------------------
 * Prints the root URL of the website's active theme.
 *
 * @public
 * @param   bool    $echo      True if sent to the browser,
 *                             False if returned as a result.
 * @param   bool    $relative  True if the relative path is printed,
 *                             False if the absolute path is printed.
 * @return  string
 * -------------------------------------------------------------------------
 */

function printThemeUrl ( $echo = true, $relative = false ) {
    $folder = MIMIMI_CMS_FOLDER . 'Themes/' . getThemeName() . '/';
    return printSiteUrl($echo, $relative) .
           printValue($folder, $echo);
}

/**
 * -------------------------------------------------------------------------
 * Prints the URL of the current web page.
 *
 * @public
 * @param   bool    $echo      True if sent to the browser,
 *                             False if returned as a result.
 * @param   bool    $relative  True if the relative path is printed,
 *                             False if the absolute path is printed.
 * @return  string
 * -------------------------------------------------------------------------
 */

function printPageUrl ( $echo = true, $relative = false ) {
    $path = mimimiUri(false);
    if ($echo) {
        printSiteUrl(true, $relative);
        printValue($path);
        return '';
    }
    return printSiteUrl(false, $relative) .
           $path;
}

/**
 * -------------------------------------------------------------------------
 * Prints the field of the database record.
 *
 * @public
 * @param   string  $name  Name of the field.
 * @param   array   $item  Record to be printed. By default, global variable $item is used.
 * @param   bool    $echo  True if sent to the browser,
 *                         False if returned as a result.
 * @return  string
 * -------------------------------------------------------------------------
 */

function printItemField ( $name, $item = null, $echo = true ) {
    if (is_null($item)) {
        global $item;
    }
    if (isset($item[$name])) {
        if (! $echo) {
            return printValue($item[$name], false);
        }
        printValue($item[$name]);
    }
    return '';
}

/**
 * -------------------------------------------------------------------------
 * Retrieves the input field of the posted form.
 *
 * @public
 * @param   string  $name   Name of the field.
 * @param   mixed   $def    (optional) The default value if there is no form or required input field.
 * @param   mixed   $index  (optional) The required index if the input field has been indexed.
 * @return  string
 * -------------------------------------------------------------------------
 */

function getFormInput ( $name, $def = '', $index = null ) {
    $value = mimimiPost(
                 $name,
                 mimimiGet($name, $def)
             );
    if (! is_null($index)) {
        $value = isset($value[$index])
                 ? $value[$index]
                 : $def;
    }
    $value = preg_replace('~[ \t]+~u',      ' ',  $value);
    $value = preg_replace('~ +([\r\n])~u',  '$1', $value);
    return   preg_replace('~(^\s+|\s+$)~u', '',   $value);
}

/**
 * -------------------------------------------------------------------------
 * Retrieves the input file of the posted form.
 *
 * @public
 * @param   string  $name   Name of the FILE input field.
 * @param   bool    $temp   (optional) FALSE if you need to retrieve local (client-side) filename.
 *                                     TRUE  if you need to retrieve temporary (server-side) filename.
 * @param   mixed   $index  (optional) The required index if the input field has been indexed.
 * @return  string
 * -------------------------------------------------------------------------
 */

function getFormFile ( $name, $temp = FALSE, $index = null ) {
    $file = mimimiFiles($name);
    if (is_null($index)) {
        if (! empty($file['name'    ])
        &&  ! empty($file['tmp_name'])
        &&  ! empty($file['size'    ])
        &&  $file['error'           ] == UPLOAD_ERR_OK) {
            return $temp ? $file['tmp_name']
                         : $file['name'    ];
        }
    } else {
        if (! empty($file['name'    ][$index])
        &&  ! empty($file['tmp_name'][$index])
        &&  ! empty($file['size'    ][$index])
        &&  $file['error'           ][$index] == UPLOAD_ERR_OK) {
            return $temp ? $file['tmp_name'][$index]
                         : $file['name'    ][$index];
        }
    }
    return '';
}

/**
 * -------------------------------------------------------------------------
 * Makes a secure URL based on the provided URL.
 *
 * @public
 * @param   string  $url    The address of the web file that needs to be secured.
 * @param   bool    $local  (optional) FALSE if the URL can link to an external file.
 *                                     TRUE  if the URL refers to a local file only.
 * @return  string          The final URL of the file.
 * -------------------------------------------------------------------------
 */

function makeSecureUrl ( $url, $local = true ) {
    $scheme = '~^(https?:)?[/\\\\]{2,}~ui';
    if (preg_match($scheme, $url)) {
        if ( ! $local ) return $url;
        $domain = '~^[^/\\\\]+[/\\\\]+~u';
        $url = preg_replace($scheme, '', $url);
        $url = preg_replace($domain, '', $url);
    }
    $query   = '~^([^\#?]*)[\#?].*$~u';
    $slashes = '~[/\\\\]+~u';
    $hacks   = '~/*([\s.]+/+)+~u';
    $bad     = '~[^\w.,_/+()-]~u';
    $endings = '~(^[\s/]+|[\s/.]+$)~u';
    $url = preg_replace($query,   '$1', $url);
    $url = preg_replace($slashes, '/',  $url);
    $url = preg_replace($hacks,   '/',  $url);
    $url = preg_replace($bad,     '-',  $url);
    return preg_replace($endings, '',   $url);
}

/**
 * -------------------------------------------------------------------------
 * Builds the URL of the file located in the media folder.
 *
 * @public
 * @param   string  $url  The relative or absolute URL of the web file to be processed.
 * @return  string        The final relative URL of the file.
 * -------------------------------------------------------------------------
 */

function makeMediaUrl ( $url ) {
    $url = makeSecureUrl($url);
    $media = '~^media/~ui';
    return preg_replace($media, '', $url);
}

/**
 * -------------------------------------------------------------------------
 * Validates the file URL to match the media folder extensions.
 *
 * @public
 * @param   string  $url    Relative file URL to be validated.
 * @param   array   $valid  (optional) The list of valid file extensions.
 * @return  bool            TRUE if the file URL is valid.
 * -------------------------------------------------------------------------
 */

function validateMedia ( $url, $valid = [] ) {
    $ending = '~^.*\.([\w,_/+()-]+?)$~u';
    $url = makeMediaUrl($url);
    $ext = preg_replace($ending, '$1', $url);
    return empty($valid)
        || ($ext != $url
            && is_array($valid)
            && in_array(mb_strtolower($ext, 'UTF-8'), $valid));
}

/**
 * -------------------------------------------------------------------------
 * Removes the file from the media folder.
 *
 * @public
 * @param   string  $url  Relative file URL to be removed.
 * @return  bool          True if successful,
 *                        False if that file is not found or there is an error.
 * -------------------------------------------------------------------------
 */

function removeMedia ( $url ) {
    $url = makeMediaUrl($url);
    if ($url) {
        $path = dirname(__FILE__) . '/../../media/';
        if (file_exists($path . $url)) {
            @ unlink($path . $url);
            $ok = ! file_exists($path . $url);
            if ($ok) {
                $segment = '~[/\\\\]+[^/\\\\]+$~u';
                while ($ok && $url) {
                    $previous = $url;
                    $url      = preg_replace($segment, '', $previous);
                    if ($previous == $url) break;
                    if ($url) {
                        $ok = @ rmdir($path . $url);
                    }
                }
                return true;
            }
        }
    }
    return false;
}

/**
 * -------------------------------------------------------------------------
 * Uploads the file to the media folder.
 *
 * @public
 * @param   string       $url    Relative file URL to be uploaded.
 * @param   string       $from   The temporary name of the file in which the uploaded file was stored on the server.
 * @param   array        $valid  (optional) The list of valid file extensions.
 * @return  string|bool          STRING on success, it is the final relative URL of the uploaded file.
 *                               FALSE  on failure.
 * -------------------------------------------------------------------------
 */

function uploadMedia ( $url, $from, $valid = ['webp', 'jpg', 'jpeg', 'png', 'gif', 'svg',
                                              'mp3', 'ogg', 'wav',
                                              'mp4'] ) {
    if (is_uploaded_file($from)) {
        if (validateMedia($url, $valid)) {
            removeMedia($url);
            $file = makeMediaUrl($url);
            $path = dirname(__FILE__) . '/../../media/';
            $segment = '~[/\\\\]+[^/\\\\]+$~u';
            $folder = preg_replace($segment, '', $file);
            if ($folder != $file) {
                @ mkdir($path . $folder, 0755, true);
            }
            @ move_uploaded_file($from, $path . $file);
            return $file;
        }
    }
    return false;
}

/**
 * -------------------------------------------------------------------------
 * Prints the variable structure and content.
 *
 * @public
 * @param   mixed  $var   Variable to be printed.
 * @param   bool   $stop  True if you need to stop execution after printing.
 * @return  void
 * -------------------------------------------------------------------------
 */

function dumpVar ( & $var, $stop = true ) {
    $border = '----------------------------------------';
    echo '<pre>' . $border . '<br>';
    echo preg_replace('~<~u', '&lt;', print_r($var, true));
    echo '<br>' . $border . '</pre>';
    if ($stop) {
        exit;
    }
}
