PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / trunk
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions vtrunk
260909 260829 260814 260805 110710 110731 110812 110815 110912 110913 110915 110926 110927 111002 111003 111011 111017 111029 111105 111206 111216 111220 120213 120219 120301 All 187 releases
s2member / src / includes / classes / utils-dirs.inc.php

utils-dirs.inc.php in s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions trunk, at src/includes/classes/utils-dirs.inc.php

347 lines 15.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // @codingStandardsIgnoreFile
3 /**
4 * Directory utilities.
5 *
6 * Copyright: © 2009-2011
7 * {@link http://websharks-inc.com/ WebSharks, Inc.}
8 * (coded in the USA)
9 *
10 * Released under the terms of the GNU General Public License.
11 * You should have received a copy of the GNU General Public License,
12 * along with this software. In the main directory, see: /licensing/
13 * If not, see: {@link http://www.gnu.org/licenses/}.
14 *
15 * @package s2Member\Utilities
16 * @since 3.5
17 */
18 if(!defined('WPINC')) // MUST have WordPress.
19 exit ("Do not access this file directly.");
20
21 if (!class_exists ("c_ws_plugin__s2member_utils_dirs"))
22 {
23 /**
24 * Directory utilities.
25 *
26 * @package s2Member\Utilities
27 * @since 3.5
28 */
29 class c_ws_plugin__s2member_utils_dirs
30 {
31 /**
32 * Normalizes directory separators in dir/file paths.
33 *
34 * @package s2Member\Utilities
35 * @since 111017
36 *
37 * @param string $path Directory or file path.
38 * @return string Directory or file path, after having been normalized by this routine.
39 */
40 public static function n_dir_seps ($path = FALSE)
41 {
42 return rtrim (preg_replace ("/\/+/", "/", str_replace (array(DIRECTORY_SEPARATOR, "\\", "/"), "/", (string)$path)), "/");
43 }
44 /**
45 * Resolves and checks a custom template selected by a shortcode.
46 *
47 * @package s2Member\Utilities
48 * @since 260812
49 *
50 * @param string $template Template path supplied by a shortcode.
51 * @param array $legacy_filenames Exact legacy filenames allowed automatically in the content root or active theme roots.
52 * @param string $shortcode Shortcode name for administrator notices.
53 * @param int $post_id Post/Page ID where the template was detected, when available.
54 * @param bool $enforce When true, return only approved templates. During the transition period this defaults to false.
55 * @return string Canonical template path, or an empty string if invalid or blocked.
56 */
57 public static function shortcode_template ($template = FALSE, $legacy_filenames = array(), $shortcode = '', $post_id = 0, $enforce = FALSE)
58 {
59 $template = c_ws_plugin__s2member_utils_dirs::n_dir_seps (trim (str_replace (chr (0), '', (string)$template)));
60 if (!$template)
61 return '';
62
63 // Reject parent-directory segments before resolving the path.
64 if (preg_match ('/(?:^|\/)\.\.(?:\/|$)/', $template))
65 return '';
66
67 // Preserve the historical lookup precedence and resolve the file to its actual filesystem path.
68 $candidate = '';
69 foreach (array(TEMPLATEPATH . '/' . $template, get_stylesheet_directory () . '/' . $template, WP_CONTENT_DIR . '/' . $template) as $_path)
70 if (is_file ($_path) && ($_realpath = realpath ($_path)))
71 $candidate = c_ws_plugin__s2member_utils_dirs::n_dir_seps ($_realpath);
72 unset($_path, $_realpath);
73
74 if (!$candidate || !($_content_dir = realpath (WP_CONTENT_DIR)))
75 return '';
76 $content_dir = c_ws_plugin__s2member_utils_dirs::n_dir_seps ($_content_dir);
77 $is_windows = stripos (PHP_OS, 'WIN') === 0;
78 $candidate_cmp = ($is_windows) ? strtolower ($candidate) : $candidate;
79 $content_prefix = $content_dir . '/';
80 $content_prefix_cmp = ($is_windows) ? strtolower ($content_prefix) : $content_prefix;
81
82 // Require the resolved file to remain under WP_CONTENT_DIR, including after symlink resolution.
83 if (strpos ($candidate_cmp, $content_prefix_cmp) !== 0)
84 return '';
85
86 // Keep shortcode templates out of WordPress uploads, where uploaded files can be user-controlled.
87 $_uploads_dirs = array();
88 $_uploads = wp_upload_dir();
89 if (!empty($_uploads['basedir']) && ($_uploads_dir = realpath ($_uploads['basedir'])))
90 {
91 $_uploads_dir = c_ws_plugin__s2member_utils_dirs::n_dir_seps ($_uploads_dir);
92
93 //260812 On Multisite, check the shared uploads root instead of only the current site's subdirectory.
94 if (is_multisite ())
95 {
96 foreach (array('/sites/' . get_current_blog_id (), '/' . get_current_blog_id () . '/files', '/' . get_current_blog_id ()) as $_site_suffix)
97 if (substr ($_uploads_dir, -strlen ($_site_suffix)) === $_site_suffix)
98 {
99 $_uploads_dir = substr ($_uploads_dir, 0, -strlen ($_site_suffix));
100 break;
101 }
102 }
103 $_uploads_dirs[] = $_uploads_dir;
104 }
105
106 //260812 Also check the standard and legacy Multisite uploads roots.
107 foreach (array(WP_CONTENT_DIR . '/uploads', (is_multisite () && defined ('UPLOADBLOGSDIR')) ? ABSPATH . ltrim (UPLOADBLOGSDIR, '/\\') : '') as $_uploads_dir)
108 if ($_uploads_dir && ($_realpath = realpath ($_uploads_dir)))
109 $_uploads_dirs[] = c_ws_plugin__s2member_utils_dirs::n_dir_seps ($_realpath);
110
111 foreach (array_unique ($_uploads_dirs) as $_uploads_dir)
112 {
113 $_uploads_dir_cmp = ($is_windows) ? strtolower ($_uploads_dir) : $_uploads_dir;
114 if ($candidate_cmp === $_uploads_dir_cmp || strpos ($candidate_cmp, $_uploads_dir_cmp . '/') === 0)
115 return '';
116 }
117 unset($_uploads_dirs, $_uploads, $_uploads_dir, $_uploads_dir_cmp, $_site_suffix, $_realpath);
118
119 $allowed_paths = array();
120 foreach ((array)$legacy_filenames as $_legacy_filename)
121 {
122 $_legacy_filename = ltrim (c_ws_plugin__s2member_utils_dirs::n_dir_seps (trim ((string)$_legacy_filename)), '/');
123 if (!$_legacy_filename)
124 continue;
125
126 // Allow only the exact historical filename in the content root or active theme roots.
127 foreach (array(TEMPLATEPATH . '/' . $_legacy_filename, get_stylesheet_directory () . '/' . $_legacy_filename, WP_CONTENT_DIR . '/' . $_legacy_filename) as $_path)
128 if (is_file ($_path) && ($_realpath = realpath ($_path)))
129 $allowed_paths[] = c_ws_plugin__s2member_utils_dirs::n_dir_seps ($_realpath);
130 }
131 unset($_legacy_filename, $_path, $_realpath);
132
133 // Add exact custom template files from the whitelist and track whether the current file is listed.
134 $whitelisted = FALSE;
135 $_whitelist = apply_filters ('ws_plugin__s2member_shortcode_templates_whitelist', array());
136 if (is_string ($_whitelist))
137 $_whitelist = preg_split ("/[\\r\\n]+/", $_whitelist, -1, PREG_SPLIT_NO_EMPTY);
138 foreach ((array)$_whitelist as $_allowed_file)
139 {
140 $_allowed_file = ltrim (c_ws_plugin__s2member_utils_dirs::n_dir_seps (trim ((string)$_allowed_file)), '/');
141 if ($_allowed_file && is_file (WP_CONTENT_DIR . '/' . $_allowed_file) && ($_realpath = realpath (WP_CONTENT_DIR . '/' . $_allowed_file)))
142 {
143 $_realpath = c_ws_plugin__s2member_utils_dirs::n_dir_seps ($_realpath);
144 $allowed_paths[] = $_realpath;
145 if ((($is_windows) ? strtolower ($_realpath) : $_realpath) === $candidate_cmp)
146 $whitelisted = TRUE;
147 }
148 }
149 unset($_whitelist, $_allowed_file, $_realpath);
150
151 $allowed = FALSE;
152 foreach (array_unique ($allowed_paths) as $_allowed_path)
153 if ((($is_windows) ? strtolower ($_allowed_path) : $_allowed_path) === $candidate_cmp)
154 {
155 $allowed = TRUE;
156 break;
157 }
158 unset($allowed_paths, $_allowed_path);
159
160 $_relative_path = ltrim (substr ($candidate, strlen ($content_dir)), '/');
161
162 if ($allowed)
163 {
164 // Clear a saved warning only when the site owner explicitly added this file to the whitelist.
165 if ($whitelisted)
166 do_action ('ws_plugin__s2member_shortcode_template_approved', $_relative_path, $shortcode, (int)$post_id);
167 return $candidate;
168 }
169
170 // Notify Pro about an unapproved template so it can be included in the admin warning.
171 if ($_relative_path && $shortcode)
172 do_action ('ws_plugin__s2member_shortcode_template_unapproved', $_relative_path, $shortcode, (int)$post_id);
173
174 // During the transition period unapproved templates still work; enforcement can later be enabled centrally.
175 return ($enforce) ? '' : $candidate;
176 }
177 /**
178 * Strips a trailing `/app_data/` sub-directory.
179 *
180 * @package s2Member\Utilities
181 * @since 3.5
182 *
183 * @param string $path Directory or file path.
184 * @return string Directory or file path without `/app_data/`.
185 */
186 public static function strip_dir_app_data ($path = FALSE)
187 {
188 return preg_replace ("/\/app_data$/", "", c_ws_plugin__s2member_utils_dirs::n_dir_seps ((string)$path));
189 }
190 /**
191 * Basename from a full directory or file path.
192 *
193 * @package s2Member\Utilities
194 * @since 110815
195 *
196 * @param string $path Directory or file path.
197 * @return string Basename; including a possible `/app_data/` directory.
198 */
199 public static function basename_dir_app_data ($path = FALSE)
200 {
201 $path = preg_replace ("/\/app_data$/", "", c_ws_plugin__s2member_utils_dirs::n_dir_seps ((string)$path), 1, $app_data);
202
203 return basename ($path) . (($app_data) ? "/app_data" : "");
204 }
205 /**
206 * Shortens to a directory or file path, from document root.
207 *
208 * @package s2Member\Utilities
209 * @since 110815
210 *
211 * @param string $path Directory or file path.
212 * @return string Shorther path, from document root.
213 */
214 public static function doc_root_path ($path = FALSE)
215 {
216 $doc_root = c_ws_plugin__s2member_utils_dirs::n_dir_seps ($_SERVER["DOCUMENT_ROOT"]);
217
218 return preg_replace ("/^" . preg_quote ($doc_root, "/") . "/", "", c_ws_plugin__s2member_utils_dirs::n_dir_seps ((string)$path));
219 }
220 /**
221 * Finds the relative path, from one location to another.
222 *
223 * @package s2Member\Utilities
224 * @since 110815
225 *
226 * @param string $from The full directory path to calculate a relative path `from`.
227 * @param string $to The full directory or file path, which this routine will build a relative path `to`.
228 * @param bool $try_realpaths Defaults to true. When true, try to acquire ``realpath()``, thereby resolving all relative paths and/or symlinks in ``$from`` and ``$to`` args.
229 * @param bool $use_win_diff_drive_jctn Defaults to true. When true, we'll work around issues with different drives on Windows by trying to create a directory junction.
230 * @return string String with the relative path to: ``$to``.
231 */
232 public static function rel_path ($from = FALSE, $to = FALSE, $try_realpaths = TRUE, $use_win_diff_drive_jctn = TRUE)
233 {
234 if ( /* Initialize/validate. */!($rel_path = array()) && is_string ($from) && strlen ($from) && is_string ($to) && strlen ($to))
235 {
236 $from = ($try_realpaths && ($_real_from = realpath ($from))) ? $_real_from : $from; // Try this?
237 $to = ($try_realpaths && ($_real_to = realpath ($to))) ? $_real_to : $to; // Try to find realpath?
238
239 $from = (is_file ($from)) ? dirname ($from) . "/" : $from . "/"; // A (directory) with trailing `/`.
240
241 $from = c_ws_plugin__s2member_utils_dirs::n_dir_seps ($from); // Normalize directory separators now.
242 $to = c_ws_plugin__s2member_utils_dirs::n_dir_seps ($to); // Normalize directory separators here too.
243
244 $from = preg_split ("/\//", $from); // Convert ``$from``, to an array. Split on each directory separator.
245 $to = preg_split ("/\//", $to); // Also convert ``$to``, to an array. Split this on each directory separator.
246
247 if ($use_win_diff_drive_jctn && stripos (PHP_OS, "win") === 0 /* Test for different drives on Windows servers? */)
248
249 if (/*Drive? */preg_match ("/^([A-Z])\:$/i", $from[0], $_m) && ($_from_drive = $_m[1]) && preg_match ("/^([A-Z])\:$/i", $to[0], $_m) && ($_to_drive = $_m[1]))
250 if ( /* Are these locations on completely different drives? */$_from_drive !== $_to_drive)
251 {
252 $_from_drive_jctn = $_from_drive . ":/s2-" . $_to_drive . "-jctn";
253 $_sys_temp_dir_jctn = c_ws_plugin__s2member_utils_dirs::get_temp_dir (false) . "/s2-" . $_to_drive . "-jctn";
254
255 $_jctn = ($_sys_temp_dir_jctn && strpos ($_sys_temp_dir_jctn, $_from_drive) === 0) ? $_sys_temp_dir_jctn : $_from_drive_jctn;
256
257 if (($_from_drive_jctn_exists = (is_dir ($_from_drive_jctn)) ? true : false) || c_ws_plugin__s2member_utils_dirs::create_win_jctn ($_jctn, $_to_drive . ":/"))
258 {
259 array_shift /* Shift drive off and use junction now. */ ($to);
260 foreach (array_reverse (preg_split ("/\//", (($_from_drive_jctn_exists) ? $_from_drive_jctn : $_jctn))) as $_jctn_dir)
261 array_unshift ($to, $_jctn_dir);
262 }
263 else // Else, we should trigger an error in this case. It's NOT possible to generate this.
264 {
265 $error = "Unable to generate a relative path across different Windows drives." .
266 " Please create a Directory Junction here: " . $_from_drive_jctn . ", pointing to: " . $_to_drive . ":/";
267
268 //260816 PHP 8.4 deprecates trigger_error(..., E_USER_ERROR); preserve the previous fatal-style path on older PHP.
269 if(PHP_VERSION_ID >= 80400)
270 throw new \RuntimeException($error);
271 else
272 trigger_error($error, E_USER_ERROR);
273 }
274 }
275
276 unset($_real_from, $_real_to, $_from_drive, $_to_drive, $_from_drive_jctn, $_sys_temp_dir_jctn, $_jctn, $_from_drive_jctn_exists, $_jctn_dir, $_m);
277
278 $rel_path = $to; // Re-initialize. Start ``$rel_path`` as the value of the ``$to`` array.
279
280 foreach (array_keys ($from) as $_depth) // Each ``$from`` directory ``$_depth``.
281 {
282 if (isset ($from[$_depth], $to[$_depth]) && $from[$_depth] === $to[$_depth])
283 array_shift ($rel_path);
284
285 else if (($_remaining = count ($from) - $_depth) > 1)
286 {
287 $_left_p = -1 * (count ($rel_path) + ($_remaining - 1));
288 $rel_path = array_pad ($rel_path, $_left_p, "..");
289 break; // Stop now, no need to go any further.
290 }
291 else // Else, set as the same directory `./[0]`.
292 {
293 $rel_path[0] = "./" . $rel_path[0];
294 break; // Stop now.
295 }
296 }
297 }
298
299 return implode ("/", $rel_path);
300 }
301 /**
302 * Creates a directory Junction in Windows.
303 *
304 * @package s2Member\Utilities
305 * @since 111013
306 *
307 * @param string $jctn Directory location of the Junction (i.e., the link).
308 * @param string $target Target directory that this Junction will connect to.
309 * @return bool True if created successfully, or already exists, else false.
310 */
311 public static function create_win_jctn ($jctn = FALSE, $target = FALSE)
312 {
313 if ($jctn && is_string ($jctn) && $target && is_string ($target) && stripos (PHP_OS, "win") === 0)
314 {
315 if (is_dir ($jctn)) // Does it already exist? If so return now.
316 return true; // Return now to save extra processing time below.
317
318 else if ( /* Possible? */function_exists ("shell_exec") && ($esa = "escapeshellarg"))
319 {
320 @shell_exec ("mklink /J " . $esa ($jctn) . " " . $esa ($target));
321
322 clearstatcache (); // Clear ``stat()`` cache now.
323 if (is_dir ($jctn)) // Created successfully?
324 return true;
325 }
326 }
327 return false; // Else return false.
328 }
329 /**
330 * Get the system's temporary directory.
331 *
332 * @package s2Member\Utilities
333 * @since 111017
334 *
335 * @param string $fallback Defaults to true. If true, fallback on WordPress routine if not available, or if not writable.
336 * @return str|bool Full string path to a writable temp directory, else false on failure.
337 */
338 public static function get_temp_dir ($fallback = TRUE)
339 {
340 $temp_dir = (($temp_dir = realpath (sys_get_temp_dir ())) && is_writable ($temp_dir)) ? $temp_dir : false;
341 $temp_dir = (!$temp_dir && $fallback && ($wp_temp_dir = realpath (get_temp_dir ())) && is_writable ($wp_temp_dir)) ? $wp_temp_dir : $temp_dir;
342
343 return ($temp_dir) ? c_ws_plugin__s2member_utils_dirs::n_dir_seps ($temp_dir) : false;
344 }
345 }
346 }
347