File "SelfTestHelper.php"

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

<?php

namespace WebPExpress;

use \WebPExpress\Paths;

class SelfTestHelper
{

    public static function deleteFilesInDir($dir, $filePattern = "*")
    {
        foreach (glob($dir . DIRECTORY_SEPARATOR . $filePattern) as $filename) {
            unlink($filename);
        }
    }

    /**
     * Remove files in dir and the dir. Does not remove files recursively.
     */
    public static function deleteDir($dir)
    {
        if (@file_exists($dir)) {
            self::deleteFilesInDir($dir);
            rmdir($dir);
        }
    }


    public static function deleteTestImagesInFolder($rootId)
    {
        $testDir = Paths::getAbsDirById($rootId) . '/webp-express-test-images';
        self::deleteDir($testDir);
    }

    public static function cleanUpTestImages($rootId, $config)
    {
        // Clean up test images in source folder
        self::deleteTestImagesInFolder($rootId);

        // Clean up dummy webp images in cache folder for the root
        $cacheDirForRoot = Paths::getCacheDirForImageRoot(
            $config['destination-folder'],
            $config['destination-structure'],
            $rootId
        );

        $testDir = $cacheDirForRoot . '/webp-express-test-images';
        self::deleteDir($testDir);
    }

    public static function copyFile($source, $destination)
    {
        $log = [];
        if (@copy($source, $destination)) {
            return [true, $log];
        } else {
            $log[] = 'Failed to copy *' . $source . '* to *' . $destination . '*';
            if (!@file_exists($source)) {
                $log[] = 'The source file was not found';
            } else {
                if (!@file_exists(dirname($destination))) {
                    $log[] = 'The destination folder does not exist!';
                } else {
                    $log[] = 'This is probably a permission issue. Check that your webserver has permission to ' .
                        'write files in the directory (*' . dirname($destination) . '*)';
                }
            }
            return [false, $log];
        }
    }

    public static function randomDigitsAndLetters($length)
    {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $charactersLength = strlen($characters);
        $randomString = '';
        for ($i = 0; $i < $length; $i++) {
            $randomString .= $characters[rand(0, $charactersLength - 1)];
        }
        return $randomString;
    }

    public static function copyTestImageToRoot($rootId, $imageType = 'jpeg')
    {
        // TODO: Copy to a subfolder instead
        // TODO: Use smaller jpeg / pngs please.
        $log = [];
        switch ($imageType) {
            case 'jpeg':
                $fileNameToCopy = 'very-small.jpg';
                break;
            case 'png':
                $fileNameToCopy = 'test.png';
                break;
        }
        $testSource = Paths::getPluginDirAbs() . '/webp-express/test/' . $fileNameToCopy;
        $filenameOfDestination = self::randomDigitsAndLetters(6) . '.' . strtoupper($imageType);
        //$filenameOfDestination = self::randomDigitsAndLetters(6) . '.' . $imageType;
        $log[] = 'Copying ' . strtoupper($imageType) . ' to ' . $rootId . ' folder (*webp-express-test-images/' . $filenameOfDestination . '*)';

        $destDir = Paths::getAbsDirById($rootId) . '/webp-express-test-images';
        $destination = $destDir . '/' . $filenameOfDestination;

        if (!@file_exists($destDir)) {
            if (!@mkdir($destDir)) {
                $log[count($log) - 1] .= '. FAILED';
                $log[] = 'Failed to create folder for test images: ' . $destDir;
                return [$log, false, ''];
            }
        }

        list($success, $errors) = self::copyFile($testSource, $destination);
        if (!$success) {
            $log[count($log) - 1] .= '. FAILED';
            $log = array_merge($log, $errors);
            return [$log, false, ''];
        } else {
            $log[count($log) - 1] .= '. ok!';
            $log[] = 'We now have a ' . $imageType . ' stored here:';
            $log[] = '*' . $destination . '*';
        }
        return [$log, true, $filenameOfDestination];
    }

    public static function copyTestImageToUploadFolder($imageType = 'jpeg')
    {
        return self::copyTestImageToRoot('uploads', $imageType);
    }

    public static function copyDummyWebPToCacheFolder($rootId, $destinationFolder, $destinationExtension, $destinationStructure, $sourceFileName, $imageType = 'jpeg')
    {
        $log = [];
        $dummyWebP = Paths::getPluginDirAbs() . '/webp-express/test/test.jpg.webp';

        $log[] = 'Copying dummy webp to the cache root for ' . $rootId;
        $destDir = Paths::getCacheDirForImageRoot($destinationFolder, $destinationStructure, $rootId);
        if (!file_exists($destDir)) {
            $log[] = 'The folder did not exist. Creating folder at: ' . $destinationFolder;
            if (!mkdir($destDir, 0777, true)) {
                $log[] = 'Failed creating folder!';
                return [$log, false, ''];
            }
        }
        $destDir .= '/webp-express-test-images';
        if (!file_exists($destDir)) {
            if (!mkdir($destDir, 0755, false)) {
                $log[] = 'Failed creating the folder for the test images:';
                $log[] = $destDir;
                $log[] = 'To run this test, you must grant write permissions';
                return [$log, false, ''];
            }
        }

        $filenameOfDestination = ConvertHelperIndependent::appendOrSetExtension(
            $sourceFileName,
            $destinationFolder,
            $destinationExtension,
            ($rootId == 'uploads')
        );

        //$filenameOfDestination = $destinationFileNameNoExt . ($destinationExtension == 'append' ? '.' . $imageType : '') . '.webp';
        $destination = $destDir . '/' . $filenameOfDestination;

        list($success, $errors) = self::copyFile($dummyWebP, $destination);
        if (!$success) {
            $log[count($log) - 1] .= '. FAILED';
            $log = array_merge($log, $errors);
            return [$log, false, ''];
        } else {
            $log[count($log) - 1] .= '. ok!';
            $log[] = 'We now have a webp file stored here:';
            $log[] = '*' . $destination . '*';
            $log[] = '';
        }
        return [$log, true, $destination];
    }

    /**
     *  Perform HTTP request.
     *
     *  @param  string  $requestUrl    URL
     *  @param  array   $args          Args to pass to wp_remote_get. Note however that "redirection" is set to 0
     *  @param  int     $maxRedirects  For internal use
     *  @return array   The result
     *                  $success (boolean):  If we got a 200 response in the end (after max 2 redirects)
     *                  $log (array)      :  Message log
     *                  $results          :  Array of results from wp_remote_get. If no redirection occured, it will only contain one item.
     *
     */
    public static function remoteGet($requestUrl, $args = [], $maxRedirects = 2)
    {
        $log = [];
        $args['redirection'] = 0;

        if (defined('WP_DEBUG') && WP_DEBUG ) {
           // Prevent errors with unverified certificates (#379)
           $args['sslverify'] = false;
        }

        $log[] = 'Request URL: ' . $requestUrl;

        $results = [];
        $wpResult = wp_remote_get($requestUrl, $args);
        if (is_wp_error($wpResult)) {
            $log[] = 'The remote request errored';
            $log[] = $wpResult->get_error_message();
            //$log[] = print_r($wpResult, true);
            return [false, $log, $results];
        }
        if (!is_wp_error($wpResult) && !isset($wpResult['headers'])) {
            $wpResult['headers'] = [];
        }
        $results[] = $wpResult;
        $responseCode = $wpResult['response']['code'];

        $log[] = 'Response: ' . $responseCode . ' ' . $wpResult['response']['message'];
        $log = array_merge($log, SelfTestHelper::printHeaders($wpResult['headers']));

        if (isset($wpResult['headers']['content-type'])) {
            if (strpos($wpResult['headers']['content-type'], 'text/html') !== false) {
                if (isset($wpResult['body']) && (!empty($wpResult['body']))) {
                    $log[] = 'Body:';
                    $log[] = print_r($wpResult['body'], true);
                }
            }
        }

        if (($responseCode == '302') || ($responseCode == '301')) {
            if ($maxRedirects > 0) {
                if (isset($wpResult['headers']['location'])) {
                    $url = $wpResult['headers']['location'];
                    if (strpos($url, 'http') !== 0) {
                        $url = $requestUrl . $url;
                    }
                    $log[] = 'Following that redirect';

                    list($success, $newLog, $newResult) = self::remoteGet($url, $args, $maxRedirects - 1);
                    $log = array_merge($log, $newLog);
                    $results = array_merge($results, $newResult);

                    return [$success, $log, $results];

                }
            } else {
                $log[] = 'Not following the redirect (max redirects exceeded)';
            }
        }

        $success = ($responseCode == '200');
        return [$success, $log, $results];
    }

    public static function hasHeaderContaining($headers, $headerToInspect, $containString)
    {
        if (!isset($headers[$headerToInspect])) {
            return false;
        }

        // If there are multiple headers, check all
        if (gettype($headers[$headerToInspect]) == 'string') {
            $h = [$headers[$headerToInspect]];
        } else {
            $h = $headers[$headerToInspect];
        }
        foreach ($h as $headerValue) {
            if (stripos($headerValue, $containString) !== false) {
                return true;
            }
        }
        return false;
    }

    public static function hasVaryAcceptHeader($headers)
    {
        if (!isset($headers['vary'])) {
            return false;
        }

        // There may be multiple Vary headers. Or they might be combined in one.
        // Both are acceptable, according to https://stackoverflow.com/a/28799169/842756
        if (gettype($headers['vary']) == 'string') {
            $varyHeaders = [$headers['vary']];
        } else {
            $varyHeaders = $headers['vary'];
        }
        foreach ($varyHeaders as $headerValue) {
            $values = explode(',', $headerValue);
            foreach ($values as $value) {
                if (strtolower($value) == 'accept') {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @param  string  $rule  existing|webp-on-demand|webp-realizer
     */
    public static function diagnoseNoVaryHeader($rootId, $rule)
    {
        $log = [];
        $log[] = '**However, we did not receive a Vary:Accept header. ' .
            'That header should be set in order to tell proxies that the response varies depending on the ' .
            'Accept header. Otherwise browsers not supporting webp might get a cached webp and vice versa.**{: .warn}';

        $log[] = 'Too technical? ';
        $log[] = 'Here is an explanation of what this means: ' .
            'Some companies have set up proxies which caches resources. This way, if employee A have downloaded an ' .
            'image and employee B requests it, the proxy can deliver the image directly to employee B without needing to ' .
            'send a request to the server. ' .
            'This is clever, but it can go wrong. If B for some reason is meant to get another image than A, it will not ' .
            'happen, as the server does not get the request. That is where the Vary header comes in. It tells the proxy ' .
            'that the image is dependent upon something. In this case, we need to signal proxies that the image depends upon ' .
            'the "Accept" header, as this is the one browsers use to tell the server if it accepts webps or not. ' .
            'We do that using the "Vary:Accept" header. However - it is missing :( ' .
            'Which means that employees at (larger) companies might experience problems if some are using browsers ' .
            'that supports webp and others are using browsers that does not. Worst case is that the request to an image ' .
            'is done with a browser that supports webp, as this will cache the webp in the proxy, and deliver webps to ' .
            'all employees - even to those who uses browsers that does not support webp. These employees will get blank images.';

        if ($rule == 'existing') {
            $log[] = 'So, what should you do? **I would recommend that you either try to fix the problem with the missing Vary:Accept ' .
                'header or change to "CDN friendly" mode.**{: .warn}';
        } elseif ($rule == 'webp-on-demand') {
            $log[] = 'So, what should you do? **I would recommend that you either try to fix the problem with the missing Vary:Accept ' .
                'header or disable the "Enable redirection to converter?" option and use another way to get the images converted - ie ' .
                'Bulk Convert or Convert on Upload**{: .warn}';
        }



        return $log;
    }

    public static function hasCacheControlOrExpiresHeader($headers)
    {
        if (isset($headers['cache-control'])) {
            return true;
        }
        if (isset($headers['expires'])) {
            return true;
        }
        return false;
    }


    public static function flattenHeaders($headers)
    {
        $log = [];
        foreach ($headers as $headerName => $headerValue) {
            if (gettype($headerValue) == 'array') {
                foreach ($headerValue as $i => $value) {
                    $log[] = [$headerName, $value];
                }
            } else {
                $log[] = [$headerName, $headerValue];
            }
        }
        return $log;
    }

    public static function printHeaders($headers)
    {
        $log = [];
        $log[] = '#### Response headers:';

        $headersFlat = self::flattenHeaders($headers);
        //
        foreach ($headersFlat as $i => list($headerName, $headerValue)) {
            if ($headerName == 'x-webp-express-error') {
                $headerValue = '**' . $headerValue . '**{: .error}';
            }
            $log[] = '- ' . $headerName . ': ' . $headerValue;
        }
        $log[] = '';
        return $log;
    }

    private static function trueFalseNullString($var)
    {
        if ($var === true) {
            return 'yes';
        }
        if ($var === false) {
            return 'no';
        }
        return 'could not be determined';
    }

    public static function systemInfo()
    {
        $log = [];
        $log[] = '#### System info:';
        $log[] = '- PHP version: ' . phpversion();
        $log[] = '- OS: ' . PHP_OS;
        $log[] = '- Server software: ' . $_SERVER["SERVER_SOFTWARE"];
        $log[] = '- Document Root status: ' . Paths::docRootStatusText();
        if (PathHelper::isDocRootAvailable()) {
            $log[] = '- Document Root: ' . $_SERVER['DOCUMENT_ROOT'];
        }
        if (PathHelper::isDocRootAvailableAndResolvable()) {
            if ($_SERVER['DOCUMENT_ROOT'] != realpath($_SERVER['DOCUMENT_ROOT'])) {
                $log[] = '- Document Root (symlinked resolved): ' . realpath($_SERVER['DOCUMENT_ROOT']);
            }
        }

        $log[] = '- Document Root: ' . Paths::docRootStatusText();
        $log[] = '- Apache module "mod_rewrite" enabled?: ' . self::trueFalseNullString(PlatformInfo::gotApacheModule('mod_rewrite'));
        $log[] = '- Apache module "mod_headers" enabled?: ' . self::trueFalseNullString(PlatformInfo::gotApacheModule('mod_headers'));
        return $log;
    }

    public static function wordpressInfo()
    {
        $log = [];
        $log[] = '#### Wordpress info:';
        $log[] = '- Version: ' . get_bloginfo('version');
        $log[] = '- Multisite?: ' . self::trueFalseNullString(is_multisite());
        $log[] = '- Is wp-content moved?: ' . self::trueFalseNullString(Paths::isWPContentDirMoved());
        $log[] = '- Is uploads moved out of wp-content?: ' . self::trueFalseNullString(Paths::isUploadDirMovedOutOfWPContentDir());
        $log[] = '- Is plugins moved out of wp-content?: ' . self::trueFalseNullString(Paths::isPluginDirMovedOutOfWpContent());

        $log[] = '';

        $log[] = '#### Image roots (absolute paths)';
        foreach (Paths::getImageRootIds() as $rootId) {
            $absDir = Paths::getAbsDirById($rootId);

            if (PathHelper::pathExistsAndIsResolvable($absDir) && ($absDir != realpath($absDir))) {
                $log[] = '*' . $rootId . '*: ' . $absDir . ' (resolved for symlinks: ' .  realpath($absDir) . ')';
            } else {
                $log[] = '*' . $rootId . '*: ' . $absDir;

            }
        }

        $log[] = '#### Image roots (relative to document root)';
        foreach (Paths::getImageRootIds() as $rootId) {
            $absPath = Paths::getAbsDirById($rootId);
            if (PathHelper::canCalculateRelPathFromDocRootToDir($absPath)) {
                $log[] = '*' . $rootId . '*: ' . PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed($absPath);
            } else {
                $log[] = '*' . $rootId . '*: ' . 'n/a (not within document root)';
            }
        }

        $log[] = '#### Image roots (URLs)';
        foreach (Paths::getImageRootIds() as $rootId) {
            $url = Paths::getUrlById($rootId);
            $log[] = '*' . $rootId . '*: ' . $url;
        }


        return $log;
    }

    public static function configInfo($config)
    {
        $log = [];
        $log[] = '#### WebP Express configuration info:';
        $log[] = '- Destination folder: ' . $config['destination-folder'];
        $log[] = '- Destination extension: ' . $config['destination-extension'];
        $log[] = '- Destination structure: ' . $config['destination-structure'];
        //$log[] = 'Image types: ' . ;
        //$log[] = '';
        $log[] = '(To view all configuration, take a look at the config file, which is stored in *' . Paths::getConfigFileName() . '*)';
        //$log[] = '- Config file: (config.json)';
        //$log[] = "'''\n" . json_encode($config, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT) . "\n'''\n";
        return $log;
    }

    public static function htaccessInfo($config, $printRules = true)
    {
        $log = [];
        //$log[] = '*.htaccess info:*';
        //$log[] = '- Image roots with WebP Express rules: ' . implode(', ', HTAccess::getRootsWithWebPExpressRulesIn());
        $log[] = '#### .htaccess files that WebP Express have placed rules in the following files:';
        $rootIds = HTAccess::getRootsWithWebPExpressRulesIn();
        foreach ($rootIds as $imageRootId) {
            $log[] = '- ' . Paths::getAbsDirById($imageRootId) . '/.htaccess';
        }

        foreach ($rootIds as $imageRootId) {
            $log = array_merge($log, self::rulesInImageRoot($config, $imageRootId));
        }

        return $log;
    }

    public static function rulesInImageRoot($config, $imageRootId)
    {
        $log = [];
        $file = Paths::getAbsDirById($imageRootId) . '/.htaccess';
        $log[] = '#### WebP rules in *' .
            ($imageRootId == 'cache' ? 'webp image cache' : $imageRootId) . '*:';
        $log[] = 'File: ' . $file;
        if (!HTAccess::haveWeRulesInThisHTAccess($file)) {
            $log[] = '**NONE!**{: .warn}';
        } else {
            $weRules = HTAccess::extractWebPExpressRulesFromHTAccess($file);
            // remove unindented comments
            //$weRules = preg_replace('/^\#\s[^\n\r]*[\n\r]+/ms', '', $weRules);

            // remove comments in the beginning
            $weRulesArr = preg_split("/\r\n|\n|\r/", $weRules);  // https://stackoverflow.com/a/11165332/842756
            while ((strlen($weRulesArr[0]) > 0) && ($weRulesArr[0][0] == '#')) {
                array_shift($weRulesArr);
            }
            $weRules = implode("\n", $weRulesArr);

            $log[] = '```' . $weRules . '```';
        }
        return $log;
    }

    public static function rulesInUpload($config)
    {
        return self::rulesInImageRoot($config, 'uploads');
    }

    public static function allInfo($config)
    {
        $log = [];

        $log = array_merge($log, self::systemInfo());
        $log = array_merge($log, self::wordpressInfo());
        $log = array_merge($log, self::configInfo($config));
        $log = array_merge($log, self::capabilityTests($config));
        $log = array_merge($log, self::htaccessInfo($config, true));
        //$log = array_merge($log, self::rulesInImageRoot($config, 'upload'));
        //$log = array_merge($log, self::rulesInImageRoot($config, 'wp-content'));
        return $log;
    }

    public static function capabilityTests($config)
    {
        $capTests = $config['base-htaccess-on-these-capability-tests'];
        $log = [];
        $log[] = '#### Live tests of .htaccess capabilities / system configuration:';
        $log[] = 'Unless noted otherwise, the tests are run in *wp-content/webp-express/htaccess-capability-tester*. ';
        $log[] = 'WebPExpress currently treats the results as they neccessarily applies to all scopes (upload, themes, etc), ';
        $log[] = 'but note that a server might be configured to have mod_rewrite disallowed in some folders and allowed in others.';
        /*$log[] = 'Exactly what you can do in a *.htaccess* depends on the server setup. WebP Express ' .
            'makes some live tests to verify if a certain feature in fact works. This is done by creating ' .
            'test files (*.htaccess* files and php files) in a dir inside the content dir and running these. ' .
            'These test results are used when creating the rewrite rules. Here are the results:';*/

//        $log[] = '';
        $log[] = '- .htaccess files enabled?: ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::htaccessEnabled());
        $log[] = '- mod_rewrite working?: ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::modRewriteWorking());
        $log[] = '- mod_headers loaded?: ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::modHeadersLoaded());
        $log[] = '- mod_headers working (header set): ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::modHeaderWorking());
        //$log[] = '- passing variables from *.htaccess* to PHP script through environment variable working?: ' . self::trueFalseNullString($capTests['passThroughEnvWorking']);
        $log[] = '- passing variables from *.htaccess* to PHP script through environment variable working?: ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::passThroughEnvWorking());
        $log[] = '- Can run php test file in plugins/webp-express/wod/ ?: ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::canRunTestScriptInWOD());
        $log[] = '- Can run php test file in plugins/webp-express/wod2/ ?: ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::canRunTestScriptInWOD2());
        $log[] = '- Directives for granting access like its done in wod/.htaccess allowed?: ' . self::trueFalseNullString(HTAccessCapabilityTestRunner::grantAllAllowed());
        /*$log[] = '- pass variable from *.htaccess* to script through header working?: ' .
            self::trueFalseNullString($capTests['passThroughHeaderWorking']);*/
        return $log;
    }

    public static function diagnoseFailedRewrite($config, $headers)
    {
        if (($config['destination-structure'] == 'image-roots') && (!PathHelper::isDocRootAvailableAndResolvable())) {
            $log[] = 'The problem is probably this combination:';
            if (!PathHelper::isDocRootAvailable()) {
                $log[] = '1. Your document root isn`t available';
            } else {
                $log[] = '1. Your document root isn`t resolvable for symlinks (it is probably subject to open_basedir restriction)';
            }
            $log[] = '2. Your document root is symlinked';
            $log[] = '3. The wordpress function that tells the path of the uploads folder returns the symlink resolved path';

            $log[] = 'I cannot check if your document root is in fact symlinked (as document root isnt resolvable). ' .
                'But if it is, there you have it. The line beginning with "RewriteCond %{REQUEST_FILENAME}"" points to your resolved root, ' .
                'but it should point to your symlinked root. WebP Express cannot do that for you because it cannot discover what the symlink is. ' .
                'Try changing the line manually. When it works, you can move the rules outside the WebP Express block so they dont get ' .
                'overwritten. OR you can change your server configuration (document root / open_basedir restrictions)';
        }

        //$log[] = '## Diagnosing';

        //if (PlatformInfo::isNginx()) {
        if (strpos($headers['server'], 'nginx') === 0) {

            // Nginx
            $log[] = 'Notice that you are on Nginx and the rules that WebP Express stores in the *.htaccess* files probably does not ' .
                'have any effect. ';
            $log[] = 'Please read the "I am on Nginx" section in the FAQ (https://wordpress.org/plugins/webp-express/)';
            $log[] = 'And did you remember to restart the nginx service after updating the configuration?';

            $log[] = 'PS: If you cannot get the redirect to work, you can simply rely on Alter HTML as described in the FAQ.';
            return $log;
        }

        $modRewriteWorking = HTAccessCapabilityTestRunner::modRewriteWorking();
        if ($modRewriteWorking !== null) {
            $log[] = 'Running a special designed capability test to test if rewriting works with *.htaccess* files';
        }
        if ($modRewriteWorking === true) {
            $log[] = 'Result: Yes, rewriting works.';
            $log[] = 'It seems something is wrong with the *.htaccess* rules then. You could try ' .
                'to change "Destination structure" - the rules there are quite different.';
            $log[] = 'It could also be that the server has cached the configuration a while. Some servers ' .
                'does that. In that case, simply give it a few minutes and try again.';
        } elseif ($modRewriteWorking === false) {
            $log[] = 'Result: No, rewriting does not seem to work within *.htaccess* rules.';
            if (PlatformInfo::definitelyNotGotModRewrite()) {
                $log[] = 'It actually seems "mod_write" is disabled on your server. ' .
                    '**You must enable mod_rewrite on the server**';
            } elseif (PlatformInfo::definitelyGotApacheModule('mod_rewrite')) {
                $log[] = 'However, "mod_write" *is* enabled on your server. This seems to indicate that ' .
                    '*.htaccess* files has been disabled for configuration on your server. ' .
                    'In that case, you need to copy the WebP Express rules from the *.htaccess* files into your virtual host configuration files. ' .
                    '(WebP Express generates multiple *.htaccess* files. Look in the upload folder, the wp-content folder, etc).';
                $log[] = 'It could however alse simply be that your server simply needs some time. ' .
                    'Some servers caches the *.htaccess* rules for a bit. In that case, simply give it a few minutes and try again.';
            } else {
                $log[] = 'However, this could be due to your server being a bit slow on picking up changes in *.htaccess*.' .
                    'Give it a few minutes and try again.';
            }
        } else {
            // The mod_rewrite test could not conclude anything.
            if (PlatformInfo::definitelyNotGotApacheModule('mod_rewrite')) {
                $log[] = 'It actually seems "mod_write" is disabled on your server. ' .
                    '**You must enable mod_rewrite on the server**';
            } elseif (PlatformInfo::definitelyGotApacheModule('mod_rewrite')) {
                $log[] = '"mod_write" is enabled on your server, so rewriting ought to work. ' .
                    'However, it could be that your server setup has disabled *.htaccess* files for configuration. ' .
                    'In that case, you need to copy the WebP Express rules from the *.htaccess* files into your virtual host configuration files. ' .
                    '(WebP Express generates multiple *.htaccess* files. Look in the upload folder, the wp-content folder, etc). ';
            } else {
                $log[] = 'It seems something is wrong with the *.htaccess* rules. ';
                $log[] = 'Or perhaps the server has cached the configuration a while. Some servers ' .
                    'does that. In that case, simply give it a few minutes and try again.';
            }
        }
        $log[] = 'Note that if you cannot get redirection to work, you can switch to "CDN friendly" mode and ' .
            'rely on the "Alter HTML" functionality to point to the webp images. If you do a bulk conversion ' .
            'and make sure that "Convert upon upload" is activated, you should be all set. Alter HTML even handles ' .
            'inline css (unless you select "picture tag" syntax). It does however not handle images in external css or ' .
            'which is added dynamically with javascript.';

        $log[] = '## Info for manually diagnosing';
        $log = array_merge($log, self::allInfo($config));
        return $log;
    }

    public static function diagnoseWod403or500($config, $rootId, $responseCode)
    {
        $log = [];

        $htaccessRules = SelfTestHelper::rulesInImageRoot($config, $rootId);
        $rulesText = implode('', $htaccessRules);
        $rulesPointsToWod = (strpos($rulesText, '/wod/') > 0);
        $rulesPointsToWod2 = (strpos($rulesText, '/wod2/') !== false);

        $log[] = '';
        $log[] = '**diagnosing**';
        $canRunTestScriptInWod = HTAccessCapabilityTestRunner::canRunTestScriptInWOD();
        $canRunTestScriptInWod2 = HTAccessCapabilityTestRunner::canRunTestScriptInWOD2();
        $canRunInAnyWod = ($canRunTestScriptInWod || $canRunTestScriptInWod2);

        $responsePingPhp = wp_remote_get(Paths::getPluginsUrl() . '/webp-express/wod/ping.php', ['timeout' => 7]);
        $pingPhpResponseCode = wp_remote_retrieve_response_code($responsePingPhp);

        $responsePingText = wp_remote_get(Paths::getPluginsUrl() . '/webp-express/wod/ping.txt', ['timeout' => 7]);
        $pingTextResponseCode = wp_remote_retrieve_response_code($responsePingText);

        if ($responseCode == 500) {
            $log[] = 'The response was a *500 Internal Server Error*. There can be different reasons for that. ' .
                'Lets dig a bit deeper...';
        }

        $log[] = 'Examining where the *.htaccess* rules in the ' . $rootId . ' folder points to. ';

        if ($rulesPointsToWod) {
            $log[] = 'They point to **wod**/webp-on-demand.php';
        } elseif ($rulesPointsToWod2) {
            $log[] = 'They point to **wod2**/webp-on-demand.php';
        } else {
            $log[] = '**There are no redirect rule to *webp-on-demand.php* in the .htaccess!**{: .warn}';
            $log[] = 'Here is the rules:';
            $log = array_merge($log, $htaccessRules);
        }

        if ($rulesPointsToWod) {
            $log[] = 'Requesting simple test script "wod/ping.php"... ' .
                'Result: ' . ($pingPhpResponseCode == '200' ? 'ok' : 'failed (response code: ' . $pingPhpResponseCode . ')');
                //'Result: ' . ($canRunTestScriptInWod ? 'ok' : 'failed');

            if ($canRunTestScriptInWod) {
                if ($responseCode == '500') {
                    $log[] = '';
                    $log[] = '**As the test script works, it would seem that the explanation for the 500 internal server ' .
                        'error is that the PHP script (webp-on-demand.php) crashes. ' .
                        'You can help me by enabling debugging and post the error on the support forum on Wordpress ' .
                        '(https://wordpress.org/support/plugin/webp-express/), or create an issue on github ' .
                        '(https://github.com/rosell-dk/webp-express/issues)**';
                    $log[] = '';
                }
            } else {
                $log[] = 'Requesting simple test file "wod/ping.txt". ' .
                    'Result: ' . ($pingTextResponseCode == '200' ? 'ok' : 'failed (response code: ' . $pingTextResponseCode . ')');

                if ($canRunTestScriptInWod2) {
                    if ($responseCode == 500) {
                        if ($pingTextResponseCode == '500') {
                            $log[] = 'The problem appears to be that the *.htaccess* placed in *plugins/webp-express/wod/.htaccess*' .
                                ' contains auth directives ("Allow" and "Request") and your server is set up to go fatal about it. ' .
                                'Luckily, it seems that running scripts in the "wod2" folder works. ' .
                                '**What you need to do is simply to click the "Save settings and force new .htacess rules"' .
                                ' button. WebP Express wil then change the .htaccess rules to point to the "wod2" folder**';
                        } else {
                            $log[] = 'The problem appears to be running PHP scripts in the "wod". ' .
                                'Luckily, it seems that running scripts in the "wod2" folder works ' .
                                '(it has probably something to do with the *.htaccess* file placed in "wod"). ' .
                                '**What you need to do is simply to click the "Save settings and force new .htacess rules"' .
                                ' button. WebP Express wil then change the .htaccess rules to point to the "wod2" folder**';
                        }
                    } elseif ($responseCode == 403) {
                        $log[] = 'The problem appears to be running PHP scripts in the "wod". ' .
                            'Luckily, it seems that running scripts in the "wod2" folder works ' .
                            '(it could perhaps have something to do with the *.htaccess* file placed in "wod", ' .
                            'although it ought not result in a 403). **What you need to do is simply to click the "Save settings and force new .htacess rules"' .
                            ' button. WebP Express wil then change the .htaccess rules to point to the "wod2" folder**';
                    }

                    return $log;
                }
            }
        }

        $log[] = 'Requesting simple test script "wod2/ping.php". Result: ' . ($canRunTestScriptInWod2 ? 'ok' : 'failed');
        $responsePingText2 = wp_remote_get(Paths::getPluginsUrl() . '/webp-express/wod2/ping.txt', ['timeout' => 7]);
        $pingTextResponseCode2 = wp_remote_retrieve_response_code($responsePingText2);
        $log[] = 'Requesting simple test file "wod2/ping.txt". ' .
            'Result: ' . ($pingTextResponseCode == '200' ? 'ok' : 'failed (response code: ' . $pingTextResponseCode2 . ')');

        if ($rulesPointsToWod2) {
            if ($canRunTestScriptInWod2) {
                if ($responseCode == '500') {
                    $log[] = '';
                    $log[] = '**As the test script works, it would seem that the explanation for the 500 internal server ' .
                        'error is that the PHP script (webp-on-demand.php) crashes. ' .
                        'You can help me by enabling debugging and post the error on the support forum on Wordpress ' .
                        '(https://wordpress.org/support/plugin/webp-express/), or create an issue on github ' .
                        '(https://github.com/rosell-dk/webp-express/issues)**';
                    $log[] = '';
                }
            } else {
                if ($canRunTestScriptInWod) {
                    $log[] = '';
                    $log[] = 'The problem appears to be running PHP scripts in the "wod2" folder. ' .
                        'Luckily, it seems that running scripts in the "wod" folder works ' .
                        '**What you need to do is simply to click the "Save settings and force new .htacess rules"' .
                        ' button. WebP Express wil then change the .htaccess rules to point to the "wod" folder**';
                    $log[] = '';
                } else {
                    if ($responseCode == 500) {

                        if ($pingTextResponseCode2 == '500') {
                            $log[] = 'All our requests results in 500 Internal Error. Even ' .
                                'the request to plugins/webp-express/wod2/ping.txt. ' .
                                'Surprising!';
                        } else {
                            $log[] = 'The internal server error happens for php files, but not txt files. ' .
                                'It could be the result of a restrictive server configuration or the works of a security plugin. ' .
                                'Try to examine the .htaccess file in the plugins folder and its parent folders. ' .
                                'Or try to look in the httpd.conf. Look for the "AllowOverride" and the "AllowOverrideList" directives. ';
                        }

                        //$log[] = 'We get *500 Internal Server Error*';
                        /*
                        It can for example be that the *.htaccess* ' .
                            'in the ' . $rootId . ' folder (or a parent folder) contains directives that the server either ' .
                            'doesnt support or has not allowed (using AllowOverride in ie httpd.conf). It could also be that the redirect succeded, ' .
                            'but the *.htaccess* in the folder of the script (or a parent folder) results in such problems. Also, ' .
                            'it could be that the script (webp-on-demand.php) for some reason fails.';

                        */
                    }
                }


            }
        }
        return $log;
    }
}