File "Paths.php"

Full Path: /home/amervokv/ecomlive.net/wp-content/plugins/webp-express/lib/classes/Paths.php
File size: 29.58 KB
MIME-type: text/x-php
Charset: utf-8

<?php

namespace WebPExpress;

use \WebPExpress\FileHelper;
use \WebPExpress\Multisite;
use \WebPExpress\PathHelper;

class Paths
{
    public static function areAllImageRootsWithinDocRoot() {
        if (!PathHelper::isDocRootAvailable()) {
            return false;
        }

        $roots = self::getImageRootIds();
        foreach ($roots as $dirId) {
            $dir = self::getAbsDirById($dirId);
            if (!PathHelper::canCalculateRelPathFromDocRootToDir($dir)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Check if we can use document root for calculating relative paths (which may not contain "/.." directory traversal)
     *
     * Note that this method allows document root to be outside open_basedir as long as document root is
     * non-empty AND it is possible to calculate relative paths to all image roots (including "index").
     * Here is a case when a relative CAN be calculated:
     * - Document root is configured to "/var/www/website" - which is also the absolute file path.
     * - open_basedir is set to "/var/www/website/wordpress"
     * - uploads is in "/var/www/website/wordpress/wp-content/uploads" (within open_basedir, as it should)
     * - "/wp-uploads" symlinks to "/var/www/website/wordpress")
     * - Wordpress has been configured to use "/wp-uploads" path for uploads.
     *
     * What happens?
     * First, it is tested if the configured upload path ("/wp-uploads") begins with the configured document root ("/var/www/website").
     * This fails.
     * Next, it is tested if the uploads path can be resolved. It can, as it is within the open_basedir.
     * Next, it is tested if the *resolved* the uploads path begins with the configured document root.
     * As "/var/www/website/wordpress/wp-content/uploads" begins with "/var/www/website", we have a match.
     * The relative path can be calculated to be "wordpress/wp-content/uploads".
     * Later, when the relative path is used, it will be used as $docRoot + "/" + $relPath, which
     * will be "/var/www/website/wordpress/wp-content/uploads". All is well.
     *
     * Here is a case where it CAN NOT be calculated:
     * - Document root is configured to "/the-website", which symlinks to "/var/www/website"
     * - open_basedir is set to "/var/www/website/wordpress"
     * - uploads is in "/var/www/website/wordpress/wp-content/uploads" and wordpress is configured to use that upload path.
     *
     * What happens?
     * First, it is tested if the configured upload path begins with the configured document root
     * "/var/www/website/wordpress/wp-content/uploads" does not begin with "/the-website", so it fails.
     * Next, it is tested if the *resolved* the uploads path begins with the configured document root.
     * The resolved uploads path is the same as the configured so it also fails.
     * Next, it is tested if Document root can be resolved. It can not, as the resolved path is not within open_basedir.
     * If it could, it would have been tested if the resolved path begins with the resolved document root and we would have
     * gotten a yes, and the relative path would have been "wordpress/wp-content/uploads" and it would work.
     * However: Document root could not be resolved and we could not get a result.
     * To sum the scenario up:
     * If document root is configured to a symlink which cannot be resolved then it will only be possible to get relative paths
     * when all other configured paths begins are relative to that symlink.
     */
    public static function canUseDocRootForRelPaths() {
        if (!PathHelper::isDocRootAvailable()) {
            return false;
        }
        return self::areAllImageRootsWithinDocRoot();
    }

    public static function canCalculateRelPathFromDocRootToDir($absPath) {
    }

    /**
     * Check if we can use document root for structuring the cache dir.
     *
     * In order to structure the images by doc-root, WebP Express needs all images to be within document root.
     * Does WebP Express in addition to this need to be able to resolve document root?
     * Short answer is yes.
     * The long answer is available as a comment inside ConvertHelperIndependent::getDestination()
     *
     */
    public static function canUseDocRootForStructuringCacheDir() {
        return (PathHelper::isDocRootAvailableAndResolvable() && self::canUseDocRootForRelPaths());
    }

    public static function docRootStatusText()
    {
        if (!PathHelper::isDocRootAvailable()) {
            if (!isset($_SERVER['DOCUMENT_ROOT'])) {
                return 'Unavailable (DOCUMENT_ROOT is not set in the global $_SERVER var)';
            }
            if ($_SERVER['DOCUMENT_ROOT'] == '') {
                return 'Unavailable (empty string)';
            }
            return 'Unavailable';
        }

        $imageRootsWithin = self::canUseDocRootForRelPaths();
        if (!PathHelper::isDocRootAvailableAndResolvable()) {
            $status = 'Available, but either non-existing or not within open_basedir.' .
                ($imageRootsWithin ? '' : ' And not all image roots are within that document root.');
        } elseif (!$imageRootsWithin) {
            $status = 'Available, but not all image roots are within that document root.';
        } else {
            $status = 'Available and its "realpath" is available too.';
        }
        if (self::canUseDocRootForStructuringCacheDir()) {
            $status .= ' Can be used for structuring cache dir.';
        } else {
            $status .= ' Cannot be used for structuring cache dir.';
        }
        return $status;
    }

    public static function getAbsDirId($absDir) {
        switch ($absDir) {
            case self::getContentDirAbs():
                return 'wp-content';
            case self::getIndexDirAbs():
                return 'index';
            case self::getHomeDirAbs():
                return 'home';
            case self::getPluginDirAbs():
                return 'plugins';
            case self::getUploadDirAbs():
                return 'uploads';
            case self::getThemesDirAbs():
                return 'themes';
            case self::getCacheDirAbs():
                return 'cache';
        }
        return false;
    }

    public static function getAbsDirById($dirId) {
        switch ($dirId) {
            case 'wp-content':
                return self::getContentDirAbs();
            case 'index':
                return self::getIndexDirAbs();
            case 'home':
                // "home" is still needed (used in PluginDeactivate.php)
                return self::getHomeDirAbs();
            case 'plugins':
                return self::getPluginDirAbs();
            case 'uploads':
                return self::getUploadDirAbs();
            case 'themes':
                return self::getThemesDirAbs();
            case 'cache':
                return self::getCacheDirAbs();
        }
        return false;
    }

    /**
     * Get ids for folders where SOURCE images may reside
     */
    public static function getImageRootIds() {
        return ['uploads', 'themes', 'plugins', 'wp-content', 'index'];
    }

    /**
     * Find which rootId a path belongs to.
     *
     * Note: If the root ids passed are ordered the way getImageRootIds() returns them, the root id
     * returned will be the "deepest"
     */
    public static function findImageRootOfPath($path, $rootIdsToSearch) {
        foreach ($rootIdsToSearch as $rootId) {
            if (PathHelper::isPathWithinExistingDirPath($path, self::getAbsDirById($rootId))) {
                return $rootId;
            }
        }
        return false;
    }

    public static function getImageRootsDefForSelectedIds($ids) {
        $canUseDocRootForRelPaths = self::canUseDocRootForRelPaths();

        $mapping = [];
        foreach ($ids as $rootId) {
            $obj = [
                'id' => $rootId,
            ];
            $absPath = self::getAbsDirById($rootId);
            if ($canUseDocRootForRelPaths) {
                $obj['rel-path'] = PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed($absPath);
            } else {
                $obj['abs-path'] = $absPath;
            }
            $obj['url'] = self::getUrlById($rootId);
            $mapping[] = $obj;
        }
        return $mapping;
    }

    public static function getImageRootsDef()
    {
        return self::getImageRootsDefForSelectedIds(self::getImageRootIds());
    }

    public static function filterOutSubRoots($rootIds)
    {
        // Get dirs of enabled roots
        $dirs = [];
        foreach ($rootIds as $rootId) {
            $dirs[] = self::getAbsDirById($rootId);
        }

        // Filter out dirs which are below other dirs
        $dirsToSkip = [];
        foreach ($dirs as $dirToExamine) {
            foreach ($dirs as $dirToCompareAgainst) {
                if ($dirToExamine == $dirToCompareAgainst) {
                    continue;
                }
                if (self::isDirInsideDir($dirToExamine, $dirToCompareAgainst)) {
                    $dirsToSkip[] = $dirToExamine;
                    break;
                }
            }
        }
        $dirs = array_diff($dirs, $dirsToSkip);

        // back to ids
        $result = [];
        foreach ($dirs as $dir) {
            $result[] = self::getAbsDirId($dir);
        }
        return $result;
    }

    public static function createDirIfMissing($dir)
    {
        if (!@file_exists($dir)) {
            // We use the wp_mkdir_p, because it takes care of setting folder
            // permissions to that of parent, and handles creating deep structures too
            wp_mkdir_p($dir);
        }
        return file_exists($dir);
    }

    /**
    *  Find out if $dir1 is inside - or equal to - $dir2
    */
    public static function isDirInsideDir($dir1, $dir2)
    {
        $rel = PathHelper::getRelDir($dir2, $dir1);
        return (substr($rel, 0, 3) != '../');
    }

    /**
     *  Return absolute dir.
     *
     *  - Path is canonicalized (without resolving symlinks)
     *  - trailing dash is removed - we don't use that around here.
     *
     *  We do not resolve symlinks anymore. Information was lost that way.
     *  And in some cases we needed the unresolved path - for example in the .htaccess.
     */
    public static function getAbsDir($dir)
    {
        $dir = PathHelper::canonicalize($dir);
        return rtrim($dir, '/');
        /*
        $result = realpath($dir);
        if ($result === false) {
            $dir = PathHelper::canonicalize($dir);
        } else {
            $dir = $result;
        }*/

    }

    // ------------ Home Dir -------------

    // PS: Home dir is not the same as index dir.
    // For example, if Wordpress folder has been moved (method 2), the home dir could be below.
    public static function getHomeDirAbs()
    {
        if (!function_exists('get_home_path')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }
        return self::getAbsDir(get_home_path());
    }

    // ------------ Index Dir  (WP root dir) -------------
    // (The Wordpress installation dir- where index.php and wp-load.php resides)

    public static function getIndexDirAbs()
    {
        // We used to return self::getAbsDir(ABSPATH), which used realpath.
        // It has been changed now, as it seems we do not need realpath for ABSPATH, as it is defined
        // (in wp-load.php) as dirname(__FILE__) . "/" and according to this link, __FILE__ returns resolved paths:
        // https://stackoverflow.com/questions/3221771/how-do-you-get-php-symlinks-and-file-to-work-together-nicely
        // AND a user reported an open_basedir restriction problem thrown by realpath($_SERVER['DOCUMENT_ROOT']),
        // due to symlinking and opendir restriction (see #322)

        return rtrim(ABSPATH, '/');

        // TODO: read up on this, regarding realpath:
        // https://github.com/twigphp/Twig/issues/2707

    }

    // ------------ .htaccess dir -------------
    // (directory containing the relevant .htaccess)
    // (see https://github.com/rosell-dk/webp-express/issues/36)



    public static function canWriteHTAccessRulesHere($dirName) {
        return FileHelper::canEditOrCreateFileHere($dirName . '/.htaccess');
    }

    public static function canWriteHTAccessRulesInDir($dirId) {
        return self::canWriteHTAccessRulesHere(self::getAbsDirById($dirId));
    }

    public static function returnFirstWritableHTAccessDir($dirs)
    {
        foreach ($dirs as $dir) {
            if (self::canWriteHTAccessRulesHere($dir)) {
                return $dir;
            }
        }
        return false;
    }

    // ------------ Content Dir (the "WP" content dir) -------------

    public static function getContentDirAbs()
    {
        return self::getAbsDir(WP_CONTENT_DIR);
    }
    public static function getContentDirRel()
    {
        return PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(self::getContentDirAbs());
    }
    public static function getContentDirRelToPluginDir()
    {
        return PathHelper::getRelDir(self::getPluginDirAbs(), self::getContentDirAbs());
    }
    public static function getContentDirRelToWebPExpressPluginDir()
    {
        return PathHelper::getRelDir(self::getWebPExpressPluginDirAbs(), self::getContentDirAbs());
    }


    public static function isWPContentDirMoved()
    {
        return (self::getContentDirAbs() != (ABSPATH . 'wp-content'));
    }

    public static function isWPContentDirMovedOutOfAbsPath()
    {
        return !(self::isDirInsideDir(self::getContentDirAbs(), ABSPATH));
    }

    // ------------ Themes Dir -------------

    public static function getThemesDirAbs()
    {
        return self::getContentDirAbs() . '/themes';
    }

    // ------------ WebPExpress Content Dir -------------
    // (the "webp-express" directory inside wp-content)

    public static function getWebPExpressContentDirAbs()
    {
        return self::getContentDirAbs() . '/webp-express';
    }

    public static function getWebPExpressContentDirRel()
    {
        return PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(self::getWebPExpressContentDirAbs());
    }

    public static function createContentDirIfMissing()
    {
        return self::createDirIfMissing(self::getWebPExpressContentDirAbs());
    }

    // ------------ Upload Dir -------------
    public static function getUploadDirAbs()
    {
        $upload_dir = wp_upload_dir(null, false);
        return self::getAbsDir($upload_dir['basedir']);
    }
    public static function getUploadDirRel()
    {
        return PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(self::getUploadDirAbs());
    }

    /*
    public static function getUploadDirAbs()
    {
        if ( defined( 'UPLOADS' ) ) {
            return ABSPATH . rtrim(UPLOADS, '/');
        } else {
            return self::getContentDirAbs() . '/uploads';
        }
    }*/

    public static function isUploadDirMovedOutOfWPContentDir()
    {
        return !(self::isDirInsideDir(self::getUploadDirAbs(), self::getContentDirAbs()));
    }

    public static function isUploadDirMovedOutOfAbsPath()
    {
        return !(self::isDirInsideDir(self::getUploadDirAbs(), ABSPATH));
    }

    // ------------ Config Dir -------------

    public static function getConfigDirAbs()
    {
        return self::getWebPExpressContentDirAbs() . '/config';
    }

    public static function getConfigDirRel()
    {
        return PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(self::getConfigDirAbs());
    }

    public static function createConfigDirIfMissing()
    {
        $configDir = self::getConfigDirAbs();
        // Using code from Wordfence bootstrap.php...
        // Why not simply use wp_mkdir_p ? - it sets the permissions to same as parent. Isn't that better?
        // or perhaps not... - Because we need write permissions in the config dir.
        if (!is_dir($configDir)) {
            @mkdir($configDir, 0775);
            @chmod($configDir, 0775);
            @file_put_contents(rtrim($configDir . '/') . '/.htaccess', <<<APACHE
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
APACHE
            );
            @chmod($configDir . '/.htaccess', 0664);
        }
        return is_dir($configDir);
    }

    public static function getConfigFileName()
    {
        return self::getConfigDirAbs() . '/config.json';
    }

    public static function getWodOptionsFileName()
    {
        return self::getConfigDirAbs() . '/wod-options.json';
    }

    // ------------ Cache Dir -------------

    public static function getCacheDirAbs()
    {
        return self::getWebPExpressContentDirAbs() . '/webp-images';
    }

    public static function getCacheDirRelToDocRoot()
    {
        return PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(self::getCacheDirAbs());
    }

    public static function getCacheDirForImageRoot($destinationFolder, $destinationStructure, $imageRootId)
    {
        if (($destinationFolder == 'mingled') && ($imageRootId == 'uploads')) {
            return self::getUploadDirAbs();
        }

        if ($destinationStructure == 'doc-root') {
            $relPath = PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(
                self::getAbsDirById($imageRootId)
            );
            return self::getCacheDirAbs() . '/doc-root/' . $relPath;
        } else {
            return self::getCacheDirAbs() . '/' . $imageRootId;
        }
    }

    public static function createCacheDirIfMissing()
    {
        return self::createDirIfMissing(self::getCacheDirAbs());
    }

    // ------------ Log Dir -------------

    public static function getLogDirAbs()
    {
        return self::getWebPExpressContentDirAbs() . '/log';
    }

    // ------------ Bigger-than-source  dir -------------

    public static function getBiggerThanSourceDirAbs()
    {
        return self::getWebPExpressContentDirAbs() . '/webp-images-bigger-than-source';
    }

    // ------------ Plugin Dir (all plugins) -------------

    public static function getPluginDirAbs()
    {
        return self::getAbsDir(WP_PLUGIN_DIR);
    }


    public static function isPluginDirMovedOutOfAbsPath()
    {
        return !(self::isDirInsideDir(self::getPluginDirAbs(), ABSPATH));
    }

    public static function isPluginDirMovedOutOfWpContent()
    {
        return !(self::isDirInsideDir(self::getPluginDirAbs(), self::getContentDirAbs()));
    }

    // ------------ WebP Express Plugin Dir -------------

    public static function getWebPExpressPluginDirAbs()
    {
        return self::getAbsDir(WEBPEXPRESS_PLUGIN_DIR);
    }

    // ------------------------------------
    // ---------    Url paths    ----------
    // ------------------------------------

    /**
     *  Get url path (relative to domain) from absolute url.
     *  Ie: "http://example.com/blog" => "blog"
     *  Btw: By "url path" we shall always mean relative to domain
     *       By "url" we shall always mean complete URL (with domain and everything)
     *                                (or at least something that starts with it...)
     *
     *  Also note that in this library, we never returns trailing or leading slashes.
     */
    public static function getUrlPathFromUrl($url)
    {
        $parsed = parse_url($url);
        if (!isset($parsed['path'])) {
            return '';
        }
        if (is_null($parsed['path'])) {
            return '';
        }
        $path = untrailingslashit($parsed['path']);
        return ltrim($path, '/\\');
    }

    public static function getUrlById($dirId) {
        switch ($dirId) {
            case 'wp-content':
                return self::getContentUrl();
            case 'index':
                return self::getHomeUrl();
            case 'home':
                return self::getHomeUrl();
            case 'plugins':
                return self::getPluginsUrl();
            case 'uploads':
                return self::getUploadUrl();
            case 'themes':
                return self::getThemesUrl();
        }
        return false;
    }

    /**
     * Get destination root url and path, provided rootId and some configuration options
     *
     * This method kind of establishes the overall structure of the cache dir.
     * (but not quite, as the logic is also in ConverterHelperIndependent::getDestination).
     *
     * @param  string  $rootId
     * @param  DestinationOptions  $destinationOptions
     *
     * @return array   url and abs-path of destination root
     */
    public static function destinationRoot($rootId, $destinationOptions)
    {
        if (($destinationOptions->mingled) && ($rootId == 'uploads')) {
            return [
                'url' => self::getUrlById('uploads'),
                'abs-path' => self::getUploadDirAbs()
            ];
        } else {

            // Its within these bases:
            $destUrl = self::getUrlById('wp-content') . '/webp-express/webp-images';
            $destPath = self::getAbsDirById('wp-content') . '/webp-express/webp-images';

            if (($destinationOptions->useDocRoot) && self::canUseDocRootForStructuringCacheDir()) {
                $relPathFromDocRootToSourceImageRoot = PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(
                    self::getAbsDirById($rootId)
                );
                return [
                    'url' => $destUrl . '/doc-root/' . $relPathFromDocRootToSourceImageRoot,
                    'abs-path' => $destPath  . '/doc-root/' . $relPathFromDocRootToSourceImageRoot
                ];
            } else {
                $extraPath = '';
                if (is_multisite() && (get_current_blog_id() != 1)) {
                    $extraPath = '/sites/' . get_current_blog_id();   // #510
                }
                return [
                    'url' => $destUrl . '/' . $rootId . $extraPath,
                    'abs-path' => $destPath  . '/' . $rootId . $extraPath
                ];
            }
        }
    }

    public static function getRootAndRelPathForDestination($destinationPath, $imageRoots) {
        foreach ($imageRoots->getArray() as $i => $imageRoot) {
            $rootPath = $imageRoot->getAbsPath();
            if (strpos($destinationPath, realpath($rootPath)) !== false) {
                $relPath = substr($destinationPath, strlen(realpath($rootPath)) + 1);
                return [$imageRoot->id, $relPath];
            }
        }
        return ['', ''];
    }



    // PST:
    // appendOrSetExtension() have been copied from ConvertHelperIndependent.
    // TODO: I should complete the move ASAP.

    /**
     * Append ".webp" to path or replace extension with "webp", depending on what is appropriate.
     *
     * If destination-folder is set to mingled and destination-extension is set to "set" and
     * the path is inside upload folder, the appropriate thing is to SET the extension.
     * Otherwise, it is to APPEND.
     *
     * @param  string  $path
     * @param  string  $destinationFolder
     * @param  string  $destinationExt
     * @param  boolean $inUploadFolder
     */
    public static function appendOrSetExtension($path, $destinationFolder, $destinationExt, $inUploadFolder)
    {
        if (($destinationFolder == 'mingled') && ($destinationExt == 'set') && $inUploadFolder) {
            return preg_replace('/\\.(jpe?g|png)$/i', '', $path) . '.webp';
        } else {
            return $path . '.webp';
        }
    }

    /**
     * Get destination root url and path, provided rootId and some configuration options
     *
     * This method kind of establishes the overall structure of the cache dir.
     * (but not quite, as the logic is also in ConverterHelperIndependent::getDestination).
     *
     * @param  string  $rootId
     * @param  string  $relPath
     * @param  string  $destinationFolder     ("mingled" or "separate")
     * @param  string  $destinationExt        ('append' or 'set')
     * @param  string  $destinationStructure  ("doc-root" or "image-roots")
     *
     * @return array   url and abs-path of destination
     */
   /*
    public static function destinationPath($rootId, $relPath, $destinationFolder, $destinationExt, $destinationStructure) {

        // TODO: Current logic will not do!
        // We must use ConvertHelper::getDestination for the abs path.
        // And we must use logic from AlterHtmlHelper to get the URL
        // Perhaps this method must be abandonned

        $root = self::destinationRoot($rootId, $destinationFolder, $destinationStructure);
        $inUploadFolder = ($rootId == 'upload');
        $relPath = ConvertHelperIndependent::appendOrSetExtension($relPath, $destinationFolder, $destinationExt, $inUploadFolder);

        return [
            'abs-path' => $root['abs-path'] . '/' . $relPath,
            'url' => $root['url'] . '/' . $relPath,
        ];
    }

    public static function destinationPathConvenience($rootId, $relPath, $config) {
        return self::destinationPath(
            $rootId,
            $relPath,
            $config['destination-folder'],
            $config['destination-extension'],
            $config['destination-structure']
        );
    }*/

    public static function getDestinationPathCorrespondingToSource($source, $destinationOptions) {
        return Destination::getDestinationPathCorrespondingToSource(
            $source,
            Paths::getWebPExpressContentDirAbs(),
            Paths::getUploadDirAbs(),
            $destinationOptions,
            new ImageRoots(self::getImageRootsDef())
        );
    }

    public static function getUrlPathById($dirId) {
        return self::getUrlPathFromUrl(self::getUrlById($dirId));
    }

    public static function getHostNameOfUrl($url) {
        $urlComponents = parse_url($url);
        /* ie:
        (
            [scheme] => http
            [host] => we0
            [path] => /wordpress/uploads-moved
        )*/

        if (!isset($urlComponents['host'])) {
            return '';
        } else {
            return $urlComponents['host'];
        }
    }

    // Get complete home url (no trailing slash). Ie: "http://example.com/blog"
    public static function getHomeUrl()
    {
        if (!function_exists('home_url')) {
            // silence is golden?
            // bad joke. Need to handle this...
        }
        return untrailingslashit(home_url());
    }

    /** Get home url, relative to domain. Ie "" or "blog"
     *  If home url is for example http://example.com/blog/, the result is "blog"
     */
    public static function getHomeUrlPath()
    {
        return self::getUrlPathFromUrl(self::getHomeUrl());
    }


    public static function getUploadUrl()
    {
        $uploadDir = wp_upload_dir(null, false);
        return untrailingslashit($uploadDir['baseurl']);
    }

    public static function getUploadUrlPath()
    {
        return self::getUrlPathFromUrl(self::getUploadUrl());
    }

    public static function getContentUrl()
    {
        return untrailingslashit(content_url());
    }

    public static function getContentUrlPath()
    {
        return self::getUrlPathFromUrl(self::getContentUrl());
    }

    public static function getThemesUrl()
    {
        return self::getContentUrl() . '/themes';
    }

    /**
     *  Get Url to plugins (base)
     */
    public static function getPluginsUrl()
    {
        return untrailingslashit(plugins_url());
    }

    /**
     *  Get Url to WebP Express plugin (this is in fact an incomplete URL, you need to append ie '/webp-on-demand.php' to get a full URL)
     */
    public static function getWebPExpressPluginUrl()
    {
        return untrailingslashit(plugins_url('', WEBPEXPRESS_PLUGIN));
    }

    public static function getWebPExpressPluginUrlPath()
    {
        return self::getUrlPathFromUrl(self::getWebPExpressPluginUrl());
    }

    public static function getWodFolderUrlPath()
    {
        return
            self::getWebPExpressPluginUrlPath() .
            '/wod';
    }

    public static function getWod2FolderUrlPath()
    {
        return
            self::getWebPExpressPluginUrlPath() .
            '/wod2';
    }

    public static function getWodUrlPath()
    {
        return
            self::getWodFolderUrlPath() .
            '/webp-on-demand.php';
    }

    public static function getWod2UrlPath()
    {
        return
            self::getWod2FolderUrlPath() .
            '/webp-on-demand.php';
    }

    public static function getWebPRealizerUrlPath()
    {
        return
            self::getWodFolderUrlPath() .
            '/webp-realizer.php';
    }

    public static function getWebPRealizer2UrlPath()
    {
        return
            self::getWod2FolderUrlPath()  .
            '/webp-realizer.php';
    }

    public static function getWebServiceUrl()
    {
        //return self::getWebPExpressPluginUrl() . '/wpc.php';
        //return self::getHomeUrl() . '/webp-express-server';
        return self::getHomeUrl() . '/webp-express-web-service';
    }

    public static function getUrlsAndPathsForTheJavascript()
    {
        return [
            'urls' => [
                'webpExpressRoot' => self::getWebPExpressPluginUrlPath(),
                'content' => self::getContentUrlPath(),
            ],
            'filePaths' => [
                'webpExpressRoot' => self::getWebPExpressPluginDirAbs(),
                'destinationRoot' => self::getCacheDirAbs(),
            ]
        ];
    }

    public static function getSettingsUrl()
    {
        if (!function_exists('admin_url')) {
            require_once ABSPATH . 'wp-includes/link-template.php';
        }
        if (Multisite::isNetworkActivated()) {
            // network_admin_url is also defined in link-template.php.
            return network_admin_url('settings.php?page=webp_express_settings_page');
        } else {
            return admin_url('options-general.php?page=webp_express_settings_page');
        }
    }

}