=' ); } /** * Get the PDF library directory path. * * @return string * @since 1.0.0 */ public static function get_library_path() { return WP_PLUGIN_DIR . '/suredonation-libraries/pdf'; } /** * Get the receipts directory path or URL. * * @param string $type 'path' or 'url'. * @return string * @since 1.0.0 */ public static function get_receipts_dir( $type = 'path' ) { $upload_dir = wp_upload_dir(); if ( 'url' === $type ) { return $upload_dir['baseurl'] . '/suredonation/receipts'; } return $upload_dir['basedir'] . '/suredonation/receipts'; } /** * Ensure the receipts directory exists with security files. * * The denial files only bind where the server reads them: .htaccess on * Apache and LiteSpeed, web.config on IIS. nginx and Caddy read neither, so * there the unguessable 128-bit filename is the only control — which is why * the generator proceeds regardless of what this returns. A host that * cannot write a denial file should not lose the ability to issue receipts; * refusing would trade a real feature for a control those servers were * never going to enforce anyway. * * There is no receipt download route in this plugin. Delivery — email * attachment and authenticated streaming download alike — lives entirely * in Pro; an earlier version of this note claimed a REST streaming * endpoint here, and none exists. * * @return bool True when the directory exists *and* every denial file this * method writes is present. Callers that need to know whether * the directory is actually hardened can act on it; the * generator deliberately does not, for the reason above. * @since 1.0.0 */ public static function ensure_receipts_dir() { $dir = self::get_receipts_dir(); if ( ! file_exists( $dir ) ) { wp_mkdir_p( $dir ); } // Bail before writing if the directory is not there. Every write below // is unconditional on the directory existing, so on a host where // wp_mkdir_p() fails — an unwritable uploads path, an open_basedir // restriction — each one emitted a PHP warning into the log before // failing anyway. Nothing downstream is worse off: the caller already // treats a false return as "not hardened". if ( ! is_dir( $dir ) ) { return false; } // Add .htaccess to prevent direct access. $htaccess = $dir . '/.htaccess'; if ( ! file_exists( $htaccess ) ) { file_put_contents( $htaccess, "deny from all\n" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents } // Add index.php for directory listing protection. $index = $dir . '/index.php'; if ( ! file_exists( $index ) ) { file_put_contents( $index, " block. That block // is belt-and-braces on top of two controls already here, and IIS raises // a configuration error when names a handler the site does not // have (the names are host-specific — SureMail hardcodes // 'php-7.4.33'). Shipping something that can itself 500 the directory // is the exact failure this rewrite exists to remove, and it cannot be // tested from here, so it is left out: hiddenSegments already refuses // every request to the path, and fileExtensions already refuses // executables. // // The first attempt used // system.webServer/authorization, which is not a real section — the // ASP.NET element misplaced. IIS answers 500.19 on an unrecognised // section, so the directory ended up "protected" by a configuration // error rather than by the control this claims, and would have opened // the moment somebody fixed the 500. requestFiltering also needs no // extra role service, where URL Authorization 500s unless it is // installed. $web_config = $dir . '/web.config'; if ( ! file_exists( $web_config ) ) { $config = '' . "\n" . '' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . '' . "\n"; file_put_contents( $web_config, $config ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents } // The directory has to exist *and* be protected. Returning only // is_dir() reported success on a directory whose hardening writes had // silently failed — an open receipts directory holding donor names, // addresses and amounts, with the caller told everything was fine. // web.config included: it was left out, so on IIS — the one platform it // exists for — a failed write still reported success. return is_dir( $dir ) && file_exists( $htaccess ) && file_exists( $index ) && file_exists( $web_config ); } /** * Get the temp directory for mPDF. * * @return string * @since 1.0.0 */ public static function get_temp_dir() { $temp_dir = wp_normalize_path( trailingslashit( \get_temp_dir() ) ) . 'suredonation-mpdf'; if ( ! file_exists( $temp_dir ) ) { wp_mkdir_p( $temp_dir ); } return $temp_dir; } }