PluginProbe ʕ •ᴥ•ʔ
Paid Membership Plugin, Ecommerce, User Registration Form, Login Form, User Profile & Restrict Content – ProfilePress / trunk
Paid Membership Plugin, Ecommerce, User Registration Form, Login Form, User Profile & Restrict Content – ProfilePress vtrunk
4.16.18 4.16.17 4.16.16 trunk 1.0 1.0.1 1.0.2 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5a 1.1.6 1.1.7 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.4 1.4.1 1.4.2 1.5 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.7 1.7.1 1.7.2 1.8 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.1.9 2.2.10 2.2.11 2.2.12 2.2.13 2.2.14 2.2.15 2.2.16 2.2.2 2.2.5 2.2.6 2.2.7 2.2.8 2.2.9 3.0 3.1 3.1.1 3.1.10 3.1.11 3.1.12 3.1.13 3.1.14 3.1.15 3.1.16 3.1.17 3.1.18 3.1.19 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.2.0 3.2.1 3.2.10 3.2.11 3.2.12 3.2.13 3.2.14 3.2.15 3.2.16 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 3.2.9 4.0.0 4.0.1 4.0.2 4.0.3 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.10.0 4.10.1 4.10.2 4.10.3 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.13.3 4.13.4 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.15.0 4.15.1 4.15.10 4.15.11 4.15.12 4.15.13 4.15.14 4.15.15 4.15.16 4.15.17 4.15.18 4.15.19 4.15.2 4.15.20 4.15.20.1 4.15.21 4.15.22 4.15.23 4.15.24 4.15.25 4.15.3 4.15.4 4.15.5 4.15.6 4.15.7 4.15.8 4.15.9 4.16.0 4.16.1 4.16.10 4.16.11 4.16.12 4.16.13 4.16.14 4.16.15 4.16.2 4.16.3 4.16.4 4.16.5 4.16.6 4.16.7 4.16.8 4.16.9 4.2.0 4.3.0 4.3.1 4.3.2 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.7.0 4.8.0 4.9.0
wp-user-avatar / src / Membership / DigitalProducts / UploadHandler.php
wp-user-avatar / src / Membership / DigitalProducts Last commit date
DownloadHandler.php 5 months ago DownloadService.php 3 years ago Init.php 3 years ago UploadHandler.php 25 minutes ago index.php 3 years ago
UploadHandler.php
311 lines
1 <?php
2
3 namespace ProfilePress\Core\Membership\DigitalProducts;
4
5 use ProfilePressVendor\PAnD;
6
7 class UploadHandler
8 {
9 public function __construct()
10 {
11 add_filter('upload_dir', [$this, 'upload_dir']);
12 add_filter('wp_unique_filename', [$this, 'update_filename'], 10, 3);
13
14 add_action('admin_init', [$this, 'create_protection_files']);
15
16 add_action('ppress_admin_notices', [$this, 'admin_notice']);
17
18 add_filter('upload_mimes', [$this, 'allowed_mime_types'], 999);
19
20 add_filter('file_is_displayable_image', [$this, 'file_is_displayable_image'], 99, 2);
21 }
22
23 /**
24 * Stop WordPress from creating different image sizes.
25 *
26 * @param $result
27 * @param $path
28 *
29 * @return bool
30 */
31 public function file_is_displayable_image($result, $path)
32 {
33 if (strpos($path, ppress_var(wp_upload_dir(), 'basedir') . '/ppress_uploads') !== false) {
34 $result = false;
35 }
36
37 return $result;
38 }
39
40 public function allowed_mime_types($existing_mimes)
41 {
42 if (isset($_POST['type']) && 'ppress_downloadable_plan' === $_POST['type']) {
43
44 $existing_mimes['zip'] = 'application/zip';
45 $existing_mimes['epub'] = 'application/epub+zip';
46 $existing_mimes['mobi'] = 'application/x-mobipocket-ebook';
47 $existing_mimes['m4r'] = 'audio/aac';
48 $existing_mimes['aif'] = 'audio/x-aiff';
49 $existing_mimes['aiff'] = 'audio/aiff';
50 $existing_mimes['psd'] = 'image/photoshop';
51 // exe removed as it is dangerous. even get_allowed_mime_types() removes/unset it. apk and msi also removed
52 //$existing_mimes['exe'] = 'application/octet-stream';
53 //$existing_mimes['apk'] = 'application/vnd.android.package-archive';
54 //$existing_mimes['msi'] = 'application/x-ole-storage';
55 $existing_mimes['csv'] = 'text/csv';
56 $existing_mimes['doc'] = 'application/msword';
57 $existing_mimes["pot|pps|ppt"] = "application/vnd.ms-powerpoint";
58 $existing_mimes["xla|xls|xlt|xlw"] = "application/vnd.ms-excel";
59 $existing_mimes["docx"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
60 $existing_mimes["pptx"] = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
61 $existing_mimes["odt"] = "application/vnd.oasis.opendocument.text";
62 $existing_mimes["odp"] = "application/vnd.oasis.opendocument.presentation";
63 $existing_mimes["ods"] = "application/vnd.oasis.opendocument.spreadsheet";
64 }
65
66 return $existing_mimes;
67 }
68
69 public function create_protection_files($force = false, $method = false)
70 {
71 if (false === get_transient('ppress_check_protection_files') || $force) {
72
73 $upload_path = $this->get_upload_dir();
74
75 // Top level .htaccess file
76 $rules = 'redirect' === $method ? 'Options -Indexes' : 'deny from all';
77
78 if (file_exists($upload_path . '/.htaccess')) {
79 $contents = @file_get_contents($upload_path . '/.htaccess');
80 if ($contents !== $rules || ! $contents) {
81 // Update the .htaccess rules if they don't match
82 @file_put_contents($upload_path . '/.htaccess', $rules);
83 }
84 } elseif (wp_is_writable($upload_path)) {
85 // Create the file if it doesn't exist
86 @file_put_contents($upload_path . '/.htaccess', $rules);
87 }
88
89 // Top level blank index.php
90 if ( ! file_exists($upload_path . '/index.php') && wp_is_writable($upload_path)) {
91 @file_put_contents($upload_path . '/index.php', '<?php' . PHP_EOL . '// Silence is golden.');
92 }
93
94 // Now place index.php files in all sub folders
95 $folders = $this->scan_folders($upload_path);
96
97 foreach ($folders as $folder) {
98 // Create index.php, if it doesn't exist
99 if ( ! file_exists($folder . 'index.php') && wp_is_writable($folder)) {
100 @file_put_contents($folder . 'index.php', '<?php' . PHP_EOL . '// Silence is golden.');
101 }
102 }
103
104 // Check for the files once per day
105 set_transient('ppress_check_protection_files', true, DAY_IN_SECONDS);
106 }
107 }
108
109 /**
110 * Change upload dir for downloadable files.
111 *
112 * @param array $pathdata Array of paths.
113 *
114 * @return array
115 */
116 public function upload_dir($pathdata)
117 {
118 if (isset($_POST['type']) && 'ppress_downloadable_plan' === $_POST['type']) {
119
120 if (empty($pathdata['subdir'])) {
121 $pathdata['path'] = $pathdata['path'] . '/ppress_uploads';
122 $pathdata['url'] = $pathdata['url'] . '/ppress_uploads';
123 $pathdata['subdir'] = '/ppress_uploads';
124 } else {
125
126 $new_subdir = '/ppress_uploads' . $pathdata['subdir'];
127
128 $pathdata['path'] = str_replace($pathdata['subdir'], $new_subdir, $pathdata['path']);
129 $pathdata['url'] = str_replace($pathdata['subdir'], $new_subdir, $pathdata['url']);
130 $pathdata['subdir'] = str_replace($pathdata['subdir'], $new_subdir, $pathdata['subdir']);
131 }
132 }
133
134 return $pathdata;
135 }
136
137 /**
138 * Change filename for WooCommerce uploads and prepend unique chars for security.
139 *
140 * @param string $full_filename Original filename.
141 * @param string $ext Extension of file.
142 * @param string $dir Directory path.
143 *
144 * @return string New filename with unique hash.
145 */
146 public function update_filename($full_filename, $ext, $dir)
147 {
148 if ( ! isset($_POST['type']) || ! 'ppress_downloadable_plan' === $_POST['type']) {
149 return $full_filename;
150 }
151
152 if ( ! strpos($dir, 'ppress_uploads')) {
153 return $full_filename;
154 }
155
156 if ('true' !== ppress_get_file_downloads_setting('downloads_add_hash_filename')) {
157 return $full_filename;
158 }
159
160 return $this->unique_filename($full_filename, $ext);
161 }
162
163 /**
164 * Change filename to append random text.
165 *
166 * @param string $full_filename Original filename with extension.
167 * @param string $ext Extension.
168 *
169 * @return string Modified filename.
170 */
171 public function unique_filename($full_filename, $ext)
172 {
173 $ideal_random_char_length = 6; // Not going with a larger length because then downloaded filename will not be pretty.
174 $max_filename_length = 255; // Max file name length for most file systems.
175 $length_to_prepend = min($ideal_random_char_length, $max_filename_length - strlen($full_filename) - 1);
176
177 if (1 > $length_to_prepend) {
178 return $full_filename;
179 }
180
181 $suffix = strtolower(wp_generate_password($length_to_prepend, false, false));
182 $filename = $full_filename;
183
184 if (strlen($ext) > 0) {
185 $filename = substr($filename, 0, strlen($filename) - strlen($ext));
186 }
187
188 return str_replace(
189 $filename,
190 "$filename-$suffix",
191 $full_filename
192 );
193 }
194
195 public function get_upload_dir()
196 {
197 $wp_upload_dir = wp_upload_dir();
198 $path = $wp_upload_dir['basedir'] . '/' . 'ppress_uploads';
199
200 wp_mkdir_p($path);
201
202 return $path;
203 }
204
205 public function scan_folders($path = '', $return = array())
206 {
207 $path = ($path === '') ? dirname(__FILE__) : $path;
208 $lists = @scandir($path);
209
210 // Bail early if nothing to scan
211 if (empty($lists)) {
212 return $return;
213 }
214
215 // Loop through directory items
216 foreach ($lists as $f) {
217 $dir = $path . DIRECTORY_SEPARATOR . $f;
218
219 // Skip if not a directory
220 if ( ! is_dir($dir) || ($f === ".") || ($f === "..")) {
221 continue;
222 }
223
224 // Maybe add directory to return array
225 if ( ! in_array($dir, $return, true)) {
226 $return[] = trailingslashit($dir);
227 }
228
229 // Recursively scan
230 $this->scan_folders($dir, $return);
231 }
232
233 return $return;
234 }
235
236 /**
237 * Check if uploads directory is protected.
238 *
239 * @return bool
240 */
241 protected function is_uploads_directory_protected()
242 {
243 $cache_key = '_ppress_upload_directory_status';
244 $status = get_transient($cache_key);
245
246 // Check for cache.
247 if (false !== $status) {
248 return 'protected' === $status;
249 }
250
251 // Get only data from the uploads directory.
252 $uploads = wp_get_upload_dir();
253
254 // Check for the "uploads/ppress_uploads" directory.
255 $response = wp_safe_remote_get(
256 esc_url_raw($uploads['baseurl'] . '/ppress_uploads/'),
257 ['redirection' => 0]
258 );
259
260 $response_code = intval(wp_remote_retrieve_response_code($response));
261 $response_content = wp_remote_retrieve_body($response);
262
263 // Check if returns 200 with empty content in case can open an index.html file,
264 // and check for non-200 codes in case the directory is protected.
265 $is_protected = (200 === $response_code && empty($response_content)) || (200 !== $response_code);
266 set_transient($cache_key, $is_protected ? 'protected' : 'unprotected', 1 * DAY_IN_SECONDS);
267
268 return $is_protected;
269 }
270
271 /**
272 * Notice about uploads directory begin unprotected.
273 */
274 public function admin_notice()
275 {
276 if ( ! ppress_is_any_active_plan() || ! ppress_is_any_enabled_payment_method()) {
277 return;
278 }
279
280 if ( ! PAnD::is_admin_notice_active('ppress_dismissed_uploads_directory_is_unprotected-forever')) return;
281
282 if ($this->is_uploads_directory_protected()) return;
283
284 $uploads = wp_get_upload_dir();
285
286 $notice = wp_kses_post(
287 sprintf(
288 /* translators: 1: uploads directory URL 2: documentation URL */
289 __('Your store\'s uploads directory is <a href="%1$s">browsable via the web</a>. We strongly recommend <a href="%2$s">configuring your web server to prevent directory indexing</a>.', 'wp-user-avatar'),
290 esc_url($uploads['baseurl'] . '/ppress_uploads'),
291 'https://profilepress.com/article/sell-downloads-wordpress-membership/#protecting-uploads-directory'
292 )
293 );
294
295 echo '<div data-dismissible="ppress_dismissed_uploads_directory_is_unprotected-forever" class="notice notice-warning is-dismissible">';
296 echo "<p>$notice</p>";
297 echo '</div>';
298 }
299
300 public static function get_instance()
301 {
302 static $instance = null;
303
304 if (is_null($instance)) {
305 $instance = new self();
306 }
307
308 return $instance;
309 }
310 }
311