'' !== $path, 'loader_path' => '' !== $path ? $path : self::target(), 'loader_target' => self::target(), 'loader_current' => self::current(), 'loader_writable' => self::writable(), 'loader_dir' => self::display_path( WPMU_PLUGIN_DIR ), ); } /** * A path as shown in messages: relative to the WordPress folder when it is inside it (for * example wp-content/mu-plugins), else absolute. * * @param string $path Absolute path. */ public static function display_path( string $path ): string { $path = rtrim( wp_normalize_path( $path ), '/' ); $root = trailingslashit( \Bloatwright\Profiler\Paths::core() ); return '/' !== $root && 0 === strpos( $path, $root ) && strlen( $path ) > strlen( $root ) ? substr( $path, strlen( $root ) ) : $path; } /** * Loader present under its current name and generated by this version, for this plugin path, * from the current template? A template change within one version (or a loader stamped before * the hash was recorded) makes it stale, so the next run rewrites it. */ public static function current(): bool { if ( ! is_file( self::target() ) ) { return false; } $stamp = get_option( self::OPTION ); return is_array( $stamp ) && ( $stamp['version'] ?? '' ) === BLOATWRIGHT_VERSION && ( $stamp['path'] ?? '' ) === self::plugin_dir() && ( $stamp['template'] ?? '' ) === self::template_hash(); } /** * Hash of the loader template ('' when it cannot be read). */ public static function template_hash(): string { $hash = is_readable( self::source() ) ? md5_file( self::source() ) : false; return is_string( $hash ) ? $hash : ''; } /** * Can the loader be written (or is it already there)? */ public static function writable(): bool { if ( is_file( self::target() ) ) { return wp_is_writable( self::target() ); } return is_dir( WPMU_PLUGIN_DIR ) ? wp_is_writable( WPMU_PLUGIN_DIR ) : wp_is_writable( dirname( WPMU_PLUGIN_DIR ) ); } /** * Render the loader for a plugin directory (pure: no WordPress calls, usable before load). * * @param string $template Template contents. * @param string $plugin_dir Plugin directory without trailing slash. * @param string $version Plugin version for the header. */ public static function render( string $template, string $plugin_dir, string $version ): string { $header = "is_dir( WPMU_PLUGIN_DIR ) && ! $wp_filesystem->mkdir( WPMU_PLUGIN_DIR, FS_CHMOD_DIR ) ) { /* translators: %s: must-use plugins directory, relative to the WordPress folder when inside it. */ return new \WP_Error( 'bloatwright_loader_dir', sprintf( __( 'Could not create the must-use plugins directory %s. Create it, or make its parent directory writable, then try again.', 'bloatwright' ), $dir ) ); } $template = $wp_filesystem->get_contents( self::source() ); if ( ! is_string( $template ) || false === strpos( $template, self::PLACEHOLDER ) ) { return new \WP_Error( 'bloatwright_loader_template', __( 'Could not read the profiler loader template.', 'bloatwright' ) ); } $content = self::render( $template, self::plugin_dir(), BLOATWRIGHT_VERSION ); if ( ! $wp_filesystem->put_contents( self::target(), $content, FS_CHMOD_FILE ) ) { /* translators: 1: loader file, 2: must-use plugins directory (both relative to the WordPress folder when inside it). */ return new \WP_Error( 'bloatwright_loader_write', sprintf( __( 'Could not write the profiler loader to %1$s. Make %2$s writable, then try again.', 'bloatwright' ), self::display_path( self::target() ), $dir ) ); } self::remove_legacy(); update_option( self::OPTION, array( 'version' => BLOATWRIGHT_VERSION, 'path' => self::plugin_dir(), 'template' => md5( $template ), ), false ); return true; } /** * Install only when missing or stale. * * @return true|\WP_Error */ public static function ensure() { return self::current() ? true : self::install(); } /** * Remove the loader and its stamp. Returns true when no loader is left behind. */ public static function remove(): bool { if ( is_file( self::target() ) ) { wp_delete_file( self::target() ); } self::remove_legacy(); delete_option( self::OPTION ); return ! self::installed(); } /** * Delete a loader left under the pre-release file name. */ public static function remove_legacy(): void { if ( is_file( self::legacy_target() ) ) { wp_delete_file( self::legacy_target() ); } } }