get_base_directory() . '/' . sanitize_file_name( $name ); } /** * Creates a subfolder and the access protection of it and of the base directory. * * @param string $name Subfolder name. * @return string Absolute path, without trailing slash. * @throws SyncException When the folder cannot be created. */ public function ensure_directory( string $name ): string { $directory = $this->get_directory( $name ); if ( ! wp_mkdir_p( $directory ) ) { throw new SyncException( esc_html__( 'The plugin folder in the uploads directory could not be created.', 'cherrylift' ) ); } foreach ( array( $this->get_base_directory(), $directory ) as $folder ) { $this->protect( $folder ); } return $directory; } /** * Deletes files with the given extension older than the lifetime from a subfolder. * * @param string $name Subfolder name. * @param string $extension File extension, without dot. * @param int $lifetime Lifetime in seconds. * @return int Number of files deleted. */ public function delete_expired_files( string $name, string $extension, int $lifetime ): int { $directory = $this->get_directory( $name ); if ( ! is_dir( $directory ) ) { return 0; } $deleted = 0; $suffix = '.' . $extension; $entries = $this->filesystem()->dirlist( $directory, false ); foreach ( is_array( $entries ) ? $entries : array() as $file => $entry ) { if ( 'f' === $entry['type'] && substr( (string) $file, -strlen( $suffix ) ) === $suffix && (int) $entry['lastmodunix'] < time() - $lifetime ) { wp_delete_file( $directory . '/' . $file ); ++$deleted; } } return $deleted; } /** * Adds the files that deny web access to a folder, if missing. * * @param string $directory Absolute path. * @return void */ private function protect( string $directory ): void { $filesystem = $this->filesystem(); if ( ! $filesystem->exists( $directory . '/index.php' ) ) { // Explicit mode: FS_CHMOD_FILE is only defined once WP_Filesystem() has run, which REST requests don't do. $filesystem->put_contents( $directory . '/index.php', "exists( $directory . '/.htaccess' ) ) { $filesystem->put_contents( $directory . '/.htaccess', "\n\tRequire all denied\n\n\n\tDeny from all\n\n", 0644 ); } } /** * Returns a direct filesystem instance. Only used for the plugin's own folders. * * @return WP_Filesystem_Direct */ private function filesystem(): WP_Filesystem_Direct { require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; return new WP_Filesystem_Direct( null ); } }