PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 4.7.0
WP STAGING – WordPress Backup, Restore, Migration & Clone v4.7.0
4.9.3 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 / Backend / Modules / SystemInfoParser.php
wp-staging / Backend / Modules Last commit date
Jobs 4 months ago Views 4 months ago SystemInfo.php 5 months ago SystemInfoParser.php 5 months ago
SystemInfoParser.php
542 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 // Map storage ID to WordPress option name
76 // Pattern: googleDrive -> wpstg_googledrive, amazonS3 -> wpstg_amazons3
77 $identifier = strtolower(str_replace(['-'], [''], $storage['id']));
78 $optionName = 'wpstg_' . $identifier;
79
80 $storages[] = [
81 'id' => $storage['id'],
82 'name' => $storage['name'],
83 'optionName' => $optionName,
84 'title' => $storage['name'] . ' Settings',
85 ];
86 }
87
88 return $storages;
89 }
90
91 /**
92 * Get storage provider ID by provider name
93 *
94 * @param string $name Provider name (e.g., "Google Drive")
95 * @return string Provider ID or empty string if not found
96 */
97 public function getStorageProviderIdByName($name): string
98 {
99 $providers = $this->getStorageProvidersForSystemInfo();
100 $found = array_filter($providers, function ($provider) use ($name) {
101 return $provider['name'] === $name;
102 });
103
104 return !empty($found) ? reset($found)['id'] : '';
105 }
106
107 /**
108 * Get section ID from section name
109 *
110 * @param string $sectionName Section name
111 * @param array $navItems Navigation items
112 * @return string Section ID
113 */
114 public function getSectionId($sectionName, $navItems)
115 {
116 foreach ($navItems as $navItem) {
117 if (in_array($sectionName, $navItem['sections'])) {
118 return $navItem['id'];
119 }
120 }
121
122 return sanitize_title($sectionName);
123 }
124
125 /**
126 * Get navigation items ordered by content appearance
127 *
128 * @param array $sections Parsed sections
129 * @return array Ordered navigation items
130 */
131 public function getOrderedNavigationItems($sections): array
132 {
133 $allNavItems = $this->getAllNavigationItems();
134 $orderedNavItems = [];
135 $sectionOrder = array_keys($sections);
136
137 // First, add items in the order they appear in sections
138 foreach ($sectionOrder as $sectionName) {
139 foreach ($allNavItems as $navItem) {
140 if (in_array($sectionName, $navItem['sections']) && !in_array($navItem['id'], array_column($orderedNavItems, 'id'))) {
141 $orderedNavItems[] = $navItem;
142 break;
143 }
144 }
145 }
146
147 // Add items that weren't found in sections (like logs)
148 foreach ($allNavItems as $navItem) {
149 if (!in_array($navItem['id'], array_column($orderedNavItems, 'id'))) {
150 $orderedNavItems[] = $navItem;
151 }
152 }
153
154 return $orderedNavItems;
155 }
156
157 /**
158 * Get section subtitle by section name
159 *
160 * @param string $sectionName Display name of the section
161 * @return string Section subtitle or empty string
162 */
163 public function getSectionSubtitle($sectionName): string
164 {
165 // Iterate through section IDs to find matching display name
166 foreach (self::SECTIONS as $sectionId) {
167 if (self::getDisplayName($sectionId) === $sectionName) {
168 return self::getSubtitle($sectionId);
169 }
170 }
171
172 return '';
173 }
174
175 /**
176 * Check if section is the storage providers section
177 *
178 * @param string $sectionName Section name to check
179 * @return bool True if storage providers section
180 */
181 public function isStorageProvidersSection($sectionName): bool
182 {
183 return $sectionName === self::SECTION_STORAGE_PROVIDERS;
184 }
185
186 /**
187 * Check if label is a staging sites label
188 *
189 * @param string $label Label to check
190 * @return bool True if staging sites label
191 */
192 public function isStagingSitesLabel(string $label = ''): bool
193 {
194 $labelLower = strtolower(trim($label));
195 return strpos($labelLower, 'wpstg_staging_sites') !== false;
196 }
197
198 /**
199 * Find storage provider by label
200 *
201 * @param string $label Label to search for
202 * @return array|null Provider data or null if not found
203 */
204 private function findStorageProviderByLabel(string $label)
205 {
206 $label = strtolower(trim($label));
207 $providers = $this->getStorageProvidersForSystemInfo();
208
209 foreach ($providers as $provider) {
210 if (strpos($label, strtolower($provider['name'] . ' settings')) !== false) {
211 return $provider;
212 }
213 }
214
215 return null;
216 }
217
218 /**
219 * Check if label is a storage provider label
220 *
221 * @param string $label Label to check
222 * @return bool True if storage provider label
223 */
224 public function isStorageProviderLabel($label): bool
225 {
226 return $this->findStorageProviderByLabel($label) !== null;
227 }
228
229 /**
230 * Get storage provider name from label
231 *
232 * @param string $label Label containing provider name
233 * @return string Provider name or empty string
234 */
235 public function getStorageProviderName($label): string
236 {
237 $provider = $this->findStorageProviderByLabel($label);
238 return $provider !== null ? $provider['name'] : '';
239 }
240
241 /**
242 * Process structured data into display-ready sections
243 *
244 * @param array $structuredData Raw structured data from SystemInfo
245 * @return array Processed sections with categorized items
246 */
247 public function processStructuredData($structuredData): array
248 {
249 $processedSections = [];
250
251 foreach ($structuredData as $sectionName => $sectionItems) {
252 if (empty($sectionItems)) {
253 continue;
254 }
255
256 $infoItems = [];
257 $stagingSites = [];
258 $processedStagingSites = [];
259 $storageProviders = [];
260 $storageProviderItems = [];
261 $isStorageProvidersSection = $this->isStorageProvidersSection($sectionName);
262
263 // Process items
264 foreach ($sectionItems as $item) {
265 $processedItem = $this->processItem($item, $stagingSites, $processedStagingSites, $storageProviderItems, $isStorageProvidersSection);
266 if ($processedItem !== null) {
267 $infoItems[] = $processedItem;
268 }
269 }
270
271 // Handle storage providers section - group collected items
272 if ($isStorageProvidersSection && !empty($storageProviderItems)) {
273 $storageProviders = $this->groupStorageProviders($storageProviderItems);
274 }
275
276 $processedSections[] = [
277 'sectionName' => $sectionName,
278 'infoItems' => array_values($infoItems), // Re-index array
279 'stagingSites' => $stagingSites,
280 'storageProviders' => $storageProviders,
281 ];
282 }
283
284 return $processedSections;
285 }
286
287 /**
288 * Get field configuration for staging site display
289 *
290 * @return array Field definitions with labels and display options
291 */
292 public function getStagingSiteFields(): array
293 {
294 return [
295 'prefix' => [
296 'label' => __('DB Prefix', 'wp-staging'),
297 'is_link' => false,
298 ],
299 'path' => [
300 'label' => __('Path', 'wp-staging'),
301 'is_link' => false,
302 ],
303 'url' => [
304 'label' => __('URL', 'wp-staging'),
305 'is_link' => true,
306 ],
307 'version' => [
308 'label' => __('Version', 'wp-staging'),
309 'is_link' => false,
310 ],
311 'wpVersion' => [
312 'label' => __('WP Version', 'wp-staging'),
313 'is_link' => false,
314 ],
315 ];
316 }
317
318 private function getAllNavigationItems(): array
319 {
320 return [
321 [
322 'id' => 'start-system-info',
323 'title' => __('Server Information', 'wp-staging'),
324 'icon' => 'nav-server',
325 'sections' => ['System Info', 'Server & Operating System', 'Database (MySQL / MariaDB)', 'PHP Environment', 'PHP Limits'],
326 ],
327 [
328 'id' => 'wordpress',
329 'title' => __('WordPress Info', 'wp-staging'),
330 'icon' => 'nav-wordpress',
331 'sections' => ['WordPress', 'WordPress Environment', 'URLs & Paths', 'WordPress Directories', 'Media & Uploads', 'WordPress Memory Settings', 'Filesystem & Permissions', 'Theme & Permalinks', 'WordPress Cron Jobs', 'Site Configuration'],
332 ],
333 [
334 'id' => 'wp-staging',
335 'title' => __('WP Staging Info', 'wp-staging'),
336 'icon' => 'nav-sync',
337 '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'],
338 ],
339 [
340 'id' => 'plugins',
341 'title' => __('Plugins', 'wp-staging'),
342 'icon' => 'nav-plugins',
343 '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'],
344 ],
345 [
346 'id' => 'php-extensions',
347 'title' => __('Network & cURL', 'wp-staging'),
348 'icon' => 'nav-code',
349 'sections' => ['cURL Environment', 'cURL Features', 'Supported Protocols', 'PHP Network Extensions'],
350 ],
351 [
352 'id' => 'user-browser',
353 'title' => __('Browser Info', 'wp-staging'),
354 'icon' => 'nav-browser',
355 'sections' => ['Client / Browser Information'],
356 ],
357 [
358 'id' => 'logs',
359 'title' => __('Logs', 'wp-staging'),
360 'icon' => 'nav-logs',
361 'sections' => ['WP STAGING Logs', 'PHP debug.log'],
362 ],
363 ];
364 }
365
366
367 private function groupStorageProviders($storageProviderItems): array
368 {
369 $storageProviders = [];
370 $currentProvider = null;
371 $providerId = null;
372 $currentProviderData = [];
373 $processedProviders = []; // Track processed providers to avoid duplicates
374 $hasCurrentProviderData = false;
375
376 // Closure to add the current provider to the collection if valid and not already added
377 $addProvider = function () use (&$storageProviders, &$currentProvider, &$providerId, &$currentProviderData, &$hasCurrentProviderData, &$processedProviders) {
378 if ($currentProvider === null || !$hasCurrentProviderData) {
379 return;
380 }
381
382 $providerKey = strtolower($currentProvider);
383 if (isset($processedProviders[$providerKey])) {
384 return;
385 }
386
387 $storageProviders[] = [
388 'id' => $providerId,
389 'name' => $currentProvider,
390 'settings' => $currentProviderData,
391 ];
392
393 $processedProviders[$providerKey] = true;
394 };
395
396 foreach ($storageProviderItems as $item) {
397 if ($this->isStorageProviderLabel($item['label'])) {
398 $addProvider();
399
400 // Start new provider
401 $currentProvider = $this->getStorageProviderName($item['label']);
402 $providerId = $this->getStorageProviderIdByName($currentProvider);
403 $currentProviderData = [];
404 $hasCurrentProviderData = false;
405 } elseif ($currentProvider !== null) {
406 // Add setting to current provider (skip empty header values)
407 if ($item['value'] !== '') {
408 $hasCurrentProviderData = true;
409 $currentProviderData[] = [
410 'label' => $item['label'],
411 'value' => $item['value'],
412 ];
413 }
414 }
415 }
416
417 // Add the last provider to the collection
418 $addProvider();
419
420 return $storageProviders;
421 }
422
423 /**
424 * Process a single item and categorize it
425 *
426 * @param array $item
427 * @param array $stagingSites
428 * @param array $processedStagingSites
429 * @param array $storageProviderItems
430 * @param bool $isStorageProvidersSection
431 * @return array|null Returns processed item or null if skipped
432 */
433 private function processItem($item, &$stagingSites, &$processedStagingSites, &$storageProviderItems, $isStorageProvidersSection = false)
434 {
435 $label = $item['label'];
436 $value = $item['value'];
437
438 // Handle serialized data
439 if (is_string($value) && $this->isSerializedData($value)) {
440 $unserialized = @unserialize($value);
441 if ($unserialized === false || !is_array($unserialized)) {
442 return ['type' => 'regular', 'label' => $label, 'value' => $value];
443 }
444
445 // Handle staging sites
446 if ($this->isStagingSitesLabel($label)) {
447 $stagingSites = array_merge($stagingSites, $this->processStagingSites($unserialized, $processedStagingSites));
448 return null; // Skip this item from display
449 }
450
451 return ['type' => 'serialized', 'label' => $label, 'value' => $unserialized];
452 }
453
454 // Handle storage provider items - collect them separately in storage providers section
455 // In storage providers section, all items are provider-related, so collect all of them
456 // This prevents duplicates by removing them from regular infoItems display
457 if ($isStorageProvidersSection) {
458 $storageProviderItems[] = $item;
459 return null; // Skip from regular display to avoid duplicates
460 }
461
462 return ['type' => 'regular', 'label' => $label, 'value' => $value];
463 }
464
465 /**
466 * Process serialized staging sites data
467 *
468 * @param array $unserialized
469 * @param array $processedStagingSites
470 * @return array
471 */
472 private function processStagingSites($unserialized, &$processedStagingSites): array
473 {
474 $stagingSites = [];
475 foreach ($unserialized as $siteData) {
476 if (!is_array($siteData)) {
477 continue;
478 }
479
480
481 if (!empty($siteData['directoryName']) && !isset($processedStagingSites[$siteData['directoryName']])) {
482 $stagingSites[] = $siteData;
483 $processedStagingSites[$siteData['directoryName']] = true;
484 }
485 }
486
487 return $stagingSites;
488 }
489
490 /**
491 * Get section metadata by section ID (from SECTIONS['KEY']['id'])
492 *
493 * @param string $sectionId The section ID (e.g., 'server_and_os')
494 * @return array ['name' => string, 'subtitle' => string]
495 */
496 public static function getSectionMetadata(string $sectionId): array
497 {
498 foreach (self::SECTIONS as $section) {
499 if ($section['id'] === $sectionId) {
500 return [
501 'name' => __($section['name'], 'wp-staging'),
502 'subtitle' => __($section['subtitle'], 'wp-staging'),
503 ];
504 }
505 }
506
507 return ['name' => $sectionId, 'subtitle' => ''];
508 }
509
510 /**
511 * Get display name for section definition or section ID
512 *
513 * @param array|string $section Section definition array or section ID string
514 * @return string Display name for the section
515 */
516 public static function getDisplayName($section): string
517 {
518 if (is_array($section)) {
519 return __($section['name'], 'wp-staging');
520 }
521
522 $metadata = self::getSectionMetadata($section);
523 return $metadata['name'];
524 }
525
526 /**
527 * Get subtitle for section definition or section ID
528 *
529 * @param array|string $section Section definition array or section ID string
530 * @return string Subtitle for the section
531 */
532 public static function getSubtitle($section): string
533 {
534 if (is_array($section)) {
535 return __($section['subtitle'], 'wp-staging');
536 }
537
538 $metadata = self::getSectionMetadata($section);
539 return $metadata['subtitle'];
540 }
541 }
542