PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 4.8.1
WP STAGING – WordPress Backup, Restore, Migration & Clone v4.8.1
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 / Filesystem / PathIdentifier.php
wp-staging / Framework / Filesystem Last commit date
Filters 6 months ago Scanning 5 years ago AbstractFileObject.php 1 year ago AbstractFilesystemScanner.php 2 months ago DebugLogReader.php 2 years ago DirectoryListing.php 5 months ago DiskWriteCheck.php 5 months ago FileObject.php 1 year ago Filesystem.php 6 months ago FilesystemExceptions.php 5 years ago FilesystemScanner.php 2 months ago FilesystemScannerDto.php 1 year ago FilterableDirectoryIterator.php 1 year ago LogCleanup.php 5 months ago LogFiles.php 1 year ago MissingFileException.php 3 years ago OPcache.php 5 months ago PartIdentifier.php 8 months ago PathChecker.php 2 years ago PathIdentifier.php 6 months ago Permissions.php 5 months ago WpUploadsFolderSymlinker.php 2 months ago
PathIdentifier.php
307 lines
1 <?php
2
3 namespace WPStaging\Framework\Filesystem;
4
5 use WPStaging\Framework\Adapter\Directory;
6 use WPStaging\Framework\Adapter\DirectoryInterface;
7
8 /**
9 * This class is used to shorten the full file path
10 * to reduce the overall file size of the backup file.
11 *
12 * A file like wp-content/uploads/wp-staging-pro/wp-staging-pro.zip turn into
13 * wpstg_p_/wp-staging-pro/wp-staging.zip
14 *
15 * @todo rename this class to PathShortener
16 */
17
18 class PathIdentifier
19 {
20 /** @var string */
21 const IDENTIFIER_ABSPATH = 'wpstg_a_';
22
23 /** @var string */
24 const IDENTIFIER_WP_CONTENT = 'wpstg_c_';
25
26 /** @var string */
27 const IDENTIFIER_PLUGINS = 'wpstg_p_';
28
29 /** @var string */
30 const IDENTIFIER_THEMES = 'wpstg_t_';
31
32 /** @var string */
33 const IDENTIFIER_MUPLUGINS = 'wpstg_m_';
34
35 /** @var string */
36 const IDENTIFIER_UPLOADS = 'wpstg_u_';
37
38 /** @var string */
39 const IDENTIFIER_LANG = 'wpstg_l_';
40
41 /**
42 * @var string|null The identifier of the last match.
43 * We will try to match the path/identifier of the next item starting from this one. It's a form of cache,
44 * making it more efficient to transform long lists of similar paths.
45 */
46 protected $lastIdentifier;
47
48 /** @var DirectoryInterface */
49 protected $directory;
50
51 public function __construct(DirectoryInterface $directory)
52 {
53 $this->directory = $directory;
54 }
55
56 /** @var string */
57 public function getBackupDirectory()
58 {
59 return $this->directory->getBackupDirectory();
60 }
61
62 /**
63 * Convert an absolute file path of a file into an abbreviated path.
64 *
65 * E.g.:
66 *
67 * /var/www/single/wp-content/plugins/index.php => wpstg_p_index.php
68 * /var/www/single/wp-content/mu-plugins/index.php => wpstg_m_index.php
69 * /var/www/single/wp-content/uploads/2019/image.png => wpstg_c_uploads/2019/image.png
70 * /var/www/single/wp-content/themes/twentytwentyone/index.php => wpstg_t_twentytwentyone/index.php
71 *
72 * @param string $path /var/www/single/wp-content/plugins/index.php
73 *
74 * @return string wpstg_p_index.php
75 */
76 public function transformPathToIdentifiable($path)
77 {
78 // Start looking from the same placeholder as the last item, unless it was wp-content, which would cause false-positives.
79 if (isset($this->lastIdentifier) && $this->lastIdentifier !== self::IDENTIFIER_WP_CONTENT) {
80 $basePath = $this->getIdentifierPath($this->lastIdentifier);
81
82 // Early bail: This item has the same type as the previous one.
83 if (strpos($path, $basePath) === 0) {
84 return $this->lastIdentifier . substr($path, strlen($basePath));
85 }
86 }
87
88 // Uploads are usually the largest folders, so let's start with them.
89 if (strpos($path, $this->directory->getUploadsDirectory()) === 0) {
90 $this->lastIdentifier = self::IDENTIFIER_UPLOADS;
91
92 return $this->lastIdentifier . substr($path, strlen($this->directory->getUploadsDirectory()));
93 }
94
95 if ($this->directory->getPluginUploadsDirectory() !== $this->directory->getUploadsDirectory()) {
96 if (strpos($path, $this->directory->getPluginUploadsDirectory()) === 0) {
97 $this->lastIdentifier = self::IDENTIFIER_UPLOADS;
98
99 return $this->lastIdentifier . substr($path, strlen($this->directory->getPluginUploadsDirectory()));
100 }
101 }
102
103 if (strpos($path, $this->directory->getPluginsDirectory()) === 0) {
104 $this->lastIdentifier = self::IDENTIFIER_PLUGINS;
105
106 return $this->lastIdentifier . substr($path, strlen($this->directory->getPluginsDirectory()));
107 }
108
109 foreach ($this->directory->getAllThemesDirectories() as $themesDirectory) {
110 if (strpos($path, $themesDirectory) === 0) {
111 $this->lastIdentifier = self::IDENTIFIER_THEMES;
112
113 return $this->lastIdentifier . substr($path, strlen($themesDirectory));
114 }
115 }
116
117 if (strpos($path, $this->directory->getMuPluginsDirectory()) === 0) {
118 $this->lastIdentifier = self::IDENTIFIER_MUPLUGINS;
119
120 return $this->lastIdentifier . substr($path, strlen($this->directory->getMuPluginsDirectory()));
121 }
122
123 if (strpos($path, $this->directory->getLangsDirectory()) === 0) {
124 $this->lastIdentifier = self::IDENTIFIER_LANG;
125
126 return $this->lastIdentifier . substr($path, strlen($this->directory->getLangsDirectory()));
127 }
128
129 if (strpos($path, $this->directory->getWpContentDirectory()) === 0) {
130 $this->lastIdentifier = self::IDENTIFIER_WP_CONTENT;
131
132 return $this->lastIdentifier . substr($path, strlen($this->directory->getWpContentDirectory()));
133 }
134
135 if (strpos($path, $this->directory->getAbspath()) === 0) {
136 $this->lastIdentifier = self::IDENTIFIER_ABSPATH;
137
138 return $this->lastIdentifier . substr($path, strlen($this->directory->getAbspath()));
139 }
140
141 // This should never happen on Backups, as we only scan the folders above explicitly and don't follow links.
142 throw new \RuntimeException("Unknown entity type for path: $path");
143 }
144
145 /**
146 * @param string $path wpstg_p_index.php
147 *
148 * @return string /var/www/single/wp-content/plugins/index.php
149 */
150 public function transformIdentifiableToPath($path)
151 {
152 $identifier = $this->getIdentifierFromPath($path);
153 $pathWithoutIdentifier = $this->getPathWithoutIdentifier($path);
154
155 return $this->getIdentifierPath($identifier) . $pathWithoutIdentifier;
156 }
157
158 /**
159 * @param string $path wpstg_p_index.php
160 *
161 * @return string index.php
162 */
163 public function getPathWithoutIdentifier($path)
164 {
165 return substr($path, 8);
166 }
167
168 /**
169 * @param string $path wpstg_p_index.php
170 *
171 * @return string wpstg_p_
172 */
173 public function getIdentifierFromPath($path)
174 {
175 return substr($path, 0, 8);
176 }
177
178 /**
179 * @return string
180 */
181 public function transformIdentifiableToRelativePath(string $string): string
182 {
183 $string = trim($string);
184 if (empty($string)) {
185 return $string;
186 }
187
188 $key = substr($string, 0, 8);
189 $path = $this->getRelativePath($key);
190 if ($path !== $key && is_string($path)) {
191 return substr_replace($string, $path, 0, 8);
192 }
193
194 return $string;
195 }
196
197 /**
198 * @return string
199 */
200 public function getRelativePath(string $identifier): string
201 {
202 static $cache = [];
203
204 if (!empty($cache) && !empty($identifier) && isset($cache[$identifier])) {
205 return $cache[$identifier];
206 }
207
208 $path = [
209 self::IDENTIFIER_ABSPATH => '',
210 self::IDENTIFIER_WP_CONTENT => 'wp-content/',
211 self::IDENTIFIER_PLUGINS => 'wp-content/plugins/',
212 self::IDENTIFIER_THEMES => 'wp-content/themes/',
213 self::IDENTIFIER_MUPLUGINS => 'wp-content/mu-plugins/',
214 self::IDENTIFIER_UPLOADS => 'wp-content/uploads/',
215 self::IDENTIFIER_LANG => 'wp-content/languages/',
216 ];
217
218 if (!empty($identifier) && isset($path[$identifier])) {
219 $cache[$identifier] = $path[$identifier];
220 return $cache[$identifier];
221 }
222
223 // Add __METHOD__ for debugging in wpstg-restore
224 trigger_error(sprintf('[%s] Could not find a path for the placeholder: %s', __METHOD__, filter_var($identifier, FILTER_SANITIZE_SPECIAL_CHARS)));
225 return $identifier;
226 }
227
228 public function getAbsolutePath(string $identifier): string
229 {
230 return $this->getIdentifierPath($identifier);
231 }
232
233 /**
234 * @return string
235 */
236 public function getIdentifierByPartName(string $key): string
237 {
238 static $cache = [];
239
240 if (!empty($cache) && !empty($key) && !empty($cache[$key])) {
241 return $cache[$key];
242 }
243
244 $list = [
245 PartIdentifier::WP_CONTENT_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_WP_CONTENT,
246 PartIdentifier::PLUGIN_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_PLUGINS,
247 PartIdentifier::THEME_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_THEMES,
248 PartIdentifier::MU_PLUGIN_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_MUPLUGINS,
249 PartIdentifier::UPLOAD_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_UPLOADS,
250 PartIdentifier::LANGUAGE_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_LANG,
251 PartIdentifier::DATABASE_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_UPLOADS,
252 PartIdentifier::WP_ROOT_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_ABSPATH,
253 ];
254
255 if (!empty($key) && !empty($list[$key])) {
256 $cache[$key] = $list[$key];
257 return $cache[$key];
258 }
259
260 return '';
261 }
262
263 /**
264 * @param string $identifier wpstg_p_
265 *
266 * @return string /var/www/single/wp-content/plugins/
267 */
268 protected function getIdentifierPath($identifier)
269 {
270 // It is crucial that generic paths are placed last in this list. Eg: wp-content directory must be last.
271 switch ($identifier) {
272 case self::IDENTIFIER_ABSPATH:
273 return $this->directory->getAbspath();
274 case self::IDENTIFIER_UPLOADS:
275 return $this->directory->getUploadsDirectory();
276 case self::IDENTIFIER_PLUGINS:
277 return $this->directory->getPluginsDirectory();
278 case self::IDENTIFIER_THEMES:
279 return $this->directory->getActiveThemeParentDirectory();
280 case self::IDENTIFIER_MUPLUGINS:
281 return $this->directory->getMuPluginsDirectory();
282 case self::IDENTIFIER_LANG:
283 return $this->directory->getLangsDirectory();
284 case self::IDENTIFIER_WP_CONTENT:
285 return $this->directory->getWpContentDirectory();
286 default:
287 throw new \UnexpectedValueException(sprintf("[%s] Could not find a path for the placeholder: %s", __METHOD__, filter_var($identifier, FILTER_SANITIZE_SPECIAL_CHARS)));
288 }
289 }
290
291 /**
292 * @param string $identifiablePath wpstg_p_db.php
293 *
294 * @return bool
295 */
296 public function hasDropinsFile(string $identifiablePath): bool
297 {
298 if (!(strpos($identifiablePath, self::IDENTIFIER_WP_CONTENT) === 0)) {
299 return false;
300 }
301
302 $dropinsFile = implode('|', PartIdentifier::DROP_IN_FILES);
303
304 return preg_match('@^' . self::IDENTIFIER_WP_CONTENT . '(' . $dropinsFile . ')@', $identifiablePath) ? true : false;
305 }
306 }
307