method ? $wp_filesystem : null; } /** * Why nothing can be written, in one sentence. * * @return string Translated explanation naming the method WordPress chose. */ public static function reason(): string { $method = function_exists( 'get_filesystem_method' ) ? (string) get_filesystem_method() : ''; return sprintf( /* translators: %s: WordPress filesystem method, e.g. ftpext. */ __( 'Filesystem access requires the direct method, and WordPress reports "%s" on this host. Writing would mean asking for FTP credentials, which a scheduled task cannot do — make the webroot writable by the web server user, or leave the files virtual.', 'acid-aeo' ), '' !== $method ? $method : 'none' ); } /** * The directory the site is served from. * * Not `ABSPATH`: that is where WordPress lives, and core supports giving * WordPress its own directory, where `siteurl` is `home` plus a folder. On * such a site `ABSPATH` is `/var/www/html/wp/` while `https://example.com/ * llms.txt` — the address this plugin advertises in robots.txt, in the head * tags and in every audit check — is served from `/var/www/html/`. Writing * to `ABSPATH` there publishes nothing and then reports the file missing. * * Core's own `get_home_path()` answers this question, and is deliberately * not used: it derives the path from `$_SERVER['SCRIPT_FILENAME']`, which a * cron event or a WP-CLI run may not have, and its failure mode is to return * `/` — the filesystem root, as a place to write files. The two options say * the same thing without a superglobal. * * @return string Absolute path with a trailing slash. */ public static function webroot(): string { return self::strip_wp_directory( (string) ABSPATH, (string) get_option( 'home', '' ), (string) get_option( 'siteurl', '' ) ); } /** * The webroot, worked out from a path and the two addresses. * * Public because it is pure: everything it needs is in its arguments, which * is what the unit test exercises. The path is only shortened when it really * does end with the directory the two URLs differ by — anything else, and * the answer is the path as it was given. * * @param string $abspath Value of `ABSPATH`. * @param string $home The `home` option: where the site answers. * @param string $siteurl The `siteurl` option: where WordPress lives. * @return string Absolute path with a trailing slash. */ public static function strip_wp_directory( string $abspath, string $home, string $siteurl ): string { $root = rtrim( str_replace( '\\', '/', $abspath ), '/' ) . '/'; $home = self::bare_url( $home ); $site = self::bare_url( $siteurl ); if ( '' === $home || '' === $site || $home === $site ) { return $root; } // Anything but "WordPress in a folder below the site" — a different // host, a different port, a `home` below `siteurl` — is left alone. if ( 0 !== strpos( $site, $home . '/' ) ) { return $root; } $suffix = trim( substr( $site, strlen( $home ) ), '/' ) . '/'; $length = strlen( $suffix ); if ( 1 === $length || $length >= strlen( $root ) ) { return $root; } return substr( $root, -$length ) === $suffix ? substr( $root, 0, -$length ) : $root; } /** * One URL without its scheme, its trailing slash or its case. * * `home` and `siteurl` are compared, not fetched, and a site whose two * options disagree only about `https` is not a site with WordPress in a * folder. * * @param string $url URL from the options table. * @return string Comparable form, empty when there was nothing to compare. */ private static function bare_url( string $url ): string { $url = strtolower( trim( $url ) ); $url = (string) preg_replace( '#^[a-z][a-z0-9+.-]*://#', '', $url ); return rtrim( $url, '/' ); } /** * The permissions a written file gets. * * @return int|false The constant WordPress computed, or false to let the filesystem decide. */ public static function file_mode() { return defined( 'FS_CHMOD_FILE' ) ? FS_CHMOD_FILE : false; } /** * The permissions a created directory gets. * * @return int|false The constant WordPress computed, or false to let the filesystem decide. */ public static function dir_mode() { return defined( 'FS_CHMOD_DIR' ) ? FS_CHMOD_DIR : false; } }