PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 4.9.0
WP STAGING – WordPress Backup, Restore, Migration & Clone v4.9.0
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 / Backend / Modules / SystemInfoParser.php
wp-staging / Backend / Modules Last commit date
Jobs 1 week ago Views 3 months ago SystemInfo.php 4 months ago SystemInfoParser.php 1 month ago
SystemInfoParser.php
539 lines
1 <?php
2
3 namespace WPStaging\Backend\Modules;
4
5 use WPStaging\Core\WPStaging;
6 use WPStaging\Framework\Traits\SerializeTrait;
7 use WPStaging\Backup\Storage\Providers;
8
9 /**
10 * System Info Parser
11 * Parses system info text into structured data for display
12 */
13 class SystemInfoParser
14 {
15 use SerializeTrait;
16
17 /** @var string */
18 const SECTION_STORAGE_PROVIDERS = 'WP Staging - Storage Providers';
19
20 /**
21 * Section definitions - single source of truth for IDs, names and subtitles
22 * @var array<string, array{id: string, name: string, subtitle: string}>
23 */
24 const SECTIONS = [
25 'SERVER_AND_OS' => ['id' => 'server_and_os', 'name' => 'Server & Operating System', 'subtitle' => 'Server software and system architecture'],
26 'DATABASE_MYSQL_MARIADB' => ['id' => 'database_mysql_mariadb', 'name' => 'Database (MySQL / MariaDB)', 'subtitle' => 'Database configuration and connection details'],
27 'PHP_ENVIRONMENT' => ['id' => 'php_environment', 'name' => 'PHP Environment', 'subtitle' => 'PHP runtime and environment information'],
28 'PHP_LIMITS' => ['id' => 'php_limits', 'name' => 'PHP Limits', 'subtitle' => 'PHP resource limits and allocation'],
29 'WORDPRESS_ENVIRONMENT' => ['id' => 'wordpress_environment', 'name' => 'WordPress Environment', 'subtitle' => 'WordPress core and site configuration'],
30 'URLS_PATHS' => ['id' => 'urls_paths', 'name' => 'URLs & Paths', 'subtitle' => 'Site URLs and filesystem paths'],
31 'WORDPRESS_DIRECTORIES' => ['id' => 'wordpress_directories', 'name' => 'WordPress Directories', 'subtitle' => 'WordPress directory structure and locations'],
32 'MEDIA_UPLOADS' => ['id' => 'media_uploads', 'name' => 'Media & Uploads', 'subtitle' => 'Media upload paths and configuration'],
33 'WORDPRESS_MEMORY_SETTINGS' => ['id' => 'wordpress_memory_settings', 'name' => 'WordPress Memory Settings', 'subtitle' => 'WordPress memory allocation and limits'],
34 'FILESYSTEM_PERMISSIONS' => ['id' => 'filesystem_permissions', 'name' => 'Filesystem & Permissions', 'subtitle' => 'Filesystem access and permission settings'],
35 'THEME_PERMALINKS' => ['id' => 'theme_permalinks', 'name' => 'Theme & Permalinks', 'subtitle' => 'Active theme and permalink configuration'],
36 'WORDPRESS_CRON_JOBS' => ['id' => 'wordpress_cron_jobs', 'name' => 'WordPress Cron Jobs', 'subtitle' => 'Scheduled tasks and cron configuration'],
37 'WP_STAGING_PLUGIN_INFO' => ['id' => 'wp_staging_plugin_information', 'name' => 'WP Staging – Plugin Information', 'subtitle' => 'WP Staging plugin version and license details'],
38 'WP_STAGING_BACKUP_STATUS' => ['id' => 'wp_staging_backup_status_and_statistics', 'name' => 'WP Staging – Backup Status & Statistics', 'subtitle' => 'Backup status, storage usage, and processing metrics'],
39 'WP_STAGING_PERFORMANCE' => ['id' => 'wp_staging_performance_limits', 'name' => 'WP Staging – Performance & Limits', 'subtitle' => 'Performance settings and processing limits'],
40 'WP_STAGING_ACCESS' => ['id' => 'wp_staging_access_permissions', 'name' => 'WP Staging – Access & Permissions', 'subtitle' => 'User access rules and permission settings'],
41 'WP_STAGING_EXISTING_SITES' => ['id' => 'wp_staging_existing_staging_sites', 'name' => 'WP Staging – Existing Staging Sites', 'subtitle' => 'Configured staging sites and environments'],
42 'WP_STAGING_STORAGE_PROVIDER' => ['id' => 'wp_staging_storage_provider', 'name' => 'WP Staging - Storage Providers', 'subtitle' => 'Remote storage providers and configuration'],
43 'PLUGINS_OVERVIEW' => ['id' => 'plugins_overview', 'name' => 'Plugins Overview', 'subtitle' => 'Installed plugins and activation status'],
44 'CURL_ENVIRONMENT' => ['id' => 'curl_environment', 'name' => 'cURL Environment', 'subtitle' => 'cURL runtime, version, and SSL stack'],
45 'CURL_FEATURES' => ['id' => 'curl_features', 'name' => 'cURL Features', 'subtitle' => 'Enabled cURL features and capabilities'],
46 'SUPPORTED_PROTOCOLS' => ['id' => 'supported_protocols', 'name' => 'Supported Protocols', 'subtitle' => 'Protocols supported by the cURL library'],
47 'PHP_NETWORK_EXTENSIONS' => ['id' => 'php_network_extensions', 'name' => 'PHP Network Extensions', 'subtitle' => 'Installed PHP extensions for network communication'],
48 'CLIENT_BROWSER_INFO' => ['id' => 'client_browser_information', 'name' => 'Client / Browser Information', 'subtitle' => 'Client device and browser details'],
49 'MULTISITE' => ['id' => 'multisite', 'name' => 'Multisite', 'subtitle' => 'WordPress multisite network configuration'],
50 ];
51
52 /**
53 * @param mixed $data
54 */
55 public function isSerializedData($data): bool
56 {
57 return $this->isSerialized($data);
58 }
59
60 /**
61 * Get storage providers from canonical source with system info mappings
62 *
63 * @return array Storage provider configurations with option names and titles
64 */
65 public function getStorageProvidersForSystemInfo(): array
66 {
67 if (!class_exists('WPStaging\Backup\Storage\Providers')) {
68 return [];
69 }
70
71 $providersInstance = WPStaging::make(Providers::class);
72 $storages = [];
73
74 foreach ($providersInstance->getStorages() as $storage) {
75 $optionName = 'wpstg_' . strtolower($storage['id']);
76
77 $storages[] = [
78 'id' => $storage['id'],
79 'name' => $storage['name'],
80 'optionName' => $optionName,
81 'title' => $storage['name'] . ' Settings',
82 ];
83 }
84
85 return $storages;
86 }
87
88 /**
89 * Get storage provider ID by provider name
90 *
91 * @param string $name Provider name (e.g., "Google Drive")
92 * @return string Provider ID or empty string if not found
93 */
94 public function getStorageProviderIdByName($name): string
95 {
96 $providers = $this->getStorageProvidersForSystemInfo();
97 $found = array_filter($providers, function ($provider) use ($name) {
98 return $provider['name'] === $name;
99 });
100
101 return !empty($found) ? reset($found)['id'] : '';
102 }
103
104 /**
105 * Get section ID from section name
106 *
107 * @param string $sectionName Section name
108 * @param array $navItems Navigation items
109 * @return string Section ID
110 */
111 public function getSectionId($sectionName, $navItems)
112 {
113 foreach ($navItems as $navItem) {
114 if (in_array($sectionName, $navItem['sections'])) {
115 return $navItem['id'];
116 }
117 }
118
119 return sanitize_title($sectionName);
120 }
121
122 /**
123 * Get navigation items ordered by content appearance
124 *
125 * @param array $sections Parsed sections
126 * @return array Ordered navigation items
127 */
128 public function getOrderedNavigationItems($sections): array
129 {
130 $allNavItems = $this->getAllNavigationItems();
131 $orderedNavItems = [];
132 $sectionOrder = array_keys($sections);
133
134 // First, add items in the order they appear in sections
135 foreach ($sectionOrder as $sectionName) {
136 foreach ($allNavItems as $navItem) {
137 if (in_array($sectionName, $navItem['sections']) && !in_array($navItem['id'], array_column($orderedNavItems, 'id'))) {
138 $orderedNavItems[] = $navItem;
139 break;
140 }
141 }
142 }
143
144 // Add items that weren't found in sections (like logs)
145 foreach ($allNavItems as $navItem) {
146 if (!in_array($navItem['id'], array_column($orderedNavItems, 'id'))) {
147 $orderedNavItems[] = $navItem;
148 }
149 }
150
151 return $orderedNavItems;
152 }
153
154 /**
155 * Get section subtitle by section name
156 *
157 * @param string $sectionName Display name of the section
158 * @return string Section subtitle or empty string
159 */
160 public function getSectionSubtitle($sectionName): string
161 {
162 // Iterate through section IDs to find matching display name
163 foreach (self::SECTIONS as $sectionId) {
164 if (self::getDisplayName($sectionId) === $sectionName) {
165 return self::getSubtitle($sectionId);
166 }
167 }
168
169 return '';
170 }
171
172 /**
173 * Check if section is the storage providers section
174 *
175 * @param string $sectionName Section name to check
176 * @return bool True if storage providers section
177 */
178 public function isStorageProvidersSection($sectionName): bool
179 {
180 return $sectionName === self::SECTION_STORAGE_PROVIDERS;
181 }
182
183 /**
184 * Check if label is a staging sites label
185 *
186 * @param string $label Label to check
187 * @return bool True if staging sites label
188 */
189 public function isStagingSitesLabel(string $label = ''): bool
190 {
191 $labelLower = strtolower(trim($label));
192 return strpos($labelLower, 'wpstg_staging_sites') !== false;
193 }
194
195 /**
196 * Find storage provider by label
197 *
198 * @param string $label Label to search for
199 * @return array|null Provider data or null if not found
200 */
201 private function findStorageProviderByLabel(string $label)
202 {
203 $label = strtolower(trim($label));
204 $providers = $this->getStorageProvidersForSystemInfo();
205
206 foreach ($providers as $provider) {
207 if (strpos($label, strtolower($provider['name'] . ' settings')) !== false) {
208 return $provider;
209 }
210 }
211
212 return null;
213 }
214
215 /**
216 * Check if label is a storage provider label
217 *
218 * @param string $label Label to check
219 * @return bool True if storage provider label
220 */
221 public function isStorageProviderLabel($label): bool
222 {
223 return $this->findStorageProviderByLabel($label) !== null;
224 }
225
226 /**
227 * Get storage provider name from label
228 *
229 * @param string $label Label containing provider name
230 * @return string Provider name or empty string
231 */
232 public function getStorageProviderName($label): string
233 {
234 $provider = $this->findStorageProviderByLabel($label);
235 return $provider !== null ? $provider['name'] : '';
236 }
237
238 /**
239 * Process structured data into display-ready sections
240 *
241 * @param array $structuredData Raw structured data from SystemInfo
242 * @return array Processed sections with categorized items
243 */
244 public function processStructuredData($structuredData): array
245 {
246 $processedSections = [];
247
248 foreach ($structuredData as $sectionName => $sectionItems) {
249 if (empty($sectionItems)) {
250 continue;
251 }
252
253 $infoItems = [];
254 $stagingSites = [];
255 $processedStagingSites = [];
256 $storageProviders = [];
257 $storageProviderItems = [];
258 $isStorageProvidersSection = $this->isStorageProvidersSection($sectionName);
259
260 // Process items
261 foreach ($sectionItems as $item) {
262 $processedItem = $this->processItem($item, $stagingSites, $processedStagingSites, $storageProviderItems, $isStorageProvidersSection);
263 if ($processedItem !== null) {
264 $infoItems[] = $processedItem;
265 }
266 }
267
268 // Handle storage providers section - group collected items
269 if ($isStorageProvidersSection && !empty($storageProviderItems)) {
270 $storageProviders = $this->groupStorageProviders($storageProviderItems);
271 }
272
273 $processedSections[] = [
274 'sectionName' => $sectionName,
275 'infoItems' => array_values($infoItems), // Re-index array
276 'stagingSites' => $stagingSites,
277 'storageProviders' => $storageProviders,
278 ];
279 }
280
281 return $processedSections;
282 }
283
284 /**
285 * Get field configuration for staging site display
286 *
287 * @return array Field definitions with labels and display options
288 */
289 public function getStagingSiteFields(): array
290 {
291 return [
292 'prefix' => [
293 'label' => __('DB Prefix', 'wp-staging'),
294 'is_link' => false,
295 ],
296 'path' => [
297 'label' => __('Path', 'wp-staging'),
298 'is_link' => false,
299 ],
300 'url' => [
301 'label' => __('URL', 'wp-staging'),
302 'is_link' => true,
303 ],
304 'version' => [
305 'label' => __('Version', 'wp-staging'),
306 'is_link' => false,
307 ],
308 'wpVersion' => [
309 'label' => __('WP Version', 'wp-staging'),
310 'is_link' => false,
311 ],
312 ];
313 }
314
315 private function getAllNavigationItems(): array
316 {
317 return [
318 [
319 'id' => 'start-system-info',
320 'title' => __('Server Information', 'wp-staging'),
321 'icon' => 'nav-server',
322 'sections' => ['System Info', 'Server & Operating System', 'Database (MySQL / MariaDB)', 'PHP Environment', 'PHP Limits'],
323 ],
324 [
325 'id' => 'wordpress',
326 'title' => __('WordPress Info', 'wp-staging'),
327 'icon' => 'nav-wordpress',
328 'sections' => ['WordPress', 'WordPress Environment', 'URLs & Paths', 'WordPress Directories', 'Media & Uploads', 'WordPress Memory Settings', 'Filesystem & Permissions', 'Theme & Permalinks', 'WordPress Cron Jobs', 'Site Configuration'],
329 ],
330 [
331 'id' => 'wp-staging',
332 'title' => __('WP Staging Info', 'wp-staging'),
333 'icon' => 'nav-sync',
334 'sections' => ['WP Staging', 'WP Staging – Plugin Information', 'WP Staging – Backup Status & Statistics', 'WP Staging – Performance & Limits', 'WP Staging – Access & Permissions', 'WP Staging – Existing Staging Sites', 'WP Staging - Storage Providers'],
335 ],
336 [
337 'id' => 'plugins',
338 'title' => __('Plugins', 'wp-staging'),
339 'icon' => 'nav-plugins',
340 'sections' => ['Active Plugins', 'Active Plugins on this Site', 'Inactive Plugins', 'Inactive Plugins (Includes this and other sites in the same network)', 'Active Network Plugins (Includes this and other sites in the same network)', 'Must-Use Plugins', 'Drop-Ins', 'Plugins Overview', 'Network & cURL'],
341 ],
342 [
343 'id' => 'php-extensions',
344 'title' => __('Network & cURL', 'wp-staging'),
345 'icon' => 'nav-code',
346 'sections' => ['cURL Environment', 'cURL Features', 'Supported Protocols', 'PHP Network Extensions'],
347 ],
348 [
349 'id' => 'user-browser',
350 'title' => __('Browser Info', 'wp-staging'),
351 'icon' => 'nav-browser',
352 'sections' => ['Client / Browser Information'],
353 ],
354 [
355 'id' => 'logs',
356 'title' => __('Logs', 'wp-staging'),
357 'icon' => 'nav-logs',
358 'sections' => ['WP STAGING Logs', 'PHP debug.log'],
359 ],
360 ];
361 }
362
363
364 private function groupStorageProviders($storageProviderItems): array
365 {
366 $storageProviders = [];
367 $currentProvider = null;
368 $providerId = null;
369 $currentProviderData = [];
370 $processedProviders = []; // Track processed providers to avoid duplicates
371 $hasCurrentProviderData = false;
372
373 // Closure to add the current provider to the collection if valid and not already added
374 $addProvider = function () use (&$storageProviders, &$currentProvider, &$providerId, &$currentProviderData, &$hasCurrentProviderData, &$processedProviders) {
375 if ($currentProvider === null || !$hasCurrentProviderData) {
376 return;
377 }
378
379 $providerKey = strtolower($currentProvider);
380 if (isset($processedProviders[$providerKey])) {
381 return;
382 }
383
384 $storageProviders[] = [
385 'id' => $providerId,
386 'name' => $currentProvider,
387 'settings' => $currentProviderData,
388 ];
389
390 $processedProviders[$providerKey] = true;
391 };
392
393 foreach ($storageProviderItems as $item) {
394 if ($this->isStorageProviderLabel($item['label'])) {
395 $addProvider();
396
397 // Start new provider
398 $currentProvider = $this->getStorageProviderName($item['label']);
399 $providerId = $this->getStorageProviderIdByName($currentProvider);
400 $currentProviderData = [];
401 $hasCurrentProviderData = false;
402 } elseif ($currentProvider !== null) {
403 // Add setting to current provider (skip empty header values)
404 if ($item['value'] !== '') {
405 $hasCurrentProviderData = true;
406 $currentProviderData[] = [
407 'label' => $item['label'],
408 'value' => $item['value'],
409 ];
410 }
411 }
412 }
413
414 // Add the last provider to the collection
415 $addProvider();
416
417 return $storageProviders;
418 }
419
420 /**
421 * Process a single item and categorize it
422 *
423 * @param array $item
424 * @param array $stagingSites
425 * @param array $processedStagingSites
426 * @param array $storageProviderItems
427 * @param bool $isStorageProvidersSection
428 * @return array|null Returns processed item or null if skipped
429 */
430 private function processItem($item, &$stagingSites, &$processedStagingSites, &$storageProviderItems, $isStorageProvidersSection = false)
431 {
432 $label = $item['label'];
433 $value = $item['value'];
434
435 // Handle serialized data
436 if (is_string($value) && $this->isSerializedData($value)) {
437 $unserialized = @unserialize($value);
438 if ($unserialized === false || !is_array($unserialized)) {
439 return ['type' => 'regular', 'label' => $label, 'value' => $value];
440 }
441
442 // Handle staging sites
443 if ($this->isStagingSitesLabel($label)) {
444 $stagingSites = array_merge($stagingSites, $this->processStagingSites($unserialized, $processedStagingSites));
445 return null; // Skip this item from display
446 }
447
448 return ['type' => 'serialized', 'label' => $label, 'value' => $unserialized];
449 }
450
451 // Handle storage provider items - collect them separately in storage providers section
452 // In storage providers section, all items are provider-related, so collect all of them
453 // This prevents duplicates by removing them from regular infoItems display
454 if ($isStorageProvidersSection) {
455 $storageProviderItems[] = $item;
456 return null; // Skip from regular display to avoid duplicates
457 }
458
459 return ['type' => 'regular', 'label' => $label, 'value' => $value];
460 }
461
462 /**
463 * Process serialized staging sites data
464 *
465 * @param array $unserialized
466 * @param array $processedStagingSites
467 * @return array
468 */
469 private function processStagingSites($unserialized, &$processedStagingSites): array
470 {
471 $stagingSites = [];
472 foreach ($unserialized as $siteData) {
473 if (!is_array($siteData)) {
474 continue;
475 }
476
477
478 if (!empty($siteData['directoryName']) && !isset($processedStagingSites[$siteData['directoryName']])) {
479 $stagingSites[] = $siteData;
480 $processedStagingSites[$siteData['directoryName']] = true;
481 }
482 }
483
484 return $stagingSites;
485 }
486
487 /**
488 * Get section metadata by section ID (from SECTIONS['KEY']['id'])
489 *
490 * @param string $sectionId The section ID (e.g., 'server_and_os')
491 * @return array ['name' => string, 'subtitle' => string]
492 */
493 public static function getSectionMetadata(string $sectionId): array
494 {
495 foreach (self::SECTIONS as $section) {
496 if ($section['id'] === $sectionId) {
497 return [
498 'name' => __($section['name'], 'wp-staging'),
499 'subtitle' => __($section['subtitle'], 'wp-staging'),
500 ];
501 }
502 }
503
504 return ['name' => $sectionId, 'subtitle' => ''];
505 }
506
507 /**
508 * Get display name for section definition or section ID
509 *
510 * @param array|string $section Section definition array or section ID string
511 * @return string Display name for the section
512 */
513 public static function getDisplayName($section): string
514 {
515 if (is_array($section)) {
516 return __($section['name'], 'wp-staging');
517 }
518
519 $metadata = self::getSectionMetadata($section);
520 return $metadata['name'];
521 }
522
523 /**
524 * Get subtitle for section definition or section ID
525 *
526 * @param array|string $section Section definition array or section ID string
527 * @return string Subtitle for the section
528 */
529 public static function getSubtitle($section): string
530 {
531 if (is_array($section)) {
532 return __($section['subtitle'], 'wp-staging');
533 }
534
535 $metadata = self::getSectionMetadata($section);
536 return $metadata['subtitle'];
537 }
538 }
539