felixarntz/wp-encrypt

View on GitHub
inc/WPENC/Core/Util.php

Summary

Maintainability
C
7 hrs
Test Coverage
<?php
/**
 * WPENC\Core\Util class
 *
 * @package WPENC
 * @subpackage Core
 * @author Felix Arntz <felix-arntz@leaves-and-love.net>
 * @since 1.0.0
 */

namespace WPENC\Core;

use WP_Error;

if ( ! defined( 'ABSPATH' ) ) {
    die();
}

if ( ! class_exists( 'WPENC\Core\Util' ) ) {
    /**
     * This class contains static utility methods.
     *
     * @since 1.0.0
     */
    final class Util {
        /**
         * Checks whether filesystem credentials are required to write to the necessary server locations.
         *
         * @since 1.0.0
         * @access public
         * @static
         *
         * @param array|bool $credentials Credentials to check their validity or false for a general check.
         * @return bool True if filesystem credentials are required, false otherwise.
         */
        public static function needs_filesystem_credentials( $credentials = false ) {
            $paths = self::get_filesystem_paths();
            $type = 'direct';
            $is_direct = true;
            foreach ( $paths as $key => $path ) {
                $type = get_filesystem_method( array(), $paths[ $key ], true );
                if ( 'direct' !== $type ) {
                    $is_direct = false;
                    break;
                }
            }

            if ( $is_direct ) {
                return false;
            }

            if ( false === $credentials ) {
                ob_start();
                $credentials = request_filesystem_credentials( site_url(), $type, false, $paths[0], null, true );
                $data = ob_get_clean();
                if ( false === $credentials ) {
                    return true;
                }
            }

            return ! WP_Filesystem( $credentials, $paths[0], true );
        }

        /**
         * Sets up the filesystem to be able to write to the server.
         *
         * If filesystem credentials are required and haven't been entered yet,
         * a form to enter them will be shown and the request will exit afterwards.
         *
         * @since 1.0.0
         * @access public
         * @static
         *
         * @param string $form_post    The location to post the form to.
         * @param array  $extra_fields Additional fields to include in the form post request.
         * @return bool True if the filesystem was setup successfully, false otherwise.
         */
        public static function setup_filesystem( $form_post, $extra_fields = array() ) {
            global $wp_filesystem;

            $paths = self::get_filesystem_paths();
            $type = 'direct';
            $is_direct = true;
            foreach ( $paths as $key => $path ) {
                $type = get_filesystem_method( array(), $paths[ $key ], true );
                if ( 'direct' !== $type ) {
                    $is_direct = false;
                    break;
                }
            }

            ob_start();
            if ( false === ( $credentials = request_filesystem_credentials( $form_post, $type, false, $paths[0], $extra_fields, true ) ) ) {
                $data = ob_get_clean();

                if ( ! empty( $data ) ) {
                    include_once ABSPATH . 'wp-admin/admin-header.php';
                    echo $data;
                    include ABSPATH . 'wp-admin/admin-footer.php';
                    exit;
                }
                return false;
            }

            if ( ! WP_Filesystem( $credentials, $paths[0], true ) ) {
                $error = ( isset( $wp_filesystem ) && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) ? $wp_filesystem->errors : true;
                request_filesystem_credentials( $form_post, $type, $error, $paths[0], $extra_fields, true );
                $data = ob_get_clean();

                if ( ! empty( $data ) ) {
                    include_once ABSPATH . 'wp-admin/admin-header.php';
                    echo $data;
                    include ABSPATH . 'wp-admin/admin-footer.php';
                    exit;
                }
                return false;
            }

            if ( ! is_object( $wp_filesystem ) || is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
                return false;
            }

            return true;
        }

        /**
         * Returns the filesystem instance to access the server filesystem with.
         *
         * @since 1.0.0
         * @access public
         * @static
         *
         * @global WP_Filesystem_Base $wp_filesystem The WordPress filesystem instance.
         *
         * @return WP_Filesystem_Base The WordPress filesystem instance.
         */
        public static function get_filesystem() {
            global $wp_filesystem;

            return $wp_filesystem;
        }

        /**
         * Returns the base path the key and certificate files reside in.
         *
         * The default location is one level above the project's root directory. However, it can
         * be changed to any location using the `WP_ENCRYPT_SSL_CERTIFICATES_DIR_PATH` constant.
         *
         * @since 1.0.0
         * @access public
         * @static
         *
         * @return string Base path.
         */
        public static function get_letsencrypt_certificates_dir_path() {
            if ( defined( 'WP_ENCRYPT_SSL_CERTIFICATES_DIR_PATH' ) && WP_ENCRYPT_SSL_CERTIFICATES_DIR_PATH ) {
                return untrailingslashit( WP_ENCRYPT_SSL_CERTIFICATES_DIR_PATH );
            }
            return dirname( self::detect_base( 'path' ) ) . '/letsencrypt/live';
        }

        /**
         * Returns the base path the challenges for Let's Encrypt reside in.
         *
         * The location is inside a `.well-known` directory in the project's root directory.
         *
         * @since 1.0.0
         * @access public
         * @static
         *
         * @return string Base path.
         */
        public static function get_letsencrypt_challenges_dir_path() {
            return self::detect_base( 'path' ) . self::get_letsencrypt_challenges_relative_dir();
        }

        /**
         * Returns the base URL the challenges for Let's Encrypt reside in.
         *
         * The location is inside a `.well-known` directory in the project's root directory.
         *
         * @since 1.0.0
         * @access public
         * @static
         *
         * @return string Base URL.
         */
        public static function get_letsencrypt_challenges_dir_url() {
            return self::detect_base( 'url' ) . self::get_letsencrypt_challenges_relative_dir();
        }

        /**
         * Detects the root directory of this project.
         *
         * @since 1.0.0
         * @access public
         * @static
         *
         * @param string $mode Either 'path' or 'url'.
         * @return string The root path or URL, depending on the $mode parameter.
         */
        public static function detect_base( $mode = 'url' ) {
            $content_parts = explode( '/', str_replace( array( 'https://', 'http://' ), '', rtrim( WP_CONTENT_URL, '/' ) ) );
            $dirname_up = count( $content_parts ) - 1;

            $base = WP_CONTENT_URL;
            if ( 'path' === $mode ) {
                $base = WP_CONTENT_DIR;
            }

            $base = rtrim( $base, '/' );

            for ( $i = 0; $i < $dirname_up; $i++ ) {
                $base = dirname( $base );
            }

            return $base;
        }

        /**
         * Creates the directory for the keys and certificates if it doesn't exist yet.
         *
         * @since 1.0.0
         * @access public
         * @static
         *
         * @return bool|WP_Error True if the directory was created, an error object otherwise.
         */
        public static function maybe_create_letsencrypt_certificates_dir() {
            return self::maybe_create_dir( self::get_letsencrypt_certificates_dir_path() );
        }

        /**
         * Creates the directory for the Let's Encrypt challenges if it doesn't exist yet.
         *
         * @since 1.0.0
         * @access public
         * @static
         *
         * @return bool|WP_Error True if the directory was created, an error object otherwise.
         */
        public static function maybe_create_letsencrypt_challenges_dir() {
            return self::maybe_create_dir( self::get_letsencrypt_challenges_dir_path() );
        }

        /**
         * Base64-encodes data.
         *
         * @since 1.0.0
         * @access public
         * @static
         *
         * @param mixed $data Data to encode.
         * @return string The encoded data.
         */
        public static function base64_url_encode( $data ) {
            return str_replace( '=', '', strtr( base64_encode( $data ), '+/', '-_' ) );
        }

        /**
         * Base64-decodes a string.
         *
         * @since 1.0.0
         * @access public
         * @static
         *
         * @param string $data The string to decode.
         * @return mixed The decoded data.
         */
        public static function base64_url_decode( $data ) {
            $rest = strlen( $data ) % 4;
            if ( 0 < $rest ) {
                $pad = 4 - $rest;
                $data .= str_repeat( '=', $pad );
            }
            return base64_decode( strtr( $data, '-_', '+/' ) );
        }

        /**
         * Returns all domains for a list of domains.
         *
         * What that function does is to add the www/non-www equivalents of all passed domains
         * to the array if they are not in the array yet.
         *
         * @since 1.0.0
         * @access public
         * @static
         *
         * @param string $domain        The root domain.
         * @param array  $addon_domains Array of additional domains.
         * @return array Array of all domains, including www/non-www equivalents.
         */
        public static function get_all_domains( $domain, $addon_domains = array() ) {
            array_unshift( $addon_domains, $domain );

            $all_domains = array();

            foreach ( $addon_domains as $addon_domain ) {
                $all_domains[] = $addon_domain;
                if ( 'www.' === substr( $addon_domain, 0, 4 ) ) {
                    $all_domains[] = substr( $addon_domain, 4 );
                } else {
                    $all_domains[] = 'www.' . $addon_domain;
                }
            }

            return array_unique( $all_domains );
        }

        /**
         * Returns the relevant paths that require filesystem access.
         *
         * If the inner directories of a path do not yet exist, the method will walk up the path tree
         * until it finds an existing directory to go from.
         *
         * @since 1.0.0
         * @access private
         * @static
         *
         * @return array An array of paths.
         */
        private static function get_filesystem_paths() {
            $paths = array(
                self::get_letsencrypt_certificates_dir_path(),
                self::get_letsencrypt_challenges_dir_path(),
            );

            foreach ( $paths as $key => $path ) {
                while ( ! is_dir( $paths[ $key ] ) ) {
                    $paths[ $key ] = dirname( $paths[ $key ] );
                }
            }

            return $paths;
        }

        /**
         * Creates a specific directory if it doesn't exist yet.
         *
         * @since 1.0.0
         * @access private
         * @static
         *
         * @param string $path The path to the directory to maybe create.
         * @return bool|WP_Error True if the directory was created, an error object otherwise.
         */
        private static function maybe_create_dir( $path ) {
            $filesystem = self::get_filesystem();

            if ( ! $filesystem->is_dir( $path ) ) {
                if ( ! $filesystem->is_dir( dirname( $path ) ) ) {
                    $filesystem->mkdir( dirname( $path ) );
                }

                if ( ! $filesystem->mkdir( $path ) ) {
                    return new WP_Error( 'cannot_create_dir', sprintf( __( 'Could not create directory <code>%s</code>. Please check your filesystem permissions.', 'wp-encrypt' ), $path ) );
                }
            }
            return true;
        }

        /**
         * Returns the relative path for the Let's Encrypt challenges directory.
         *
         * @since 1.0.0
         * @access private
         * @static
         *
         * @return string The relative challenges path.
         */
        private static function get_letsencrypt_challenges_relative_dir() {
            return '/.well-known/acme-challenge';
        }
    }
}