PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 4.9.2
WP STAGING – WordPress Backup, Restore, Migration & Clone v4.9.2
4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Framework / Utils / Strings.php
wp-staging / Framework / Utils Last commit date
Cache 7 months ago DBPermissions.php 9 months ago DataEncoder.php 10 months ago DatabaseOptions.php 2 weeks ago Escape.php 9 months ago Glob.php 2 years ago Hooks.php 2 months ago Math.php 9 months ago PluginInfo.php 5 months ago Sanitize.php 2 months ago ServerVars.php 2 years ago SlashMode.php 5 years ago Strings.php 7 months ago Times.php 5 months ago Urls.php 2 weeks ago Version.php 1 year ago WpDefaultDirectories.php 2 years ago
Strings.php
255 lines
1 <?php
2
3 namespace WPStaging\Framework\Utils;
4
5 use WPStaging\Framework\Traits\UrlTrait;
6
7 /**
8 * Utility class for string manipulation and formatting operations
9 *
10 * Provides a collection of string utility methods including:
11 * - String replacement operations (first occurrence, last occurrence, conditional)
12 * - String inspection (startsWith, endsWith, substring extraction)
13 * - Path and directory separator sanitization
14 * - String masking for privacy (email addresses, backup filenames)
15 * - Prefix/suffix manipulation
16 *
17 * Used throughout the plugin for consistent string handling, especially for
18 * file paths, URLs, and sensitive data that needs to be displayed safely.
19 */
20 class Strings
21 {
22 use UrlTrait;
23
24 /**
25 * Replace first occurrence of certain string
26 * @param string $search
27 * @param string $replace
28 * @param string $subject
29 * @return string
30 *
31 * @deprecated use strReplaceFirst instead
32 * @todo replace all usage of str_replace_first with strReplaceFirst
33 */
34 public function str_replace_first($search, $replace, $subject)
35 {
36 return $this->strReplaceFirst($search, $replace, $subject);
37 }
38
39 /**
40 * Replace first occurrence of certain string
41 * @param string $search
42 * @param string $replace
43 * @param string $subject
44 * @return string
45 */
46 public function strReplaceFirst($search, $replace, $subject)
47 {
48 if (empty($search)) {
49 return $subject;
50 }
51
52 $pos = strpos($subject, $search);
53 if ($pos !== false) {
54 if ($replace === null) {
55 $replace = '';
56 }
57
58 return substr_replace($subject, $replace, $pos, strlen($search));
59 }
60
61 return $subject;
62 }
63
64 /**
65 * Get last string after last certain element in string
66 * Example: getLastElemAfterString('/', '/path/stagingsite/subfolder') returns 'subfolder'
67 * @param string $needle
68 * @param string $haystack
69 * @return string
70 */
71 public function getLastElemAfterString($needle, $haystack)
72 {
73 $pos = strrpos($haystack, $needle);
74 return $pos === false ? $haystack : substr($haystack, $pos + 1);
75 }
76
77 /**
78 * Replace backward slash with forward slash directory separator
79 * Escape Windows Backward Slash - Compatibility Fix
80 * @param string $path Path
81 *
82 * @return string
83 */
84 public function sanitizeDirectorySeparator($path)
85 {
86 $string = preg_replace('/[\\\\]+/', '/', $path);
87 return str_replace('//', '/', $string);
88 }
89
90 /**
91 * Check if a string start with a specific string
92 * @param string $haystack
93 * @param string $needle
94 * @return bool
95 */
96 public function startsWith($haystack, $needle)
97 {
98 $length = strlen($needle);
99 return ($needle === substr($haystack, 0, $length));
100 }
101
102 /**
103 * Check if a string start with a specific string from a list of strings
104 * @param string[] $needlesList
105 * @param string $haystack
106 * @return bool
107 */
108 public function startsWithAnyFromList(array $needlesList, string $haystack): bool
109 {
110 foreach ($needlesList as $needle) {
111 if ($this->startsWith($haystack, $needle)) {
112 return true;
113 }
114 }
115
116 return false;
117 }
118
119 /**
120 * Check if a string ends with a specific string
121 * @param string $haystack
122 * @param string $needle
123 * @return bool
124 */
125 public function endsWith($haystack, $needle)
126 {
127 $haystack = strrev($haystack);
128 $needle = strrev($needle);
129 return strpos($haystack, $needle) === 0;
130 }
131
132 /**
133 * Search & Replace last occurrence of string in haystack
134 * @param string $needle
135 * @param string $replace
136 * @param string $haystack
137 * @return string
138 */
139 public function replaceLastMatch($needle, $replace, $haystack)
140 {
141 $result = $haystack;
142 $pos = strrpos($haystack, $needle);
143 if ($pos !== false) {
144 $result = substr_replace($haystack, $replace, $pos, strlen($needle));
145 }
146
147 return $result;
148 }
149
150 /**
151 * Make sure prefix ends with underscore
152 * @param string $string
153 * @return string
154 */
155 public function maybeAppendUnderscore(string $string): string
156 {
157 // Early bail, if underscore is already the last character
158 if (substr($string, -1) === '_') {
159 return $string;
160 }
161
162 return $string . '_';
163 }
164
165 /**
166 * If $haystack starts with $needle, replace it with $replace
167 * Example: replaceStartWith('www.', '', 'www.example.com') returns 'example.com'
168 * But replaceStartWith('www.', '', 'https://wwww.example.com') returns 'https://www.example.com and remains unchanged'
169 *
170 * @param string $needle
171 * @param string $replace
172 * @param string $haystack
173 * @return string
174 */
175 public function replaceStartWith(string $needle, string $replace, string $haystack): string
176 {
177 if (strpos($haystack, $needle) === 0) {
178 return $replace . substr($haystack, strlen($needle));
179 }
180
181 return $haystack;
182 }
183
184 /**
185 * @param string $email
186 * @return string
187 */
188 public function maskEmail(string $email): string
189 {
190 if (empty($email)) {
191 return '';
192 }
193
194 list($username, $domain) = explode('@', $email);
195 $usernameLength = strlen($username);
196 $firstChar = substr($username, 0, 1);
197 $lastChar = substr($username, -1);
198 if ($usernameLength <= 2) {
199 $maskedUsername = $firstChar . str_repeat('*', max(0, $usernameLength - 1));
200 } else {
201 $maskedUsername = $firstChar . str_repeat('*', $usernameLength - 2) . $lastChar;
202 }
203
204 list($domainName, $topLevelDomain) = explode('.', $domain);
205 $maskedDomainName = substr($domainName, 0, 1) . str_repeat('*', max(0, strlen($domainName) - 1));
206 return $maskedUsername . '@' . $maskedDomainName . '.' . $topLevelDomain;
207 }
208
209 /**
210 * Mask a backup filename so we can show it in logs/UI without leaking the full site name.
211 *
212 * Rules:
213 * - Keep only the first 6 chars of the site prefix and last 6 chars of the timestamp/hash.
214 * - Preserve the multipart suffix (e.g. ".plugins.001.wpstg") or ".wpstg".
215 * - If the filename doesn't match known backup patterns, mask the middle of the base name.
216 *
217 * @param string $filename Full path or basename of the backup file.
218 * @return string Masked filename safe for display/logging.
219 */
220 public function maskBackupFilename(string $filename): string
221 {
222 if ($filename === '') {
223 return '';
224 }
225
226 $basename = basename($filename);
227
228 // Handle multipart backup suffix patterns (preserve them entirely)
229 if (preg_match('/^(.+?)_([0-9]{8}-[0-9]{6}_[a-f0-9]+)(\.(wpstgdb|otherfiles|rootfiles|themes|plugins|muplugins)\.\d+\.wpstg)$/', $basename, $matches)) {
230 $prefix = substr($matches[1], 0, 6);
231 $suffix = substr($matches[2], -6);
232 return $prefix . '*********' . $suffix . $matches[3];
233 }
234
235 // Handle single backup file (not multipart)
236 if (preg_match('/^(.+?)_([0-9]{8}-[0-9]{6}_[a-f0-9]+)(\.wpstg)$/', $basename, $matches)) {
237 $prefix = substr($matches[1], 0, 6);
238 $suffix = substr($matches[2], -6);
239 return $prefix . '*********' . $suffix . $matches[3];
240 }
241
242 // Fallback: generic middle masking
243 $nameWithoutExt = pathinfo($basename, PATHINFO_FILENAME);
244 $extension = pathinfo($basename, PATHINFO_EXTENSION);
245 $extension = $extension ? '.' . $extension : '';
246
247 $length = strlen($nameWithoutExt);
248 if ($length <= 2) {
249 return substr($nameWithoutExt, 0, 1) . '*********' . substr($nameWithoutExt, -1) . $extension;
250 }
251
252 return substr($nameWithoutExt, 0, 6) . '*********' . substr($nameWithoutExt, -6) . $extension;
253 }
254 }
255