PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / trunk
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management vtrunk
1.5.1 1.5.0 1.4.0 1.3.0 trunk 0.0.1 1.0.0 1.1.0 1.1.1 1.1.2 1.2.0
suredonation / inc / pdf / pdf-utils.php

pdf-utils.php in SureDonation – Donation Forms, Fundraising Campaigns & Donor Management trunk, at inc/pdf/pdf-utils.php

202 lines 7.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * PDF Utility helpers.
4 *
5 * Static methods for checking library existence, PHP compatibility, and file paths.
6 *
7 * @package SureDonation
8 */
9
10 namespace SureDonation\Inc\Pdf;
11
12 // Exit if accessed directly.
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Pdf_Utils class.
19 *
20 * @since 1.0.0
21 */
22 class Pdf_Utils {
23 /**
24 * Check if the PDF library is installed.
25 *
26 * @return bool
27 * @since 1.0.0
28 */
29 public static function check_if_library_exists() {
30 return file_exists( self::get_library_path() . '/vendor/autoload.php' );
31 }
32
33 /**
34 * Check if the current PHP version meets the minimum requirement.
35 *
36 * MPDF 8.x requires PHP 8.0+.
37 *
38 * @return bool
39 * @since 1.0.0
40 */
41 public static function is_php_compatible() {
42 return version_compare( PHP_VERSION, '8.0.0', '>=' );
43 }
44
45 /**
46 * Get the PDF library directory path.
47 *
48 * @return string
49 * @since 1.0.0
50 */
51 public static function get_library_path() {
52 return WP_PLUGIN_DIR . '/suredonation-libraries/pdf';
53 }
54
55 /**
56 * Get the receipts directory path or URL.
57 *
58 * @param string $type 'path' or 'url'.
59 * @return string
60 * @since 1.0.0
61 */
62 public static function get_receipts_dir( $type = 'path' ) {
63 $upload_dir = wp_upload_dir();
64
65 if ( 'url' === $type ) {
66 return $upload_dir['baseurl'] . '/suredonation/receipts';
67 }
68
69 return $upload_dir['basedir'] . '/suredonation/receipts';
70 }
71
72 /**
73 * Ensure the receipts directory exists with security files.
74 *
75 * The denial files only bind where the server reads them: .htaccess on
76 * Apache and LiteSpeed, web.config on IIS. nginx and Caddy read neither, so
77 * there the unguessable 128-bit filename is the only control — which is why
78 * the generator proceeds regardless of what this returns. A host that
79 * cannot write a denial file should not lose the ability to issue receipts;
80 * refusing would trade a real feature for a control those servers were
81 * never going to enforce anyway.
82 *
83 * There is no receipt download route in this plugin. Delivery — email
84 * attachment and authenticated streaming download alike — lives entirely
85 * in Pro; an earlier version of this note claimed a REST streaming
86 * endpoint here, and none exists.
87 *
88 * @return bool True when the directory exists *and* every denial file this
89 * method writes is present. Callers that need to know whether
90 * the directory is actually hardened can act on it; the
91 * generator deliberately does not, for the reason above.
92 * @since 1.0.0
93 */
94 public static function ensure_receipts_dir() {
95 $dir = self::get_receipts_dir();
96
97 if ( ! file_exists( $dir ) ) {
98 wp_mkdir_p( $dir );
99 }
100
101 // Bail before writing if the directory is not there. Every write below
102 // is unconditional on the directory existing, so on a host where
103 // wp_mkdir_p() fails — an unwritable uploads path, an open_basedir
104 // restriction — each one emitted a PHP warning into the log before
105 // failing anyway. Nothing downstream is worse off: the caller already
106 // treats a false return as "not hardened".
107 if ( ! is_dir( $dir ) ) {
108 return false;
109 }
110
111 // Add .htaccess to prevent direct access.
112 $htaccess = $dir . '/.htaccess';
113 if ( ! file_exists( $htaccess ) ) {
114 file_put_contents( $htaccess, "deny from all\n" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
115 }
116
117 // Add index.php for directory listing protection.
118 $index = $dir . '/index.php';
119 if ( ! file_exists( $index ) ) {
120 file_put_contents( $index, "<?php\n// Silence is golden.\n" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
121 }
122
123 // IIS reads neither .htaccess nor index.php as an access rule, so it
124 // needs its own denial. Written unconditionally rather than detected:
125 // the file is inert on Apache/nginx/LiteSpeed, and detecting the server
126 // from $_SERVER['SERVER_SOFTWARE'] is unreliable behind a proxy.
127 //
128 // requestFiltering under system.webServer/security, matching SureMail's
129 // attachments directory — minus its <handlers><remove> block. That block
130 // is belt-and-braces on top of two controls already here, and IIS raises
131 // a configuration error when <remove> names a handler the site does not
132 // have (the names are host-specific — SureMail hardcodes
133 // 'php-7.4.33'). Shipping something that can itself 500 the directory
134 // is the exact failure this rewrite exists to remove, and it cannot be
135 // tested from here, so it is left out: hiddenSegments already refuses
136 // every request to the path, and fileExtensions already refuses
137 // executables.
138 //
139 // The first attempt used
140 // system.webServer/authorization, which is not a real section — the
141 // ASP.NET element misplaced. IIS answers 500.19 on an unrecognised
142 // section, so the directory ended up "protected" by a configuration
143 // error rather than by the control this claims, and would have opened
144 // the moment somebody fixed the 500. requestFiltering also needs no
145 // extra role service, where URL Authorization 500s unless it is
146 // installed.
147 $web_config = $dir . '/web.config';
148 if ( ! file_exists( $web_config ) ) {
149 $config = '<?xml version="1.0" encoding="UTF-8"?>' . "\n"
150 . '<configuration>' . "\n"
151 . ' <system.webServer>' . "\n"
152 . ' <security>' . "\n"
153 . ' <requestFiltering>' . "\n"
154 . ' <hiddenSegments>' . "\n"
155 . ' <add segment="receipts" />' . "\n"
156 . ' </hiddenSegments>' . "\n"
157 . ' <fileExtensions allowUnlisted="true">' . "\n"
158 . ' <add fileExtension=".php" allowed="false" />' . "\n"
159 . ' <add fileExtension=".phtml" allowed="false" />' . "\n"
160 . ' <add fileExtension=".php3" allowed="false" />' . "\n"
161 . ' <add fileExtension=".php4" allowed="false" />' . "\n"
162 . ' <add fileExtension=".php5" allowed="false" />' . "\n"
163 . ' <add fileExtension=".php7" allowed="false" />' . "\n"
164 . ' <add fileExtension=".phar" allowed="false" />' . "\n"
165 . ' <add fileExtension=".phps" allowed="false" />' . "\n"
166 . ' <add fileExtension=".pht" allowed="false" />' . "\n"
167 . ' <add fileExtension=".phpt" allowed="false" />' . "\n"
168 . ' <add fileExtension=".inc" allowed="false" />' . "\n"
169 . ' </fileExtensions>' . "\n"
170 . ' </requestFiltering>' . "\n"
171 . ' </security>' . "\n"
172 . ' </system.webServer>' . "\n"
173 . '</configuration>' . "\n";
174 file_put_contents( $web_config, $config ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
175 }
176
177 // The directory has to exist *and* be protected. Returning only
178 // is_dir() reported success on a directory whose hardening writes had
179 // silently failed — an open receipts directory holding donor names,
180 // addresses and amounts, with the caller told everything was fine.
181 // web.config included: it was left out, so on IIS — the one platform it
182 // exists for — a failed write still reported success.
183 return is_dir( $dir ) && file_exists( $htaccess ) && file_exists( $index ) && file_exists( $web_config );
184 }
185
186 /**
187 * Get the temp directory for mPDF.
188 *
189 * @return string
190 * @since 1.0.0
191 */
192 public static function get_temp_dir() {
193 $temp_dir = wp_normalize_path( trailingslashit( \get_temp_dir() ) ) . 'suredonation-mpdf';
194
195 if ( ! file_exists( $temp_dir ) ) {
196 wp_mkdir_p( $temp_dir );
197 }
198
199 return $temp_dir;
200 }
201 }
202