WP Staging – DB & File Duplicator & Migration - Version 2.6.9

Version Description

  • Fix: Can not login to staging site under certain circumstances
  • Fix: Use user selected language setting instead global site based one
  • Fix: Fatal Error: curl_version() not defined in SystemInfo.php
  • New: Refactored structure for easier maintenance
  • New: Core support for WP Staging snapshots
  • New: Implementing of UnitTests
Download this release

Release Info

Developer ReneHermi
Plugin Icon 128x128 WP Staging – DB & File Duplicator & Migration
Version 2.6.9
Comparing to
See all releases

Code changes from version 2.6.8 to 2.6.9

Files changed (100) hide show
  1. Backend/Modules/Jobs/Cloning.php +150 -131
  2. Backend/Modules/Jobs/Delete.php +0 -12
  3. Backend/Modules/Jobs/Files.php +134 -161
  4. Backend/Modules/Jobs/Multisite/Directories.php +176 -164
  5. Backend/Modules/Jobs/Multisite/Files.php +12 -26
  6. Backend/Modules/Jobs/Multisite/SearchReplace.php +13 -7
  7. Backend/Modules/Jobs/Multisite/SearchReplaceExternal.php +13 -8
  8. Backend/Modules/Jobs/SearchReplace.php +14 -8
  9. Backend/Modules/Jobs/SearchReplaceExternal.php +13 -9
  10. Backend/Modules/Jobs/Updating.php +66 -60
  11. Backend/Modules/SystemInfo.php +225 -195
  12. Backend/Notices/Notices.php +2 -2
  13. Backend/Optimizer/wp-staging-optimizer.php +0 -5
  14. Backend/public/css/wpstg-admin.css +142 -22
  15. Backend/public/js/wpstg-admin.js +962 -722
  16. Backend/views/clone/ajax/start.php +16 -20
  17. Backend/views/clone/ajax/update.php +1 -1
  18. Backend/views/clone/single-site/index.php +0 -3
  19. Backend/views/notices/beta.php +1 -0
  20. Backend/views/notices/wp-version-compatible-message.php +1 -1
  21. Core/Utils/Logger.php +111 -37
  22. Core/Utils/functions.php +1 -47
  23. Core/WPStaging.php +33 -34
  24. Frontend/Frontend.php +65 -71
  25. Manager/Database/TableDto.php +156 -0
  26. Manager/Database/TableManager.php +48 -0
  27. Manager/FileSystem/FileManager.php +84 -0
  28. Manager/SnapshotManager.php +29 -0
  29. Plugin.php +33 -0
  30. Service/AbstractPlugin.php +175 -0
  31. Service/Adapter/Database.php +100 -0
  32. Service/Adapter/Database/AbstractDatabase.php +8 -0
  33. Service/Adapter/Database/DatabaseException.php +14 -0
  34. Service/Adapter/Database/DatabaseQueryDto.php +90 -0
  35. Service/Adapter/Database/InterfaceDatabase.php +59 -0
  36. Service/Adapter/Database/WpDbAdapter.php +102 -0
  37. Service/Adapter/DateTimeAdapter.php +34 -0
  38. Service/Adapter/Directory.php +74 -0
  39. Service/Adapter/Dto/HookDto.php +103 -0
  40. Service/Adapter/Dto/JedDto.php +120 -0
  41. Service/Adapter/Hooks.php +47 -0
  42. Service/Adapter/Translation.php +85 -0
  43. Service/Collection/Collection.php +59 -0
  44. Service/Collection/OptionCollection.php +85 -0
  45. Service/Command/CommandInterface.php +11 -0
  46. Service/Command/HandlerInterface.php +10 -0
  47. Service/Component/AbstractComponent.php +66 -0
  48. Service/Component/AbstractTemplateComponent.php +42 -0
  49. Service/Component/AjaxTrait.php +34 -0
  50. Service/Component/ComponentInterface.php +10 -0
  51. Service/Component/RenderableComponentInterface.php +10 -0
  52. Service/Container/AbstractContainerAware.php +34 -0
  53. Service/Container/Container.php +350 -0
  54. Service/Container/ContainerInterface.php +28 -0
  55. Service/Container/InvalidConstructorParamException.php +16 -0
  56. Service/Dto/AbstractDto.php +23 -0
  57. Service/Entity/AbstractEntity.php +42 -0
  58. Service/Entity/EntityException.php +12 -0
  59. Service/Entity/IdentifyableEntityInterface.php +13 -0
  60. Service/Interfaces/ArrayableInterface.php +14 -0
  61. Service/Interfaces/HydrateableInterface.php +16 -0
  62. Service/InvalidPluginException.php +16 -0
  63. Service/PluginFactory.php +43 -0
  64. Service/PluginFactoryInterface.php +13 -0
  65. Service/PluginInterface.php +30 -0
  66. Service/TemplateEngine/TemplateEngine.php +146 -0
  67. Service/TemplateEngine/TemplateEngineException.php +11 -0
  68. Service/TemplateEngine/TemplateEngineInterface.php +11 -0
  69. Service/Traits/ArrayableTrait.php +41 -0
  70. Service/Traits/HydrateTrait.php +65 -0
  71. Service/Utils/FileSize.php +15 -0
  72. Service/Utils/FileSystem.php +64 -0
  73. Service/Utils/WpDefaultDirectories.php +16 -0
  74. config.php +59 -0
  75. install.php +4 -3
  76. readme.txt +15 -13
  77. uninstall.php +3 -0
  78. vendor/autoload.php +7 -0
  79. vendor/composer/ClassLoader.php +445 -0
  80. vendor/composer/LICENSE +56 -0
  81. vendor/composer/autoload_classmap.php +9 -0
  82. vendor/composer/autoload_namespaces.php +9 -0
  83. vendor/composer/autoload_psr4.php +12 -0
  84. vendor/composer/autoload_real.php +52 -0
  85. vendor/composer/autoload_static.php +43 -0
  86. vendor/composer/installed.json +51 -0
  87. vendor/psr/log/LICENSE +19 -0
  88. vendor/psr/log/Psr/Log/AbstractLogger.php +128 -0
  89. vendor/psr/log/Psr/Log/InvalidArgumentException.php +7 -0
  90. vendor/psr/log/Psr/Log/LogLevel.php +18 -0
  91. vendor/psr/log/Psr/Log/LoggerAwareInterface.php +18 -0
  92. vendor/psr/log/Psr/Log/LoggerAwareTrait.php +26 -0
  93. vendor/psr/log/Psr/Log/LoggerInterface.php +125 -0
  94. vendor/psr/log/Psr/Log/LoggerTrait.php +142 -0
  95. vendor/psr/log/Psr/Log/NullLogger.php +30 -0
  96. vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php +146 -0
  97. vendor/psr/log/Psr/Log/Test/TestLogger.php +147 -0
  98. vendor/psr/log/README.md +58 -0
  99. vendor/psr/log/composer.json +26 -0
  100. wp-staging.php +6 -3
Backend/Modules/Jobs/Cloning.php CHANGED
@@ -19,46 +19,49 @@ use WPStaging\Utils\Helper;
19
  * Class Cloning
20
  * @package WPStaging\Backend\Modules\Jobs
21
  */
22
- class Cloning extends Job {
 
23
 
24
  /**
25
  * Initialize is called in \Job
26
  */
27
- public function initialize() {
28
- $this->db = WPStaging::getInstance()->get( "wpdb" );
 
29
  }
30
 
31
  /**
32
  * Save Chosen Cloning Settings
33
  * @return bool
34
  */
35
- public function save() {
36
- if( !isset( $_POST ) || !isset( $_POST["cloneID"] ) ) {
 
37
  return false;
38
  }
39
 
40
  // Delete files to copy listing
41
- $this->cache->delete( "files_to_copy" );
42
 
43
  // Generate Options
44
  // Clone
45
  //$this->options->clone = $_POST["cloneID"];
46
- $this->options->clone = preg_replace( "#\W+#", '-', strtolower( $_POST["cloneID"] ) );
47
- $this->options->cloneDirectoryName = preg_replace( "#\W+#", '-', strtolower( $this->options->clone ) );
48
- $this->options->cloneNumber = 1;
49
- $this->options->prefix = $this->setStagingPrefix();
50
- $this->options->includedDirectories = array();
51
- $this->options->excludedDirectories = array();
52
- $this->options->extraDirectories = array();
53
- $this->options->excludedFiles = array(
54
  '.htaccess',
55
  '.DS_Store',
56
- '.git',
57
- '.svn',
58
- '.tmp',
59
  'desktop.ini',
60
  '.gitignore',
61
- '.log',
62
  'web.config', // Important: Windows IIS configuration file. Must not be in the staging site!
63
  '.wp-staging' // Determines if a site is a staging site
64
  );
@@ -67,36 +70,36 @@ class Cloning extends Job {
67
  'wp-content' . DIRECTORY_SEPARATOR . 'object-cache.php',
68
  'wp-content' . DIRECTORY_SEPARATOR . 'advanced-cache.php'
69
  );
70
- $this->options->currentStep = 0;
71
 
72
  // Job
73
  $this->options->job = new \stdClass();
74
 
75
  // Check if clone data already exists and use that one
76
- if( isset( $this->options->existingClones[$this->options->clone] ) ) {
77
 
78
  $this->options->cloneNumber = $this->options->existingClones[$this->options->clone]->number;
79
 
80
- $this->options->prefix = isset( $this->options->existingClones[$this->options->clone]->prefix ) ?
81
- $this->options->existingClones[$this->options->clone]->prefix :
82
- $this->setStagingPrefix();
83
  }
84
  // Clone does not exist but there are other clones in db
85
  // Get data and increment it
86
- elseif( !empty( $this->options->existingClones ) ) {
87
- $this->options->cloneNumber = count( $this->options->existingClones ) + 1;
88
  }
89
 
90
  // Included Tables
91
- if( isset( $_POST["includedTables"] ) && is_array( $_POST["includedTables"] ) ) {
92
  $this->options->tables = $_POST["includedTables"];
93
  } else {
94
  $this->options->tables = array();
95
  }
96
 
97
  // Excluded Directories
98
- if( isset( $_POST["excludedDirectories"] ) && is_array( $_POST["excludedDirectories"] ) ) {
99
- $this->options->excludedDirectories = wpstg_urldecode( $_POST["excludedDirectories"] );
100
  }
101
 
102
  // Excluded Directories TOTAL
@@ -109,59 +112,59 @@ class Cloning extends Job {
109
  \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wp-spamshield',
110
  );
111
 
112
- $this->options->excludedDirectories = array_merge( $excludedDirectories, wpstg_urldecode( $this->options->excludedDirectories ) );
113
 
114
- array_unshift( $this->options->directoriesToCopy, \WPStaging\WPStaging::getWPpath() );
115
 
116
  // Included Directories
117
- if( isset( $_POST["includedDirectories"] ) && is_array( $_POST["includedDirectories"] ) ) {
118
- $this->options->includedDirectories = wpstg_urldecode( $_POST["includedDirectories"] );
119
  }
120
 
121
  // Extra Directories
122
- if( isset( $_POST["extraDirectories"] ) && !empty( $_POST["extraDirectories"] ) ) {
123
- $this->options->extraDirectories = wpstg_urldecode( $_POST["extraDirectories"] );
124
  }
125
 
126
  // Directories to Copy
127
  $this->options->directoriesToCopy = array_merge(
128
- $this->options->includedDirectories, $this->options->extraDirectories
129
  );
130
 
131
 
132
  $this->options->databaseServer = 'localhost';
133
- if( isset( $_POST["databaseServer"] ) && !empty( $_POST["databaseServer"] ) ) {
134
  $this->options->databaseServer = $_POST["databaseServer"];
135
  }
136
  $this->options->databaseUser = '';
137
- if( isset( $_POST["databaseUser"] ) && !empty( $_POST["databaseUser"] ) ) {
138
  $this->options->databaseUser = $_POST["databaseUser"];
139
  }
140
  $this->options->databasePassword = '';
141
- if( isset( $_POST["databasePassword"] ) && !empty( $_POST["databasePassword"] ) ) {
142
  $this->options->databasePassword = $_POST["databasePassword"];
143
  }
144
  $this->options->databaseDatabase = '';
145
- if( isset( $_POST["databaseDatabase"] ) && !empty( $_POST["databaseDatabase"] ) ) {
146
  $this->options->databaseDatabase = $_POST["databaseDatabase"];
147
  }
148
  $this->options->databasePrefix = '';
149
- if( isset( $_POST["databasePrefix"] ) && !empty( $_POST["databasePrefix"] ) ) {
150
- $this->options->databasePrefix = strtolower( $this->sanitizePrefix( $_POST["databasePrefix"] ) );
151
  }
152
  $this->options->cloneDir = '';
153
- if( isset( $_POST["cloneDir"] ) && !empty( $_POST["cloneDir"] ) ) {
154
- $this->options->cloneDir = trailingslashit( wpstg_urldecode( $_POST["cloneDir"] ) );
155
  }
156
  $this->options->cloneHostname = '';
157
- if( isset( $_POST["cloneHostname"] ) && !empty( $_POST["cloneHostname"] ) ) {
158
- $this->options->cloneHostname = trim( $_POST["cloneHostname"] );
159
  }
160
 
161
  $this->options->destinationHostname = $this->getDestinationHostname();
162
- $this->options->destinationDir = $this->getDestinationDir();
163
 
164
- $helper = new Helper();
165
  $this->options->homeHostname = $helper->get_home_url_without_scheme();
166
 
167
  // Process lock state
@@ -178,28 +181,29 @@ class Cloning extends Job {
178
  * Save clone data initially
179
  * @return boolean
180
  */
181
- private function saveClone() {
 
182
  // Save new clone data
183
- $this->log( "Cloning: {$this->options->clone}'s clone job's data is not in database, generating data" );
184
 
185
  $this->options->existingClones[$this->options->clone] = array(
186
- "directoryName" => $this->options->cloneDirectoryName,
187
- "path" => trailingslashit( $this->options->destinationDir ),
188
- "url" => $this->getDestinationUrl(),
189
- "number" => $this->options->cloneNumber,
190
- "version" => WPStaging::getVersion(),
191
- "status" => "unfinished or broken",
192
- "prefix" => $this->options->prefix,
193
- "datetime" => time(),
194
- "databaseUser" => $this->options->databaseUser,
195
  "databasePassword" => $this->options->databasePassword,
196
  "databaseDatabase" => $this->options->databaseDatabase,
197
- "databaseServer" => $this->options->databaseServer,
198
- "databasePrefix" => $this->options->databasePrefix
199
  );
200
 
201
- if( false === update_option( "wpstg_existing_clones_beta", $this->options->existingClones ) ) {
202
- $this->log( "Cloning: Failed to save {$this->options->clone}'s clone job data to database'" );
203
  return false;
204
  }
205
 
@@ -210,25 +214,27 @@ class Cloning extends Job {
210
  * Get destination Hostname depending on wheather WP has been installed in sub dir or not
211
  * @return type
212
  */
213
- private function getDestinationUrl() {
 
214
 
215
- if( !empty( $this->options->cloneHostname ) ) {
216
  return $this->options->cloneHostname;
217
  }
218
 
219
- return trailingslashit( get_site_url() ) . $this->options->cloneDirectoryName;
220
  }
221
 
222
  /**
223
  * Return target hostname
224
  * @return string
225
  */
226
- private function getDestinationHostname() {
227
- if( empty( $this->options->cloneHostname ) ) {
 
228
  $helper = new Helper();
229
  return $helper->get_home_url_without_scheme();
230
  }
231
- return $this->getHostnameWithoutScheme( $this->options->cloneHostname );
232
  }
233
 
234
  /**
@@ -236,39 +242,42 @@ class Cloning extends Job {
236
  * @param string $str
237
  * @return string
238
  */
239
- private function getHostnameWithoutScheme( $string ) {
240
- return preg_replace( '#^https?://#', '', rtrim( $string, '/' ) );
 
241
  }
242
 
243
  /**
244
  * Get Destination Directory including staging subdirectory
245
  * @return type
246
  */
247
- private function getDestinationDir() {
 
248
  // Throw fatal error
249
- if( !empty( $this->options->cloneDir ) & (trailingslashit( $this->options->cloneDir ) === ( string ) trailingslashit( \WPStaging\WPStaging::getWPpath() )) ) {
250
- $this->returnException('Error: Target Directory must be different from the root of the production website.' );
251
  die();
252
  }
253
 
254
  // No custom clone dir so clone path will be in subfolder of root
255
- if( empty( $this->options->cloneDir ) ) {
256
- $this->options->cloneDir = trailingslashit( \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName );
257
  return $this->options->cloneDir;
258
  //return trailingslashit( \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName );
259
  }
260
- return trailingslashit( $this->options->cloneDir );
261
  }
262
 
263
  /**
264
  * Make sure prefix contains appending underscore
265
- *
266
  * @param string $string
267
  * @return string
268
  */
269
- private function sanitizePrefix( $string ) {
270
- $lastCharacter = substr( $string, -1 );
271
- if( $lastCharacter === '_' ) {
 
272
  return $string;
273
  }
274
  return $string . '_';
@@ -277,38 +286,40 @@ class Cloning extends Job {
277
  /**
278
  * Create a new staging prefix which does not already exists in database
279
  */
280
- private function setStagingPrefix() {
 
281
 
282
  // Get & find a new prefix that does not already exist in database.
283
  // Loop through up to 1000 different possible prefixes should be enough here;)
284
- for ( $i = 0; $i <= 10000; $i++ ) {
285
- $this->options->prefix = isset( $this->options->existingClones ) ?
286
- 'wpstg' . (count( $this->options->existingClones ) + $i) . '_' :
287
- 'wpstg' . $i . '_';
288
 
289
- $sql = "SHOW TABLE STATUS LIKE '{$this->options->prefix}%'";
290
- $tables = $this->db->get_results( $sql );
291
 
292
  // Prefix does not exist. We can use it
293
- if( !$tables ) {
294
- return strtolower( $this->options->prefix );
295
  }
296
  }
297
- $this->returnException( "Fatal Error: Can not create staging prefix. '{$this->options->prefix}' already exists! Stopping for security reasons. Contact support@wp-staging.com" );
298
- wp_die( "Fatal Error: Can not create staging prefix. Prefix '{$this->options->prefix}' already exists! Stopping for security reasons. Contact support@wp-staging.com" );
299
  }
300
 
301
  /**
302
- * Check if potential new prefix of staging site would be identical with live site.
303
  * @return boolean
304
  */
305
- private function isPrefixIdentical() {
306
- $db = WPStaging::getInstance()->get( "wpdb" );
 
307
 
308
- $livePrefix = $db->prefix;
309
  $stagingPrefix = $this->options->prefix;
310
 
311
- if( $livePrefix == $stagingPrefix ) {
312
  return true;
313
  }
314
  return false;
@@ -317,17 +328,18 @@ class Cloning extends Job {
317
  /**
318
  * Start the cloning job
319
  */
320
- public function start() {
321
- if( !property_exists( $this->options, 'currentJob' ) || null === $this->options->currentJob ) {
322
- $this->log( "Cloning job finished" );
 
323
  return true;
324
  }
325
 
326
- $methodName = "job" . ucwords( $this->options->currentJob );
327
 
328
- if( !method_exists( $this, $methodName ) ) {
329
- $this->log( "Can't execute job; Job's method {$methodName} is not found" );
330
- throw new JobNotFoundException( $methodName );
331
  }
332
 
333
  // Call the job
@@ -340,15 +352,16 @@ class Cloning extends Job {
340
  * @param string $nextJob
341
  * @return object
342
  */
343
- private function handleJobResponse( $response, $nextJob ) {
 
344
  // Job is not done
345
- if( true !== $response->status ) {
346
  return $response;
347
  }
348
 
349
- $this->options->currentJob = $nextJob;
350
  $this->options->currentStep = 0;
351
- $this->options->totalSteps = 0;
352
 
353
  // Save options
354
  $this->saveOptions();
@@ -360,13 +373,14 @@ class Cloning extends Job {
360
  * Clone Database
361
  * @return object
362
  */
363
- public function jobDatabase() {
 
364
 
365
  // Could be written more elegant
366
  // but for xdebug purposes and breakpoints its cleaner to have separate if blocks
367
- if( defined('WPSTGPRO_VERSION') && is_multisite() ) {
368
  // Is Multisite
369
- if( empty( $this->options->databaseUser ) && empty( $this->options->databasePassword ) ) {
370
  $database = new muDatabase();
371
  } else {
372
  $database = new muDatabaseExternal();
@@ -374,60 +388,63 @@ class Cloning extends Job {
374
  } else {
375
 
376
  // No Multisite
377
- if( empty( $this->options->databaseUser ) && empty( $this->options->databasePassword ) ) {
378
  $database = new Database();
379
  } else {
380
  $database = new DatabaseExternal();
381
  }
382
  }
383
- return $this->handleJobResponse( $database->start(), "SearchReplace" );
384
  }
385
 
386
  /**
387
  * Search & Replace
388
  * @return object
389
  */
390
- public function jobSearchReplace() {
391
- if( defined('WPSTGPRO_VERSION') && is_multisite() ) {
392
- if( empty( $this->options->databaseUser ) && empty( $this->options->databasePassword ) ) {
 
393
  $searchReplace = new muSearchReplace();
394
  } else {
395
  $searchReplace = new muSearchReplaceExternal();
396
  }
397
  } else {
398
- if( empty( $this->options->databaseUser ) && empty( $this->options->databasePassword ) ) {
399
  $searchReplace = new SearchReplace();
400
  } else {
401
  $searchReplace = new SearchReplaceExternal();
402
  }
403
  }
404
- return $this->handleJobResponse( $searchReplace->start(), "directories" );
405
  }
406
 
407
  /**
408
  * Get All Files From Selected Directories Recursively Into a File
409
  * @return object
410
  */
411
- public function jobDirectories() {
412
- if( defined('WPSTGPRO_VERSION') && is_multisite() ) {
 
413
  $directories = new muDirectories();
414
  } else {
415
  $directories = new Directories();
416
  }
417
- return $this->handleJobResponse( $directories->start(), "files" );
418
  }
419
 
420
  /**
421
  * Copy Files
422
  * @return object
423
  */
424
- public function jobFiles() {
425
- if( defined('WPSTGPRO_VERSION') && is_multisite() ) {
 
426
  $files = new muFiles();
427
  } else {
428
  $files = new Files();
429
  }
430
- return $this->handleJobResponse( $files->start(), "data" );
431
  }
432
 
433
 
@@ -435,35 +452,37 @@ class Cloning extends Job {
435
  * Replace Data
436
  * @return object
437
  */
438
- public function jobData() {
439
- if( defined('WPSTGPRO_VERSION') && is_multisite() ) {
440
- if( empty( $this->options->databaseUser ) && empty( $this->options->databasePassword ) ) {
 
441
  $data = new muData();
442
  } else {
443
  $data = new muDataExternal();
444
  }
445
  } else {
446
 
447
- if( empty( $this->options->databaseUser ) && empty( $this->options->databasePassword ) ) {
448
  $data = new Data();
449
  } else {
450
  $data = new DataExternal();
451
  }
452
  }
453
- return $this->handleJobResponse( $data->start(), "finish" );
454
  }
455
 
456
  /**
457
  * Save Clone Data
458
  * @return object
459
  */
460
- public function jobFinish() {
461
- if( defined('WPSTGPRO_VERSION') && is_multisite() ) {
 
462
  $finish = new muFinish();
463
  } else {
464
  $finish = new Finish();
465
  }
466
- return $this->handleJobResponse( $finish->start(), '' );
467
  }
468
 
469
  }
19
  * Class Cloning
20
  * @package WPStaging\Backend\Modules\Jobs
21
  */
22
+ class Cloning extends Job
23
+ {
24
 
25
  /**
26
  * Initialize is called in \Job
27
  */
28
+ public function initialize()
29
+ {
30
+ $this->db = WPStaging::getInstance()->get("wpdb");
31
  }
32
 
33
  /**
34
  * Save Chosen Cloning Settings
35
  * @return bool
36
  */
37
+ public function save()
38
+ {
39
+ if (!isset($_POST) || !isset($_POST["cloneID"])) {
40
  return false;
41
  }
42
 
43
  // Delete files to copy listing
44
+ $this->cache->delete("files_to_copy");
45
 
46
  // Generate Options
47
  // Clone
48
  //$this->options->clone = $_POST["cloneID"];
49
+ $this->options->clone = preg_replace("#\W+#", '-', strtolower($_POST["cloneID"]));
50
+ $this->options->cloneDirectoryName = preg_replace("#\W+#", '-', strtolower($this->options->clone));
51
+ $this->options->cloneNumber = 1;
52
+ $this->options->prefix = $this->setStagingPrefix();
53
+ $this->options->includedDirectories = array();
54
+ $this->options->excludedDirectories = array();
55
+ $this->options->extraDirectories = array();
56
+ $this->options->excludedFiles = array(
57
  '.htaccess',
58
  '.DS_Store',
59
+ '*.git',
60
+ '*.svn',
61
+ '*.tmp',
62
  'desktop.ini',
63
  '.gitignore',
64
+ '*.log',
65
  'web.config', // Important: Windows IIS configuration file. Must not be in the staging site!
66
  '.wp-staging' // Determines if a site is a staging site
67
  );
70
  'wp-content' . DIRECTORY_SEPARATOR . 'object-cache.php',
71
  'wp-content' . DIRECTORY_SEPARATOR . 'advanced-cache.php'
72
  );
73
+ $this->options->currentStep = 0;
74
 
75
  // Job
76
  $this->options->job = new \stdClass();
77
 
78
  // Check if clone data already exists and use that one
79
+ if (isset($this->options->existingClones[$this->options->clone])) {
80
 
81
  $this->options->cloneNumber = $this->options->existingClones[$this->options->clone]->number;
82
 
83
+ $this->options->prefix = isset($this->options->existingClones[$this->options->clone]->prefix) ?
84
+ $this->options->existingClones[$this->options->clone]->prefix :
85
+ $this->setStagingPrefix();
86
  }
87
  // Clone does not exist but there are other clones in db
88
  // Get data and increment it
89
+ elseif (!empty($this->options->existingClones)) {
90
+ $this->options->cloneNumber = count($this->options->existingClones) + 1;
91
  }
92
 
93
  // Included Tables
94
+ if (isset($_POST["includedTables"]) && is_array($_POST["includedTables"])) {
95
  $this->options->tables = $_POST["includedTables"];
96
  } else {
97
  $this->options->tables = array();
98
  }
99
 
100
  // Excluded Directories
101
+ if (isset($_POST["excludedDirectories"]) && is_array($_POST["excludedDirectories"])) {
102
+ $this->options->excludedDirectories = wpstg_urldecode($_POST["excludedDirectories"]);
103
  }
104
 
105
  // Excluded Directories TOTAL
112
  \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wp-spamshield',
113
  );
114
 
115
+ $this->options->excludedDirectories = array_merge($excludedDirectories, wpstg_urldecode($this->options->excludedDirectories));
116
 
117
+ array_unshift($this->options->directoriesToCopy, \WPStaging\WPStaging::getWPpath());
118
 
119
  // Included Directories
120
+ if (isset($_POST["includedDirectories"]) && is_array($_POST["includedDirectories"])) {
121
+ $this->options->includedDirectories = wpstg_urldecode($_POST["includedDirectories"]);
122
  }
123
 
124
  // Extra Directories
125
+ if (isset($_POST["extraDirectories"]) && !empty($_POST["extraDirectories"])) {
126
+ $this->options->extraDirectories = wpstg_urldecode($_POST["extraDirectories"]);
127
  }
128
 
129
  // Directories to Copy
130
  $this->options->directoriesToCopy = array_merge(
131
+ $this->options->includedDirectories, $this->options->extraDirectories
132
  );
133
 
134
 
135
  $this->options->databaseServer = 'localhost';
136
+ if (isset($_POST["databaseServer"]) && !empty($_POST["databaseServer"])) {
137
  $this->options->databaseServer = $_POST["databaseServer"];
138
  }
139
  $this->options->databaseUser = '';
140
+ if (isset($_POST["databaseUser"]) && !empty($_POST["databaseUser"])) {
141
  $this->options->databaseUser = $_POST["databaseUser"];
142
  }
143
  $this->options->databasePassword = '';
144
+ if (isset($_POST["databasePassword"]) && !empty($_POST["databasePassword"])) {
145
  $this->options->databasePassword = $_POST["databasePassword"];
146
  }
147
  $this->options->databaseDatabase = '';
148
+ if (isset($_POST["databaseDatabase"]) && !empty($_POST["databaseDatabase"])) {
149
  $this->options->databaseDatabase = $_POST["databaseDatabase"];
150
  }
151
  $this->options->databasePrefix = '';
152
+ if (isset($_POST["databasePrefix"]) && !empty($_POST["databasePrefix"])) {
153
+ $this->options->databasePrefix = strtolower($this->sanitizePrefix($_POST["databasePrefix"]));
154
  }
155
  $this->options->cloneDir = '';
156
+ if (isset($_POST["cloneDir"]) && !empty($_POST["cloneDir"])) {
157
+ $this->options->cloneDir = trailingslashit(wpstg_urldecode($_POST["cloneDir"]));
158
  }
159
  $this->options->cloneHostname = '';
160
+ if (isset($_POST["cloneHostname"]) && !empty($_POST["cloneHostname"])) {
161
+ $this->options->cloneHostname = trim($_POST["cloneHostname"]);
162
  }
163
 
164
  $this->options->destinationHostname = $this->getDestinationHostname();
165
+ $this->options->destinationDir = $this->getDestinationDir();
166
 
167
+ $helper = new Helper();
168
  $this->options->homeHostname = $helper->get_home_url_without_scheme();
169
 
170
  // Process lock state
181
  * Save clone data initially
182
  * @return boolean
183
  */
184
+ private function saveClone()
185
+ {
186
  // Save new clone data
187
+ $this->log("Cloning: {$this->options->clone}'s clone job's data is not in database, generating data");
188
 
189
  $this->options->existingClones[$this->options->clone] = array(
190
+ "directoryName" => $this->options->cloneDirectoryName,
191
+ "path" => trailingslashit($this->options->destinationDir),
192
+ "url" => $this->getDestinationUrl(),
193
+ "number" => $this->options->cloneNumber,
194
+ "version" => WPStaging::getVersion(),
195
+ "status" => "unfinished or broken",
196
+ "prefix" => $this->options->prefix,
197
+ "datetime" => time(),
198
+ "databaseUser" => $this->options->databaseUser,
199
  "databasePassword" => $this->options->databasePassword,
200
  "databaseDatabase" => $this->options->databaseDatabase,
201
+ "databaseServer" => $this->options->databaseServer,
202
+ "databasePrefix" => $this->options->databasePrefix
203
  );
204
 
205
+ if (false === update_option("wpstg_existing_clones_beta", $this->options->existingClones)) {
206
+ $this->log("Cloning: Failed to save {$this->options->clone}'s clone job data to database'");
207
  return false;
208
  }
209
 
214
  * Get destination Hostname depending on wheather WP has been installed in sub dir or not
215
  * @return type
216
  */
217
+ private function getDestinationUrl()
218
+ {
219
 
220
+ if (!empty($this->options->cloneHostname)) {
221
  return $this->options->cloneHostname;
222
  }
223
 
224
+ return trailingslashit(get_site_url()) . $this->options->cloneDirectoryName;
225
  }
226
 
227
  /**
228
  * Return target hostname
229
  * @return string
230
  */
231
+ private function getDestinationHostname()
232
+ {
233
+ if (empty($this->options->cloneHostname)) {
234
  $helper = new Helper();
235
  return $helper->get_home_url_without_scheme();
236
  }
237
+ return $this->getHostnameWithoutScheme($this->options->cloneHostname);
238
  }
239
 
240
  /**
242
  * @param string $str
243
  * @return string
244
  */
245
+ private function getHostnameWithoutScheme($string)
246
+ {
247
+ return preg_replace('#^https?://#', '', rtrim($string, '/'));
248
  }
249
 
250
  /**
251
  * Get Destination Directory including staging subdirectory
252
  * @return type
253
  */
254
+ private function getDestinationDir()
255
+ {
256
  // Throw fatal error
257
+ if (!empty($this->options->cloneDir) & (trailingslashit($this->options->cloneDir) === ( string )trailingslashit(\WPStaging\WPStaging::getWPpath()))) {
258
+ $this->returnException('Error: Target Directory must be different from the root of the production website.');
259
  die();
260
  }
261
 
262
  // No custom clone dir so clone path will be in subfolder of root
263
+ if (empty($this->options->cloneDir)) {
264
+ $this->options->cloneDir = trailingslashit(\WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName);
265
  return $this->options->cloneDir;
266
  //return trailingslashit( \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName );
267
  }
268
+ return trailingslashit($this->options->cloneDir);
269
  }
270
 
271
  /**
272
  * Make sure prefix contains appending underscore
273
+ *
274
  * @param string $string
275
  * @return string
276
  */
277
+ private function sanitizePrefix($string)
278
+ {
279
+ $lastCharacter = substr($string, -1);
280
+ if ($lastCharacter === '_') {
281
  return $string;
282
  }
283
  return $string . '_';
286
  /**
287
  * Create a new staging prefix which does not already exists in database
288
  */
289
+ private function setStagingPrefix()
290
+ {
291
 
292
  // Get & find a new prefix that does not already exist in database.
293
  // Loop through up to 1000 different possible prefixes should be enough here;)
294
+ for ($i = 0; $i <= 10000; $i++) {
295
+ $this->options->prefix = isset($this->options->existingClones) ?
296
+ 'wpstg' . (count($this->options->existingClones) + $i) . '_' :
297
+ 'wpstg' . $i . '_';
298
 
299
+ $sql = "SHOW TABLE STATUS LIKE '{$this->options->prefix}%'";
300
+ $tables = $this->db->get_results($sql);
301
 
302
  // Prefix does not exist. We can use it
303
+ if (!$tables) {
304
+ return strtolower($this->options->prefix);
305
  }
306
  }
307
+ $this->returnException("Fatal Error: Can not create staging prefix. '{$this->options->prefix}' already exists! Stopping for security reasons. Contact support@wp-staging.com");
308
+ wp_die("Fatal Error: Can not create staging prefix. Prefix '{$this->options->prefix}' already exists! Stopping for security reasons. Contact support@wp-staging.com");
309
  }
310
 
311
  /**
312
+ * Check if potential new prefix of staging site would be identical with live site.
313
  * @return boolean
314
  */
315
+ private function isPrefixIdentical()
316
+ {
317
+ $db = WPStaging::getInstance()->get("wpdb");
318
 
319
+ $livePrefix = $db->prefix;
320
  $stagingPrefix = $this->options->prefix;
321
 
322
+ if ($livePrefix == $stagingPrefix) {
323
  return true;
324
  }
325
  return false;
328
  /**
329
  * Start the cloning job
330
  */
331
+ public function start()
332
+ {
333
+ if (!property_exists($this->options, 'currentJob') || null === $this->options->currentJob) {
334
+ $this->log("Cloning job finished");
335
  return true;
336
  }
337
 
338
+ $methodName = "job" . ucwords($this->options->currentJob);
339
 
340
+ if (!method_exists($this, $methodName)) {
341
+ $this->log("Can't execute job; Job's method {$methodName} is not found");
342
+ throw new JobNotFoundException($methodName);
343
  }
344
 
345
  // Call the job
352
  * @param string $nextJob
353
  * @return object
354
  */
355
+ private function handleJobResponse($response, $nextJob)
356
+ {
357
  // Job is not done
358
+ if (true !== $response->status) {
359
  return $response;
360
  }
361
 
362
+ $this->options->currentJob = $nextJob;
363
  $this->options->currentStep = 0;
364
+ $this->options->totalSteps = 0;
365
 
366
  // Save options
367
  $this->saveOptions();
373
  * Clone Database
374
  * @return object
375
  */
376
+ public function jobDatabase()
377
+ {
378
 
379
  // Could be written more elegant
380
  // but for xdebug purposes and breakpoints its cleaner to have separate if blocks
381
+ if (defined('WPSTGPRO_VERSION') && is_multisite()) {
382
  // Is Multisite
383
+ if (empty($this->options->databaseUser) && empty($this->options->databasePassword)) {
384
  $database = new muDatabase();
385
  } else {
386
  $database = new muDatabaseExternal();
388
  } else {
389
 
390
  // No Multisite
391
+ if (empty($this->options->databaseUser) && empty($this->options->databasePassword)) {
392
  $database = new Database();
393
  } else {
394
  $database = new DatabaseExternal();
395
  }
396
  }
397
+ return $this->handleJobResponse($database->start(), "SearchReplace");
398
  }
399
 
400
  /**
401
  * Search & Replace
402
  * @return object
403
  */
404
+ public function jobSearchReplace()
405
+ {
406
+ if (defined('WPSTGPRO_VERSION') && is_multisite()) {
407
+ if (empty($this->options->databaseUser) && empty($this->options->databasePassword)) {
408
  $searchReplace = new muSearchReplace();
409
  } else {
410
  $searchReplace = new muSearchReplaceExternal();
411
  }
412
  } else {
413
+ if (empty($this->options->databaseUser) && empty($this->options->databasePassword)) {
414
  $searchReplace = new SearchReplace();
415
  } else {
416
  $searchReplace = new SearchReplaceExternal();
417
  }
418
  }
419
+ return $this->handleJobResponse($searchReplace->start(), "directories");
420
  }
421
 
422
  /**
423
  * Get All Files From Selected Directories Recursively Into a File
424
  * @return object
425
  */
426
+ public function jobDirectories()
427
+ {
428
+ if (defined('WPSTGPRO_VERSION') && is_multisite()) {
429
  $directories = new muDirectories();
430
  } else {
431
  $directories = new Directories();
432
  }
433
+ return $this->handleJobResponse($directories->start(), "files");
434
  }
435
 
436
  /**
437
  * Copy Files
438
  * @return object
439
  */
440
+ public function jobFiles()
441
+ {
442
+ if (defined('WPSTGPRO_VERSION') && is_multisite()) {
443
  $files = new muFiles();
444
  } else {
445
  $files = new Files();
446
  }
447
+ return $this->handleJobResponse($files->start(), "data");
448
  }
449
 
450
 
452
  * Replace Data
453
  * @return object
454
  */
455
+ public function jobData()
456
+ {
457
+ if (defined('WPSTGPRO_VERSION') && is_multisite()) {
458
+ if (empty($this->options->databaseUser) && empty($this->options->databasePassword)) {
459
  $data = new muData();
460
  } else {
461
  $data = new muDataExternal();
462
  }
463
  } else {
464
 
465
+ if (empty($this->options->databaseUser) && empty($this->options->databasePassword)) {
466
  $data = new Data();
467
  } else {
468
  $data = new DataExternal();
469
  }
470
  }
471
+ return $this->handleJobResponse($data->start(), "finish");
472
  }
473
 
474
  /**
475
  * Save Clone Data
476
  * @return object
477
  */
478
+ public function jobFinish()
479
+ {
480
+ if (defined('WPSTGPRO_VERSION') && is_multisite()) {
481
  $finish = new muFinish();
482
  } else {
483
  $finish = new Finish();
484
  }
485
+ return $this->handleJobResponse($finish->start(), '');
486
  }
487
 
488
  }
Backend/Modules/Jobs/Delete.php CHANGED
@@ -451,15 +451,6 @@ class Delete extends Job {
451
  }
452
  }
453
 
454
- /**
455
- * @return bool
456
- */
457
- // public function isDirectoryDeletingFinished() {
458
- // return (
459
- // (false === $this->forceDeleteDirectories && (!isset( $_POST["deleteDir"] ) || '1' !== $_POST["deleteDir"])) ||
460
- // !is_dir( $this->clone->path ) || ABSPATH === $this->job->nextDirectoryToDelete
461
- // );
462
- // }
463
 
464
  /**
465
  *
@@ -490,9 +481,6 @@ class Delete extends Job {
490
  if( $clone["path"] == $this->clone->path ) {
491
  unset( $existingClones[$name] );
492
  }
493
- // if( !is_dir( $clone["path"] ) ) {
494
- // unset( $existingClones[$name] );
495
- // }
496
  }
497
 
498
  if( false === update_option( "wpstg_existing_clones_beta", $existingClones ) ) {
451
  }
452
  }
453
 
 
 
 
 
 
 
 
 
 
454
 
455
  /**
456
  *
481
  if( $clone["path"] == $this->clone->path ) {
482
  unset( $existingClones[$name] );
483
  }
 
 
 
484
  }
485
 
486
  if( false === update_option( "wpstg_existing_clones_beta", $existingClones ) ) {
Backend/Modules/Jobs/Files.php CHANGED
@@ -2,19 +2,15 @@
2
 
3
  namespace WPStaging\Backend\Modules\Jobs;
4
 
5
- // No Direct Access
6
- use WPStaging\WPStaging;
7
  use WPStaging\Utils\Logger;
8
 
9
- if( !defined( "WPINC" ) ) {
10
- die;
11
- }
12
-
13
  /**
14
  * Class Files
15
  * @package WPStaging\Backend\Modules\Jobs
16
  */
17
- class Files extends JobExecutable {
 
18
 
19
  /**
20
  * @var \SplFileObject
@@ -34,23 +30,24 @@ class Files extends JobExecutable {
34
  /**
35
  * Initialization
36
  */
37
- public function initialize() {
 
38
 
39
  $this->destination = $this->options->destinationDir;
40
 
41
  $filePath = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
42
 
43
- if( is_file( $filePath ) ) {
44
- $this->file = new \SplFileObject( $filePath, 'r' );
45
  }
46
 
47
  // Informational logs
48
- if( 0 == $this->options->currentStep ) {
49
- $this->log( "Copying files..." );
50
  }
51
 
52
  $this->settings->batchSize = $this->settings->batchSize * 1000000;
53
- $this->maxFilesPerRun = $this->settings->fileLimit;
54
  //$this->maxFilesPerRun = ($this->settings->cpuLoad === 'low') ? 50 : 1;
55
  }
56
 
@@ -58,8 +55,9 @@ class Files extends JobExecutable {
58
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
59
  * @return void
60
  */
61
- protected function calculateTotalSteps() {
62
- $this->options->totalSteps = ceil( $this->options->totalFiles / $this->maxFilesPerRun );
 
63
  }
64
 
65
  /**
@@ -67,17 +65,18 @@ class Files extends JobExecutable {
67
  * Returns false when over threshold limits are hit or when the job is done, true otherwise
68
  * @return bool
69
  */
70
- protected function execute() {
 
71
  // Finished
72
- if( $this->isFinished() ) {
73
- $this->log( "Copying files finished" );
74
- $this->prepareResponse( true, false );
75
  return false;
76
  }
77
 
78
  // Get files and copy'em
79
- if( !$this->getFilesAndCopy() ) {
80
- $this->prepareResponse( false, false );
81
  return false;
82
  }
83
 
@@ -92,59 +91,46 @@ class Files extends JobExecutable {
92
  * Get files and copy
93
  * @return bool
94
  */
95
- private function getFilesAndCopy() {
 
96
  // Over limits threshold
97
- if( $this->isOverThreshold() ) {
98
  // Prepare response and save current progress
99
- $this->prepareResponse( false, false );
100
  $this->saveOptions();
101
  return false;
102
  }
103
 
104
  // Go to last copied line and than to next one
105
  //if ($this->options->copiedFiles != 0) {
106
- if( isset( $this->options->copiedFiles ) && $this->options->copiedFiles != 0 ) {
107
- $this->file->seek( $this->options->copiedFiles - 1 );
108
  }
109
 
110
- $this->file->setFlags( \SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD );
111
-
112
- // Start time
113
- //$start = microtime( true );
114
- // Loop x files at a time
115
- //$this->maxFilesPerRun = 300;
116
- for ( $i = 0; $i < $this->maxFilesPerRun; $i++ ) {
117
-
118
- // Reached timeout
119
- // if( ( $timeout = apply_filters( 'wpstg_job_timeout', 10 ) ) ) {
120
- // if( ( \microtime( true ) - $start ) > $timeout ) {
121
- // // Prepare response and save current progress
122
- // $this->prepareResponse( false, true );
123
- // $this->saveOptions();
124
- // return false;
125
- // }
126
- // }
127
  // Increment copied files
128
  // Do this anytime to make sure to not stuck in the same step / files
129
  $this->options->copiedFiles++;
130
 
131
  // End of file
132
- if( $this->file->eof() ) {
133
  break;
134
  }
135
 
136
  $file = str_replace(PHP_EOL, null, $this->file->fgets());
137
 
138
 
139
- $this->copyFile( $file );
140
  }
141
 
142
 
143
-
144
  $totalFiles = $this->options->copiedFiles;
145
  // Log this only every 50 entries to keep the log small and to not block the rendering browser
146
- if( $this->options->copiedFiles % 50 == 0 ) {
147
- $this->log( "Total {$totalFiles} files processed" );
148
  }
149
 
150
  return true;
@@ -154,87 +140,88 @@ class Files extends JobExecutable {
154
  * Checks Whether There is Any Job to Execute or Not
155
  * @return bool
156
  */
157
- private function isFinished() {
 
158
  return
159
  !$this->isRunning() ||
160
  $this->options->currentStep > $this->options->totalSteps ||
161
- $this->options->copiedFiles >= $this->options->totalFiles
162
- ;
163
  }
164
 
165
  /**
166
  * @param string $file
167
  * @return bool
168
  */
169
- private function copyFile( $file ) {
 
170
 
171
- $file = trim( \WPStaging\WPStaging::getWPpath() . $file );
172
 
173
  $file = wpstg_replace_windows_directory_separator($file);
174
 
175
- $directory = dirname( $file );
176
 
177
  // Directory is excluded
178
- if( $this->isDirectoryExcluded( $directory ) ) {
179
- $this->debugLog( "Skipping directory by rule: {$file}", Logger::TYPE_INFO );
180
  return false;
181
  }
182
 
183
  // File is excluded
184
- if( $this->isFileExcluded( $file ) ) {
185
- $this->debugLog( "Skipping file by rule: {$file}", Logger::TYPE_INFO );
186
  return false;
187
  }
188
  // Path + File is excluded
189
- if( $this->isFileExcludedFullPath( $file ) ) {
190
- $this->debugLog( "Skipping file by rule: {$file}", Logger::TYPE_INFO );
191
  return false;
192
  }
193
 
194
  // Invalid file, skipping it as if succeeded
195
- if( !is_file( $file ) ) {
196
- $this->log( "File doesn't exist {$file}", Logger::TYPE_WARNING );
197
  return true;
198
  }
199
  // Invalid file, skipping it as if succeeded
200
- if( !is_readable( $file ) ) {
201
- $this->log( "Can't read file {$file}", Logger::TYPE_WARNING );
202
  return true;
203
  }
204
 
205
 
206
  // Get file size
207
- $fileSize = filesize( $file );
208
 
209
  // File is over maximum allowed file size (8MB)
210
- if( $fileSize >= $this->settings->maxFileSize * 1000000 ) {
211
- $this->log( "Skipping big file: {$file}", Logger::TYPE_INFO );
212
  return false;
213
  }
214
 
215
  // Failed to get destination
216
- if( false === ($destination = $this->getDestination( $file )) ) {
217
- $this->log( "Can't get the destination of {$file}", Logger::TYPE_WARNING );
218
  return false;
219
  }
220
 
221
  // File is over batch size
222
- if( $fileSize >= $this->settings->batchSize ) {
223
- $this->log( "Trying to copy big file: {$file} -> {$destination}", Logger::TYPE_INFO );
224
- return $this->copyBig( $file, $destination, $this->settings->batchSize );
225
  }
226
 
227
  // Attempt to copy
228
- if( !@copy( $file, $destination ) ) {
229
  $errors = error_get_last();
230
- $this->log( "Files: Failed to copy file to destination. Error: {$errors['message']} {$file} -> {$destination}", Logger::TYPE_ERROR );
231
  return false;
232
  }
233
 
234
  // Set file permissions
235
- @chmod( $destination, wpstg_get_permissions_for_file() );
236
 
237
- $this->setDirPermissions( $destination );
238
 
239
  return true;
240
  }
@@ -245,28 +232,29 @@ class Files extends JobExecutable {
245
  *
246
  * @return string
247
  */
248
- protected function getWpContentPath( $file ) {
 
249
  // Get upload directory information
250
  $uploads = wp_upload_dir();
251
 
252
  // Get absolute path to wordpress uploads directory e.g srv/www/htdocs/sitename/wp-content/uploads
253
- $uploadsAbsPath = trailingslashit( $uploads['basedir'] );
254
 
255
  // Get relative path to the uploads folder, e.g assets
256
  $uploadsRelPath = wpstg_get_rel_upload_dir();
257
 
258
  // Get absolute path to wp-content directory e.g srv/www/htdocs/sitename/wp-content
259
- $wpContentDir = trailingslashit( WP_CONTENT_DIR );
260
 
261
  // Check if there is a custom uploads directory, then do a search $ replace. Do this only if custom upload path is not identical to WP_CONTENT_DIR
262
- if( $uploadsAbsPath != $wpContentDir ) {
263
  //$file = str_replace( $uploadsAbsPath, ABSPATH . 'wp-content/uploads/', $file, $count );
264
- $file = str_replace( $uploadsAbsPath, ABSPATH . $uploadsRelPath, $file, $count );
265
  }
266
  // If there is no custom upload directory do a search & replace of the custom wp-content directory
267
- if( empty( $count ) || $count === 0 ) {
268
  //$file = str_replace( $wpContentDir, ABSPATH . 'wp-content/', $file );
269
- $file = str_replace( $wpContentDir, ABSPATH . 'wp-content/', $file );
270
  }
271
 
272
 
@@ -278,10 +266,11 @@ class Files extends JobExecutable {
278
  * @param type $file
279
  * @return boolean
280
  */
281
- private function setDirPermissions( $file ) {
282
- $dir = dirname( $file );
283
- if( is_dir( $dir ) ) {
284
- @chmod( $dir, wpstg_get_permissions_for_directory() );
 
285
  }
286
  return false;
287
  }
@@ -292,20 +281,21 @@ class Files extends JobExecutable {
292
  * @param string $file
293
  * @return bool|string
294
  */
295
- private function getDestination( $file ) {
 
296
  //$file = $this->getMultisiteUploadFolder( $file );
297
  $file = wpstg_replace_windows_directory_separator($file);
298
  $rootPath = wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath());
299
- $relativePath = str_replace( $rootPath, null, $file );
300
- $destinationPath = $this->destination . $relativePath;
301
- $destinationDirectory = dirname( $destinationPath );
302
 
303
- if( !is_dir( $destinationDirectory ) && !@mkdir( $destinationDirectory, wpstg_get_permissions_for_directory(), true ) ) {
304
- $this->log( "Files: Can not create directory {$destinationDirectory}", Logger::TYPE_ERROR );
305
  return false;
306
  }
307
 
308
- return $this->sanitizeDirectorySeparator( $destinationPath );
309
  }
310
 
311
  /**
@@ -315,105 +305,84 @@ class Files extends JobExecutable {
315
  * @param int $buffersize
316
  * @return boolean
317
  */
318
- private function copyBig( $src, $dst, $buffersize ) {
319
- $src = fopen( $src, 'r' );
320
- $dest = fopen( $dst, 'w' );
 
321
 
322
  // Try first method:
323
- while ( !feof( $src ) ) {
324
- if( false === fwrite( $dest, fread( $src, $buffersize ) ) ) {
325
  $error = true;
326
  }
327
  }
328
  // Try second method if first one failed
329
- if( isset( $error ) && ($error === true) ) {
330
- while ( !feof( $src ) ) {
331
- if( false === stream_copy_to_stream( $src, $dest, 1024 ) ) {
332
- $this->log( "Can not copy file; {$src} -> {$dest}" );
333
- fclose( $src );
334
- fclose( $dest );
335
  return false;
336
  }
337
  }
338
  }
339
  // Close any open handler
340
- fclose( $src );
341
- fclose( $dest );
342
  return true;
343
  }
344
 
345
  /**
346
  * Check if certain file is excluded from copying process
347
  *
348
- * @param string $file filename including ending without full path
349
  * @return boolean
350
  */
351
- private function isFileExcluded( $file ) {
 
352
 
353
- $excludedFiles = ( array ) $this->options->excludedFiles;
354
 
355
- $basenameFile = basename( $file );
356
-
357
-
358
- // Remove .htaccess and web.config from array excludedFiles if staging site is copied to a subdomain
359
- //if( $this->isCustomDirectory() ) {
360
- if( false === $this->isIdenticalHostname() ) {
361
- $excludedFiles = \array_diff( $excludedFiles, array("web.config", ".htaccess") );
362
  }
363
 
364
-
365
- // If file name exists
366
- if( in_array( $basenameFile, $excludedFiles ) ) {
367
  return true;
368
  }
369
 
370
- // Check for wildcards
371
- foreach ($excludedFiles as $pattern) {
372
- if (wpstg_fnmatch($pattern, $basenameFile)) {
373
- return true;
374
- }
375
- }
376
-
377
  // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
378
  // because if the updating process fails, the staging site would not be accessable any longer
379
- if( isset( $this->options->mainJob ) && $this->options->mainJob == "updating" && stripos( strrev( $file ), strrev( "wp-config.php" ) ) === 0 ) {
 
380
  return true;
381
  }
382
 
383
-
384
  return false;
385
  }
386
 
387
- /**
388
- * Check if custom target directory is used
389
- * @return boolean
390
- */
391
- // private function isCustomDirectory() {
392
- //
393
- // if( empty( $this->options->cloneDir ) ) {
394
- // return false;
395
- // }
396
- // return true;
397
- // }
398
-
399
  /**
400
  * Check if production and staging hostname are identical
401
  * If they are not identical we assume website is cloned to a subdomain and not into a subfolder
402
  * @return boolean
403
  */
404
- private function isIdenticalHostname() {
 
405
  // hostname of production site without scheme
406
- $siteurl = get_site_url();
407
- $url = parse_url( $siteurl );
408
  $productionHostname = $url['host'];
409
 
410
  // hostname of staging site without scheme
411
- $cloneUrl = empty($this->options->cloneHostname) ? $url : parse_url( $this->options->cloneHostname );
412
  $targetHostname = $cloneUrl['host'];
413
 
414
  // Check if target hostname beginns with the production hostname
415
  // Only compare the hostname without path
416
- if( wpstg_starts_with( $productionHostname, $targetHostname ) ) {
417
  return true;
418
  }
419
  return false;
@@ -425,10 +394,11 @@ class Files extends JobExecutable {
425
  * @param string $file filename including ending + (part) path e.g wp-content/db.php
426
  * @return boolean
427
  */
428
- private function isFileExcludedFullPath( $file ) {
 
429
  // If path + file exists
430
- foreach ( $this->options->excludedFilesFullPath as $excludedFile ) {
431
- if( false !== strpos( $file, $excludedFile ) ) {
432
  return true;
433
  }
434
  }
@@ -443,8 +413,9 @@ class Files extends JobExecutable {
443
  * @param string $path Path
444
  * @return string
445
  */
446
- private function sanitizeDirectorySeparator( $path ) {
447
- return preg_replace( '/[\\\\]+/', '/', $path );
 
448
  }
449
 
450
  /**
@@ -452,17 +423,18 @@ class Files extends JobExecutable {
452
  * @param string $directory
453
  * @return bool
454
  */
455
- private function isDirectoryExcluded( $directory ) {
 
456
  // Make sure that wp-staging-pro directory / plugin is never excluded
457
- if( false !== strpos( $directory, 'wp-staging' ) || false !== strpos( $directory, 'wp-staging-pro' ) ) {
458
  return false;
459
  }
460
 
461
- $directory = trailingslashit( $this->sanitizeDirectorySeparator( $directory ) );
462
 
463
- foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
464
- $excludedDirectory = trailingslashit( $this->sanitizeDirectorySeparator( $excludedDirectory ) );
465
- if( strpos( $directory, $excludedDirectory ) === 0 && !$this->isExtraDirectory( $directory ) ) {
466
  return true;
467
  }
468
  }
@@ -475,11 +447,12 @@ class Files extends JobExecutable {
475
  * @param string $directory
476
  * @return boolean
477
  */
478
- private function isExtraDirectory( $directory ) {
479
- $directory = $this->sanitizeDirectorySeparator( $directory );
 
480
 
481
- foreach ( $this->options->extraDirectories as $extraDirectory ) {
482
- if( strpos( $directory, $this->sanitizeDirectorySeparator( $extraDirectory ) ) === 0 ) {
483
  return true;
484
  }
485
  }
2
 
3
  namespace WPStaging\Backend\Modules\Jobs;
4
 
5
+ use WPStaging\Manager\FileSystem\FileManager;
 
6
  use WPStaging\Utils\Logger;
7
 
 
 
 
 
8
  /**
9
  * Class Files
10
  * @package WPStaging\Backend\Modules\Jobs
11
  */
12
+ class Files extends JobExecutable
13
+ {
14
 
15
  /**
16
  * @var \SplFileObject
30
  /**
31
  * Initialization
32
  */
33
+ public function initialize()
34
+ {
35
 
36
  $this->destination = $this->options->destinationDir;
37
 
38
  $filePath = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
39
 
40
+ if (is_file($filePath)) {
41
+ $this->file = new \SplFileObject($filePath, 'r');
42
  }
43
 
44
  // Informational logs
45
+ if (0 == $this->options->currentStep) {
46
+ $this->log("Copying files...");
47
  }
48
 
49
  $this->settings->batchSize = $this->settings->batchSize * 1000000;
50
+ $this->maxFilesPerRun = $this->settings->fileLimit;
51
  //$this->maxFilesPerRun = ($this->settings->cpuLoad === 'low') ? 50 : 1;
52
  }
53
 
55
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
56
  * @return void
57
  */
58
+ protected function calculateTotalSteps()
59
+ {
60
+ $this->options->totalSteps = ceil($this->options->totalFiles / $this->maxFilesPerRun);
61
  }
62
 
63
  /**
65
  * Returns false when over threshold limits are hit or when the job is done, true otherwise
66
  * @return bool
67
  */
68
+ protected function execute()
69
+ {
70
  // Finished
71
+ if ($this->isFinished()) {
72
+ $this->log("Copying files finished");
73
+ $this->prepareResponse(true, false);
74
  return false;
75
  }
76
 
77
  // Get files and copy'em
78
+ if (!$this->getFilesAndCopy()) {
79
+ $this->prepareResponse(false, false);
80
  return false;
81
  }
82
 
91
  * Get files and copy
92
  * @return bool
93
  */
94
+ private function getFilesAndCopy()
95
+ {
96
  // Over limits threshold
97
+ if ($this->isOverThreshold()) {
98
  // Prepare response and save current progress
99
+ $this->prepareResponse(false, false);
100
  $this->saveOptions();
101
  return false;
102
  }
103
 
104
  // Go to last copied line and than to next one
105
  //if ($this->options->copiedFiles != 0) {
106
+ if (isset($this->options->copiedFiles) && $this->options->copiedFiles != 0) {
107
+ $this->file->seek($this->options->copiedFiles - 1);
108
  }
109
 
110
+ $this->file->setFlags(\SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD);
111
+
112
+ for ($i = 0; $i < $this->maxFilesPerRun; $i++) {
113
+
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  // Increment copied files
115
  // Do this anytime to make sure to not stuck in the same step / files
116
  $this->options->copiedFiles++;
117
 
118
  // End of file
119
+ if ($this->file->eof()) {
120
  break;
121
  }
122
 
123
  $file = str_replace(PHP_EOL, null, $this->file->fgets());
124
 
125
 
126
+ $this->copyFile($file);
127
  }
128
 
129
 
 
130
  $totalFiles = $this->options->copiedFiles;
131
  // Log this only every 50 entries to keep the log small and to not block the rendering browser
132
+ if ($this->options->copiedFiles % 50 == 0) {
133
+ $this->log("Total {$totalFiles} files processed");
134
  }
135
 
136
  return true;
140
  * Checks Whether There is Any Job to Execute or Not
141
  * @return bool
142
  */
143
+ private function isFinished()
144
+ {
145
  return
146
  !$this->isRunning() ||
147
  $this->options->currentStep > $this->options->totalSteps ||
148
+ $this->options->copiedFiles >= $this->options->totalFiles;
 
149
  }
150
 
151
  /**
152
  * @param string $file
153
  * @return bool
154
  */
155
+ private function copyFile($file)
156
+ {
157
 
158
+ $file = trim(\WPStaging\WPStaging::getWPpath() . $file);
159
 
160
  $file = wpstg_replace_windows_directory_separator($file);
161
 
162
+ $directory = dirname($file);
163
 
164
  // Directory is excluded
165
+ if ($this->isDirectoryExcluded($directory)) {
166
+ $this->debugLog("Skipping directory by rule: {$file}", Logger::TYPE_INFO);
167
  return false;
168
  }
169
 
170
  // File is excluded
171
+ if ($this->isFileExcluded($file)) {
172
+ $this->debugLog("Skipping file by rule: {$file}", Logger::TYPE_INFO);
173
  return false;
174
  }
175
  // Path + File is excluded
176
+ if ($this->isFileExcludedFullPath($file)) {
177
+ $this->debugLog("Skipping file by rule: {$file}", Logger::TYPE_INFO);
178
  return false;
179
  }
180
 
181
  // Invalid file, skipping it as if succeeded
182
+ if (!is_file($file)) {
183
+ $this->log("File doesn't exist {$file}", Logger::TYPE_WARNING);
184
  return true;
185
  }
186
  // Invalid file, skipping it as if succeeded
187
+ if (!is_readable($file)) {
188
+ $this->log("Can't read file {$file}", Logger::TYPE_WARNING);
189
  return true;
190
  }
191
 
192
 
193
  // Get file size
194
+ $fileSize = filesize($file);
195
 
196
  // File is over maximum allowed file size (8MB)
197
+ if ($fileSize >= $this->settings->maxFileSize * 1000000) {
198
+ $this->log("Skipping big file: {$file}", Logger::TYPE_INFO);
199
  return false;
200
  }
201
 
202
  // Failed to get destination
203
+ if (false === ($destination = $this->getDestination($file))) {
204
+ $this->log("Can't get the destination of {$file}", Logger::TYPE_WARNING);
205
  return false;
206
  }
207
 
208
  // File is over batch size
209
+ if ($fileSize >= $this->settings->batchSize) {
210
+ $this->log("Trying to copy big file: {$file} -> {$destination}", Logger::TYPE_INFO);
211
+ return $this->copyBig($file, $destination, $this->settings->batchSize);
212
  }
213
 
214
  // Attempt to copy
215
+ if (!@copy($file, $destination)) {
216
  $errors = error_get_last();
217
+ $this->log("Files: Failed to copy file to destination. Error: {$errors['message']} {$file} -> {$destination}", Logger::TYPE_ERROR);
218
  return false;
219
  }
220
 
221
  // Set file permissions
222
+ @chmod($destination, wpstg_get_permissions_for_file());
223
 
224
+ $this->setDirPermissions($destination);
225
 
226
  return true;
227
  }
232
  *
233
  * @return string
234
  */
235
+ protected function getWpContentPath($file)
236
+ {
237
  // Get upload directory information
238
  $uploads = wp_upload_dir();
239
 
240
  // Get absolute path to wordpress uploads directory e.g srv/www/htdocs/sitename/wp-content/uploads
241
+ $uploadsAbsPath = trailingslashit($uploads['basedir']);
242
 
243
  // Get relative path to the uploads folder, e.g assets
244
  $uploadsRelPath = wpstg_get_rel_upload_dir();
245
 
246
  // Get absolute path to wp-content directory e.g srv/www/htdocs/sitename/wp-content
247
+ $wpContentDir = trailingslashit(WP_CONTENT_DIR);
248
 
249
  // Check if there is a custom uploads directory, then do a search $ replace. Do this only if custom upload path is not identical to WP_CONTENT_DIR
250
+ if ($uploadsAbsPath != $wpContentDir) {
251
  //$file = str_replace( $uploadsAbsPath, ABSPATH . 'wp-content/uploads/', $file, $count );
252
+ $file = str_replace($uploadsAbsPath, ABSPATH . $uploadsRelPath, $file, $count);
253
  }
254
  // If there is no custom upload directory do a search & replace of the custom wp-content directory
255
+ if (empty($count) || $count === 0) {
256
  //$file = str_replace( $wpContentDir, ABSPATH . 'wp-content/', $file );
257
+ $file = str_replace($wpContentDir, ABSPATH . 'wp-content/', $file);
258
  }
259
 
260
 
266
  * @param type $file
267
  * @return boolean
268
  */
269
+ private function setDirPermissions($file)
270
+ {
271
+ $dir = dirname($file);
272
+ if (is_dir($dir)) {
273
+ @chmod($dir, wpstg_get_permissions_for_directory());
274
  }
275
  return false;
276
  }
281
  * @param string $file
282
  * @return bool|string
283
  */
284
+ private function getDestination($file)
285
+ {
286
  //$file = $this->getMultisiteUploadFolder( $file );
287
  $file = wpstg_replace_windows_directory_separator($file);
288
  $rootPath = wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath());
289
+ $relativePath = str_replace($rootPath, null, $file);
290
+ $destinationPath = $this->destination . $relativePath;
291
+ $destinationDirectory = dirname($destinationPath);
292
 
293
+ if (!is_dir($destinationDirectory) && !@mkdir($destinationDirectory, wpstg_get_permissions_for_directory(), true)) {
294
+ $this->log("Files: Can not create directory {$destinationDirectory}", Logger::TYPE_ERROR);
295
  return false;
296
  }
297
 
298
+ return $this->sanitizeDirectorySeparator($destinationPath);
299
  }
300
 
301
  /**
305
  * @param int $buffersize
306
  * @return boolean
307
  */
308
+ private function copyBig($src, $dst, $buffersize)
309
+ {
310
+ $src = fopen($src, 'r');
311
+ $dest = fopen($dst, 'w');
312
 
313
  // Try first method:
314
+ while (!feof($src)) {
315
+ if (false === fwrite($dest, fread($src, $buffersize))) {
316
  $error = true;
317
  }
318
  }
319
  // Try second method if first one failed
320
+ if (isset($error) && ($error === true)) {
321
+ while (!feof($src)) {
322
+ if (false === stream_copy_to_stream($src, $dest, 1024)) {
323
+ $this->log("Can not copy file; {$src} -> {$dest}");
324
+ fclose($src);
325
+ fclose($dest);
326
  return false;
327
  }
328
  }
329
  }
330
  // Close any open handler
331
+ fclose($src);
332
+ fclose($dest);
333
  return true;
334
  }
335
 
336
  /**
337
  * Check if certain file is excluded from copying process
338
  *
339
+ * @param string $file full path + filename
340
  * @return boolean
341
  */
342
+ private function isFileExcluded($file)
343
+ {
344
 
345
+ $excludedFiles = (array) $this->options->excludedFiles;
346
 
347
+ // Remove .htaccess and web.config from 'excludedFiles' if staging site is copied to a subdomain
348
+ if (false === $this->isIdenticalHostname()) {
349
+ $excludedFiles = \array_diff($excludedFiles,
350
+ array("web.config", ".htaccess"));
 
 
 
351
  }
352
 
353
+ if ((new FileManager)->isFilenameExcluded($file, $excludedFiles)) {
 
 
354
  return true;
355
  }
356
 
 
 
 
 
 
 
 
357
  // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
358
  // because if the updating process fails, the staging site would not be accessable any longer
359
+ if (isset($this->options->mainJob) && $this->options->mainJob == "updating"
360
+ && stripos(strrev($file), strrev("wp-config.php")) === 0) {
361
  return true;
362
  }
363
 
 
364
  return false;
365
  }
366
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  /**
368
  * Check if production and staging hostname are identical
369
  * If they are not identical we assume website is cloned to a subdomain and not into a subfolder
370
  * @return boolean
371
  */
372
+ private function isIdenticalHostname()
373
+ {
374
  // hostname of production site without scheme
375
+ $siteurl = get_site_url();
376
+ $url = parse_url($siteurl);
377
  $productionHostname = $url['host'];
378
 
379
  // hostname of staging site without scheme
380
+ $cloneUrl = empty($this->options->cloneHostname) ? $url : parse_url($this->options->cloneHostname);
381
  $targetHostname = $cloneUrl['host'];
382
 
383
  // Check if target hostname beginns with the production hostname
384
  // Only compare the hostname without path
385
+ if (wpstg_starts_with($productionHostname, $targetHostname)) {
386
  return true;
387
  }
388
  return false;
394
  * @param string $file filename including ending + (part) path e.g wp-content/db.php
395
  * @return boolean
396
  */
397
+ private function isFileExcludedFullPath($file)
398
+ {
399
  // If path + file exists
400
+ foreach ($this->options->excludedFilesFullPath as $excludedFile) {
401
+ if (false !== strpos($file, $excludedFile)) {
402
  return true;
403
  }
404
  }
413
  * @param string $path Path
414
  * @return string
415
  */
416
+ private function sanitizeDirectorySeparator($path)
417
+ {
418
+ return preg_replace('/[\\\\]+/', '/', $path);
419
  }
420
 
421
  /**
423
  * @param string $directory
424
  * @return bool
425
  */
426
+ private function isDirectoryExcluded($directory)
427
+ {
428
  // Make sure that wp-staging-pro directory / plugin is never excluded
429
+ if (false !== strpos($directory, 'wp-staging') || false !== strpos($directory, 'wp-staging-pro')) {
430
  return false;
431
  }
432
 
433
+ $directory = trailingslashit($this->sanitizeDirectorySeparator($directory));
434
 
435
+ foreach ($this->options->excludedDirectories as $excludedDirectory) {
436
+ $excludedDirectory = trailingslashit($this->sanitizeDirectorySeparator($excludedDirectory));
437
+ if (strpos($directory, $excludedDirectory) === 0 && !$this->isExtraDirectory($directory)) {
438
  return true;
439
  }
440
  }
447
  * @param string $directory
448
  * @return boolean
449
  */
450
+ private function isExtraDirectory($directory)
451
+ {
452
+ $directory = $this->sanitizeDirectorySeparator($directory);
453
 
454
+ foreach ($this->options->extraDirectories as $extraDirectory) {
455
+ if (strpos($directory, $this->sanitizeDirectorySeparator($extraDirectory)) === 0) {
456
  return true;
457
  }
458
  }
Backend/Modules/Jobs/Multisite/Directories.php CHANGED
@@ -3,7 +3,7 @@
3
  namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
 
5
  // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
  die;
8
  }
9
 
@@ -18,7 +18,8 @@ use WPStaging\Backend\Modules\Jobs\JobExecutable;
18
  * Class Files
19
  * @package WPStaging\Backend\Modules\Directories
20
  */
21
- class Directories extends JobExecutable {
 
22
 
23
  /**
24
  * @var array
@@ -33,14 +34,15 @@ class Directories extends JobExecutable {
33
 
34
  /**
35
  * path to the cache file
36
- * @var string
37
  */
38
  private $filename;
39
 
40
  /**
41
  * Initialize
42
  */
43
- public function initialize() {
 
44
  $this->filename = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
45
  }
46
 
@@ -48,16 +50,18 @@ class Directories extends JobExecutable {
48
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
49
  * @return void
50
  */
51
- protected function calculateTotalSteps() {
 
52
 
53
- $this->options->totalSteps = $this->total + count( $this->options->extraDirectories );
54
  }
55
 
56
  /**
57
  * Start Module
58
  * @return object
59
  */
60
- public function start() {
 
61
 
62
  // Execute steps
63
  $this->run();
@@ -65,30 +69,31 @@ class Directories extends JobExecutable {
65
  // Save option, progress
66
  $this->saveProgress();
67
 
68
- return ( object ) $this->response;
69
  }
70
 
71
  /**
72
- * Step 0
73
  * Get WP Root files
74
  * Does not collect any sub folders
75
  */
76
- private function getWpRootFiles() {
 
77
 
78
  // open file handle
79
- $files = $this->open( $this->filename, 'a' );
80
 
81
  try {
82
 
83
  // Iterate over wp root directory
84
- $iterator = new \DirectoryIterator( \WPStaging\WPStaging::getWPpath() );
85
 
86
- $this->log( "Scanning / for files" );
87
 
88
  // Write path line
89
- foreach ( $iterator as $item ) {
90
- if( !$item->isDot() && $item->isFile() ) {
91
- if( $this->write( $files, $iterator->getFilename() . PHP_EOL ) ) {
92
  $this->options->totalFiles++;
93
 
94
  // Too much cpu time
@@ -96,11 +101,11 @@ class Directories extends JobExecutable {
96
  }
97
  }
98
  }
99
- } catch ( \Exception $e ) {
100
- $this->returnException( 'Error: ' . $e->getMessage() );
101
  }
102
 
103
- $this->close( $files );
104
  return true;
105
  }
106
 
@@ -108,15 +113,16 @@ class Directories extends JobExecutable {
108
  * Step 2
109
  * Get WP Content Files without multisite folder wp-content/uploads/sites or wp-content/blogs.dir/
110
  */
111
- private function getWpContentFiles() {
 
112
 
113
  // Skip it
114
- if( $this->isDirectoryExcluded( WP_CONTENT_DIR ) ) {
115
- $this->log( "Skip " . WP_CONTENT_DIR );
116
  return true;
117
  }
118
  // open file handle
119
- $files = $this->open( $this->filename, 'a' );
120
 
121
  /**
122
  * Excluded folders relative to the folder to iterate
@@ -132,36 +138,36 @@ class Directories extends JobExecutable {
132
  * Get user excluded folders
133
  */
134
  $directory = array();
135
- foreach ( $this->options->excludedDirectories as $dir ) {
136
  // Windows compatibility fix
137
- $dir = wpstg_replace_windows_directory_separator( $dir );
138
- $wpContentDir = wpstg_replace_windows_directory_separator( WP_CONTENT_DIR );
139
 
140
- if( strpos( $dir, $wpContentDir ) !== false ) {
141
- $directory[] = ltrim( str_replace( $wpContentDir, '', $dir ), '/\\' );
142
  }
143
  }
144
 
145
- $excludePaths = array_merge( $excludePaths, $directory );
146
 
147
  try {
148
 
149
  // Iterate over content directory
150
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( WP_CONTENT_DIR );
151
 
152
  // Exclude sites, uploads, plugins or themes
153
- $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, apply_filters( 'wpstg_clone_mu_excl_folders', $excludePaths ) );
154
 
155
  // Recursively iterate over content directory
156
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
157
 
158
- $this->log( "Scanning wp-content folder " . WP_CONTENT_DIR );
159
 
160
  // Write path line
161
- foreach ( $iterator as $item ) {
162
- if( $item->isFile() ) {
163
  $file = 'wp-content/' . $iterator->getSubPathName() . PHP_EOL;
164
- if( $this->write( $files, $file ) ) {
165
  $this->options->totalFiles++;
166
 
167
  // Add current file size
@@ -169,13 +175,13 @@ class Directories extends JobExecutable {
169
  }
170
  }
171
  }
172
- } catch ( \Exception $e ) {
173
- $this->returnException( 'Error: ' . $e->getMessage() );
174
  //throw new \Exception( 'Error: ' . $e->getMessage() );
175
  }
176
 
177
  // close the file handler
178
- $this->close( $files );
179
  return true;
180
  }
181
 
@@ -184,31 +190,32 @@ class Directories extends JobExecutable {
184
  * @return boolean
185
  * @throws \Exception
186
  */
187
- private function getWpIncludesFiles() {
 
188
 
189
  // Skip it
190
- if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-includes' ) ) {
191
- $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-includes' );
192
  return true;
193
  }
194
 
195
  // open file handle and attach data to end of file
196
- $files = $this->open( $this->filename, 'a' );
197
 
198
  try {
199
 
200
  // Iterate over wp-admin directory
201
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-includes/' );
202
 
203
  // Recursively iterate over wp-includes directory
204
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
205
 
206
- $this->log( "Scanning /wp-includes for its sub-directories and files" );
207
 
208
  // Write files
209
- foreach ( $iterator as $item ) {
210
- if( $item->isFile() ) {
211
- if( $this->write( $files, 'wp-includes/' . $iterator->getSubPathName() . PHP_EOL ) ) {
212
  $this->options->totalFiles++;
213
 
214
  // Add current file size
@@ -216,12 +223,12 @@ class Directories extends JobExecutable {
216
  }
217
  }
218
  }
219
- } catch ( \Exception $e ) {
220
- $this->returnException( 'Error: ' . $e->getMessage() );
221
  }
222
 
223
  // close the file handler
224
- $this->close( $files );
225
  return true;
226
  }
227
 
@@ -230,43 +237,44 @@ class Directories extends JobExecutable {
230
  * @return boolean
231
  * @throws \Exception
232
  */
233
- private function getWpAdminFiles() {
 
234
 
235
  // Skip it
236
- if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-admin/' ) ) {
237
- $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-admin/' );
238
  return true;
239
  }
240
 
241
  // open file handle and attach data to end of file
242
- $files = $this->open( $this->filename, 'a' );
243
 
244
  try {
245
 
246
  // Iterate over wp-admin directory
247
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-admin/' );
248
 
249
  // Recursively iterate over content directory
250
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
251
 
252
- $this->log( "Scanning /wp-admin for its sub-directories and files" );
253
 
254
  // Write path line
255
- foreach ( $iterator as $item ) {
256
- if( $item->isFile() ) {
257
- if( $this->write( $files, 'wp-admin/' . $iterator->getSubPathName() . PHP_EOL ) ) {
258
  $this->options->totalFiles++;
259
  // Too much cpu time
260
  //$this->options->totalFileSize += $iterator->getSize();
261
  }
262
  }
263
  }
264
- } catch ( \Exception $e ) {
265
- $this->returnException( 'Error: ' . $e->getMessage() );
266
  }
267
 
268
  // close the file handler
269
- $this->close( $files );
270
  return true;
271
  }
272
 
@@ -274,10 +282,11 @@ class Directories extends JobExecutable {
274
  * Step 4
275
  * Get WP Content Uploads Files multisite folder wp-content/uploads/sites or wp-content/blogs.dir/ID/files
276
  */
277
- private function getWpContentUploadsSites() {
 
278
 
279
  // Skip if main site is cloned
280
- if( is_main_site() ) {
281
  return true;
282
  }
283
 
@@ -288,20 +297,20 @@ class Directories extends JobExecutable {
288
  $path = $this->getAbsUploadPath();
289
 
290
  // Skip it
291
- if( !is_dir( $path ) ) {
292
- $this->log( "Skipping: {$path} does not exist." );
293
  return true;
294
  }
295
 
296
  // Skip it
297
- if( $this->isDirectoryExcluded( $path ) ) {
298
- $this->log( "Skipping: {$path}" );
299
  return true;
300
  }
301
 
302
 
303
  // open file handle
304
- $files = $this->open( $this->filename, 'a' );
305
 
306
  /**
307
  * Excluded folders relative to the folder to iterate
@@ -316,34 +325,34 @@ class Directories extends JobExecutable {
316
  * Get user excluded folders
317
  */
318
  $directory = array();
319
- foreach ( $this->options->excludedDirectories as $dir ) {
320
- $path = wpstg_replace_windows_directory_separator( $path );
321
- $dir = wpstg_replace_windows_directory_separator( $dir );
322
- if( strpos( $dir, $path ) !== false ) {
323
- $directory[] = ltrim( str_replace( $path, '', $dir ), '/' );
324
  }
325
  }
326
 
327
- $excludePaths = array_merge( $excludePaths, $directory );
328
 
329
  try {
330
 
331
  // Iterate over content directory
332
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $path );
333
 
334
  // Exclude sites, uploads, plugins or themes
335
- $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $excludePaths );
336
 
337
  // Recursively iterate over content directory
338
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
339
  $uploadDir = wpstg_get_upload_dir();
340
- $this->log( "Scanning {$uploadDir} for its sub-directories and files..." );
341
 
342
  // Write path line
343
- foreach ( $iterator as $item ) {
344
- if( $item->isFile() ) {
345
  $file = $this->getRelUploadPath() . $iterator->getSubPathName() . PHP_EOL;
346
- if( $this->write( $files, $file ) ) {
347
  $this->options->totalFiles++;
348
 
349
  // Add current file size
@@ -351,12 +360,12 @@ class Directories extends JobExecutable {
351
  }
352
  }
353
  }
354
- } catch ( \Exception $e ) {
355
- $this->returnException( 'Error: ' . $e->getMessage() );
356
  }
357
 
358
  // close the file handler
359
- $this->close( $files );
360
  return true;
361
  }
362
 
@@ -364,96 +373,101 @@ class Directories extends JobExecutable {
364
  * Get absolute path to the upload folder e.g. /srv/www/wp-content/blogs.dir/ID/files or /srv/www/wp-content/uploads/sites/ID/
365
  * @return type
366
  */
367
- private function getAbsUploadPath() {
 
368
  // Check first which method is used
369
  $uploads = wp_upload_dir();
370
  $basedir = $uploads['basedir'];
371
 
372
- return trailingslashit( $basedir );
373
  }
374
 
375
  /**
376
  * Get relative path to the upload folder like wp-content/uploads or wp-content/blogs.dir/2/files
377
  * @return string
378
  */
379
- private function getRelUploadPath() {
 
380
  $uploads = wp_upload_dir();
381
  $basedir = $uploads['basedir'];
382
 
383
- return trailingslashit( str_replace( wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath()), null, wpstg_replace_windows_directory_separator($basedir) ) );
384
  }
385
 
386
  /**
387
- * Step 5 - x
388
  * Get extra folders of the wp root level
389
  * Does not collect wp-includes, wp-admin and wp-content folder
390
  */
391
- private function getExtraFiles( $folder ) {
 
392
 
393
- if( !is_dir( $folder ) ) {
394
  return true;
395
  }
396
 
397
  // open file handle and attach data to end of file
398
- $files = $this->open( $this->filename, 'a' );
399
 
400
  try {
401
 
402
  // Iterate over extra directory
403
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $folder );
404
 
405
  $exclude = array();
406
 
407
- $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $exclude );
408
  // Recursively iterate over content directory
409
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
410
 
411
  $strings = new Strings();
412
- $this->log( "Scanning {$strings->getLastElemAfterString( '/', $folder )} for its sub-directories and files" );
413
 
414
  // Write path line
415
- foreach ( $iterator as $item ) {
416
- if( $item->isFile() ) {
417
- $path = str_replace( wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath()), '', wpstg_replace_windows_directory_separator($folder) ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL;
418
- if( $this->write( $files, $path ) ) {
419
  $this->options->totalFiles++;
420
  // Add current file size
421
  $this->options->totalFileSize += $iterator->getSize();
422
  }
423
  }
424
  }
425
- } catch ( \Exception $e ) {
426
- $this->returnException( 'Error: ' . $e->getMessage() );
427
  }
428
 
429
  // close the file handler
430
- $this->close( $files );
431
  return true;
432
  }
433
 
434
  /**
435
  * Closes a file handle
436
  *
437
- * @param resource $handle File handle to close
438
  * @return boolean
439
  */
440
- public function close( $handle ) {
441
- return @fclose( $handle );
 
442
  }
443
 
444
  /**
445
  * Opens a file in specified mode
446
  *
447
- * @param string $file Path to the file to open
448
- * @param string $mode Mode in which to open the file
449
  * @return resource
450
  * @throws Exception
451
  */
452
- public function open( $file, $mode ) {
 
453
 
454
- $file_handle = @fopen( $file, $mode );
455
- if( false === $file_handle ) {
456
- $this->returnException( sprintf( __( 'Unable to open %s with mode %s', 'wp-staging' ), $file, $mode ) );
457
  }
458
 
459
  return $file_handle;
@@ -462,20 +476,21 @@ class Directories extends JobExecutable {
462
  /**
463
  * Write contents to a file
464
  *
465
- * @param resource $handle File handle to write to
466
- * @param string $content Contents to write to the file
467
  * @return integer
468
  * @throws Exception
469
  * @throws Exception
470
  */
471
- public function write( $handle, $content ) {
472
- $write_result = @fwrite( $handle, $content );
473
- if( false === $write_result ) {
474
- if( ( $meta = \stream_get_meta_data( $handle ) ) ) {
475
- throw new \Exception( sprintf( __( 'Unable to write to: %s', 'wp-staging' ), $meta['uri'] ) );
 
476
  }
477
- } elseif( strlen( $content ) !== $write_result ) {
478
- throw new \Exception( __( 'Out of disk space.', 'wp-staging' ) );
479
  }
480
 
481
  return $write_result;
@@ -486,54 +501,55 @@ class Directories extends JobExecutable {
486
  * Returns false when over threshold limits are hit or when the job is done, true otherwise
487
  * @return bool
488
  */
489
- protected function execute() {
 
490
 
491
  // No job left to execute
492
- if( $this->isFinished() ) {
493
- $this->prepareResponse( true, false );
494
  return false;
495
  }
496
 
497
 
498
- if( $this->options->currentStep == 0 ) {
499
  $this->getWpRootFiles();
500
- $this->prepareResponse( false, true );
501
  return false;
502
  }
503
 
504
- if( $this->options->currentStep == 1 ) {
505
  $this->getWpContentFiles();
506
- $this->prepareResponse( false, true );
507
  return false;
508
  }
509
 
510
- if( $this->options->currentStep == 2 ) {
511
  $this->getWpIncludesFiles();
512
- $this->prepareResponse( false, true );
513
  return false;
514
  }
515
 
516
- if( $this->options->currentStep == 3 ) {
517
  $this->getWpAdminFiles();
518
- $this->prepareResponse( false, true );
519
  return false;
520
  }
521
 
522
- if( $this->options->currentStep == 4 ) {
523
  $this->getWpContentUploadsSites();
524
- $this->prepareResponse( false, true );
525
  return false;
526
  }
527
 
528
- if( isset( $this->options->extraDirectories[$this->options->currentStep - $this->total] ) ) {
529
- $this->getExtraFiles( $this->options->extraDirectories[$this->options->currentStep - $this->total] );
530
- $this->prepareResponse( false, true );
531
  return false;
532
  }
533
 
534
 
535
  // Prepare response
536
- $this->prepareResponse( false, true );
537
  // Not finished
538
  return true;
539
  }
@@ -542,8 +558,9 @@ class Directories extends JobExecutable {
542
  * Checks Whether There is Any Job to Execute or Not
543
  * @return bool
544
  */
545
- protected function isFinished() {
546
- if( $this->options->currentStep >= $this->options->totalSteps ) {
 
547
  return true;
548
  }
549
  }
@@ -552,7 +569,8 @@ class Directories extends JobExecutable {
552
  * Save files
553
  * @return bool
554
  */
555
- protected function saveProgress() {
 
556
  return $this->saveOptions();
557
  }
558
 
@@ -560,39 +578,33 @@ class Directories extends JobExecutable {
560
  * Get files
561
  * @return void
562
  */
563
- protected function getFiles() {
 
564
  $fileName = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
565
 
566
- if( false === ($this->files = @file_get_contents( $fileName )) ) {
567
  $this->files = array();
568
  return;
569
  }
570
 
571
- $this->files = explode( PHP_EOL, $this->files );
572
  }
573
 
574
- /**
575
- * Replace forward slash with backslash directory separator
576
- *
577
- * @param string $path Path
578
- *
579
- * @return string
580
- */
581
- // private function sanitizeDirectorySeparator( $path ) {
582
- // $string = str_replace( "/", "\\", $path );
583
- // return str_replace( '\\\\', '\\', $string );
584
- // }
585
 
586
  /**
587
  * Check if directory is excluded
588
  * @param string $directory
589
  * @return bool
590
  */
591
- protected function isDirectoryExcluded( $directory ) {
592
- $directory = wpstg_replace_windows_directory_separator( $directory );
593
- foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
594
- $excludedDirectory = wpstg_replace_windows_directory_separator( $excludedDirectory );
595
- if( strpos( trailingslashit( $directory ), trailingslashit( $excludedDirectory ) ) === 0 ) {
 
 
 
 
596
  return true;
597
  }
598
  }
3
  namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
 
5
  // No Direct Access
6
+ if (!defined("WPINC")) {
7
  die;
8
  }
9
 
18
  * Class Files
19
  * @package WPStaging\Backend\Modules\Directories
20
  */
21
+ class Directories extends JobExecutable
22
+ {
23
 
24
  /**
25
  * @var array
34
 
35
  /**
36
  * path to the cache file
37
+ * @var string
38
  */
39
  private $filename;
40
 
41
  /**
42
  * Initialize
43
  */
44
+ public function initialize()
45
+ {
46
  $this->filename = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
47
  }
48
 
50
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
51
  * @return void
52
  */
53
+ protected function calculateTotalSteps()
54
+ {
55
 
56
+ $this->options->totalSteps = $this->total + count($this->options->extraDirectories);
57
  }
58
 
59
  /**
60
  * Start Module
61
  * @return object
62
  */
63
+ public function start()
64
+ {
65
 
66
  // Execute steps
67
  $this->run();
69
  // Save option, progress
70
  $this->saveProgress();
71
 
72
+ return ( object )$this->response;
73
  }
74
 
75
  /**
76
+ * Step 0
77
  * Get WP Root files
78
  * Does not collect any sub folders
79
  */
80
+ private function getWpRootFiles()
81
+ {
82
 
83
  // open file handle
84
+ $files = $this->open($this->filename, 'a');
85
 
86
  try {
87
 
88
  // Iterate over wp root directory
89
+ $iterator = new \DirectoryIterator(\WPStaging\WPStaging::getWPpath());
90
 
91
+ $this->log("Scanning / for files");
92
 
93
  // Write path line
94
+ foreach ($iterator as $item) {
95
+ if (!$item->isDot() && $item->isFile()) {
96
+ if ($this->write($files, $iterator->getFilename() . PHP_EOL)) {
97
  $this->options->totalFiles++;
98
 
99
  // Too much cpu time
101
  }
102
  }
103
  }
104
+ } catch (\Exception $e) {
105
+ $this->returnException('Error: ' . $e->getMessage());
106
  }
107
 
108
+ $this->close($files);
109
  return true;
110
  }
111
 
113
  * Step 2
114
  * Get WP Content Files without multisite folder wp-content/uploads/sites or wp-content/blogs.dir/
115
  */
116
+ private function getWpContentFiles()
117
+ {
118
 
119
  // Skip it
120
+ if ($this->isDirectoryExcluded(WP_CONTENT_DIR)) {
121
+ $this->log("Skip " . WP_CONTENT_DIR);
122
  return true;
123
  }
124
  // open file handle
125
+ $files = $this->open($this->filename, 'a');
126
 
127
  /**
128
  * Excluded folders relative to the folder to iterate
138
  * Get user excluded folders
139
  */
140
  $directory = array();
141
+ foreach ($this->options->excludedDirectories as $dir) {
142
  // Windows compatibility fix
143
+ $dir = wpstg_replace_windows_directory_separator($dir);
144
+ $wpContentDir = wpstg_replace_windows_directory_separator(WP_CONTENT_DIR);
145
 
146
+ if (strpos($dir, $wpContentDir) !== false) {
147
+ $directory[] = ltrim(str_replace($wpContentDir, '', $dir), '/\\');
148
  }
149
  }
150
 
151
+ $excludePaths = array_merge($excludePaths, $directory);
152
 
153
  try {
154
 
155
  // Iterate over content directory
156
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator(WP_CONTENT_DIR);
157
 
158
  // Exclude sites, uploads, plugins or themes
159
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude($iterator, apply_filters('wpstg_clone_mu_excl_folders', $excludePaths));
160
 
161
  // Recursively iterate over content directory
162
+ $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD);
163
 
164
+ $this->log("Scanning wp-content folder " . WP_CONTENT_DIR);
165
 
166
  // Write path line
167
+ foreach ($iterator as $item) {
168
+ if ($item->isFile()) {
169
  $file = 'wp-content/' . $iterator->getSubPathName() . PHP_EOL;
170
+ if ($this->write($files, $file)) {
171
  $this->options->totalFiles++;
172
 
173
  // Add current file size
175
  }
176
  }
177
  }
178
+ } catch (\Exception $e) {
179
+ $this->returnException('Error: ' . $e->getMessage());
180
  //throw new \Exception( 'Error: ' . $e->getMessage() );
181
  }
182
 
183
  // close the file handler
184
+ $this->close($files);
185
  return true;
186
  }
187
 
190
  * @return boolean
191
  * @throws \Exception
192
  */
193
+ private function getWpIncludesFiles()
194
+ {
195
 
196
  // Skip it
197
+ if ($this->isDirectoryExcluded(\WPStaging\WPStaging::getWPpath() . 'wp-includes')) {
198
+ $this->log("Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-includes');
199
  return true;
200
  }
201
 
202
  // open file handle and attach data to end of file
203
+ $files = $this->open($this->filename, 'a');
204
 
205
  try {
206
 
207
  // Iterate over wp-admin directory
208
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator(\WPStaging\WPStaging::getWPpath() . 'wp-includes/');
209
 
210
  // Recursively iterate over wp-includes directory
211
+ $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD);
212
 
213
+ $this->log("Scanning /wp-includes for its sub-directories and files");
214
 
215
  // Write files
216
+ foreach ($iterator as $item) {
217
+ if ($item->isFile()) {
218
+ if ($this->write($files, 'wp-includes/' . $iterator->getSubPathName() . PHP_EOL)) {
219
  $this->options->totalFiles++;
220
 
221
  // Add current file size
223
  }
224
  }
225
  }
226
+ } catch (\Exception $e) {
227
+ $this->returnException('Error: ' . $e->getMessage());
228
  }
229
 
230
  // close the file handler
231
+ $this->close($files);
232
  return true;
233
  }
234
 
237
  * @return boolean
238
  * @throws \Exception
239
  */
240
+ private function getWpAdminFiles()
241
+ {
242
 
243
  // Skip it
244
+ if ($this->isDirectoryExcluded(\WPStaging\WPStaging::getWPpath() . 'wp-admin/')) {
245
+ $this->log("Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-admin/');
246
  return true;
247
  }
248
 
249
  // open file handle and attach data to end of file
250
+ $files = $this->open($this->filename, 'a');
251
 
252
  try {
253
 
254
  // Iterate over wp-admin directory
255
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator(\WPStaging\WPStaging::getWPpath() . 'wp-admin/');
256
 
257
  // Recursively iterate over content directory
258
+ $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD);
259
 
260
+ $this->log("Scanning /wp-admin for its sub-directories and files");
261
 
262
  // Write path line
263
+ foreach ($iterator as $item) {
264
+ if ($item->isFile()) {
265
+ if ($this->write($files, 'wp-admin/' . $iterator->getSubPathName() . PHP_EOL)) {
266
  $this->options->totalFiles++;
267
  // Too much cpu time
268
  //$this->options->totalFileSize += $iterator->getSize();
269
  }
270
  }
271
  }
272
+ } catch (\Exception $e) {
273
+ $this->returnException('Error: ' . $e->getMessage());
274
  }
275
 
276
  // close the file handler
277
+ $this->close($files);
278
  return true;
279
  }
280
 
282
  * Step 4
283
  * Get WP Content Uploads Files multisite folder wp-content/uploads/sites or wp-content/blogs.dir/ID/files
284
  */
285
+ private function getWpContentUploadsSites()
286
+ {
287
 
288
  // Skip if main site is cloned
289
+ if (is_main_site()) {
290
  return true;
291
  }
292
 
297
  $path = $this->getAbsUploadPath();
298
 
299
  // Skip it
300
+ if (!is_dir($path)) {
301
+ $this->log("Skipping: {$path} does not exist.");
302
  return true;
303
  }
304
 
305
  // Skip it
306
+ if ($this->isDirectoryExcluded($path)) {
307
+ $this->log("Skipping: {$path}");
308
  return true;
309
  }
310
 
311
 
312
  // open file handle
313
+ $files = $this->open($this->filename, 'a');
314
 
315
  /**
316
  * Excluded folders relative to the folder to iterate
325
  * Get user excluded folders
326
  */
327
  $directory = array();
328
+ foreach ($this->options->excludedDirectories as $dir) {
329
+ $path = wpstg_replace_windows_directory_separator($path);
330
+ $dir = wpstg_replace_windows_directory_separator($dir);
331
+ if (strpos($dir, $path) !== false) {
332
+ $directory[] = ltrim(str_replace($path, '', $dir), '/');
333
  }
334
  }
335
 
336
+ $excludePaths = array_merge($excludePaths, $directory);
337
 
338
  try {
339
 
340
  // Iterate over content directory
341
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator($path);
342
 
343
  // Exclude sites, uploads, plugins or themes
344
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude($iterator, $excludePaths);
345
 
346
  // Recursively iterate over content directory
347
+ $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD);
348
  $uploadDir = wpstg_get_upload_dir();
349
+ $this->log("Scanning {$uploadDir} for its sub-directories and files...");
350
 
351
  // Write path line
352
+ foreach ($iterator as $item) {
353
+ if ($item->isFile()) {
354
  $file = $this->getRelUploadPath() . $iterator->getSubPathName() . PHP_EOL;
355
+ if ($this->write($files, $file)) {
356
  $this->options->totalFiles++;
357
 
358
  // Add current file size
360
  }
361
  }
362
  }
363
+ } catch (\Exception $e) {
364
+ $this->returnException('Error: ' . $e->getMessage());
365
  }
366
 
367
  // close the file handler
368
+ $this->close($files);
369
  return true;
370
  }
371
 
373
  * Get absolute path to the upload folder e.g. /srv/www/wp-content/blogs.dir/ID/files or /srv/www/wp-content/uploads/sites/ID/
374
  * @return type
375
  */
376
+ private function getAbsUploadPath()
377
+ {
378
  // Check first which method is used
379
  $uploads = wp_upload_dir();
380
  $basedir = $uploads['basedir'];
381
 
382
+ return trailingslashit($basedir);
383
  }
384
 
385
  /**
386
  * Get relative path to the upload folder like wp-content/uploads or wp-content/blogs.dir/2/files
387
  * @return string
388
  */
389
+ private function getRelUploadPath()
390
+ {
391
  $uploads = wp_upload_dir();
392
  $basedir = $uploads['basedir'];
393
 
394
+ return trailingslashit(str_replace(wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath()), null, wpstg_replace_windows_directory_separator($basedir)));
395
  }
396
 
397
  /**
398
+ * Step 5 - x
399
  * Get extra folders of the wp root level
400
  * Does not collect wp-includes, wp-admin and wp-content folder
401
  */
402
+ private function getExtraFiles($folder)
403
+ {
404
 
405
+ if (!is_dir($folder)) {
406
  return true;
407
  }
408
 
409
  // open file handle and attach data to end of file
410
+ $files = $this->open($this->filename, 'a');
411
 
412
  try {
413
 
414
  // Iterate over extra directory
415
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator($folder);
416
 
417
  $exclude = array();
418
 
419
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude($iterator, $exclude);
420
  // Recursively iterate over content directory
421
+ $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD);
422
 
423
  $strings = new Strings();
424
+ $this->log("Scanning {$strings->getLastElemAfterString( '/', $folder )} for its sub-directories and files");
425
 
426
  // Write path line
427
+ foreach ($iterator as $item) {
428
+ if ($item->isFile()) {
429
+ $path = str_replace(wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath()), '', wpstg_replace_windows_directory_separator($folder)) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL;
430
+ if ($this->write($files, $path)) {
431
  $this->options->totalFiles++;
432
  // Add current file size
433
  $this->options->totalFileSize += $iterator->getSize();
434
  }
435
  }
436
  }
437
+ } catch (\Exception $e) {
438
+ $this->returnException('Error: ' . $e->getMessage());
439
  }
440
 
441
  // close the file handler
442
+ $this->close($files);
443
  return true;
444
  }
445
 
446
  /**
447
  * Closes a file handle
448
  *
449
+ * @param resource $handle File handle to close
450
  * @return boolean
451
  */
452
+ public function close($handle)
453
+ {
454
+ return @fclose($handle);
455
  }
456
 
457
  /**
458
  * Opens a file in specified mode
459
  *
460
+ * @param string $file Path to the file to open
461
+ * @param string $mode Mode in which to open the file
462
  * @return resource
463
  * @throws Exception
464
  */
465
+ public function open($file, $mode)
466
+ {
467
 
468
+ $file_handle = @fopen($file, $mode);
469
+ if (false === $file_handle) {
470
+ $this->returnException(sprintf(__('Unable to open %s with mode %s', 'wp-staging'), $file, $mode));
471
  }
472
 
473
  return $file_handle;
476
  /**
477
  * Write contents to a file
478
  *
479
+ * @param resource $handle File handle to write to
480
+ * @param string $content Contents to write to the file
481
  * @return integer
482
  * @throws Exception
483
  * @throws Exception
484
  */
485
+ public function write($handle, $content)
486
+ {
487
+ $write_result = @fwrite($handle, $content);
488
+ if (false === $write_result) {
489
+ if (($meta = \stream_get_meta_data($handle))) {
490
+ throw new \Exception(sprintf(__('Unable to write to: %s', 'wp-staging'), $meta['uri']));
491
  }
492
+ } elseif (strlen($content) !== $write_result) {
493
+ throw new \Exception(__('Out of disk space.', 'wp-staging'));
494
  }
495
 
496
  return $write_result;
501
  * Returns false when over threshold limits are hit or when the job is done, true otherwise
502
  * @return bool
503
  */
504
+ protected function execute()
505
+ {
506
 
507
  // No job left to execute
508
+ if ($this->isFinished()) {
509
+ $this->prepareResponse(true, false);
510
  return false;
511
  }
512
 
513
 
514
+ if ($this->options->currentStep == 0) {
515
  $this->getWpRootFiles();
516
+ $this->prepareResponse(false, true);
517
  return false;
518
  }
519
 
520
+ if ($this->options->currentStep == 1) {
521
  $this->getWpContentFiles();
522
+ $this->prepareResponse(false, true);
523
  return false;
524
  }
525
 
526
+ if ($this->options->currentStep == 2) {
527
  $this->getWpIncludesFiles();
528
+ $this->prepareResponse(false, true);
529
  return false;
530
  }
531
 
532
+ if ($this->options->currentStep == 3) {
533
  $this->getWpAdminFiles();
534
+ $this->prepareResponse(false, true);
535
  return false;
536
  }
537
 
538
+ if ($this->options->currentStep == 4) {
539
  $this->getWpContentUploadsSites();
540
+ $this->prepareResponse(false, true);
541
  return false;
542
  }
543
 
544
+ if (isset($this->options->extraDirectories[$this->options->currentStep - $this->total])) {
545
+ $this->getExtraFiles($this->options->extraDirectories[$this->options->currentStep - $this->total]);
546
+ $this->prepareResponse(false, true);
547
  return false;
548
  }
549
 
550
 
551
  // Prepare response
552
+ $this->prepareResponse(false, true);
553
  // Not finished
554
  return true;
555
  }
558
  * Checks Whether There is Any Job to Execute or Not
559
  * @return bool
560
  */
561
+ protected function isFinished()
562
+ {
563
+ if ($this->options->currentStep >= $this->options->totalSteps) {
564
  return true;
565
  }
566
  }
569
  * Save files
570
  * @return bool
571
  */
572
+ protected function saveProgress()
573
+ {
574
  return $this->saveOptions();
575
  }
576
 
578
  * Get files
579
  * @return void
580
  */
581
+ protected function getFiles()
582
+ {
583
  $fileName = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
584
 
585
+ if (false === ($this->files = @file_get_contents($fileName))) {
586
  $this->files = array();
587
  return;
588
  }
589
 
590
+ $this->files = explode(PHP_EOL, $this->files);
591
  }
592
 
 
 
 
 
 
 
 
 
 
 
 
593
 
594
  /**
595
  * Check if directory is excluded
596
  * @param string $directory
597
  * @return bool
598
  */
599
+ protected function isDirectoryExcluded($directory)
600
+ {
601
+ $directory = wpstg_replace_windows_directory_separator($directory);
602
+ foreach ($this->options->excludedDirectories as $excludedDirectory) {
603
+ if (empty($excludedDirectory)) {
604
+ continue;
605
+ }
606
+ $excludedDirectory = wpstg_replace_windows_directory_separator($excludedDirectory);
607
+ if (strpos(trailingslashit($directory), trailingslashit($excludedDirectory)) === 0) {
608
  return true;
609
  }
610
  }
Backend/Modules/Jobs/Multisite/Files.php CHANGED
@@ -3,13 +3,9 @@
3
  namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
 
5
  use WPStaging\Backend\Modules\Jobs\JobExecutable;
6
- // No Direct Access
7
  use WPStaging\Utils\Logger;
8
 
9
- if (!defined("WPINC")) {
10
- die;
11
- }
12
-
13
  /**
14
  * Class Files
15
  * @package WPStaging\Backend\Modules\Jobs
@@ -31,6 +27,11 @@ class Files extends JobExecutable
31
  */
32
  private $destination;
33
 
 
 
 
 
 
34
  /**
35
  * Initialization
36
  */
@@ -167,8 +168,7 @@ class Files extends JobExecutable
167
 
168
  // Directory is excluded
169
  if ($this->isDirectoryExcluded($directory)) {
170
- $this->debugLog("Skipping directory by rule: {$file}",
171
- Logger::TYPE_INFO);
172
  return false;
173
  }
174
 
@@ -264,8 +264,7 @@ class Files extends JobExecutable
264
  $destinationPath = $this->destination.$relativePath;
265
  $destinationDirectory = dirname($destinationPath);
266
 
267
- if (!is_dir($destinationDirectory) && !@mkdir($destinationDirectory,
268
- wpstg_get_permissions_for_directory(), true)) {
269
  $this->log("Files: Can not create directory {$destinationDirectory}",
270
  Logger::TYPE_ERROR);
271
  return false;
@@ -338,7 +337,7 @@ class Files extends JobExecutable
338
  /**
339
  * Check if certain file is excluded from copying process
340
  *
341
- * @param string $file filename including ending without full path
342
  * @return boolean
343
  */
344
  private function isFileExcluded($file)
@@ -346,27 +345,16 @@ class Files extends JobExecutable
346
 
347
  $excludedFiles = (array) $this->options->excludedFiles;
348
 
349
- $basenameFile = basename($file);
350
-
351
  // Remove .htaccess and web.config from 'excludedFiles' if staging site is copied to a subdomain
352
  if (false === $this->isIdenticalHostname()) {
353
  $excludedFiles = \array_diff($excludedFiles,
354
  array("web.config", ".htaccess"));
355
  }
356
 
357
-
358
- // Check for file name
359
- if (in_array($basenameFile, $excludedFiles)) {
360
  return true;
361
  }
362
 
363
- // Check for wildcard patterns like *.log or *-log*
364
- foreach ($excludedFiles as $pattern) {
365
- if (wpstg_fnmatch($pattern, $basenameFile)) {
366
- return true;
367
- }
368
- }
369
-
370
  // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
371
  // because if the updating process fails, the staging site would not be accessable any longer
372
  if (isset($this->options->mainJob) && $this->options->mainJob == "updating"
@@ -374,7 +362,6 @@ class Files extends JobExecutable
374
  return true;
375
  }
376
 
377
-
378
  return false;
379
  }
380
 
@@ -440,8 +427,7 @@ class Files extends JobExecutable
440
  private function isDirectoryExcluded($directory)
441
  {
442
  // Make sure that wp-staging-pro directory / plugin is never excluded
443
- if (false !== strpos($directory, 'wp-staging') || false !== strpos($directory,
444
- 'wp-staging-pro')) {
445
  return false;
446
  }
447
 
@@ -475,4 +461,4 @@ class Files extends JobExecutable
475
 
476
  return false;
477
  }
478
- }
3
  namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
 
5
  use WPStaging\Backend\Modules\Jobs\JobExecutable;
6
+ use WPStaging\Manager\FileSystem\FileManager;
7
  use WPStaging\Utils\Logger;
8
 
 
 
 
 
9
  /**
10
  * Class Files
11
  * @package WPStaging\Backend\Modules\Jobs
27
  */
28
  private $destination;
29
 
30
+ /**
31
+ * @var object
32
+ */
33
+ private $fileSystem;
34
+
35
  /**
36
  * Initialization
37
  */
168
 
169
  // Directory is excluded
170
  if ($this->isDirectoryExcluded($directory)) {
171
+ $this->debugLog("Skipping directory by rule: {$file}",Logger::TYPE_INFO);
 
172
  return false;
173
  }
174
 
264
  $destinationPath = $this->destination.$relativePath;
265
  $destinationDirectory = dirname($destinationPath);
266
 
267
+ if (!is_dir($destinationDirectory) && !@mkdir($destinationDirectory, wpstg_get_permissions_for_directory(), true)) {
 
268
  $this->log("Files: Can not create directory {$destinationDirectory}",
269
  Logger::TYPE_ERROR);
270
  return false;
337
  /**
338
  * Check if certain file is excluded from copying process
339
  *
340
+ * @param string $file full path + filename
341
  * @return boolean
342
  */
343
  private function isFileExcluded($file)
345
 
346
  $excludedFiles = (array) $this->options->excludedFiles;
347
 
 
 
348
  // Remove .htaccess and web.config from 'excludedFiles' if staging site is copied to a subdomain
349
  if (false === $this->isIdenticalHostname()) {
350
  $excludedFiles = \array_diff($excludedFiles,
351
  array("web.config", ".htaccess"));
352
  }
353
 
354
+ if ((new FileManager)->isFilenameExcluded($file, $excludedFiles)) {
 
 
355
  return true;
356
  }
357
 
 
 
 
 
 
 
 
358
  // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
359
  // because if the updating process fails, the staging site would not be accessable any longer
360
  if (isset($this->options->mainJob) && $this->options->mainJob == "updating"
362
  return true;
363
  }
364
 
 
365
  return false;
366
  }
367
 
427
  private function isDirectoryExcluded($directory)
428
  {
429
  // Make sure that wp-staging-pro directory / plugin is never excluded
430
+ if (false !== strpos($directory, 'wp-staging') || false !== strpos($directory,'wp-staging-pro')) {
 
431
  return false;
432
  }
433
 
461
 
462
  return false;
463
  }
464
+ }
Backend/Modules/Jobs/Multisite/SearchReplace.php CHANGED
@@ -204,16 +204,22 @@ class SearchReplace extends JobExecutable
204
  }
205
 
206
  /**
207
- * Get destination Hostname depending on WP installed in sub dir or not
208
- * Return host name without scheme
 
 
 
 
 
 
209
  * @return string
210
  */
211
  private function getDestinationHostname()
212
  {
213
 
214
- // Staging site is updated so do not change hostname
215
  if ($this->options->mainJob === 'updating') {
216
- // If target hostname is defined in advanced settings prefer its use (pro only)
217
  if (!empty($this->options->cloneHostname)) {
218
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
219
  } else {
@@ -221,17 +227,17 @@ class SearchReplace extends JobExecutable
221
  }
222
  }
223
 
224
- // Target hostname defined in advanced settings (pro only)
225
  if (!empty($this->options->cloneHostname)) {
226
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
227
  }
228
 
229
- // WP installed in sub directory under root
230
  if ($this->isSubDir()) {
231
  return trailingslashit($this->strings->getUrlWithoutScheme(get_home_url())) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName;
232
  }
233
 
234
- // Path to root of main multisite without leading or trailing slash e.g.: wordpress
235
  $multisitePath = defined('PATH_CURRENT_SITE') ? PATH_CURRENT_SITE : '/';
236
  return rtrim($this->strings->getUrlWithoutScheme(get_home_url()), '/\\') . $multisitePath . $this->options->cloneDirectoryName;
237
  }
204
  }
205
 
206
  /**
207
+ * Get destination hostname without scheme e.g example.com/staging or staging.example.com
208
+ *
209
+ * Conditions:
210
+ * - Main job is 'update'
211
+ * - WP installed in sub dir
212
+ * - Target hostname in advanced settings defined (Pro version only)
213
+ *
214
+ * @todo Complex conditions. Might need refactor
215
  * @return string
216
  */
217
  private function getDestinationHostname()
218
  {
219
 
220
+ // Update process: Neither 'push' nor 'clone'
221
  if ($this->options->mainJob === 'updating') {
222
+ // Defined and created in advanced settings with pro version
223
  if (!empty($this->options->cloneHostname)) {
224
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
225
  } else {
227
  }
228
  }
229
 
230
+ // Clone process: Defined and created in advanced settings with pro version
231
  if (!empty($this->options->cloneHostname)) {
232
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
233
  }
234
 
235
+ // Clone process: WP installed in sub directory under root
236
  if ($this->isSubDir()) {
237
  return trailingslashit($this->strings->getUrlWithoutScheme(get_home_url())) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName;
238
  }
239
 
240
+ // Clone process: DefaultPath to root of main multisite without leading or trailing slash e.g.: wordpress
241
  $multisitePath = defined('PATH_CURRENT_SITE') ? PATH_CURRENT_SITE : '/';
242
  return rtrim($this->strings->getUrlWithoutScheme(get_home_url()), '/\\') . $multisitePath . $this->options->cloneDirectoryName;
243
  }
Backend/Modules/Jobs/Multisite/SearchReplaceExternal.php CHANGED
@@ -188,16 +188,22 @@ class SearchReplaceExternal extends JobExecutable
188
  }
189
 
190
  /**
191
- * Get destination Hostname depending on WP installed in sub dir or not
192
- * Retun host name without scheme
 
 
 
 
 
 
193
  * @return string
194
  */
195
  private function getDestinationHostname()
196
  {
197
 
198
- // Staging site is updated so do not change hostname
199
  if ($this->options->mainJob === 'updating') {
200
- // If target hostname is defined in advanced settings prefer its use (pro only)
201
  if (!empty($this->options->cloneHostname)) {
202
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
203
  } else {
@@ -205,22 +211,21 @@ class SearchReplaceExternal extends JobExecutable
205
  }
206
  }
207
 
208
- // Target hostname defined in advanced settings (pro only)
209
  if (!empty($this->options->cloneHostname)) {
210
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
211
  }
212
 
213
- // WP installed in sub directory under root
214
  if ($this->isSubDir()) {
215
  return trailingslashit($this->strings->getUrlWithoutScheme(get_home_url())) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName;
216
  }
217
 
218
- // Default: Path to root of main multisite without leading or trailing slash e.g.: wordpress
219
  $multisitePath = defined('PATH_CURRENT_SITE') ? PATH_CURRENT_SITE : '/';
220
  return rtrim($this->strings->getUrlWithoutScheme($this->multisiteDomainWithoutScheme), '/\\') . $multisitePath . $this->options->cloneDirectoryName;
221
  }
222
 
223
-
224
  /**
225
  * Get the install sub directory if WP is installed in sub directory
226
  * @return string
188
  }
189
 
190
  /**
191
+ * Get destination hostname without scheme e.g example.com/staging or staging.example.com
192
+ *
193
+ * Conditions:
194
+ * - Main job is 'update'
195
+ * - WP installed in sub dir
196
+ * - Target hostname in advanced settings defined (Pro version only)
197
+ *
198
+ * @todo Complex conditions. Might need refactor
199
  * @return string
200
  */
201
  private function getDestinationHostname()
202
  {
203
 
204
+ // Update process: Neither 'push' nor 'clone'
205
  if ($this->options->mainJob === 'updating') {
206
+ // Defined and created in advanced settings with pro version
207
  if (!empty($this->options->cloneHostname)) {
208
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
209
  } else {
211
  }
212
  }
213
 
214
+ // Clone process: Defined and created in advanced settings with pro version
215
  if (!empty($this->options->cloneHostname)) {
216
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
217
  }
218
 
219
+ // Clone process: WP installed in sub directory under root
220
  if ($this->isSubDir()) {
221
  return trailingslashit($this->strings->getUrlWithoutScheme(get_home_url())) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName;
222
  }
223
 
224
+ // Clone process: DefaultPath to root of main multisite without leading or trailing slash e.g.: wordpress
225
  $multisitePath = defined('PATH_CURRENT_SITE') ? PATH_CURRENT_SITE : '/';
226
  return rtrim($this->strings->getUrlWithoutScheme($this->multisiteDomainWithoutScheme), '/\\') . $multisitePath . $this->options->cloneDirectoryName;
227
  }
228
 
 
229
  /**
230
  * Get the install sub directory if WP is installed in sub directory
231
  * @return string
Backend/Modules/Jobs/SearchReplace.php CHANGED
@@ -175,7 +175,7 @@ class SearchReplace extends JobExecutable
175
 
176
  /**
177
  * Get source Hostname depending on wheather WP has been installed in sub dir or not
178
- * @return type
179
  */
180
  private function getSourceHostname()
181
  {
@@ -189,15 +189,21 @@ class SearchReplace extends JobExecutable
189
  }
190
 
191
  /**
192
- * Get destination Hostname depending on WP installed in sub dir or not
 
 
 
 
 
 
 
193
  * @return string
194
  */
195
  private function getDestinationHostname()
196
  {
197
-
198
- // Staging site is updated so do not change hostname
199
  if ($this->options->mainJob === 'updating') {
200
- // If target hostname is defined in advanced settings prefer its use (pro only)
201
  if (!empty($this->options->cloneHostname)) {
202
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
203
  } else {
@@ -205,17 +211,17 @@ class SearchReplace extends JobExecutable
205
  }
206
  }
207
 
208
- // Target hostname defined in advanced settings (pro only)
209
  if (!empty($this->options->cloneHostname)) {
210
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
211
  }
212
 
213
- // WP installed in sub directory under root
214
  if ($this->isSubDir()) {
215
  return $this->strings->getUrlWithoutScheme(trailingslashit($this->options->destinationHostname) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName);
216
  }
217
 
218
- // Default destination hostname
219
  return $this->strings->getUrlWithoutScheme(trailingslashit($this->options->destinationHostname) . $this->options->cloneDirectoryName);
220
  }
221
 
175
 
176
  /**
177
  * Get source Hostname depending on wheather WP has been installed in sub dir or not
178
+ * @return string
179
  */
180
  private function getSourceHostname()
181
  {
189
  }
190
 
191
  /**
192
+ * Get destination hostname without scheme e.g example.com/staging or staging.example.com
193
+ *
194
+ * Conditions:
195
+ * - Main job is 'update'
196
+ * - WP installed in sub dir
197
+ * - Target hostname in advanced settings defined (Pro version only)
198
+ *
199
+ * @todo Complex conditions. Might need refactor
200
  * @return string
201
  */
202
  private function getDestinationHostname()
203
  {
204
+ // Update process: Neither 'push' nor 'clone'
 
205
  if ($this->options->mainJob === 'updating') {
206
+ // Defined and created in advanced settings with pro version
207
  if (!empty($this->options->cloneHostname)) {
208
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
209
  } else {
211
  }
212
  }
213
 
214
+ // Clone process: Defined and created in advanced settings with pro version
215
  if (!empty($this->options->cloneHostname)) {
216
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
217
  }
218
 
219
+ // Clone process: WP installed in sub directory under root
220
  if ($this->isSubDir()) {
221
  return $this->strings->getUrlWithoutScheme(trailingslashit($this->options->destinationHostname) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName);
222
  }
223
 
224
+ // Clone process: Default
225
  return $this->strings->getUrlWithoutScheme(trailingslashit($this->options->destinationHostname) . $this->options->cloneDirectoryName);
226
  }
227
 
Backend/Modules/Jobs/SearchReplaceExternal.php CHANGED
@@ -192,16 +192,21 @@ class SearchReplaceExternal extends JobExecutable
192
  }
193
 
194
  /**
195
- * Get destination Hostname depending on WP installed in sub dir or not
196
- * Retun host name without scheme
 
 
 
 
 
 
197
  * @return string
198
  */
199
  private function getDestinationHostname()
200
  {
201
-
202
- // Staging site is updated so do not change hostname
203
  if ($this->options->mainJob === 'updating') {
204
- // If target hostname is defined in advanced settings prefer its use (pro only)
205
  if (!empty($this->options->cloneHostname)) {
206
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
207
  } else {
@@ -209,21 +214,20 @@ class SearchReplaceExternal extends JobExecutable
209
  }
210
  }
211
 
212
- // Target hostname defined in advanced settings (pro only)
213
  if (!empty($this->options->cloneHostname)) {
214
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
215
  }
216
 
217
- // WP installed in sub directory under root
218
  if ($this->isSubDir()) {
219
  return $this->strings->getUrlWithoutScheme(trailingslashit($this->options->destinationHostname) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName);
220
  }
221
 
222
- // Default destination hostname
223
  return $this->strings->getUrlWithoutScheme(trailingslashit($this->options->destinationHostname) . $this->options->cloneDirectoryName);
224
  }
225
 
226
-
227
  /**
228
  * Get the install sub directory if WP is installed in sub directory
229
  * @return string
192
  }
193
 
194
  /**
195
+ * Get destination hostname without scheme e.g example.com/staging or staging.example.com
196
+ *
197
+ * Conditions:
198
+ * - Main job is 'update'
199
+ * - WP installed in sub dir
200
+ * - Target hostname in advanced settings defined (Pro version only)
201
+ *
202
+ * @todo Complex conditions. Might need refactor
203
  * @return string
204
  */
205
  private function getDestinationHostname()
206
  {
207
+ // Update process: Neither 'push' nor 'clone'
 
208
  if ($this->options->mainJob === 'updating') {
209
+ // Defined and created in advanced settings with pro version
210
  if (!empty($this->options->cloneHostname)) {
211
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
212
  } else {
214
  }
215
  }
216
 
217
+ // Clone process: Defined and created in advanced settings with pro version
218
  if (!empty($this->options->cloneHostname)) {
219
  return $this->strings->getUrlWithoutScheme($this->options->cloneHostname);
220
  }
221
 
222
+ // Clone process: WP installed in sub directory under root
223
  if ($this->isSubDir()) {
224
  return $this->strings->getUrlWithoutScheme(trailingslashit($this->options->destinationHostname) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName);
225
  }
226
 
227
+ // Clone process: Default
228
  return $this->strings->getUrlWithoutScheme(trailingslashit($this->options->destinationHostname) . $this->options->cloneDirectoryName);
229
  }
230
 
 
231
  /**
232
  * Get the install sub directory if WP is installed in sub directory
233
  * @return string
Backend/Modules/Jobs/Updating.php CHANGED
@@ -9,53 +9,56 @@ use WPStaging\Utils\Helper;
9
  * Class Cloning
10
  * @package WPStaging\Backend\Modules\Jobs
11
  */
12
- class Updating extends Job {
 
13
 
14
  /**
15
  * External Database Used
16
- * @var bool
17
  */
18
  public $isExternal;
19
 
20
  /**
21
  * Initialize is called in \Job
22
  */
23
- public function initialize() {
24
- $this->db = WPStaging::getInstance()->get( "wpdb" );
 
25
  }
26
 
27
  /**
28
  * Save Chosen Cloning Settings
29
  * @return bool
30
  */
31
- public function save() {
32
- if( !isset( $_POST ) || !isset( $_POST["cloneID"] ) ) {
 
33
  return false;
34
  }
35
 
36
  // Delete files to copy listing
37
- $this->cache->delete( "files_to_copy" );
38
 
39
  // Generate Options
40
  // Clone
41
  //$this->options->clone = $_POST["cloneID"];
42
- $this->options->clone = preg_replace( "#\W+#", '-', strtolower( $_POST["cloneID"] ) );
43
- $this->options->cloneDirectoryName = preg_replace( "#\W+#", '-', strtolower( $this->options->clone ) );
44
- $this->options->cloneNumber = 1;
45
- $this->options->includedDirectories = array();
46
- $this->options->excludedDirectories = array();
47
- $this->options->extraDirectories = array();
48
- $this->options->excludedFiles = array(
49
  '.htaccess',
50
  '.DS_Store',
51
- '.git',
52
- '.svn',
53
- '.tmp',
54
  'desktop.ini',
55
  '.gitignore',
56
- '.log',
57
  'object-cache.php',
58
- 'web.config' // Important: Windows IIS configuartion file. Must not be in the staging site!
59
 
60
  );
61
 
@@ -72,32 +75,32 @@ class Updating extends Job {
72
  $this->options->job = new \stdClass();
73
 
74
  // Check if clone data already exists and use that one
75
- if( isset( $this->options->existingClones[$this->options->clone] ) ) {
76
- $this->options->cloneNumber = $this->options->existingClones[$this->options->clone]['number'];
77
- $this->options->databaseUser = $this->options->existingClones[$this->options->clone]['databaseUser'];
78
- $this->options->databasePassword = $this->options->existingClones[$this->options->clone]['databasePassword'];
79
- $this->options->databaseDatabase = $this->options->existingClones[$this->options->clone]['databaseDatabase'];
80
- $this->options->databaseServer = $this->options->existingClones[$this->options->clone]['databaseServer'];
81
- $this->options->databasePrefix = $this->options->existingClones[$this->options->clone]['databasePrefix'];
82
  $this->options->destinationHostname = $this->options->existingClones[$this->options->clone]['url'];
83
- $this->options->prefix = $this->getStagingPrefix();
84
- $helper = new Helper();
85
- $this->options->homeHostname = $helper->get_home_url_without_scheme();
86
  } else {
87
- wp_die( 'Fatal Error: Can not update clone because there is no clone data.' );
88
  }
89
 
90
- $this->isExternal = (empty( $this->options->databaseUser ) && empty( $this->options->databasePassword )) ? false : true;
91
 
92
  // Included Tables
93
- if( isset( $_POST["includedTables"] ) && is_array( $_POST["includedTables"] ) ) {
94
  $this->options->tables = $_POST["includedTables"];
95
  } else {
96
  $this->options->tables = array();
97
  }
98
 
99
  // Excluded Directories
100
- if( isset( $_POST["excludedDirectories"] ) && is_array( $_POST["excludedDirectories"] ) ) {
101
  $this->options->excludedDirectories = wpstg_urldecode($_POST["excludedDirectories"]);
102
  }
103
 
@@ -110,36 +113,36 @@ class Updating extends Job {
110
  \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'peters-login-redirect',
111
  );
112
 
113
- $this->options->excludedDirectories = array_merge( $excludedDirectories, $this->options->excludedDirectories );
114
 
115
  // Included Directories
116
- if( isset( $_POST["includedDirectories"] ) && is_array( $_POST["includedDirectories"] ) ) {
117
  $this->options->includedDirectories = wpstg_urldecode($_POST["includedDirectories"]);
118
  }
119
 
120
  // Extra Directories
121
- if( isset( $_POST["extraDirectories"] ) && !empty( $_POST["extraDirectories"] ) ) {
122
  $this->options->extraDirectories = wpstg_urldecode($_POST["extraDirectories"]);
123
  }
124
 
125
  $this->options->cloneDir = '';
126
- if( isset( $_POST["cloneDir"] ) && !empty( $_POST["cloneDir"] ) ) {
127
- $this->options->cloneDir = wpstg_urldecode(trailingslashit( $_POST["cloneDir"] ));
128
  }
129
 
130
  $this->options->destinationDir = $this->getDestinationDir();
131
 
132
  $this->options->cloneHostname = '';
133
- if( isset( $_POST["cloneHostname"] ) && !empty( $_POST["cloneHostname"] ) ) {
134
  $this->options->cloneHostname = $_POST["cloneHostname"];
135
  }
136
 
137
  // Directories to Copy
138
  $this->options->directoriesToCopy = array_merge(
139
- $this->options->includedDirectories, $this->options->extraDirectories
140
  );
141
 
142
- array_unshift( $this->options->directoriesToCopy, ABSPATH );
143
 
144
  // Process lock state
145
  $this->options->isRunning = true;
@@ -151,45 +154,47 @@ class Updating extends Job {
151
  * Get Destination Directory including staging subdirectory
152
  * @return type
153
  */
154
- private function getDestinationDir() {
155
- if( empty( $this->options->cloneDir ) ) {
156
- return trailingslashit( \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName );
 
157
  }
158
  //return trailingslashit( $this->options->cloneDir . $this->options->cloneDirectoryName );
159
- return trailingslashit( $this->options->cloneDir );
160
  }
161
 
162
  /**
163
  * Check and return prefix of the staging site
164
  */
165
- public function getStagingPrefix() {
 
166
  // prefix not defined! Happens if staging site has ben generated with older version of wpstg
167
  // Try to get staging prefix from wp-config.php of staging site
168
  $this->options->prefix = $this->options->existingClones[$this->options->clone]['prefix'];
169
- if( empty( $this->options->prefix ) ) {
170
  // Throw error if wp-config.php is not readable
171
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
172
- if( false === ($content = @file_get_contents( $path )) ) {
173
- $this->log( "Can not open {$path}. Can't read contents", Logger::TYPE_ERROR );
174
- $this->returnException( "Fatal Error: Can not read {$path} to get correct table prefix. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com" );
175
- wp_die( "Fatal Error: Can not read {$path} to get correct table prefix. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com" );
176
  } else {
177
  // Get prefix from wp-config.php
178
- preg_match( "/table_prefix\s*=\s*'(\w*)';/", $content, $matches );
179
 
180
- if( !empty( $matches[1] ) ) {
181
  $this->options->prefix = $matches[1];
182
  } else {
183
- $this->returnException( "Fatal Error: Can not detect prefix from {$path}. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com" );
184
- wp_die( "Fatal Error: Can not detect prefix from {$path}. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com" );
185
  }
186
  }
187
  }
188
 
189
  // Die() if staging prefix is the same as the live prefix
190
- if( false === $this->isExternal && $this->db->prefix == $this->options->prefix ) {
191
- $this->log( "Fatal Error: Can not update staging site. Prefix. '{$this->options->prefix}' is used for the live site. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com" );
192
- wp_die( "Fatal Error: Can not update staging site. Prefix. '{$this->options->prefix}' is used for the live site. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com" );
193
  }
194
 
195
  // Else
@@ -200,7 +205,8 @@ class Updating extends Job {
200
  * Start the cloning job
201
  * not used but is abstract
202
  */
203
- public function start() {
 
204
  }
205
 
206
  }
9
  * Class Cloning
10
  * @package WPStaging\Backend\Modules\Jobs
11
  */
12
+ class Updating extends Job
13
+ {
14
 
15
  /**
16
  * External Database Used
17
+ * @var bool
18
  */
19
  public $isExternal;
20
 
21
  /**
22
  * Initialize is called in \Job
23
  */
24
+ public function initialize()
25
+ {
26
+ $this->db = WPStaging::getInstance()->get("wpdb");
27
  }
28
 
29
  /**
30
  * Save Chosen Cloning Settings
31
  * @return bool
32
  */
33
+ public function save()
34
+ {
35
+ if (!isset($_POST) || !isset($_POST["cloneID"])) {
36
  return false;
37
  }
38
 
39
  // Delete files to copy listing
40
+ $this->cache->delete("files_to_copy");
41
 
42
  // Generate Options
43
  // Clone
44
  //$this->options->clone = $_POST["cloneID"];
45
+ $this->options->clone = preg_replace("#\W+#", '-', strtolower($_POST["cloneID"]));
46
+ $this->options->cloneDirectoryName = preg_replace("#\W+#", '-', strtolower($this->options->clone));
47
+ $this->options->cloneNumber = 1;
48
+ $this->options->includedDirectories = array();
49
+ $this->options->excludedDirectories = array();
50
+ $this->options->extraDirectories = array();
51
+ $this->options->excludedFiles = array(
52
  '.htaccess',
53
  '.DS_Store',
54
+ '*.git',
55
+ '*.svn',
56
+ '*.tmp',
57
  'desktop.ini',
58
  '.gitignore',
59
+ '*.log',
60
  'object-cache.php',
61
+ 'web.config' // Important: Windows IIS configuration file. Do not copy this to the staging site is staging site is placed into subfolder
62
 
63
  );
64
 
75
  $this->options->job = new \stdClass();
76
 
77
  // Check if clone data already exists and use that one
78
+ if (isset($this->options->existingClones[$this->options->clone])) {
79
+ $this->options->cloneNumber = $this->options->existingClones[$this->options->clone]['number'];
80
+ $this->options->databaseUser = $this->options->existingClones[$this->options->clone]['databaseUser'];
81
+ $this->options->databasePassword = $this->options->existingClones[$this->options->clone]['databasePassword'];
82
+ $this->options->databaseDatabase = $this->options->existingClones[$this->options->clone]['databaseDatabase'];
83
+ $this->options->databaseServer = $this->options->existingClones[$this->options->clone]['databaseServer'];
84
+ $this->options->databasePrefix = $this->options->existingClones[$this->options->clone]['databasePrefix'];
85
  $this->options->destinationHostname = $this->options->existingClones[$this->options->clone]['url'];
86
+ $this->options->prefix = $this->getStagingPrefix();
87
+ $helper = new Helper();
88
+ $this->options->homeHostname = $helper->get_home_url_without_scheme();
89
  } else {
90
+ wp_die('Fatal Error: Can not update clone because there is no clone data.');
91
  }
92
 
93
+ $this->isExternal = (empty($this->options->databaseUser) && empty($this->options->databasePassword)) ? false : true;
94
 
95
  // Included Tables
96
+ if (isset($_POST["includedTables"]) && is_array($_POST["includedTables"])) {
97
  $this->options->tables = $_POST["includedTables"];
98
  } else {
99
  $this->options->tables = array();
100
  }
101
 
102
  // Excluded Directories
103
+ if (isset($_POST["excludedDirectories"]) && is_array($_POST["excludedDirectories"])) {
104
  $this->options->excludedDirectories = wpstg_urldecode($_POST["excludedDirectories"]);
105
  }
106
 
113
  \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'peters-login-redirect',
114
  );
115
 
116
+ $this->options->excludedDirectories = array_merge($excludedDirectories, $this->options->excludedDirectories);
117
 
118
  // Included Directories
119
+ if (isset($_POST["includedDirectories"]) && is_array($_POST["includedDirectories"])) {
120
  $this->options->includedDirectories = wpstg_urldecode($_POST["includedDirectories"]);
121
  }
122
 
123
  // Extra Directories
124
+ if (isset($_POST["extraDirectories"]) && !empty($_POST["extraDirectories"])) {
125
  $this->options->extraDirectories = wpstg_urldecode($_POST["extraDirectories"]);
126
  }
127
 
128
  $this->options->cloneDir = '';
129
+ if (isset($_POST["cloneDir"]) && !empty($_POST["cloneDir"])) {
130
+ $this->options->cloneDir = wpstg_urldecode(trailingslashit($_POST["cloneDir"]));
131
  }
132
 
133
  $this->options->destinationDir = $this->getDestinationDir();
134
 
135
  $this->options->cloneHostname = '';
136
+ if (isset($_POST["cloneHostname"]) && !empty($_POST["cloneHostname"])) {
137
  $this->options->cloneHostname = $_POST["cloneHostname"];
138
  }
139
 
140
  // Directories to Copy
141
  $this->options->directoriesToCopy = array_merge(
142
+ $this->options->includedDirectories, $this->options->extraDirectories
143
  );
144
 
145
+ array_unshift($this->options->directoriesToCopy, ABSPATH);
146
 
147
  // Process lock state
148
  $this->options->isRunning = true;
154
  * Get Destination Directory including staging subdirectory
155
  * @return type
156
  */
157
+ private function getDestinationDir()
158
+ {
159
+ if (empty($this->options->cloneDir)) {
160
+ return trailingslashit(\WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName);
161
  }
162
  //return trailingslashit( $this->options->cloneDir . $this->options->cloneDirectoryName );
163
+ return trailingslashit($this->options->cloneDir);
164
  }
165
 
166
  /**
167
  * Check and return prefix of the staging site
168
  */
169
+ public function getStagingPrefix()
170
+ {
171
  // prefix not defined! Happens if staging site has ben generated with older version of wpstg
172
  // Try to get staging prefix from wp-config.php of staging site
173
  $this->options->prefix = $this->options->existingClones[$this->options->clone]['prefix'];
174
+ if (empty($this->options->prefix)) {
175
  // Throw error if wp-config.php is not readable
176
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
177
+ if (false === ($content = @file_get_contents($path))) {
178
+ $this->log("Can not open {$path}. Can't read contents", Logger::TYPE_ERROR);
179
+ $this->returnException("Fatal Error: Can not read {$path} to get correct table prefix. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
180
+ wp_die("Fatal Error: Can not read {$path} to get correct table prefix. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
181
  } else {
182
  // Get prefix from wp-config.php
183
+ preg_match("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
184
 
185
+ if (!empty($matches[1])) {
186
  $this->options->prefix = $matches[1];
187
  } else {
188
+ $this->returnException("Fatal Error: Can not detect prefix from {$path}. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
189
+ wp_die("Fatal Error: Can not detect prefix from {$path}. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
190
  }
191
  }
192
  }
193
 
194
  // Die() if staging prefix is the same as the live prefix
195
+ if (false === $this->isExternal && $this->db->prefix == $this->options->prefix) {
196
+ $this->log("Fatal Error: Can not update staging site. Prefix. '{$this->options->prefix}' is used for the live site. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
197
+ wp_die("Fatal Error: Can not update staging site. Prefix. '{$this->options->prefix}' is used for the live site. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
198
  }
199
 
200
  // Else
205
  * Start the cloning job
206
  * not used but is abstract
207
  */
208
+ public function start()
209
+ {
210
  }
211
 
212
  }
Backend/Modules/SystemInfo.php CHANGED
@@ -9,7 +9,7 @@ use WPStaging\Utils;
9
  use WPStaging\Utils\Multisite;
10
 
11
  // No Direct Access
12
- if( !defined( "WPINC" ) ) {
13
  die;
14
  }
15
 
@@ -17,7 +17,8 @@ if( !defined( "WPINC" ) ) {
17
  * Class SystemInfo
18
  * @package WPStaging\Backend\Modules
19
  */
20
- class SystemInfo extends InjectionAware {
 
21
 
22
  /**
23
  * @var bool
@@ -33,16 +34,18 @@ class SystemInfo extends InjectionAware {
33
  /**
34
  * Initialize class
35
  */
36
- public function initialize() {
 
37
  $this->isMultiSite = is_multisite();
38
- $this->helper = new Utils\Helper();
39
  }
40
 
41
  /**
42
  * Magic method
43
  * @return string
44
  */
45
- public function __toString() {
 
46
  return $this->get();
47
  }
48
 
@@ -50,7 +53,8 @@ class SystemInfo extends InjectionAware {
50
  * Get System Information as text
51
  * @return string
52
  */
53
- public function get() {
 
54
  $output = "### Begin System Info ###" . PHP_EOL . PHP_EOL;
55
 
56
  $output .= $this->wpstaging();
@@ -82,7 +86,8 @@ class SystemInfo extends InjectionAware {
82
  * @param string $string
83
  * @return string
84
  */
85
- public function header( $string ) {
 
86
  return PHP_EOL . "-- {$string}" . PHP_EOL . PHP_EOL;
87
  }
88
 
@@ -92,18 +97,20 @@ class SystemInfo extends InjectionAware {
92
  * @param string $value
93
  * @return string
94
  */
95
- public function info( $title, $value ) {
96
- return str_pad( $title, 56, ' ', STR_PAD_RIGHT ) . $value . PHP_EOL;
 
97
  }
98
 
99
  /**
100
  * Theme Information
101
  * @return string
102
  */
103
- public function theme() {
 
104
  // Versions earlier than 3.4
105
- if( get_bloginfo( "version" ) < "3.4" ) {
106
- $themeData = get_theme_data( get_stylesheet_directory() . "/style.css" );
107
  return "{$themeData["Name"]} {$themeData["Version"]}";
108
  }
109
 
@@ -115,129 +122,134 @@ class SystemInfo extends InjectionAware {
115
  * Site Information
116
  * @return string
117
  */
118
- public function site() {
119
- $output = $this->header( "-- Site Info" );
120
- $output .= $this->info( "Site URL:", site_url() );
121
- $output .= $this->info( "Home URL:", $this->helper->get_home_url() );
122
- $output .= $this->info( "Home Path:", get_home_path() );
123
- $output .= $this->info( "ABSPATH:", ABSPATH );
124
- $output .= $this->info( "Installed in subdir:", ( $this->isSubDir() ? 'Yes' : 'No' ) );
125
-
126
- return apply_filters( "wpstg_sysinfo_after_site_info", $output );
 
127
  }
128
 
129
  /**
130
  * Multisite information
131
  * @return string
132
  */
133
- private function getMultisiteInfo() {
134
- if( !$this->isMultiSite ) {
 
135
  return '';
136
  }
137
 
138
  $multisite = new Multisite();
139
 
140
- $output = $this->info( "Multisite:", ($this->isMultiSite ? "Yes" : "No" ) );
141
- $output .= $this->info( "Multisite Blog ID:", get_current_blog_id() );
142
- $output .= $this->info( "MultiSite URL:", $multisite->getHomeURL() );
143
- $output .= $this->info( "MultiSite URL without scheme:", $multisite->getHomeUrlWithoutScheme() );
144
- $output .= $this->info( "MultiSite is Main Site:", is_main_site() ? 'Yes' : 'No' );
145
 
146
- return apply_filters( "wpstg_sysinfo_after_multisite_info", $output );
147
  }
148
 
149
  /**
150
  * Wp Staging plugin Information
151
  * @return string
152
  */
153
- public function wpstaging() {
 
154
  // Get wpstg settings
155
- $settings = ( object ) get_option( 'wpstg_settings', array() );
156
 
157
  // Clones data < 1.1.6.x
158
- $clones = ( object ) get_option( 'wpstg_existing_clones', array() );
159
  // Clones data version > 2.x
160
- $clonesBeta = get_option( 'wpstg_existing_clones_beta', array() );
161
 
162
 
163
  $output = "-- WP Staging Settings" . PHP_EOL . PHP_EOL;
164
- $output .= $this->info( "Query Limit:", isset( $settings->queryLimit ) ? $settings->queryLimit : 'undefined' );
165
- $output .= $this->info( "DB Search & Replace Limit:", isset( $settings->querySRLimit ) ? $settings->querySRLimit : 'undefined' );
166
- $output .= $this->info( "File Copy Limit:", isset( $settings->fileLimit ) ? $settings->fileLimit : 'undefined' );
167
- $output .= $this->info( "Batch Size:", isset( $settings->batchSize ) ? $settings->batchSize : 'undefined' );
168
- $output .= $this->info( "CPU Load:", isset( $settings->cpuLoad ) ? $settings->cpuLoad : 'undefined' );
169
- $output .= $this->info( "WP in Subdir:", $this->isSubDir() ? 'true' : 'false' );
170
 
171
  $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version < 1.1.6.x" . PHP_EOL . PHP_EOL;
172
 
173
- foreach ( $clones as $key => $value ) {
174
- $output .= $this->info( "Site name & subfolder :", $value );
175
  }
176
  $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version > 2.0.x" . PHP_EOL . PHP_EOL;
177
 
178
- foreach ( $clonesBeta as $key => $clone ) {
179
 
180
- $path = !empty( $clone['path'] ) ? $clone['path'] : 'undefined';
181
 
182
- $output .= $this->info( "Number:", isset( $clone['number'] ) ? $clone['number'] : 'undefined' );
183
- $output .= $this->info( "directoryName:", isset( $clone['directoryName'] ) ? $clone['directoryName'] : 'undefined' );
184
- $output .= $this->info( "Path:", $path );
185
- $output .= $this->info( "URL:", isset( $clone['url'] ) ? $clone['url'] : 'undefined' );
186
- $output .= $this->info( "DB Prefix:", isset( $clone['prefix'] ) ? $clone['prefix'] : 'undefined' );
187
- $output .= $this->info( "DB Prefix wp-config.php:", $this->getStagingPrefix( $clone ) );
188
- $output .= $this->info( "WP Staging Version:", isset( $clone['version'] ) ? $clone['version'] : 'undefined' );
189
- $output .= $this->info( "WP Version:", $this->getStagingWpVersion( $path ) ) . PHP_EOL . PHP_EOL;
190
  }
191
 
192
 
193
- $output .= $this->info( "Raw Clones Data:", json_encode( get_option( 'wpstg_existing_clones_beta', 'undefined' ) ) );
194
 
195
  $output .= '' . PHP_EOL;
196
 
197
 
198
  //$output .= PHP_EOL . PHP_EOL;
199
 
200
- $output .= $this->info( "Plugin Pro Version:", get_option( 'wpstgpro_version', 'undefined' ) );
201
- $output .= $this->info( "Plugin Free Version:", get_option( 'wpstg_version', 'undefined' ) );
202
- $output .= $this->info( "Install Date:", get_option( 'wpstg_installDate', 'undefined' ) );
203
- $output .= $this->info( "Upgraded from Pro:", get_option( 'wpstgpro_version_upgraded_from', 'undefined' ) );
204
- $output .= $this->info( "Upgraded from Free:", get_option( 'wpstg_version_upgraded_from', 'undefined' ) );
205
- $output .= $this->info( "Is Staging Site:", wpstg_is_stagingsite() ? 'true' : 'false' ) . PHP_EOL . PHP_EOL;
206
 
207
 
208
- return apply_filters( "wpstg_sysinfo_after_wpstaging_info", $output );
209
  }
210
 
211
  /**
212
  * Browser Information
213
  * @return string
214
  */
215
- public function browser() {
216
- $output = $this->header( "User Browser" );
 
217
  $output .= (new Browser);
218
 
219
- return apply_filters( "wpstg_sysinfo_after_user_browser", $output );
220
  }
221
 
222
  /**
223
  * Frontpage Information when frontpage is set to "page"
224
  * @return string
225
  */
226
- public function frontPage() {
227
- if( get_option( "show_on_front" ) !== "page" ) {
 
228
  return '';
229
  }
230
 
231
- $frontPageID = get_option( "page_on_front" );
232
- $blogPageID = get_option( "page_for_posts" );
233
 
234
  // Front Page
235
- $pageFront = ($frontPageID != 0) ? get_the_title( $frontPageID ) . " (#{$frontPageID})" : "Unset";
236
  // Blog Page ID
237
- $pageBlog = ($blogPageID != 0) ? get_the_title( $blogPageID ) . " (#{$blogPageID})" : "Unset";
238
 
239
- $output = $this->info( "Page On Front:", $pageFront );
240
- $output .= $this->info( "Page For Posts:", $pageBlog );
241
 
242
  return $output;
243
  }
@@ -246,43 +258,44 @@ class SystemInfo extends InjectionAware {
246
  * Check wp_remote_post() functionality
247
  * @return string
248
  */
249
- public function wpRemotePost() {
 
250
  // Make sure wp_remote_post() is working
251
  $wpRemotePost = "wp_remote_post() does not work";
252
 
253
  // Send request
254
  $response = wp_remote_post(
255
- "https://www.paypal.com/cgi-bin/webscr", array(
256
- "sslverify" => false,
257
- "timeout" => 60,
258
- "user-agent" => "WPSTG/" . WPStaging::getVersion(),
259
- "body" => array("cmd" => "_notify-validate")
260
- )
261
  );
262
 
263
  // Validate it worked
264
- if( !is_wp_error( $response ) && 200 <= $response["response"]["code"] && 300 > $response["response"]["code"] ) {
265
  $wpRemotePost = "wp_remote_post() works";
266
  }
267
 
268
- return $this->info( "Remote Post:", $wpRemotePost );
269
  }
270
 
271
  /**
272
  * WordPress Configuration
273
  * @return string
274
  */
275
- public function wp() {
276
- $output = $this->header( "WordPress Configuration" );
277
- $output .= $this->info( "Version:", get_bloginfo( "version" ) );
278
- $output .= $this->info( "Language:", (defined( "WPLANG" ) && WPLANG) ? WPLANG : "en_US" );
 
279
 
280
- $permalinkStructure = get_option( "permalink_structure" );
281
- ;
282
- $output .= $this->info( "Permalink Structure:", ($permalinkStructure) ? $permalinkStructure : "Default" );
283
 
284
- $output .= $this->info( "Active Theme:", $this->theme() );
285
- $output .= $this->info( "Show On Front:", get_option( "show_on_front" ) );
286
 
287
  // Frontpage information
288
  $output .= $this->frontPage();
@@ -291,29 +304,29 @@ class SystemInfo extends InjectionAware {
291
  $output .= $this->wpRemotePost();
292
 
293
  // Table Prefix
294
- $wpDB = $this->di->get( "wpdb" );
295
  $tablePrefix = "DB Prefix: " . $wpDB->prefix . ' ';
296
- $tablePrefix .= "Length: " . strlen( $wpDB->prefix ) . " Status: ";
297
- $tablePrefix .= (strlen( $wpDB->prefix ) > 16) ? " ERROR: Too long" : " Acceptable";
298
 
299
- $output .= $this->info( "Table Prefix:", $tablePrefix );
300
 
301
  // Constants
302
- $output .= $this->info( "WP Content Path:", WP_CONTENT_DIR );
303
- $output .= $this->info( "WP Plugin Dir:", WP_PLUGIN_DIR );
304
- if( defined( 'UPLOADS' ) )
305
- $output .= $this->info( "WP UPLOADS CONST:", UPLOADS );
306
  $uploads = wp_upload_dir();
307
- $output .= $this->info( "WP Uploads Dir:", $uploads['basedir'] );
308
- if( defined( 'WP_TEMP_DIR' ) )
309
- $output .= $this->info( "WP Temp Dir:", WP_TEMP_DIR );
310
 
311
  // WP Debug
312
- $output .= $this->info( "WP_DEBUG:", (defined( "WP_DEBUG" )) ? WP_DEBUG ? "Enabled" : "Disabled" : "Not set" );
313
- $output .= $this->info( "Memory Limit:", WP_MEMORY_LIMIT );
314
- $output .= $this->info( "Registered Post Stati:", implode( ", ", \get_post_stati() ) );
315
 
316
- return apply_filters( "wpstg_sysinfo_after_wpstg_config", $output );
317
  }
318
 
319
  /**
@@ -322,18 +335,19 @@ class SystemInfo extends InjectionAware {
322
  * @param array $activePlugins
323
  * @return string
324
  */
325
- public function activePlugins( $plugins, $activePlugins ) {
326
- $output = $this->header( "WordPress Active Plugins" );
 
327
 
328
- foreach ( $plugins as $path => $plugin ) {
329
- if( !in_array( $path, $activePlugins ) ) {
330
  continue;
331
  }
332
 
333
  $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
334
  }
335
 
336
- return apply_filters( "wpstg_sysinfo_after_wordpress_plugins", $output );
337
  }
338
 
339
  /**
@@ -342,32 +356,34 @@ class SystemInfo extends InjectionAware {
342
  * @param array $activePlugins
343
  * @return string
344
  */
345
- public function inactivePlugins( $plugins, $activePlugins ) {
346
- $output = $this->header( "WordPress Inactive Plugins" );
 
347
 
348
- foreach ( $plugins as $path => $plugin ) {
349
- if( in_array( $path, $activePlugins ) ) {
350
  continue;
351
  }
352
 
353
  $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
354
  }
355
 
356
- return apply_filters( "wpstg_sysinfo_after_wordpress_plugins_inactive", $output );
357
  }
358
 
359
  /**
360
  * Get list of active and inactive plugins
361
  * @return string
362
  */
363
- public function plugins() {
 
364
  // Get plugins and active plugins
365
- $plugins = get_plugins();
366
- $activePlugins = get_option( "active_plugins", array() );
367
 
368
  // Active plugins
369
- $output = $this->activePlugins( $plugins, $activePlugins );
370
- $output .= $this->inactivePlugins( $plugins, $activePlugins );
371
 
372
  return $output;
373
  }
@@ -376,28 +392,29 @@ class SystemInfo extends InjectionAware {
376
  * Multisite Plugins
377
  * @return string
378
  */
379
- public function multiSitePlugins() {
380
- if( !$this->isMultiSite ) {
 
381
  return '';
382
  }
383
 
384
- $output = $this->header( "Network Active Plugins" );
385
 
386
- $plugins = wp_get_active_network_plugins();
387
- $activePlugins = get_site_option( "active_sitewide_plugins", array() );
388
 
389
- foreach ( $plugins as $pluginPath ) {
390
- $pluginBase = plugin_basename( $pluginPath );
391
 
392
- if( !array_key_exists( $pluginBase, $activePlugins ) ) {
393
  continue;
394
  }
395
 
396
- $plugin = get_plugin_data( $pluginPath );
397
 
398
  $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
399
  }
400
- unset( $plugins, $activePlugins );
401
 
402
  return $output;
403
  }
@@ -406,54 +423,57 @@ class SystemInfo extends InjectionAware {
406
  * Server Information
407
  * @return string
408
  */
409
- public function server() {
 
410
  // Server Configuration
411
- $output = $this->header( "Webserver Configuration" );
412
 
413
- $output .= $this->info( "PHP Version:", PHP_VERSION );
414
- $output .= $this->info( "MySQL Version:", $this->di->get( "wpdb" )->db_version() );
415
- $output .= $this->info( "Webserver Info:", $_SERVER["SERVER_SOFTWARE"] );
416
 
417
- return apply_filters( "wpstg_sysinfo_after_webserver_config", $output );
418
  }
419
 
420
  /**
421
  * PHP Configuration
422
  * @return string
423
  */
424
- public function php() {
425
- $output = $this->header( "PHP Configuration" );
426
- $output .= $this->info( "Safe Mode:", ($this->isSafeModeEnabled() ? "Enabled" : "Disabled" ) );
427
- $output .= $this->info( "PHP Max Memory Limit:", ini_get( "memory_limit" ) );
428
- $output .= $this->info( "Upload Max Size:", ini_get( "upload_max_filesize" ) );
429
- $output .= $this->info( "Post Max Size:", ini_get( "post_max_size" ) );
430
- $output .= $this->info( "Upload Max Filesize:", ini_get( "upload_max_filesize" ) );
431
- $output .= $this->info( "Time Limit:", ini_get( "max_execution_time" ) );
432
- $output .= $this->info( "Max Input Vars:", ini_get( "max_input_vars" ) );
433
- $output .= $this->info( "PHP User:", $this->getPHPUser() );
434
-
435
- $displayErrors = ini_get( "display_errors" );
436
- $output .= $this->info( "Display Errors:", ($displayErrors) ? "On ({$displayErrors})" : "N/A" );
437
-
438
- return apply_filters( "wpstg_sysinfo_after_php_config", $output );
 
439
  }
440
 
441
  /**
442
- *
443
  * @return string
444
  */
445
- private function getPHPUser() {
 
446
 
447
  $user = '';
448
 
449
- if( extension_loaded( 'posix' ) && function_exists('posix_getpwuid') ) {
450
  $file = WPSTG_PLUGIN_DIR . 'Core/WPStaging.php';
451
- $user = posix_getpwuid( fileowner( $file ) );
452
  return isset($user['name']) ? $user['name'] : 'can not detect PHP user name';
453
  }
454
 
455
- if( function_exists( 'exec' ) && @exec('echo EXEC') == 'EXEC') {
456
- $user = exec( 'whoami' );
457
  return $user;
458
  }
459
 
@@ -464,11 +484,12 @@ class SystemInfo extends InjectionAware {
464
  * Check if PHP is on Safe Mode
465
  * @return bool
466
  */
467
- public function isSafeModeEnabled() {
 
468
  return (
469
- version_compare( PHP_VERSION, "5.4.0", '<' ) &&
470
- @ini_get( "safe_mode" )
471
- );
472
  }
473
 
474
  /**
@@ -476,8 +497,9 @@ class SystemInfo extends InjectionAware {
476
  * @param string $functionName
477
  * @return string
478
  */
479
- public function isSupported( $functionName ) {
480
- return (function_exists( $functionName )) ? "Supported" : "Not Supported";
 
481
  }
482
 
483
  /**
@@ -486,11 +508,12 @@ class SystemInfo extends InjectionAware {
486
  * @param bool $isClass
487
  * @return string
488
  */
489
- public function isInstalled( $name, $isClass = true ) {
490
- if( true === $isClass ) {
491
- return (class_exists( $name )) ? "Installed" : "Not Installed";
 
492
  } else {
493
- return (extension_loaded( $name )) ? "Installed" : "Not Installed";
494
  }
495
  }
496
 
@@ -498,9 +521,10 @@ class SystemInfo extends InjectionAware {
498
  * Gets Installed Important PHP Extensions
499
  * @return string
500
  */
501
- public function phpExtensions() {
 
502
  // Important PHP Extensions
503
- $version = curl_version();
504
 
505
  $bitfields = Array(
506
  'CURL_VERSION_IPV6',
@@ -509,37 +533,41 @@ class SystemInfo extends InjectionAware {
509
  'CURL_VERSION_LIBZ'
510
  );
511
 
512
- $output = $this->header( "PHP Extensions" );
513
- $output .= $this->info( "cURL:", $this->isSupported( "curl_init" ) );
514
- $output .= $this->info( "cURL version:", $version['version'] );
515
- $output .= $this->info( "cURL ssl version number:", $version['ssl_version'] );
516
- $output .= $this->info( "cURL host:", $version['host'] );
517
- foreach ( $version['protocols'] as $protocols ) {
518
- $output .= $this->info( "cURL protocols:", $protocols );
 
 
519
  }
520
- foreach ( $bitfields as $feature ) {
521
- $output .= $feature . ($version['features'] & constant( $feature ) ? ' yes' : ' no') . PHP_EOL;
 
522
  }
523
 
524
 
525
- $output .= $this->info( "fsockopen:", $this->isSupported( "fsockopen" ) );
526
- $output .= $this->info( "SOAP Client:", $this->isInstalled( "SoapClient" ) );
527
- $output .= $this->info( "Suhosin:", $this->isInstalled( "suhosin", false ) );
528
 
529
- return apply_filters( "wpstg_sysinfo_after_php_ext", $output );
530
  }
531
 
532
  /**
533
  * Check if WP is installed in subdir
534
  * @return boolean
535
  */
536
- private function isSubDir() {
 
537
  // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
538
  // This is happening much more often than you would expect
539
- $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
540
- $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
541
 
542
- if( $home !== $siteurl ) {
543
  return true;
544
  }
545
  return false;
@@ -554,19 +582,20 @@ class SystemInfo extends InjectionAware {
554
  * @param array $clone
555
  * @return sting
556
  */
557
- private function getStagingPrefix( $clone = array() ) {
 
558
  // Throw error
559
- $path = ABSPATH . $clone['directoryName'] . DIRECTORY_SEPARATOR . "wp-config.php";
560
- if( false === ($content = @file_get_contents( $path )) ) {
561
  return 'Can\'t find staging wp-config.php';
562
  } else {
563
 
564
  // Get prefix from wp-config.php
565
  //preg_match_all("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
566
- preg_match( "/table_prefix\s*=\s*'(\w*)';/", $content, $matches );
567
  //wp_die(var_dump($matches));
568
 
569
- if( !empty( $matches[1] ) ) {
570
  return $matches[1];
571
  } else {
572
  return 'No table_prefix in wp-config.php';
@@ -578,20 +607,21 @@ class SystemInfo extends InjectionAware {
578
  * Get staging site wordpress version number
579
  * @return string
580
  */
581
- private function getStagingWpVersion( $path ) {
 
582
 
583
- if( $path === 'undefined' ) {
584
  return "Error: Cannot detect WP version";
585
  }
586
 
587
  // Get version number of wp staging
588
- $file = trailingslashit( $path ) . 'wp-includes/version.php';
589
- $versionStaging = file_get_contents( $file );
590
 
591
- preg_match( "/\\\$wp_version.*=.*'(.*)';/", $versionStaging, $matches );
592
 
593
  $error = '';
594
- if( empty( $matches[1] ) ) {
595
  $error .= "Error: Cannot detect WP version";
596
  }
597
  return $matches[1];
9
  use WPStaging\Utils\Multisite;
10
 
11
  // No Direct Access
12
+ if (!defined("WPINC")) {
13
  die;
14
  }
15
 
17
  * Class SystemInfo
18
  * @package WPStaging\Backend\Modules
19
  */
20
+ class SystemInfo extends InjectionAware
21
+ {
22
 
23
  /**
24
  * @var bool
34
  /**
35
  * Initialize class
36
  */
37
+ public function initialize()
38
+ {
39
  $this->isMultiSite = is_multisite();
40
+ $this->helper = new Utils\Helper();
41
  }
42
 
43
  /**
44
  * Magic method
45
  * @return string
46
  */
47
+ public function __toString()
48
+ {
49
  return $this->get();
50
  }
51
 
53
  * Get System Information as text
54
  * @return string
55
  */
56
+ public function get()
57
+ {
58
  $output = "### Begin System Info ###" . PHP_EOL . PHP_EOL;
59
 
60
  $output .= $this->wpstaging();
86
  * @param string $string
87
  * @return string
88
  */
89
+ public function header($string)
90
+ {
91
  return PHP_EOL . "-- {$string}" . PHP_EOL . PHP_EOL;
92
  }
93
 
97
  * @param string $value
98
  * @return string
99
  */
100
+ public function info($title, $value)
101
+ {
102
+ return str_pad($title, 56, ' ', STR_PAD_RIGHT) . $value . PHP_EOL;
103
  }
104
 
105
  /**
106
  * Theme Information
107
  * @return string
108
  */
109
+ public function theme()
110
+ {
111
  // Versions earlier than 3.4
112
+ if (get_bloginfo("version") < "3.4") {
113
+ $themeData = get_theme_data(get_stylesheet_directory() . "/style.css");
114
  return "{$themeData["Name"]} {$themeData["Version"]}";
115
  }
116
 
122
  * Site Information
123
  * @return string
124
  */
125
+ public function site()
126
+ {
127
+ $output = $this->header("-- Site Info");
128
+ $output .= $this->info("Site URL:", site_url());
129
+ $output .= $this->info("Home URL:", $this->helper->get_home_url());
130
+ $output .= $this->info("Home Path:", get_home_path());
131
+ $output .= $this->info("ABSPATH:", ABSPATH);
132
+ $output .= $this->info("Installed in subdir:", ($this->isSubDir() ? 'Yes' : 'No'));
133
+
134
+ return apply_filters("wpstg_sysinfo_after_site_info", $output);
135
  }
136
 
137
  /**
138
  * Multisite information
139
  * @return string
140
  */
141
+ private function getMultisiteInfo()
142
+ {
143
+ if (!$this->isMultiSite) {
144
  return '';
145
  }
146
 
147
  $multisite = new Multisite();
148
 
149
+ $output = $this->info("Multisite:", ($this->isMultiSite ? "Yes" : "No"));
150
+ $output .= $this->info("Multisite Blog ID:", get_current_blog_id());
151
+ $output .= $this->info("MultiSite URL:", $multisite->getHomeURL());
152
+ $output .= $this->info("MultiSite URL without scheme:", $multisite->getHomeUrlWithoutScheme());
153
+ $output .= $this->info("MultiSite is Main Site:", is_main_site() ? 'Yes' : 'No');
154
 
155
+ return apply_filters("wpstg_sysinfo_after_multisite_info", $output);
156
  }
157
 
158
  /**
159
  * Wp Staging plugin Information
160
  * @return string
161
  */
162
+ public function wpstaging()
163
+ {
164
  // Get wpstg settings
165
+ $settings = ( object )get_option('wpstg_settings', array());
166
 
167
  // Clones data < 1.1.6.x
168
+ $clones = ( object )get_option('wpstg_existing_clones', array());
169
  // Clones data version > 2.x
170
+ $clonesBeta = get_option('wpstg_existing_clones_beta', array());
171
 
172
 
173
  $output = "-- WP Staging Settings" . PHP_EOL . PHP_EOL;
174
+ $output .= $this->info("Query Limit:", isset($settings->queryLimit) ? $settings->queryLimit : 'undefined');
175
+ $output .= $this->info("DB Search & Replace Limit:", isset($settings->querySRLimit) ? $settings->querySRLimit : 'undefined');
176
+ $output .= $this->info("File Copy Limit:", isset($settings->fileLimit) ? $settings->fileLimit : 'undefined');
177
+ $output .= $this->info("Batch Size:", isset($settings->batchSize) ? $settings->batchSize : 'undefined');
178
+ $output .= $this->info("CPU Load:", isset($settings->cpuLoad) ? $settings->cpuLoad : 'undefined');
179
+ $output .= $this->info("WP in Subdir:", $this->isSubDir() ? 'true' : 'false');
180
 
181
  $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version < 1.1.6.x" . PHP_EOL . PHP_EOL;
182
 
183
+ foreach ($clones as $key => $value) {
184
+ $output .= $this->info("Site name & subfolder :", $value);
185
  }
186
  $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version > 2.0.x" . PHP_EOL . PHP_EOL;
187
 
188
+ foreach ($clonesBeta as $key => $clone) {
189
 
190
+ $path = !empty($clone['path']) ? $clone['path'] : 'undefined';
191
 
192
+ $output .= $this->info("Number:", isset($clone['number']) ? $clone['number'] : 'undefined');
193
+ $output .= $this->info("directoryName:", isset($clone['directoryName']) ? $clone['directoryName'] : 'undefined');
194
+ $output .= $this->info("Path:", $path);
195
+ $output .= $this->info("URL:", isset($clone['url']) ? $clone['url'] : 'undefined');
196
+ $output .= $this->info("DB Prefix:", isset($clone['prefix']) ? $clone['prefix'] : 'undefined');
197
+ $output .= $this->info("DB Prefix wp-config.php:", $this->getStagingPrefix($clone));
198
+ $output .= $this->info("WP Staging Version:", isset($clone['version']) ? $clone['version'] : 'undefined');
199
+ $output .= $this->info("WP Version:", $this->getStagingWpVersion($path)) . PHP_EOL . PHP_EOL;
200
  }
201
 
202
 
203
+ $output .= $this->info("Raw Clones Data:", json_encode(get_option('wpstg_existing_clones_beta', 'undefined')));
204
 
205
  $output .= '' . PHP_EOL;
206
 
207
 
208
  //$output .= PHP_EOL . PHP_EOL;
209
 
210
+ $output .= $this->info("Plugin Pro Version:", get_option('wpstgpro_version', 'undefined'));
211
+ $output .= $this->info("Plugin Free Version:", get_option('wpstg_version', 'undefined'));
212
+ $output .= $this->info("Install Date:", get_option('wpstg_installDate', 'undefined'));
213
+ $output .= $this->info("Upgraded from Pro:", get_option('wpstgpro_version_upgraded_from', 'undefined'));
214
+ $output .= $this->info("Upgraded from Free:", get_option('wpstg_version_upgraded_from', 'undefined'));
215
+ $output .= $this->info("Is Staging Site:", wpstg_is_stagingsite() ? 'true' : 'false') . PHP_EOL . PHP_EOL;
216
 
217
 
218
+ return apply_filters("wpstg_sysinfo_after_wpstaging_info", $output);
219
  }
220
 
221
  /**
222
  * Browser Information
223
  * @return string
224
  */
225
+ public function browser()
226
+ {
227
+ $output = $this->header("User Browser");
228
  $output .= (new Browser);
229
 
230
+ return apply_filters("wpstg_sysinfo_after_user_browser", $output);
231
  }
232
 
233
  /**
234
  * Frontpage Information when frontpage is set to "page"
235
  * @return string
236
  */
237
+ public function frontPage()
238
+ {
239
+ if (get_option("show_on_front") !== "page") {
240
  return '';
241
  }
242
 
243
+ $frontPageID = get_option("page_on_front");
244
+ $blogPageID = get_option("page_for_posts");
245
 
246
  // Front Page
247
+ $pageFront = ($frontPageID != 0) ? get_the_title($frontPageID) . " (#{$frontPageID})" : "Unset";
248
  // Blog Page ID
249
+ $pageBlog = ($blogPageID != 0) ? get_the_title($blogPageID) . " (#{$blogPageID})" : "Unset";
250
 
251
+ $output = $this->info("Page On Front:", $pageFront);
252
+ $output .= $this->info("Page For Posts:", $pageBlog);
253
 
254
  return $output;
255
  }
258
  * Check wp_remote_post() functionality
259
  * @return string
260
  */
261
+ public function wpRemotePost()
262
+ {
263
  // Make sure wp_remote_post() is working
264
  $wpRemotePost = "wp_remote_post() does not work";
265
 
266
  // Send request
267
  $response = wp_remote_post(
268
+ "https://www.paypal.com/cgi-bin/webscr", array(
269
+ "sslverify" => false,
270
+ "timeout" => 60,
271
+ "user-agent" => "WPSTG/" . WPStaging::getVersion(),
272
+ "body" => array("cmd" => "_notify-validate")
273
+ )
274
  );
275
 
276
  // Validate it worked
277
+ if (!is_wp_error($response) && 200 <= $response["response"]["code"] && 300 > $response["response"]["code"]) {
278
  $wpRemotePost = "wp_remote_post() works";
279
  }
280
 
281
+ return $this->info("Remote Post:", $wpRemotePost);
282
  }
283
 
284
  /**
285
  * WordPress Configuration
286
  * @return string
287
  */
288
+ public function wp()
289
+ {
290
+ $output = $this->header("WordPress Configuration");
291
+ $output .= $this->info("Version:", get_bloginfo("version"));
292
+ $output .= $this->info("Language:", (defined("WPLANG") && WPLANG) ? WPLANG : "en_US");
293
 
294
+ $permalinkStructure = get_option("permalink_structure");;
295
+ $output .= $this->info("Permalink Structure:", ($permalinkStructure) ? $permalinkStructure : "Default");
 
296
 
297
+ $output .= $this->info("Active Theme:", $this->theme());
298
+ $output .= $this->info("Show On Front:", get_option("show_on_front"));
299
 
300
  // Frontpage information
301
  $output .= $this->frontPage();
304
  $output .= $this->wpRemotePost();
305
 
306
  // Table Prefix
307
+ $wpDB = $this->di->get("wpdb");
308
  $tablePrefix = "DB Prefix: " . $wpDB->prefix . ' ';
309
+ $tablePrefix .= "Length: " . strlen($wpDB->prefix) . " Status: ";
310
+ $tablePrefix .= (strlen($wpDB->prefix) > 16) ? " ERROR: Too long" : " Acceptable";
311
 
312
+ $output .= $this->info("Table Prefix:", $tablePrefix);
313
 
314
  // Constants
315
+ $output .= $this->info("WP Content Path:", WP_CONTENT_DIR);
316
+ $output .= $this->info("WP Plugin Dir:", WP_PLUGIN_DIR);
317
+ if (defined('UPLOADS'))
318
+ $output .= $this->info("WP UPLOADS CONST:", UPLOADS);
319
  $uploads = wp_upload_dir();
320
+ $output .= $this->info("WP Uploads Dir:", $uploads['basedir']);
321
+ if (defined('WP_TEMP_DIR'))
322
+ $output .= $this->info("WP Temp Dir:", WP_TEMP_DIR);
323
 
324
  // WP Debug
325
+ $output .= $this->info("WP_DEBUG:", (defined("WP_DEBUG")) ? WP_DEBUG ? "Enabled" : "Disabled" : "Not set");
326
+ $output .= $this->info("Memory Limit:", WP_MEMORY_LIMIT);
327
+ $output .= $this->info("Registered Post Stati:", implode(", ", \get_post_stati()));
328
 
329
+ return apply_filters("wpstg_sysinfo_after_wpstg_config", $output);
330
  }
331
 
332
  /**
335
  * @param array $activePlugins
336
  * @return string
337
  */
338
+ public function activePlugins($plugins, $activePlugins)
339
+ {
340
+ $output = $this->header("WordPress Active Plugins");
341
 
342
+ foreach ($plugins as $path => $plugin) {
343
+ if (!in_array($path, $activePlugins)) {
344
  continue;
345
  }
346
 
347
  $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
348
  }
349
 
350
+ return apply_filters("wpstg_sysinfo_after_wordpress_plugins", $output);
351
  }
352
 
353
  /**
356
  * @param array $activePlugins
357
  * @return string
358
  */
359
+ public function inactivePlugins($plugins, $activePlugins)
360
+ {
361
+ $output = $this->header("WordPress Inactive Plugins");
362
 
363
+ foreach ($plugins as $path => $plugin) {
364
+ if (in_array($path, $activePlugins)) {
365
  continue;
366
  }
367
 
368
  $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
369
  }
370
 
371
+ return apply_filters("wpstg_sysinfo_after_wordpress_plugins_inactive", $output);
372
  }
373
 
374
  /**
375
  * Get list of active and inactive plugins
376
  * @return string
377
  */
378
+ public function plugins()
379
+ {
380
  // Get plugins and active plugins
381
+ $plugins = get_plugins();
382
+ $activePlugins = get_option("active_plugins", array());
383
 
384
  // Active plugins
385
+ $output = $this->activePlugins($plugins, $activePlugins);
386
+ $output .= $this->inactivePlugins($plugins, $activePlugins);
387
 
388
  return $output;
389
  }
392
  * Multisite Plugins
393
  * @return string
394
  */
395
+ public function multiSitePlugins()
396
+ {
397
+ if (!$this->isMultiSite) {
398
  return '';
399
  }
400
 
401
+ $output = $this->header("Network Active Plugins");
402
 
403
+ $plugins = wp_get_active_network_plugins();
404
+ $activePlugins = get_site_option("active_sitewide_plugins", array());
405
 
406
+ foreach ($plugins as $pluginPath) {
407
+ $pluginBase = plugin_basename($pluginPath);
408
 
409
+ if (!array_key_exists($pluginBase, $activePlugins)) {
410
  continue;
411
  }
412
 
413
+ $plugin = get_plugin_data($pluginPath);
414
 
415
  $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
416
  }
417
+ unset($plugins, $activePlugins);
418
 
419
  return $output;
420
  }
423
  * Server Information
424
  * @return string
425
  */
426
+ public function server()
427
+ {
428
  // Server Configuration
429
+ $output = $this->header("Webserver Configuration");
430
 
431
+ $output .= $this->info("PHP Version:", PHP_VERSION);
432
+ $output .= $this->info("MySQL Version:", $this->di->get("wpdb")->db_version());
433
+ $output .= $this->info("Webserver Info:", $_SERVER["SERVER_SOFTWARE"]);
434
 
435
+ return apply_filters("wpstg_sysinfo_after_webserver_config", $output);
436
  }
437
 
438
  /**
439
  * PHP Configuration
440
  * @return string
441
  */
442
+ public function php()
443
+ {
444
+ $output = $this->header("PHP Configuration");
445
+ $output .= $this->info("Safe Mode:", ($this->isSafeModeEnabled() ? "Enabled" : "Disabled"));
446
+ $output .= $this->info("PHP Max Memory Limit:", ini_get("memory_limit"));
447
+ $output .= $this->info("Upload Max Size:", ini_get("upload_max_filesize"));
448
+ $output .= $this->info("Post Max Size:", ini_get("post_max_size"));
449
+ $output .= $this->info("Upload Max Filesize:", ini_get("upload_max_filesize"));
450
+ $output .= $this->info("Time Limit:", ini_get("max_execution_time"));
451
+ $output .= $this->info("Max Input Vars:", ini_get("max_input_vars"));
452
+ $output .= $this->info("PHP User:", $this->getPHPUser());
453
+
454
+ $displayErrors = ini_get("display_errors");
455
+ $output .= $this->info("Display Errors:", ($displayErrors) ? "On ({$displayErrors})" : "N/A");
456
+
457
+ return apply_filters("wpstg_sysinfo_after_php_config", $output);
458
  }
459
 
460
  /**
461
+ *
462
  * @return string
463
  */
464
+ private function getPHPUser()
465
+ {
466
 
467
  $user = '';
468
 
469
+ if (extension_loaded('posix') && function_exists('posix_getpwuid')) {
470
  $file = WPSTG_PLUGIN_DIR . 'Core/WPStaging.php';
471
+ $user = posix_getpwuid(fileowner($file));
472
  return isset($user['name']) ? $user['name'] : 'can not detect PHP user name';
473
  }
474
 
475
+ if (function_exists('exec') && @exec('echo EXEC') == 'EXEC') {
476
+ $user = exec('whoami');
477
  return $user;
478
  }
479
 
484
  * Check if PHP is on Safe Mode
485
  * @return bool
486
  */
487
+ public function isSafeModeEnabled()
488
+ {
489
  return (
490
+ version_compare(PHP_VERSION, "5.4.0", '<') &&
491
+ @ini_get("safe_mode")
492
+ );
493
  }
494
 
495
  /**
497
  * @param string $functionName
498
  * @return string
499
  */
500
+ public function isSupported($functionName)
501
+ {
502
+ return (function_exists($functionName)) ? "Supported" : "Not Supported";
503
  }
504
 
505
  /**
508
  * @param bool $isClass
509
  * @return string
510
  */
511
+ public function isInstalled($name, $isClass = true)
512
+ {
513
+ if (true === $isClass) {
514
+ return (class_exists($name)) ? "Installed" : "Not Installed";
515
  } else {
516
+ return (extension_loaded($name)) ? "Installed" : "Not Installed";
517
  }
518
  }
519
 
521
  * Gets Installed Important PHP Extensions
522
  * @return string
523
  */
524
+ public function phpExtensions()
525
+ {
526
  // Important PHP Extensions
527
+ $version = function_exists('curl_version') ? curl_version() : array('version' => 'Error: not available', 'ssl_version' => 'Error: not available', 'host' => 'Error: not available', 'protocols' => array(), 'features' => array());
528
 
529
  $bitfields = Array(
530
  'CURL_VERSION_IPV6',
533
  'CURL_VERSION_LIBZ'
534
  );
535
 
536
+ $output = $this->header("PHP Extensions");
537
+
538
+ $output .= $this->info("cURL:", $this->isSupported("curl_init"));
539
+ $output .= $this->info("cURL version:", $version['version']);
540
+ $output .= $this->info("cURL ssl version number:", $version['ssl_version']);
541
+ $output .= $this->info("cURL host:", $version['host']);
542
+
543
+ foreach ($version['protocols'] as $protocols) {
544
+ $output .= $this->info("cURL protocols:", $protocols);
545
  }
546
+
547
+ foreach ($bitfields as $feature) {
548
+ $output .= $feature . ($version['features'] & constant($feature) ? ' yes' : ' no') . PHP_EOL;
549
  }
550
 
551
 
552
+ $output .= $this->info("fsockopen:", $this->isSupported("fsockopen"));
553
+ $output .= $this->info("SOAP Client:", $this->isInstalled("SoapClient"));
554
+ $output .= $this->info("Suhosin:", $this->isInstalled("suhosin", false));
555
 
556
+ return apply_filters("wpstg_sysinfo_after_php_ext", $output);
557
  }
558
 
559
  /**
560
  * Check if WP is installed in subdir
561
  * @return boolean
562
  */
563
+ private function isSubDir()
564
+ {
565
  // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
566
  // This is happening much more often than you would expect
567
+ $siteurl = preg_replace('#^https?://#', '', rtrim(get_option('siteurl'), '/'));
568
+ $home = preg_replace('#^https?://#', '', rtrim(get_option('home'), '/'));
569
 
570
+ if ($home !== $siteurl) {
571
  return true;
572
  }
573
  return false;
582
  * @param array $clone
583
  * @return sting
584
  */
585
+ private function getStagingPrefix($clone = array())
586
+ {
587
  // Throw error
588
+ $path = ABSPATH . $clone['directoryName'] . DIRECTORY_SEPARATOR . "wp-config.php";
589
+ if (false === ($content = @file_get_contents($path))) {
590
  return 'Can\'t find staging wp-config.php';
591
  } else {
592
 
593
  // Get prefix from wp-config.php
594
  //preg_match_all("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
595
+ preg_match("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
596
  //wp_die(var_dump($matches));
597
 
598
+ if (!empty($matches[1])) {
599
  return $matches[1];
600
  } else {
601
  return 'No table_prefix in wp-config.php';
607
  * Get staging site wordpress version number
608
  * @return string
609
  */
610
+ private function getStagingWpVersion($path)
611
+ {
612
 
613
+ if ($path === 'undefined') {
614
  return "Error: Cannot detect WP version";
615
  }
616
 
617
  // Get version number of wp staging
618
+ $file = trailingslashit($path) . 'wp-includes/version.php';
619
+ $versionStaging = file_get_contents($file);
620
 
621
+ preg_match("/\\\$wp_version.*=.*'(.*)';/", $versionStaging, $matches);
622
 
623
  $error = '';
624
+ if (empty($matches[1])) {
625
  $error .= "Error: Cannot detect WP version";
626
  }
627
  return $matches[1];
Backend/Notices/Notices.php CHANGED
@@ -153,9 +153,9 @@ class Notices {
153
  }
154
 
155
  // Beta
156
- if( false === get_option( "wpstg_beta" ) || "no" !== get_option( "wpstg_beta" ) ) {
157
  require_once "{$viewsNoticesPath}beta.php";
158
- }
159
 
160
  // WP Staging Pro and Free can not be activated both
161
  if( false !== ( $deactivatedNoticeID = get_transient( "wp_staging_deactivated_notice_id" ) ) ) {
153
  }
154
 
155
  // Beta
156
+ /*if( false === get_option( "wpstg_beta" ) || "no" !== get_option( "wpstg_beta" ) ) {
157
  require_once "{$viewsNoticesPath}beta.php";
158
+ }*/
159
 
160
  // WP Staging Pro and Free can not be activated both
161
  if( false !== ( $deactivatedNoticeID = get_transient( "wp_staging_deactivated_notice_id" ) ) ) {
Backend/Optimizer/wp-staging-optimizer.php CHANGED
@@ -45,11 +45,6 @@ function wpstg_get_plugins_dir()
45
  function wpstg_is_enabled_optimizer()
46
  {
47
 
48
- // Activate the Optimizer all the times.
49
- // Until now we never had any issue with the Optimizer so keep the optimizer activated to make sure user does not disable it accidentally
50
- // @todo remove this function in wp staging settings
51
- return true;
52
-
53
  $status = ( object )get_option('wpstg_settings');
54
 
55
  if ($status && isset($status->optimizer) && $status->optimizer == 1) {
45
  function wpstg_is_enabled_optimizer()
46
  {
47
 
 
 
 
 
 
48
  $status = ( object )get_option('wpstg_settings');
49
 
50
  if ($status && isset($status->optimizer) && $status->optimizer == 1) {
Backend/public/css/wpstg-admin.css CHANGED
@@ -245,11 +245,17 @@
245
  color: #eee;
246
  }
247
 
 
 
 
 
 
 
 
 
248
  .wpstg-clone {
249
- border: 5px solid #ffffff;
250
- margin-bottom: 5px;
251
- padding: 5px 10px;
252
- width: 400px;
253
  position: relative;
254
  overflow: hidden;
255
  transition: border-color .2s ease-in-out;
@@ -367,18 +373,21 @@
367
  width: 500px;
368
  }
369
 
370
- #wpstg-home-link,
371
  #wpstg-try-again {
372
  display: none;
373
  }
374
 
 
 
 
375
 
376
- #wpstg-loader {
377
  content: url('../img/loading.gif');
378
- margin-top:-5px;
 
379
  }
380
 
381
- #wpstg-loader.wpstg-finished {
382
  display:block;
383
  content:"Finished";
384
  background-color:#00c89a;
@@ -390,19 +399,6 @@
390
  margin-top:0px;
391
  border-radius: 3px;
392
  }
393
- .wpstg-loader {
394
- content: url('../img/loading.gif');
395
- margin-top:-5px;
396
- }
397
-
398
- .wpstg-loader.wpstg-finished {
399
- content:"Finished";
400
- background-color:#00c89a;
401
- color:white;
402
- padding:2px;
403
- margin-top:0px;
404
- max-width:60px;
405
- }
406
 
407
  #wpstg-workflow {
408
  max-width: 650px;
@@ -539,6 +535,10 @@
539
  top: 2px;
540
  }
541
 
 
 
 
 
542
  #wpstg-workflow #wpstg-start-cloning {
543
  display: inline-block;
544
  margin-left: 5px;
@@ -648,6 +648,7 @@
648
  padding:20px;
649
  border: 1px solid #fff;
650
  max-width: 600px;
 
651
  }
652
 
653
  .wpstg-header{
@@ -664,7 +665,7 @@
664
  font-weight: bold;
665
  }
666
 
667
- #wpstg-log-details{
668
  height: 300px;
669
  overflow: scroll;
670
  max-width: 650px;
@@ -859,6 +860,10 @@
859
  color:white;
860
  }
861
 
 
 
 
 
862
  .wpstg-bold{
863
  font-weight: 600;
864
  }
@@ -1101,3 +1106,118 @@
1101
  .wpstg-pointer {
1102
  cursor: pointer;
1103
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  color: #eee;
246
  }
247
 
248
+ .wpstg-box {
249
+ margin: 10px 0;
250
+ padding: 10px;
251
+ position: relative;
252
+ overflow: hidden;
253
+ transition: border-color .2s ease-in-out;
254
+ }
255
+
256
  .wpstg-clone {
257
+ margin-bottom: 1px;
258
+ padding: 10px 10px;
 
 
259
  position: relative;
260
  overflow: hidden;
261
  transition: border-color .2s ease-in-out;
373
  width: 500px;
374
  }
375
 
 
376
  #wpstg-try-again {
377
  display: none;
378
  }
379
 
380
+ #wpstg-home-link {
381
+ float: right;
382
+ }
383
 
384
+ .wpstg-loader {
385
  content: url('../img/loading.gif');
386
+ margin-top:5px;
387
+ display: none;
388
  }
389
 
390
+ .wpstg-loader.wpstg-finished {
391
  display:block;
392
  content:"Finished";
393
  background-color:#00c89a;
399
  margin-top:0px;
400
  border-radius: 3px;
401
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
402
 
403
  #wpstg-workflow {
404
  max-width: 650px;
535
  top: 2px;
536
  }
537
 
538
+ .wpstg-db-table:hover{
539
+ background-color:#f0f8ff;
540
+ }
541
+
542
  #wpstg-workflow #wpstg-start-cloning {
543
  display: inline-block;
544
  margin-left: 5px;
648
  padding:20px;
649
  border: 1px solid #fff;
650
  max-width: 600px;
651
+ margin-top:10px;
652
  }
653
 
654
  .wpstg-header{
665
  font-weight: bold;
666
  }
667
 
668
+ .wpstg-log-details{
669
  height: 300px;
670
  overflow: scroll;
671
  max-width: 650px;
860
  color:white;
861
  }
862
 
863
+ .wpstg-staging-info li{
864
+ margin-bottom: 2px;
865
+ }
866
+
867
  .wpstg-bold{
868
  font-weight: 600;
869
  }
1106
  .wpstg-pointer {
1107
  cursor: pointer;
1108
  }
1109
+
1110
+
1111
+ .wpstg--tab--header ul {
1112
+ display: flex;
1113
+ }
1114
+
1115
+ .wpstg--tab--header ul li {
1116
+ margin-right: 1em;
1117
+ }
1118
+
1119
+ .wpstg--tab--header ul li:last-child {
1120
+ margin-right: 0;
1121
+ }
1122
+
1123
+ .wpstg--tab--header a {
1124
+ min-width: 150px;
1125
+ text-align: center;
1126
+ cursor: pointer;
1127
+ display: inline-block;
1128
+ padding: 1em 1.25em;
1129
+ border-bottom: .5em solid transparent;
1130
+ border: solid 1px;
1131
+ }
1132
+
1133
+ .wpstg--tab--header a.wpstg--tab--active {
1134
+ border-bottom: .5em solid #25A1F0;
1135
+ color: #25A1F0;
1136
+ }
1137
+
1138
+ .wpstg--tab--content {
1139
+ display: none;
1140
+ }
1141
+
1142
+ .wpstg--tab--active {
1143
+ display: block;
1144
+ }
1145
+
1146
+ .wpstg--text--strong,
1147
+ .wpstg--text--strong * {
1148
+ font-weight: bold !important;
1149
+ }
1150
+
1151
+ .wpstg--text--danger {
1152
+ color: #a94442;
1153
+ }
1154
+
1155
+ .wpstg--tooltip {
1156
+ position: relative;
1157
+ display: inline-block;
1158
+ border-bottom: 1px dotted black;
1159
+ margin-left: 10px;
1160
+ }
1161
+
1162
+ .wpstg--tooltip .wpstg--tooltiptext {
1163
+ visibility: hidden;
1164
+ width: 300px;
1165
+ background-color: #ffffff;
1166
+ color: #505050;
1167
+ text-align: left;
1168
+ padding: 20px;
1169
+ border: 1px solid #e8e8e8;
1170
+ border-radius: 3px;
1171
+ position: absolute;
1172
+ z-index: 1;
1173
+ -webkit-box-shadow: -1px 1px 5px 0px rgba(0,0,0,0.75);
1174
+ -moz-box-shadow: -1px 1px 5px 0px rgba(0,0,0,0.75);
1175
+ box-shadow: -1px 1px 5px 0px rgba(0,0,0,0.75);
1176
+ }
1177
+
1178
+ .wpstg--tooltip:hover .wpstg--tooltiptext {
1179
+ visibility: visible;
1180
+ }
1181
+
1182
+ .wpstg--tooltiptext-snapshots {
1183
+ width: 120px;
1184
+ top: 100%;
1185
+ left: -150%;
1186
+ margin-left: -60px; /* Use half of the width (120/2 = 60), to center the tooltip */
1187
+ margin-top:10px;
1188
+ }
1189
+ /**
1190
+ Tooltip top arrow
1191
+ */
1192
+ .wpstg--tooltip .wpstg--tooltiptext-snapshots::after {
1193
+ content: " ";
1194
+ position: absolute;
1195
+ bottom: 100%; /* At the top of the tooltip */
1196
+ left: 50%;
1197
+ margin-left: 25px;
1198
+ border-width: 5px;
1199
+ border-style: solid;
1200
+ border-color: transparent transparent white transparent;
1201
+ }
1202
+
1203
+ .wpstg--snaphot-restore-table tr {
1204
+ line-height: 12px;
1205
+ }
1206
+
1207
+ .wpstg-float-left{
1208
+ float: left;
1209
+ }
1210
+
1211
+ .wpstg-beta-notice{
1212
+ background-color: #b0e8b0;
1213
+ border-radius: 3px;
1214
+ padding: 7px;
1215
+ margin-bottom: 20px;
1216
+ }
1217
+
1218
+ #wpstg-snapshot-name {
1219
+ font-size: 1.875em;
1220
+ font-weight: 600;
1221
+ }
1222
+
1223
+
Backend/public/js/wpstg-admin.js CHANGED
@@ -1,28 +1,25 @@
1
  "use strict";
2
 
3
- var WPStaging = (function ($)
4
- {
5
  var that = {
6
- isCancelled: false,
7
- isFinished: false,
8
- getLogs: false,
9
- time: 1,
10
- executionTime: false,
11
- progressBar: 0
12
- },
13
- cache = {elements: []},
14
- timeout, ajaxSpinner;
15
 
16
  /**
17
  * Get / Set Cache for Selector
18
  * @param {String} selector
19
  * @returns {*}
20
  */
21
- cache.get = function (selector)
22
- {
23
  // It is already cached!
24
- if ($.inArray(selector, cache.elements) !== -1)
25
- {
26
  return cache.elements[selector];
27
  }
28
 
@@ -36,8 +33,7 @@ var WPStaging = (function ($)
36
  * Refreshes given cache
37
  * @param {String} selector
38
  */
39
- cache.refresh = function (selector)
40
- {
41
  selector.elements[selector] = jQuery(selector);
42
  };
43
 
@@ -45,43 +41,70 @@ var WPStaging = (function ($)
45
  * Show and Log Error Message
46
  * @param {String} message
47
  */
48
- var showError = function (message)
49
- {
50
  cache.get("#wpstg-try-again").css("display", "inline-block");
51
  cache.get("#wpstg-cancel-cloning").text("Reset");
52
  cache.get("#wpstg-resume-cloning").show();
53
  cache.get("#wpstg-error-wrapper").show();
54
  cache.get("#wpstg-error-details").show().html(message);
55
  cache.get("#wpstg-removing-clone").removeClass("loading");
56
- cache.get("#wpstg-loader").hide();
57
  };
58
 
59
- var slugify = function(url) {
60
- return url.toString()
61
- .toLowerCase()
62
- .normalize('NFD')
63
- .replace(/[\u0300-\u036f]/g,'')
64
- .replace(/\s+/g,'-')
65
- .replace(/&/g,'-and-')
66
- .replace(/[^a-z0-9\-]/g,'')
67
- .replace(/-+/g,'-')
68
- .replace(/^-*/,'')
69
- .replace(/-*$/,'')
70
- ;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  };
72
 
 
73
  /**
74
  * Common Elements
75
  */
76
- var elements = function ()
77
- {
78
  var $workFlow = cache.get("#wpstg-workflow"),
79
- isAllChecked = true,
80
- urlSpinner = ajaxurl.replace("/admin-ajax.php", '') + "/images/spinner",
81
- timer;
82
 
83
- if (2 < window.devicePixelRatio)
84
- {
85
  urlSpinner += "-2x";
86
  }
87
 
@@ -89,228 +112,169 @@ var WPStaging = (function ($)
89
 
90
  ajaxSpinner = "<img src=''" + urlSpinner + "' alt='' class='ajax-spinner general-spinner' />";
91
 
92
- var getBaseValues = function() {
93
- var path = $('#wpstg-use-target-dir').data('base-path');
94
- var uri = $('#wpstg-use-target-hostname').data('base-uri');
95
- return {
96
- path
97
- };
98
  };
99
 
100
  $workFlow
101
- // Check / Un-check All Database Tables
102
- // .on("click", ".wpstg-button-unselect", function (e) {
103
- // e.preventDefault();
104
- //
105
- // if (false === isAllChecked)
106
- // {
107
- // cache.get(".wpstg-db-table-checkboxes").prop("checked", true);
108
- // cache.get(".wpstg-button-unselect").text("Un-check All");
109
- // isAllChecked = true;
110
- // }
111
- // else
112
- // {
113
- // cache.get(".wpstg-db-table-checkboxes").prop("checked", false);
114
- // cache.get(".wpstg-button-unselect").text("Check All");
115
- // isAllChecked = false;
116
- // }
117
- // })
118
- // Check / Un-check All Database Tables New
119
- .on("click", ".wpstg-button-unselect", function (e) {
120
- e.preventDefault();
121
-
122
- if (false === isAllChecked )
123
- {
124
- console.log('true');
125
- cache.get("#wpstg_select_tables_cloning .wpstg-db-table").prop("selected", "selected");
126
- cache.get(".wpstg-button-unselect").text("Unselect All");
127
- cache.get(".wpstg-db-table-checkboxes").prop("checked", true);
128
- isAllChecked = true;
129
- }
130
- else
131
- {
132
- console.log('false');
133
- cache.get("#wpstg_select_tables_cloning .wpstg-db-table").prop("selected", false);
134
- cache.get(".wpstg-button-unselect").text("Select All");
135
- cache.get(".wpstg-db-table-checkboxes").prop("checked", false);
136
- isAllChecked = false;
137
- }
138
- })
139
-
140
- /**
141
- * Select tables with certain tbl prefix
142
- * @param obj e
143
- * @returns {undefined}
144
- */
145
- // .on("click", ".wpstg-button-select", function (e) {
146
- // e.preventDefault();
147
- // $(".wpstg-db-table input").each(function () {
148
- //
149
- // if (wpstg.isMultisite == 1) {
150
- // if ($(this).attr('name').match("^" + wpstg.tblprefix + "([^0-9])_*")) {
151
- // $(this).prop("checked", true);
152
- // } else {
153
- // $(this).prop("checked", false);
154
- // }
155
- // }
156
- //
157
- // if (wpstg.isMultisite == 0) {
158
- // if ($(this).attr('name').match("^" + wpstg.tblprefix)) {
159
- // $(this).prop("checked", true);
160
- // } else {
161
- // $(this).prop("checked", false);
162
- // }
163
- // }
164
- // })
165
- // })
166
- /**
167
- * Select tables with certain tbl prefix | NEW
168
- * @param obj e
169
- * @returns {undefined}
170
- */
171
- .on("click", ".wpstg-button-select", function (e) {
172
- e.preventDefault();
173
- $("#wpstg_select_tables_cloning .wpstg-db-table").each(function () {
174
- if (wpstg.isMultisite == 1) {
175
- if ($(this).attr('name').match("^" + wpstg.tblprefix + "([^0-9])_*")) {
176
- $(this).prop("selected", "selected");
177
- } else {
178
- $(this).prop("selected", false);
179
- }
180
  }
 
181
 
182
- if (wpstg.isMultisite == 0) {
183
- if ($(this).attr('name').match("^" + wpstg.tblprefix)) {
184
- $(this).prop("selected", "selected");
185
- } else {
186
- $(this).prop("selected", false);
187
- }
188
  }
189
- })
190
- })
191
- // Expand Directories
192
- .on("click", ".wpstg-expand-dirs", function (e) {
193
- e.preventDefault();
194
-
195
- var $this = $(this);
196
-
197
- if (!$this.hasClass("disabled"))
198
- {
199
- $this.siblings(".wpstg-subdir").slideToggle();
200
  }
201
  })
202
- // When a directory checkbox is Selected
203
- .on("change", "input.wpstg-check-dir", function () {
204
- var $directory = $(this).parent(".wpstg-dir");
 
205
 
206
- if (this.checked)
207
- {
208
- $directory.parents(".wpstg-dir").children(".wpstg-check-dir").prop("checked", true);
209
- $directory.find(".wpstg-expand-dirs").removeClass("disabled");
210
- $directory.find(".wpstg-subdir .wpstg-check-dir").prop("checked", true);
211
- }
212
- else
213
- {
214
- $directory.find(".wpstg-dir .wpstg-check-dir").prop("checked", false);
215
- $directory.find(".wpstg-expand-dirs, .wpstg-check-subdirs").addClass("disabled");
216
- $directory.find(".wpstg-check-subdirs").data("action", "check").text("check");
217
- //$directory.children(".wpstg-subdir").slideUp();
218
- }
219
- })
220
- // When a directory name is Selected
221
- .on("change", "href.wpstg-check-dir", function () {
222
- var $directory = $(this).parent(".wpstg-dir");
223
 
224
- if (this.checked)
225
- {
226
- $directory.parents(".wpstg-dir").children(".wpstg-check-dir").prop("checked", true);
227
- $directory.find(".wpstg-expand-dirs").removeClass("disabled");
228
- $directory.find(".wpstg-subdir .wpstg-check-dir").prop("checked", true);
229
- }
230
- else
231
- {
232
- $directory.find(".wpstg-dir .wpstg-check-dir").prop("checked", false);
233
- $directory.find(".wpstg-expand-dirs, .wpstg-check-subdirs").addClass("disabled");
234
- $directory.find(".wpstg-check-subdirs").data("action", "check").text("check");
235
- //$directory.children(".wpstg-subdir").slideUp();
236
- }
237
- })
238
- // Check the max length of the clone name and if the clone name already exists
239
- .on("keyup", "#wpstg-new-clone-id", function () {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
- // Hide previous errors
242
- document.getElementById('wpstg-error-details').style.display = "none";
243
 
244
- // This request was already sent, clear it up!
245
- if ("number" === typeof (timer))
246
- {
247
- clearInterval(timer);
248
- }
249
 
250
- var cloneID = this.value;
251
-
252
- timer = setTimeout(
253
- function () {
254
- ajax(
255
- {
256
- action: "wpstg_check_clone",
257
- cloneID: cloneID
258
- },
259
- function (response)
260
- {
261
- if (response.status === "success")
262
- {
263
- cache.get("#wpstg-new-clone-id").removeClass("wpstg-error-input");
264
- cache.get("#wpstg-start-cloning").removeAttr("disabled");
265
- cache.get("#wpstg-clone-id-error").text('').hide();
266
- }
267
- else
268
- {
269
- cache.get("#wpstg-new-clone-id").addClass("wpstg-error-input");
270
- cache.get("#wpstg-start-cloning").prop("disabled", true);
271
- cache.get("#wpstg-clone-id-error").text(response.message).show();
272
- }
273
- }
274
- );
275
  },
276
- 500
277
- );
278
- })
279
- // Restart cloning process
280
- .on("click", "#wpstg-start-cloning", function () {
281
- that.isCancelled = false;
282
- that.getLogs = false;
283
- that.progressBar = 0;
284
- })
285
- .on('input', '#wpstg-new-clone-id', function() {
286
- if ($('#wpstg-clone-directory').length < 1) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  return;
288
- }
289
 
290
- var slug = slugify(this.value);
291
- var $targetDir = $('#wpstg-use-target-dir');
292
- var $targetUri = $('#wpstg-use-target-hostname');
293
- var path = $targetDir.data('base-path');
294
- var uri = $targetUri.data('base-uri');
295
 
296
- if (path) {
297
  path = path.replace(/\/+$/g, '') + '/' + slug + '/';
298
- }
299
 
300
- if (uri) {
301
  uri = uri.replace(/\/+$/g, '') + '/' + slug;
302
- }
303
 
304
 
305
- $('.wpstg-use-target-dir--value').text(path);
306
- $('.wpstg-use-target-hostname--value').text(uri);
307
 
308
- $targetDir.attr('data-path', path);
309
- $targetUri.attr('data-uri', uri);
310
- $('#wpstg_clone_dir').attr('placeholder', path);
311
- $('#wpstg_clone_hostname').attr('placeholder', uri);
312
- })
313
- ;
314
 
315
  cloneActions();
316
  };
@@ -318,161 +282,159 @@ var WPStaging = (function ($)
318
  /**
319
  * Clone actions
320
  */
321
- var cloneActions = function ()
322
- {
323
  var $workFlow = cache.get("#wpstg-workflow");
324
 
325
  $workFlow
326
- // Cancel cloning
327
- .on("click", "#wpstg-cancel-cloning", function () {
328
- if (!confirm("Are you sure you want to cancel cloning process?"))
329
- {
330
- return false;
331
- }
332
-
333
- var $this = $(this);
334
 
335
- $("#wpstg-try-again, #wpstg-home-link").hide();
336
- $this.prop("disabled", true);
337
 
338
- that.isCancelled = true;
339
- that.progressBar = 0;
340
 
341
- $("#wpstg-processing-status").text("Please wait...this can take up a while.");
342
- $("#wpstg-loader, #wpstg-show-log-button").hide();
343
 
344
- $this.parent().append(ajaxSpinner);
 
345
 
346
- cancelCloning();
347
- })
348
- // Resume cloning
349
- .on("click", "#wpstg-resume-cloning", function () {
350
 
351
- var $this = $(this);
 
 
 
 
 
352
 
353
- $("#wpstg-try-again, #wpstg-home-link").hide();
354
 
355
- that.isCancelled = false;
356
- //that.progressBar = 0;
357
 
358
- $("#wpstg-processing-status").text("Try to resume cloning process...");
359
- $("#wpstg-error-details").hide();
360
- $("#wpstg-loader").show();
361
 
362
- $this.parent().append(ajaxSpinner);
363
 
364
- that.startCloning();
365
- })
366
- // Cancel update cloning
367
- .on("click", "#wpstg-cancel-cloning-update", function () {
 
368
 
369
- var $this = $(this);
370
 
371
- $("#wpstg-try-again, #wpstg-home-link").hide();
372
- $this.prop("disabled", true);
373
 
374
- that.isCancelled = true;
375
 
376
- $("#wpstg-cloning-result").text("Please wait...this can take up a while.");
377
- $("#wpstg-loader, #wpstg-show-log-button").hide();
378
 
379
- $this.parent().append(ajaxSpinner);
380
 
381
- cancelCloningUpdate();
382
- })
383
- // Restart cloning
384
- .on("click", "#wpstg-restart-cloning", function () {
 
385
 
386
- var $this = $(this);
387
 
388
- $("#wpstg-try-again, #wpstg-home-link").hide();
389
- $this.prop("disabled", true);
390
 
391
- that.isCancelled = true;
392
 
393
- $("#wpstg-cloning-result").text("Please wait...this can take up a while.");
394
- $("#wpstg-loader, #wpstg-show-log-button").hide();
395
 
396
- $this.parent().append(ajaxSpinner);
397
 
398
- restart();
399
- })
400
- // Delete clone - confirmation
401
- .on("click", ".wpstg-remove-clone[data-clone]", function (e) {
402
- e.preventDefault();
 
403
 
404
- var $existingClones = cache.get("#wpstg-existing-clones");
405
 
406
- $workFlow.removeClass('active');
407
 
408
- cache.get("#wpstg-loader").show();
409
 
410
- ajax(
411
- {
412
- action: "wpstg_confirm_delete_clone",
413
- nonce: wpstg.nonce,
414
- clone: $(this).data("clone")
415
- },
416
- function (response)
417
  {
 
 
 
 
 
418
  cache.get("#wpstg-removing-clone").html(response);
419
 
420
  $existingClones.children("img").remove();
421
 
422
- cache.get("#wpstg-loader").hide();
423
  },
424
- "HTML"
425
- );
426
- })
427
- // Delete clone - confirmed
428
- .on("click", "#wpstg-remove-clone", function (e) {
429
- e.preventDefault();
 
430
 
431
- cache.get("#wpstg-removing-clone").addClass("loading");
432
 
433
- cache.get("#wpstg-loader").show();
434
 
435
- deleteClone($(this).data("clone"));
436
- })
437
- // Cancel deleting clone
438
- .on("click", "#wpstg-cancel-removing", function (e) {
439
- e.preventDefault();
440
- $(".wpstg-clone").removeClass("active");
441
- cache.get("#wpstg-removing-clone").html('');
442
- })
443
- // Update
444
- .on("click", ".wpstg-execute-clone", function (e) {
445
- e.preventDefault();
446
 
447
- var clone = $(this).data("clone");
448
 
449
- $workFlow.addClass("loading");
450
 
451
- ajax(
452
- {
453
- action: "wpstg_scanning",
454
- clone: clone,
455
- nonce: wpstg.nonce
456
- },
457
- function (response)
458
  {
459
- if (response.length < 1)
460
- {
 
 
 
 
461
  showError(
462
- "Something went wrong! Error: No response. Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
463
- );
464
  }
465
 
466
  $workFlow.removeClass("loading").html(response);
467
 
468
  cache.get(".wpstg-current-step")
469
- .removeClass("wpstg-current-step")
470
- .next("li")
471
- .addClass("wpstg-current-step");
472
  },
473
- "HTML"
474
- );
475
- });
476
  };
477
 
478
  /**
@@ -482,15 +444,12 @@ var WPStaging = (function ($)
482
  * @param {String} dataType
483
  * @param {Boolean} showErrors
484
  */
485
- var ajax = function (data, callback, dataType, showErrors, tryCount)
486
- {
487
- if ("undefined" === typeof (dataType))
488
- {
489
  dataType = "json";
490
  }
491
 
492
- if (false !== showErrors)
493
- {
494
  showErrors = true;
495
  }
496
 
@@ -520,53 +479,47 @@ var WPStaging = (function ($)
520
  } else {
521
  var errorCode = "undefined" === typeof (xhr.status) ? "Unknown" : xhr.status;
522
  showError(
523
- "Fatal Error: " + errorCode + " Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
524
- );
525
  }
526
 
527
 
528
-
529
  },
530
  success: function (data) {
531
- if ("function" === typeof (callback))
532
- {
533
  callback(data);
534
  }
535
  },
536
  statusCode: {
537
  404: function (data) {
538
  if (tryCount >= retryLimit) {
539
- showError("Error 404 - Can't find ajax request URL! Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report.");
540
  }
541
  },
542
  500: function () {
543
  if (tryCount >= retryLimit) {
544
- showError("Fatal Error 500 - Internal server error while processing the request! Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report.");
545
  }
546
- // var obj = new Object();
547
- // obj.status = false;
548
- // obj.error = 'custom error';
549
- // return JSON.stringify(obj);
550
  },
551
  504: function () {
552
  if (tryCount > retryLimit) {
553
- showError("Error 504 - It looks like your server is rate limiting ajax requests. Please try to resume after a minute. If this still not works try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report.\n\ ");
554
  }
555
 
556
  },
557
  502: function () {
558
  if (tryCount >= retryLimit) {
559
- showError("Error 502 - It looks like your server is rate limiting ajax requests. Please try to resume after a minute. If this still not works try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report.\n\ ");
560
  }
561
  },
562
  503: function () {
563
  if (tryCount >= retryLimit) {
564
- showError("Error 503 - It looks like your server is rate limiting ajax requests. Please try to resume after a minute. If this still not works try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report.\n\ ");
565
  }
566
  },
567
  429: function () {
568
  if (tryCount >= retryLimit) {
569
- showError("Error 429 - It looks like your server is rate limiting ajax requests. Please try to resume after a minute. If this still not works try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report.\n\ ");
570
  }
571
  },
572
  403: function () {
@@ -581,112 +534,103 @@ var WPStaging = (function ($)
581
  /**
582
  * Next / Previous Step Clicks to Navigate Through Staging Job
583
  */
584
- var stepButtons = function ()
585
- {
586
  var $workFlow = cache.get("#wpstg-workflow");
587
 
588
  $workFlow
589
- // Next Button
590
- .on("click", ".wpstg-next-step-link", function (e) {
591
- e.preventDefault();
592
 
593
- var $this = $(this);
594
- var isScan = false;
595
-
596
- if ($this.data("action") === "wpstg_update") {
597
- // Update Clone - confirmed
598
- if (!confirm("STOP! This will overwrite your staging site with all selected data from the live site! This should be used only if you want to clone again your production site. Are you sure you want to do this? \n\nMake sure to exclude all tables and folders which you do not want to overwrite, first! \n\nDo not necessarily cancel the updating process! This can break your staging site. \n\n\Make sure you have a backop of your staging website before you proceed."))
599
- {
600
- return false;
601
- }
602
 
 
 
 
 
603
  }
604
 
 
605
 
606
- // Button is disabled
607
- if ($this.attr("disabled"))
608
- {
609
- return false;
610
- }
611
 
612
- // Add loading overlay
613
- $workFlow.addClass("loading");
 
 
614
 
615
- // Prepare data
616
- that.data = {
617
- action: $this.data("action"),
618
- nonce: wpstg.nonce
619
- };
620
 
621
- // Cloning data
622
- getCloningData();
 
 
 
623
 
624
- console.log(that.data);
 
625
 
626
- isScan = ("wpstg_scanning" === that.action);
627
 
628
- // Send ajax request
629
- ajax(
630
- that.data,
631
- function (response) {
632
 
633
- // Undefined Error
634
- if (false === response)
635
- {
636
- showError(
637
- "Something went wrong!<br/><br/> Go to WP Staging > Settings and lower 'File Copy Limit' and 'DB Query Limit'. Also set 'CPU Load Priority to low '" +
638
- "and try again. If that does not help, " +
639
- "<a href='https://wp-staging.com/support/' target='_blank'>open a support ticket</a> "
640
- );
641
- }
642
 
 
 
 
 
 
 
 
 
643
 
644
- if (response.length < 1)
645
- {
646
- showError(
647
- "Something went wrong! No response. Go to WP Staging > Settings and lower 'File Copy Limit' and 'DB Query Limit'. Also set 'CPU Load Priority to low '" +
648
- "and try again. If that does not help, " +
649
- "<a href='https://wp-staging.com/support/' target='_blank'>open a support ticket</a> "
650
- );
651
- }
652
 
653
- // Styling of elements
654
- $workFlow.removeClass("loading").html(response);
 
 
 
 
 
 
 
 
655
 
656
- cache.get(".wpstg-current-step")
657
- .removeClass("wpstg-current-step")
658
- .next("li")
659
- .addClass("wpstg-current-step");
660
 
661
- // Start cloning
662
- that.startCloning();
663
 
664
- },
665
- "HTML"
666
- );
667
- })
668
- // Previous Button
669
- .on("click", ".wpstg-prev-step-link", function (e) {
670
- e.preventDefault();
671
- cache.get("#wpstg-loader").removeClass('wpstg-finished');
672
- cache.get("#wpstg-loader").hide();
673
- loadOverview();
674
- });
675
  };
676
 
677
  /**
678
  * Get Included (Checked) Database Tables
679
  * @returns {Array}
680
  */
681
- var getIncludedTables = function ()
682
- {
683
  var includedTables = [];
684
 
685
- // $(".wpstg-db-table input:checked").each(function () {
686
- // includedTables.push(this.name);
687
- // });
688
  $("#wpstg_select_tables_cloning option:selected").each(function () {
689
- //console.log(this);
690
  includedTables.push(this.value);
691
  });
692
 
@@ -698,16 +642,12 @@ var WPStaging = (function ($)
698
  * Not used anymore!
699
  * @returns {Array}
700
  */
701
- var getExcludedTables = function ()
702
- {
703
  var excludedTables = [];
704
 
705
  $(".wpstg-db-table input:not(:checked)").each(function () {
706
  excludedTables.push(this.name);
707
  });
708
- // $("#wpstg_select_tables_cloning option:not(:selected)").each(function () {
709
- // excludedTables.push(this.name);
710
- // });
711
 
712
  return excludedTables;
713
  };
@@ -716,8 +656,7 @@ var WPStaging = (function ($)
716
  * Get Included Directories
717
  * @returns {Array}
718
  */
719
- var getIncludedDirectories = function ()
720
- {
721
  var includedDirectories = [];
722
 
723
  $(".wpstg-dir input:checked.wpstg-root").each(function () {
@@ -732,8 +671,7 @@ var WPStaging = (function ($)
732
  * Get Excluded Directories
733
  * @returns {Array}
734
  */
735
- var getExcludedDirectories = function ()
736
- {
737
  var excludedDirectories = [];
738
 
739
  $(".wpstg-dir input:not(:checked).wpstg-root").each(function () {
@@ -749,8 +687,7 @@ var WPStaging = (function ($)
749
  * All directories except wp-content, wp-admin, wp-includes
750
  * @returns {Array}
751
  */
752
- var getIncludedExtraDirectories = function ()
753
- {
754
  // Add directories from the root level
755
  var extraDirectories = [];
756
  $(".wpstg-dir input:checked.wpstg-extra").each(function () {
@@ -768,33 +705,12 @@ var WPStaging = (function ($)
768
  return extraDirectories.concat(extraCustomDirectories);
769
  };
770
 
771
- /**
772
- * Get Included Extra Directories
773
- * @returns {Array}
774
- */
775
- // var getIncludedExtraDirectories = function ()
776
- // {
777
- // var extraDirectories = [];
778
- //
779
- // if (!$("#wpstg_extraDirectories").val()) {
780
- // return extraDirectories;
781
- // }
782
- //
783
- // var extraDirectories = $("#wpstg_extraDirectories").val().split(/\r?\n/);
784
- // console.log(extraDirectories);
785
- //
786
- // //excludedDirectories.push($this.val());
787
- //
788
- // return extraDirectories;
789
- // };
790
 
791
  /**
792
  * Get Cloning Step Data
793
  */
794
- var getCloningData = function ()
795
- {
796
- if ("wpstg_cloning" !== that.data.action && "wpstg_update" !== that.data.action)
797
- {
798
  return;
799
  }
800
 
@@ -813,48 +729,44 @@ var WPStaging = (function ($)
813
  var cloneDir = $("#wpstg_clone_dir").val();
814
  that.data.cloneDir = encodeURIComponent($.trim(cloneDir));
815
  that.data.cloneHostname = $("#wpstg_clone_hostname").val();
816
- //console.log(that.data);
817
 
818
  };
819
 
820
  /**
821
  * Loads Overview (first step) of Staging Job
822
  */
823
- var loadOverview = function ()
824
- {
825
  var $workFlow = cache.get("#wpstg-workflow");
826
 
827
  $workFlow.addClass("loading");
828
 
829
  ajax(
830
- {
831
- action: "wpstg_overview",
832
- nonce: wpstg.nonce
833
- },
834
- function (response) {
835
-
836
- if (response.length < 1)
837
  {
838
- showError(
 
 
 
 
 
 
839
  "Something went wrong! No response. Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
840
- );
841
- }
842
 
843
- var $currentStep = cache.get(".wpstg-current-step");
844
 
845
- // Styling of elements
846
- $workFlow.removeClass("loading").html(response);
847
 
848
- },
849
- "HTML"
850
- );
851
  };
852
 
853
  /**
854
  * Load Tabs
855
  */
856
- var tabs = function ()
857
- {
858
 
859
  cache.get("#wpstg-workflow").on("click", ".wpstg-tab-header", function (e) {
860
  e.preventDefault();
@@ -866,17 +778,13 @@ var WPStaging = (function ($)
866
 
867
  $section.slideToggle();
868
 
869
- if ($this.hasClass("expand"))
870
- {
871
  $this.find(".wpstg-tab-triangle").html("&#9660;");
872
- }
873
- else
874
- {
875
  $this.find(".wpstg-tab-triangle").html("&#9658;");
876
  }
877
 
878
 
879
-
880
  });
881
  };
882
 
@@ -884,176 +792,154 @@ var WPStaging = (function ($)
884
  * Delete Clone
885
  * @param {String} clone
886
  */
887
- var deleteClone = function (clone)
888
- {
889
 
890
  var deleteDir = $("#deleteDirectory:checked").data("deletepath");
891
 
892
  ajax(
893
- {
894
- action: "wpstg_delete_clone",
895
- clone: clone,
896
- nonce: wpstg.nonce,
897
- excludedTables: getExcludedTables(),
898
- deleteDir: deleteDir
899
- },
900
- function (response)
901
- {
902
- if (response) {
903
- // Error
904
- if ("undefined" !== typeof response.error && "undefined" !== typeof response.message) {
905
- showError(
906
- "Something went wrong! Error: " + response.message + ". Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
907
- );
908
- console.log(response.message);
909
- }
910
 
911
- // Finished
912
- if ("undefined" !== typeof response.delete && response.delete === 'finished') {
913
 
914
- cache.get("#wpstg-removing-clone").removeClass("loading").html('');
915
 
916
- $(".wpstg-clone#" + clone).remove();
917
 
918
- if ($(".wpstg-clone").length < 1)
919
- {
920
- cache.get("#wpstg-existing-clones").find("h3").text('');
921
- }
922
 
923
- cache.get("#wpstg-loader").hide();
 
 
 
 
 
 
924
  return;
925
  }
926
- }
927
- // continue
928
- if (true !== response)
929
- {
930
- deleteClone(clone);
931
- return;
932
- }
933
 
934
- }
935
  );
936
  };
937
 
938
  /**
939
  * Cancel Cloning Process
940
  */
941
- var cancelCloning = function ()
942
- {
943
 
944
  that.timer('stop');
945
 
946
 
947
- if (true === that.isFinished)
948
- {
949
  return true;
950
  }
951
 
952
  ajax(
953
- {
954
- action: "wpstg_cancel_clone",
955
- clone: that.data.cloneID,
956
- nonce: wpstg.nonce
957
- },
958
- function (response)
959
- {
 
 
 
 
 
 
 
960
 
 
 
 
 
 
961
 
962
- if (response && "undefined" !== typeof (response.delete) && response.delete === "finished") {
963
- cache.get("#wpstg-loader").hide();
964
  // Load overview
965
  loadOverview();
966
- return;
967
- }
968
-
969
- if (true !== response)
970
- {
971
- // continue
972
- cancelCloning();
973
- return;
974
  }
975
-
976
- // Load overview
977
- loadOverview();
978
- }
979
  );
980
  };
981
 
982
  /**
983
  * Cancel Cloning Process
984
  */
985
- var cancelCloningUpdate = function ()
986
- {
987
- if (true === that.isFinished)
988
- {
989
  return true;
990
  }
991
 
992
  ajax(
993
- {
994
- action: "wpstg_cancel_update",
995
- clone: that.data.cloneID,
996
- nonce: wpstg.nonce
997
- },
998
- function (response)
999
- {
1000
 
1001
 
1002
- if (response && "undefined" !== typeof (response.delete) && response.delete === "finished") {
 
 
 
 
 
 
 
 
 
 
 
1003
  // Load overview
1004
  loadOverview();
1005
- return;
1006
  }
1007
-
1008
- if (true !== response)
1009
- {
1010
- // continue
1011
- cancelCloningUpdate();
1012
- return;
1013
- }
1014
-
1015
- // Load overview
1016
- loadOverview();
1017
- }
1018
  );
1019
  };
1020
 
1021
  /**
1022
  * Cancel Cloning Process
1023
  */
1024
- var restart = function ()
1025
- {
1026
- if (true === that.isFinished)
1027
- {
1028
  return true;
1029
  }
1030
 
1031
  ajax(
1032
- {
1033
- action: "wpstg_restart",
1034
- //clone: that.data.cloneID,
1035
- nonce: wpstg.nonce
1036
- },
1037
- function (response)
1038
- {
1039
 
 
 
 
 
 
 
 
 
 
 
 
1040
 
1041
- if (response && "undefined" !== typeof (response.delete) && response.delete === "finished") {
1042
  // Load overview
1043
  loadOverview();
1044
- return;
1045
- }
1046
-
1047
- if (true !== response)
1048
- {
1049
- // continue
1050
- cancelCloningUpdate();
1051
- return;
1052
  }
1053
-
1054
- // Load overview
1055
- loadOverview();
1056
- }
1057
  );
1058
  };
1059
 
@@ -1062,7 +948,7 @@ var WPStaging = (function ($)
1062
  * @returns void
1063
  */
1064
  var logscroll = function () {
1065
- var $div = cache.get("#wpstg-log-details");
1066
  if ("undefined" !== typeof ($div[0])) {
1067
  $div.scrollTop($div[0].scrollHeight);
1068
  }
@@ -1073,8 +959,7 @@ var WPStaging = (function ($)
1073
  * @param string log
1074
  * @returns void
1075
  */
1076
- var getLogs = function (log)
1077
- {
1078
  if (log != null && "undefined" !== typeof (log)) {
1079
  if (log.constructor === Array) {
1080
  $.each(log, function (index, value) {
@@ -1082,13 +967,13 @@ var WPStaging = (function ($)
1082
  return;
1083
  }
1084
  if (value.type === 'ERROR') {
1085
- cache.get("#wpstg-log-details").append('<span style="color:red;">[' + value.type + ']</span>-' + '[' + value.date + '] ' + value.message + '</br>');
1086
  } else {
1087
- cache.get("#wpstg-log-details").append('[' + value.type + ']-' + '[' + value.date + '] ' + value.message + '</br>');
1088
  }
1089
  })
1090
  } else {
1091
- cache.get("#wpstg-log-details").append('[' + log.type + ']-' + '[' + log.date + '] ' + log.message + '</br>');
1092
  }
1093
  }
1094
  logscroll();
@@ -1101,33 +986,60 @@ var WPStaging = (function ($)
1101
  */
1102
  var checkDiskSpace = function () {
1103
  cache.get("#wpstg-check-space").on("click", function (e) {
1104
- cache.get("#wpstg-loader").show();
1105
  console.log("check disk space");
1106
  ajax(
1107
- {
1108
- action: "wpstg_check_disk_space",
1109
- nonce: wpstg.nonce
1110
- },
1111
- function (response)
1112
- {
1113
- if (false === response)
1114
  {
1115
- cache.get("#wpstg-clone-id-error").text('Can not detect required disk space').show();
1116
- cache.get("#wpstg-loader").hide();
1117
- return;
1118
- }
 
 
 
 
 
1119
 
1120
- // Show required disk space
1121
- //cache.get("#wpstg-clone-id-error").text('Available free disk space ' + response.freespace + ' | Estimated necessary disk space: ' + response.usedspace).show();
1122
- cache.get("#wpstg-clone-id-error").html('Estimated necessary disk space: ' + response.usedspace + '<br> <span style="color:#444;">Before you proceed ensure your account has enough free disk space to hold the entire instance of the production site. You can check the available space from your hosting account (cPanel or similar).</span>').show();
1123
- cache.get("#wpstg-loader").hide();
1124
- },
1125
- "json",
1126
- false
1127
- );
1128
  });
1129
  }
1130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1131
 
1132
  /**
1133
  * Count up processing execution time
@@ -1173,11 +1085,12 @@ var WPStaging = (function ($)
1173
  */
1174
  that.startCloning = (function () {
1175
 
 
 
1176
  // Register function for checking disk space
1177
  checkDiskSpace();
1178
 
1179
- if ("wpstg_cloning" !== that.data.action && "wpstg_update" !== that.data.action)
1180
- {
1181
  return;
1182
  }
1183
 
@@ -1188,12 +1101,11 @@ var WPStaging = (function ($)
1188
 
1189
  // Functions
1190
  // Start
1191
- function start()
1192
- {
1193
 
1194
  console.log("Starting cloning process...");
1195
 
1196
- cache.get("#wpstg-loader").show();
1197
  cache.get("#wpstg-cancel-cloning").text('Cancel');
1198
  cache.get("#wpstg-resume-cloning").hide();
1199
  cache.get("#wpstg-error-details").hide();
@@ -1210,113 +1122,79 @@ var WPStaging = (function ($)
1210
  }
1211
 
1212
 
1213
-
1214
  /**
1215
  * Start ajax processing
1216
  * @returns string
1217
  */
1218
  var processing = function () {
1219
 
1220
- if (true === that.isCancelled)
1221
- {
1222
  return false;
1223
  }
1224
 
1225
-
1226
-
1227
- //console.log("Start ajax processing");
1228
-
1229
- // Show loader gif
1230
- cache.get("#wpstg-loader").show();
1231
- cache.get(".wpstg-loader").show();
1232
 
1233
  // Show logging window
1234
- cache.get('#wpstg-log-details').show();
1235
 
1236
  WPStaging.ajax(
1237
- {
1238
- action: "wpstg_processing",
1239
- nonce: wpstg.nonce,
1240
- excludedTables: getExcludedTables(),
1241
- includedDirectories: getIncludedDirectories(),
1242
- excludedDirectories: getExcludedDirectories(),
1243
- extraDirectories: getIncludedExtraDirectories()
1244
- },
1245
- function (response)
1246
- {
1247
- // Undefined Error
1248
- if (false === response)
1249
  {
1250
- showError(
1251
- "Something went wrong! Error: No response. <br/><br/> Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
1252
- );
1253
- cache.get("#wpstg-loader").hide();
1254
- cache.get(".wpstg-loader").hide();
1255
- return;
1256
- }
1257
-
1258
- // Throw Error
1259
- if ("undefined" !== typeof (response.error) && response.error) {
1260
- console.log(response.message);
1261
- showError(
1262
- "Something went wrong! Error: " + response.message + ". Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
1263
- );
1264
 
1265
- return;
1266
- }
1267
 
1268
- // Add Log messages
1269
- if ("undefined" !== typeof (response.last_msg) && response.last_msg)
1270
- {
1271
- getLogs(response.last_msg);
1272
- }
1273
- // Continue processing
1274
- if (false === response.status)
1275
- {
1276
- progressBar(response);
1277
-
1278
- setTimeout(function () {
1279
- //console.log('continue processing');
1280
- cache.get("#wpstg-loader").show();
 
 
 
 
1281
  processing();
1282
- }, wpstg.delayReq);
1283
-
1284
- } else if (true === response.status && 'finished' !== response.status) {
1285
- //console.log('Processing...');
1286
- cache.get("#wpstg-error-details").hide();
1287
- cache.get("#wpstg-error-wrapper").hide();
1288
- progressBar(response, true);
1289
- processing();
1290
- } else if ('finished' === response.status || ("undefined" !== typeof (response.job_done) && response.job_done)) {
1291
- finish(response);
1292
- }
1293
- ;
1294
- },
1295
- "json",
1296
- false
1297
- );
1298
  };
1299
 
1300
  // Finish
1301
- function finish(response)
1302
- {
1303
 
1304
- if (true === that.getLogs)
1305
- {
1306
  getLogs();
1307
  }
1308
 
1309
  progressBar(response);
1310
 
1311
  // Add Log
1312
- if ("undefined" !== typeof (response.last_msg))
1313
- {
1314
  getLogs(response.last_msg);
1315
  }
1316
 
1317
  console.log("Cloning process finished");
1318
 
1319
- cache.get("#wpstg-loader").hide();
1320
  cache.get("#wpstg-processing-header").html('Processing Complete');
1321
  $("#wpstg-processing-status").text("Succesfully finished");
1322
 
@@ -1339,12 +1217,13 @@ var WPStaging = (function ($)
1339
  that.timer('stop');
1340
 
1341
 
1342
- cache.get("#wpstg-loader").hide();
1343
  cache.get("#wpstg-processing-header").html('Processing Complete');
1344
 
1345
  return false;
1346
 
1347
  }
 
1348
  /**
1349
  * Add percentage progress bar
1350
  * @param object response
@@ -1354,10 +1233,6 @@ var WPStaging = (function ($)
1354
  if ("undefined" === typeof (response.percentage))
1355
  return false;
1356
 
1357
- // //if (restart){
1358
- // cache.get("#wpstg-db-progress").width('1%');
1359
- // //}
1360
-
1361
  if (response.job === 'database') {
1362
  cache.get("#wpstg-progress-db").width(response.percentage * 0.2 + '%').html(response.percentage + '%');
1363
  cache.get("#wpstg-processing-status").html(response.percentage.toFixed(0) + '%' + ' - Step 1 of 4 Cloning Database Tables...');
@@ -1401,6 +1276,7 @@ var WPStaging = (function ($)
1401
  elements();
1402
  stepButtons();
1403
  tabs();
 
1404
  });
1405
 
1406
  /**
@@ -1412,6 +1288,365 @@ var WPStaging = (function ($)
1412
  that.getLogs = getLogs;
1413
  that.loadOverview = loadOverview;
1414
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1415
  return that;
1416
  })(jQuery);
1417
 
@@ -1425,11 +1660,16 @@ jQuery(document).ready(function () {
1425
  jQuery(document).ready(function ($) {
1426
 
1427
  $('#wpstg-report-issue-button').click(function (e) {
1428
- console.log('report issue');
1429
  $('.wpstg-report-issue-form').toggleClass('wpstg-report-show');
1430
  e.preventDefault();
1431
  });
1432
 
 
 
 
 
 
 
1433
  $('#wpstg-report-cancel').click(function (e) {
1434
  $('.wpstg-report-issue-form').removeClass('wpstg-report-show');
1435
  e.preventDefault();
1
  "use strict";
2
 
3
+ var WPStaging = (function ($) {
 
4
  var that = {
5
+ isCancelled: false,
6
+ isFinished: false,
7
+ getLogs: false,
8
+ time: 1,
9
+ executionTime: false,
10
+ progressBar: 0
11
+ },
12
+ cache = {elements: []},
13
+ timeout, ajaxSpinner;
14
 
15
  /**
16
  * Get / Set Cache for Selector
17
  * @param {String} selector
18
  * @returns {*}
19
  */
20
+ cache.get = function (selector) {
 
21
  // It is already cached!
22
+ if ($.inArray(selector, cache.elements) !== -1) {
 
23
  return cache.elements[selector];
24
  }
25
 
33
  * Refreshes given cache
34
  * @param {String} selector
35
  */
36
+ cache.refresh = function (selector) {
 
37
  selector.elements[selector] = jQuery(selector);
38
  };
39
 
41
  * Show and Log Error Message
42
  * @param {String} message
43
  */
44
+ var showError = function (message) {
 
45
  cache.get("#wpstg-try-again").css("display", "inline-block");
46
  cache.get("#wpstg-cancel-cloning").text("Reset");
47
  cache.get("#wpstg-resume-cloning").show();
48
  cache.get("#wpstg-error-wrapper").show();
49
  cache.get("#wpstg-error-details").show().html(message);
50
  cache.get("#wpstg-removing-clone").removeClass("loading");
51
+ cache.get(".wpstg-loader").hide();
52
  };
53
 
54
+ /**
55
+ *
56
+ * @param response the error object
57
+ * @param prependMessage Overwrite default error message at beginning
58
+ * @param appendMessage Overwrite default error message at end
59
+ * @returns void
60
+ */
61
+
62
+ var showAjaxFatalError = function (response, prependMessage, appendMessage) {
63
+ prependMessage = prependMessage ? prependMessage + '<br/><br/>' : 'Something went wrong! <br/><br/>';
64
+ appendMessage = appendMessage ? appendMessage + '<br/><br/>' : '<br/><br/>Please try the <a href=\'https://wp-staging.com/docs/wp-staging-settings-for-small-servers/\' target=\'_blank\'>WP Staging Small Server Settings</a> or submit an error report and contact us.';
65
+
66
+ if (response === false) {
67
+ showError(prependMessage + ' Error: No response.' + appendMessage);
68
+ return;
69
+ }
70
+
71
+ if (typeof response.error !== 'undefined' && response.error) {
72
+ console.error(response.message);
73
+ showError(prependMessage + ' Error: ' + response.message + appendMessage);
74
+ return;
75
+ }
76
+ }
77
+
78
+ /** Hide and reset previous thrown visible errors */
79
+ var resetErrors = function () {
80
+ cache.get("#wpstg-error-details").hide().html('');
81
+ }
82
+
83
+ var slugify = function (url) {
84
+ return url.toString()
85
+ .toLowerCase()
86
+ .normalize('NFD')
87
+ .replace(/[\u0300-\u036f]/g, '')
88
+ .replace(/\s+/g, '-')
89
+ .replace(/&/g, '-and-')
90
+ .replace(/[^a-z0-9\-]/g, '')
91
+ .replace(/-+/g, '-')
92
+ .replace(/^-*/, '')
93
+ .replace(/-*$/, '')
94
+ ;
95
  };
96
 
97
+
98
  /**
99
  * Common Elements
100
  */
101
+ var elements = function () {
 
102
  var $workFlow = cache.get("#wpstg-workflow"),
103
+ isAllChecked = true,
104
+ urlSpinner = ajaxurl.replace("/admin-ajax.php", '') + "/images/spinner",
105
+ timer;
106
 
107
+ if (2 < window.devicePixelRatio) {
 
108
  urlSpinner += "-2x";
109
  }
110
 
112
 
113
  ajaxSpinner = "<img src=''" + urlSpinner + "' alt='' class='ajax-spinner general-spinner' />";
114
 
115
+ var getBaseValues = function () {
116
+ var path = $('#wpstg-use-target-dir').data('base-path');
117
+ var uri = $('#wpstg-use-target-hostname').data('base-uri');
118
+ return {
119
+ path
120
+ };
121
  };
122
 
123
  $workFlow
124
+ // Check / Un-check All Database Tables New
125
+ .on("click", ".wpstg-button-unselect", function (e) {
126
+ e.preventDefault();
127
+
128
+ if (false === isAllChecked) {
129
+ console.log('true');
130
+ cache.get("#wpstg_select_tables_cloning .wpstg-db-table").prop("selected", "selected");
131
+ cache.get(".wpstg-button-unselect").text("Unselect All");
132
+ cache.get(".wpstg-db-table-checkboxes").prop("checked", true);
133
+ isAllChecked = true;
134
+ } else {
135
+ console.log('false');
136
+ cache.get("#wpstg_select_tables_cloning .wpstg-db-table").prop("selected", false);
137
+ cache.get(".wpstg-button-unselect").text("Select All");
138
+ cache.get(".wpstg-db-table-checkboxes").prop("checked", false);
139
+ isAllChecked = false;
140
+ }
141
+ })
142
+
143
+ /**
144
+ * Select tables with certain tbl prefix | NEW
145
+ * @param obj e
146
+ * @returns {undefined}
147
+ */
148
+ .on("click", ".wpstg-button-select", function (e) {
149
+ e.preventDefault();
150
+ $("#wpstg_select_tables_cloning .wpstg-db-table").each(function () {
151
+ if (wpstg.isMultisite == 1) {
152
+ if ($(this).attr('name').match("^" + wpstg.tblprefix + "([^0-9])_*")) {
153
+ $(this).prop("selected", "selected");
154
+ } else {
155
+ $(this).prop("selected", false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  }
157
+ }
158
 
159
+ if (wpstg.isMultisite == 0) {
160
+ if ($(this).attr('name').match("^" + wpstg.tblprefix)) {
161
+ $(this).prop("selected", "selected");
162
+ } else {
163
+ $(this).prop("selected", false);
 
164
  }
 
 
 
 
 
 
 
 
 
 
 
165
  }
166
  })
167
+ })
168
+ // Expand Directories
169
+ .on("click", ".wpstg-expand-dirs", function (e) {
170
+ e.preventDefault();
171
 
172
+ var $this = $(this);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
+ if (!$this.hasClass("disabled")) {
175
+ $this.siblings(".wpstg-subdir").slideToggle();
176
+ }
177
+ })
178
+ // When a directory checkbox is Selected
179
+ .on("change", "input.wpstg-check-dir", function () {
180
+ var $directory = $(this).parent(".wpstg-dir");
181
+
182
+ if (this.checked) {
183
+ $directory.parents(".wpstg-dir").children(".wpstg-check-dir").prop("checked", true);
184
+ $directory.find(".wpstg-expand-dirs").removeClass("disabled");
185
+ $directory.find(".wpstg-subdir .wpstg-check-dir").prop("checked", true);
186
+ } else {
187
+ $directory.find(".wpstg-dir .wpstg-check-dir").prop("checked", false);
188
+ $directory.find(".wpstg-expand-dirs, .wpstg-check-subdirs").addClass("disabled");
189
+ $directory.find(".wpstg-check-subdirs").data("action", "check").text("check");
190
+ }
191
+ })
192
+ // When a directory name is Selected
193
+ .on("change", "href.wpstg-check-dir", function () {
194
+ var $directory = $(this).parent(".wpstg-dir");
195
+
196
+ if (this.checked) {
197
+ $directory.parents(".wpstg-dir").children(".wpstg-check-dir").prop("checked", true);
198
+ $directory.find(".wpstg-expand-dirs").removeClass("disabled");
199
+ $directory.find(".wpstg-subdir .wpstg-check-dir").prop("checked", true);
200
+ } else {
201
+ $directory.find(".wpstg-dir .wpstg-check-dir").prop("checked", false);
202
+ $directory.find(".wpstg-expand-dirs, .wpstg-check-subdirs").addClass("disabled");
203
+ $directory.find(".wpstg-check-subdirs").data("action", "check").text("check");
204
+ }
205
+ })
206
+ // Check the max length of the clone name and if the clone name already exists
207
+ .on("keyup", "#wpstg-new-clone-id", function () {
208
 
209
+ // Hide previous errors
210
+ document.getElementById('wpstg-error-details').style.display = "none";
211
 
212
+ // This request was already sent, clear it up!
213
+ if ("number" === typeof (timer)) {
214
+ clearInterval(timer);
215
+ }
 
216
 
217
+ var cloneID = this.value;
218
+
219
+ timer = setTimeout(
220
+ function () {
221
+ ajax(
222
+ {
223
+ action: "wpstg_check_clone",
224
+ cloneID: cloneID
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  },
226
+ function (response) {
227
+ if (response.status === "success") {
228
+ cache.get("#wpstg-new-clone-id").removeClass("wpstg-error-input");
229
+ cache.get("#wpstg-start-cloning").removeAttr("disabled");
230
+ cache.get("#wpstg-clone-id-error").text('').hide();
231
+ } else {
232
+ cache.get("#wpstg-new-clone-id").addClass("wpstg-error-input");
233
+ cache.get("#wpstg-start-cloning").prop("disabled", true);
234
+ cache.get("#wpstg-clone-id-error").text(response.message).show();
235
+ }
236
+ }
237
+ );
238
+ },
239
+ 500
240
+ );
241
+ })
242
+ // Restart cloning process
243
+ .on("click", "#wpstg-start-cloning", function () {
244
+ resetErrors();
245
+ that.isCancelled = false;
246
+ that.getLogs = false;
247
+ that.progressBar = 0;
248
+ })
249
+ .on('input', '#wpstg-new-clone-id', function () {
250
+ if ($('#wpstg-clone-directory').length < 1) {
251
  return;
252
+ }
253
 
254
+ var slug = slugify(this.value);
255
+ var $targetDir = $('#wpstg-use-target-dir');
256
+ var $targetUri = $('#wpstg-use-target-hostname');
257
+ var path = $targetDir.data('base-path');
258
+ var uri = $targetUri.data('base-uri');
259
 
260
+ if (path) {
261
  path = path.replace(/\/+$/g, '') + '/' + slug + '/';
262
+ }
263
 
264
+ if (uri) {
265
  uri = uri.replace(/\/+$/g, '') + '/' + slug;
266
+ }
267
 
268
 
269
+ $('.wpstg-use-target-dir--value').text(path);
270
+ $('.wpstg-use-target-hostname--value').text(uri);
271
 
272
+ $targetDir.attr('data-path', path);
273
+ $targetUri.attr('data-uri', uri);
274
+ $('#wpstg_clone_dir').attr('placeholder', path);
275
+ $('#wpstg_clone_hostname').attr('placeholder', uri);
276
+ })
277
+ ;
278
 
279
  cloneActions();
280
  };
282
  /**
283
  * Clone actions
284
  */
285
+ var cloneActions = function () {
 
286
  var $workFlow = cache.get("#wpstg-workflow");
287
 
288
  $workFlow
289
+ // Cancel cloning
290
+ .on("click", "#wpstg-cancel-cloning", function () {
291
+ if (!confirm("Are you sure you want to cancel cloning process?")) {
292
+ return false;
293
+ }
 
 
 
294
 
295
+ var $this = $(this);
 
296
 
297
+ $("#wpstg-try-again, #wpstg-home-link").hide();
298
+ $this.prop("disabled", true);
299
 
300
+ that.isCancelled = true;
301
+ that.progressBar = 0;
302
 
303
+ $("#wpstg-processing-status").text("Please wait...this can take up a while.");
304
+ $(".wpstg-loader, #wpstg-show-log-button").hide();
305
 
306
+ $this.parent().append(ajaxSpinner);
 
 
 
307
 
308
+ cancelCloning();
309
+ })
310
+ // Resume cloning
311
+ .on("click", "#wpstg-resume-cloning", function () {
312
+ resetErrors();
313
+ var $this = $(this);
314
 
315
+ $("#wpstg-try-again, #wpstg-home-link").hide();
316
 
317
+ that.isCancelled = false;
 
318
 
319
+ $("#wpstg-processing-status").text("Try to resume cloning process...");
320
+ $("#wpstg-error-details").hide();
321
+ $(".wpstg-loader").show();
322
 
323
+ $this.parent().append(ajaxSpinner);
324
 
325
+ that.startCloning();
326
+ })
327
+ // Cancel update cloning
328
+ .on("click", "#wpstg-cancel-cloning-update", function () {
329
+ resetErrors();
330
 
331
+ var $this = $(this);
332
 
333
+ $("#wpstg-try-again, #wpstg-home-link").hide();
334
+ $this.prop("disabled", true);
335
 
336
+ that.isCancelled = true;
337
 
338
+ $("#wpstg-cloning-result").text("Please wait...this can take up a while.");
339
+ $(".wpstg-loader, #wpstg-show-log-button").hide();
340
 
341
+ $this.parent().append(ajaxSpinner);
342
 
343
+ cancelCloningUpdate();
344
+ })
345
+ // Restart cloning
346
+ .on("click", "#wpstg-restart-cloning", function () {
347
+ resetErrors();
348
 
349
+ var $this = $(this);
350
 
351
+ $("#wpstg-try-again, #wpstg-home-link").hide();
352
+ $this.prop("disabled", true);
353
 
354
+ that.isCancelled = true;
355
 
356
+ $("#wpstg-cloning-result").text("Please wait...this can take up a while.");
357
+ $(".wpstg-loader, #wpstg-show-log-button").hide();
358
 
359
+ $this.parent().append(ajaxSpinner);
360
 
361
+ restart();
362
+ })
363
+ // Delete clone - confirmation
364
+ .on("click", ".wpstg-remove-clone[data-clone]", function (e) {
365
+ resetErrors();
366
+ e.preventDefault();
367
 
368
+ var $existingClones = cache.get("#wpstg-existing-clones");
369
 
370
+ $workFlow.removeClass('active');
371
 
372
+ cache.get(".wpstg-loader").show();
373
 
374
+ ajax(
 
 
 
 
 
 
375
  {
376
+ action: "wpstg_confirm_delete_clone",
377
+ nonce: wpstg.nonce,
378
+ clone: $(this).data("clone")
379
+ },
380
+ function (response) {
381
  cache.get("#wpstg-removing-clone").html(response);
382
 
383
  $existingClones.children("img").remove();
384
 
385
+ cache.get(".wpstg-loader").hide();
386
  },
387
+ "HTML"
388
+ );
389
+ })
390
+ // Delete clone - confirmed
391
+ .on("click", "#wpstg-remove-clone", function (e) {
392
+ resetErrors();
393
+ e.preventDefault();
394
 
395
+ cache.get("#wpstg-removing-clone").addClass("loading");
396
 
397
+ cache.get(".wpstg-loader").show();
398
 
399
+ deleteClone($(this).data("clone"));
400
+ })
401
+ // Cancel deleting clone
402
+ .on("click", "#wpstg-cancel-removing", function (e) {
403
+ e.preventDefault();
404
+ $(".wpstg-clone").removeClass("active");
405
+ cache.get("#wpstg-removing-clone").html('');
406
+ })
407
+ // Update
408
+ .on("click", ".wpstg-execute-clone", function (e) {
409
+ e.preventDefault();
410
 
411
+ var clone = $(this).data("clone");
412
 
413
+ $workFlow.addClass("loading");
414
 
415
+ ajax(
 
 
 
 
 
 
416
  {
417
+ action: "wpstg_scanning",
418
+ clone: clone,
419
+ nonce: wpstg.nonce
420
+ },
421
+ function (response) {
422
+ if (response.length < 1) {
423
  showError(
424
+ "Something went wrong! Error: No response. Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report and contact us."
425
+ );
426
  }
427
 
428
  $workFlow.removeClass("loading").html(response);
429
 
430
  cache.get(".wpstg-current-step")
431
+ .removeClass("wpstg-current-step")
432
+ .next("li")
433
+ .addClass("wpstg-current-step");
434
  },
435
+ "HTML"
436
+ );
437
+ });
438
  };
439
 
440
  /**
444
  * @param {String} dataType
445
  * @param {Boolean} showErrors
446
  */
447
+ var ajax = function (data, callback, dataType, showErrors, tryCount) {
448
+ if ("undefined" === typeof (dataType)) {
 
 
449
  dataType = "json";
450
  }
451
 
452
+ if (false !== showErrors) {
 
453
  showErrors = true;
454
  }
455
 
479
  } else {
480
  var errorCode = "undefined" === typeof (xhr.status) ? "Unknown" : xhr.status;
481
  showError(
482
+ "Fatal Error: " + errorCode + " Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report and contact us."
483
+ );
484
  }
485
 
486
 
 
487
  },
488
  success: function (data) {
489
+ if ("function" === typeof (callback)) {
 
490
  callback(data);
491
  }
492
  },
493
  statusCode: {
494
  404: function (data) {
495
  if (tryCount >= retryLimit) {
496
+ showError("Error 404 - Can't find ajax request URL! Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report and contact us.");
497
  }
498
  },
499
  500: function () {
500
  if (tryCount >= retryLimit) {
501
+ showError("Fatal Error 500 - Internal server error while processing the request! Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report and contact us.");
502
  }
 
 
 
 
503
  },
504
  504: function () {
505
  if (tryCount > retryLimit) {
506
+ showError("Error 504 - It looks like your server is rate limiting ajax requests. Please try to resume after a minute. If this still not works try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report and contact us.\n\ ");
507
  }
508
 
509
  },
510
  502: function () {
511
  if (tryCount >= retryLimit) {
512
+ showError("Error 502 - It looks like your server is rate limiting ajax requests. Please try to resume after a minute. If this still not works try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report and contact us.\n\ ");
513
  }
514
  },
515
  503: function () {
516
  if (tryCount >= retryLimit) {
517
+ showError("Error 503 - It looks like your server is rate limiting ajax requests. Please try to resume after a minute. If this still not works try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report and contact us.\n\ ");
518
  }
519
  },
520
  429: function () {
521
  if (tryCount >= retryLimit) {
522
+ showError("Error 429 - It looks like your server is rate limiting ajax requests. Please try to resume after a minute. If this still not works try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report and contact us.\n\ ");
523
  }
524
  },
525
  403: function () {
534
  /**
535
  * Next / Previous Step Clicks to Navigate Through Staging Job
536
  */
537
+ var stepButtons = function () {
538
+
539
  var $workFlow = cache.get("#wpstg-workflow");
540
 
541
  $workFlow
542
+ // Next Button
543
+ .on("click", ".wpstg-next-step-link", function (e) {
544
+ e.preventDefault();
545
 
546
+ var $this = $(this);
547
+ var isScan = false;
 
 
 
 
 
 
 
548
 
549
+ if ($this.data("action") === "wpstg_update") {
550
+ // Update Clone - confirmed
551
+ if (!confirm("STOP! This will overwrite your staging site with all selected data from the live site! This should be used only if you want to clone again your production site. Are you sure you want to do this? \n\nMake sure to exclude all tables and folders which you do not want to overwrite, first! \n\nDo not necessarily cancel the updating process! This can break your staging site. \n\n\Make sure you have a backop of your staging website before you proceed.")) {
552
+ return false;
553
  }
554
 
555
+ }
556
 
 
 
 
 
 
557
 
558
+ // Button is disabled
559
+ if ($this.attr("disabled")) {
560
+ return false;
561
+ }
562
 
563
+ // Add loading overlay
564
+ $workFlow.addClass("loading");
 
 
 
565
 
566
+ // Prepare data
567
+ that.data = {
568
+ action: $this.data("action"),
569
+ nonce: wpstg.nonce
570
+ };
571
 
572
+ // Cloning data
573
+ getCloningData();
574
 
575
+ console.log(that.data);
576
 
577
+ isScan = ("wpstg_scanning" === that.action);
 
 
 
578
 
579
+ // Send ajax request
580
+ ajax(
581
+ that.data,
582
+ function (response) {
 
 
 
 
 
583
 
584
+ // Undefined Error
585
+ if (false === response) {
586
+ showError(
587
+ "Something went wrong!<br/><br/> Go to WP Staging > Settings and lower 'File Copy Limit' and 'DB Query Limit'. Also set 'CPU Load Priority to low '" +
588
+ "and try again. If that does not help, " +
589
+ "<a href='https://wp-staging.com/support/' target='_blank'>open a support ticket</a> "
590
+ );
591
+ }
592
 
 
 
 
 
 
 
 
 
593
 
594
+ if (response.length < 1) {
595
+ showError(
596
+ "Something went wrong! No response. Go to WP Staging > Settings and lower 'File Copy Limit' and 'DB Query Limit'. Also set 'CPU Load Priority to low '" +
597
+ "and try again. If that does not help, " +
598
+ "<a href='https://wp-staging.com/support/' target='_blank'>open a support ticket</a> "
599
+ );
600
+ }
601
+
602
+ // Styling of elements
603
+ $workFlow.removeClass("loading").html(response);
604
 
605
+ cache.get(".wpstg-current-step")
606
+ .removeClass("wpstg-current-step")
607
+ .next("li")
608
+ .addClass("wpstg-current-step");
609
 
610
+ // Start cloning
611
+ that.startCloning();
612
 
613
+ },
614
+ "HTML"
615
+ );
616
+ })
617
+ // Previous Button
618
+ .on("click", ".wpstg-prev-step-link", function (e) {
619
+ e.preventDefault();
620
+ cache.get(".wpstg-loader").removeClass('wpstg-finished');
621
+ cache.get(".wpstg-loader").hide();
622
+ loadOverview();
623
+ });
624
  };
625
 
626
  /**
627
  * Get Included (Checked) Database Tables
628
  * @returns {Array}
629
  */
630
+ var getIncludedTables = function () {
 
631
  var includedTables = [];
632
 
 
 
 
633
  $("#wpstg_select_tables_cloning option:selected").each(function () {
 
634
  includedTables.push(this.value);
635
  });
636
 
642
  * Not used anymore!
643
  * @returns {Array}
644
  */
645
+ var getExcludedTables = function () {
 
646
  var excludedTables = [];
647
 
648
  $(".wpstg-db-table input:not(:checked)").each(function () {
649
  excludedTables.push(this.name);
650
  });
 
 
 
651
 
652
  return excludedTables;
653
  };
656
  * Get Included Directories
657
  * @returns {Array}
658
  */
659
+ var getIncludedDirectories = function () {
 
660
  var includedDirectories = [];
661
 
662
  $(".wpstg-dir input:checked.wpstg-root").each(function () {
671
  * Get Excluded Directories
672
  * @returns {Array}
673
  */
674
+ var getExcludedDirectories = function () {
 
675
  var excludedDirectories = [];
676
 
677
  $(".wpstg-dir input:not(:checked).wpstg-root").each(function () {
687
  * All directories except wp-content, wp-admin, wp-includes
688
  * @returns {Array}
689
  */
690
+ var getIncludedExtraDirectories = function () {
 
691
  // Add directories from the root level
692
  var extraDirectories = [];
693
  $(".wpstg-dir input:checked.wpstg-extra").each(function () {
705
  return extraDirectories.concat(extraCustomDirectories);
706
  };
707
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
708
 
709
  /**
710
  * Get Cloning Step Data
711
  */
712
+ var getCloningData = function () {
713
+ if ("wpstg_cloning" !== that.data.action && "wpstg_update" !== that.data.action) {
 
 
714
  return;
715
  }
716
 
729
  var cloneDir = $("#wpstg_clone_dir").val();
730
  that.data.cloneDir = encodeURIComponent($.trim(cloneDir));
731
  that.data.cloneHostname = $("#wpstg_clone_hostname").val();
 
732
 
733
  };
734
 
735
  /**
736
  * Loads Overview (first step) of Staging Job
737
  */
738
+ var loadOverview = function () {
 
739
  var $workFlow = cache.get("#wpstg-workflow");
740
 
741
  $workFlow.addClass("loading");
742
 
743
  ajax(
 
 
 
 
 
 
 
744
  {
745
+ action: "wpstg_overview",
746
+ nonce: wpstg.nonce
747
+ },
748
+ function (response) {
749
+
750
+ if (response.length < 1) {
751
+ showError(
752
  "Something went wrong! No response. Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
753
+ );
754
+ }
755
 
756
+ var $currentStep = cache.get(".wpstg-current-step");
757
 
758
+ // Styling of elements
759
+ $workFlow.removeClass("loading").html(response);
760
 
761
+ },
762
+ "HTML"
763
+ );
764
  };
765
 
766
  /**
767
  * Load Tabs
768
  */
769
+ var tabs = function () {
 
770
 
771
  cache.get("#wpstg-workflow").on("click", ".wpstg-tab-header", function (e) {
772
  e.preventDefault();
778
 
779
  $section.slideToggle();
780
 
781
+ if ($this.hasClass("expand")) {
 
782
  $this.find(".wpstg-tab-triangle").html("&#9660;");
783
+ } else {
 
 
784
  $this.find(".wpstg-tab-triangle").html("&#9658;");
785
  }
786
 
787
 
 
788
  });
789
  };
790
 
792
  * Delete Clone
793
  * @param {String} clone
794
  */
795
+ var deleteClone = function (clone) {
 
796
 
797
  var deleteDir = $("#deleteDirectory:checked").data("deletepath");
798
 
799
  ajax(
800
+ {
801
+ action: "wpstg_delete_clone",
802
+ clone: clone,
803
+ nonce: wpstg.nonce,
804
+ excludedTables: getExcludedTables(),
805
+ deleteDir: deleteDir
806
+ },
807
+ function (response) {
808
+ if (response) {
809
+ showAjaxFatalError(response);
 
 
 
 
 
 
 
810
 
811
+ // Finished
812
+ if ("undefined" !== typeof response.delete && response.delete === 'finished') {
813
 
814
+ cache.get("#wpstg-removing-clone").removeClass("loading").html('');
815
 
816
+ $(".wpstg-clone#" + clone).remove();
817
 
818
+ if ($(".wpstg-clone").length < 1) {
819
+ cache.get("#wpstg-existing-clones").find("h3").text('');
820
+ }
 
821
 
822
+ cache.get(".wpstg-loader").hide();
823
+ return;
824
+ }
825
+ }
826
+ // continue
827
+ if (true !== response) {
828
+ deleteClone(clone);
829
  return;
830
  }
 
 
 
 
 
 
 
831
 
832
+ }
833
  );
834
  };
835
 
836
  /**
837
  * Cancel Cloning Process
838
  */
839
+ var cancelCloning = function () {
 
840
 
841
  that.timer('stop');
842
 
843
 
844
+ if (true === that.isFinished) {
 
845
  return true;
846
  }
847
 
848
  ajax(
849
+ {
850
+ action: "wpstg_cancel_clone",
851
+ clone: that.data.cloneID,
852
+ nonce: wpstg.nonce
853
+ },
854
+ function (response) {
855
+
856
+
857
+ if (response && "undefined" !== typeof (response.delete) && response.delete === "finished") {
858
+ cache.get(".wpstg-loader").hide();
859
+ // Load overview
860
+ loadOverview();
861
+ return;
862
+ }
863
 
864
+ if (true !== response) {
865
+ // continue
866
+ cancelCloning();
867
+ return;
868
+ }
869
 
 
 
870
  // Load overview
871
  loadOverview();
 
 
 
 
 
 
 
 
872
  }
 
 
 
 
873
  );
874
  };
875
 
876
  /**
877
  * Cancel Cloning Process
878
  */
879
+ var cancelCloningUpdate = function () {
880
+ if (true === that.isFinished) {
 
 
881
  return true;
882
  }
883
 
884
  ajax(
885
+ {
886
+ action: "wpstg_cancel_update",
887
+ clone: that.data.cloneID,
888
+ nonce: wpstg.nonce
889
+ },
890
+ function (response) {
 
891
 
892
 
893
+ if (response && "undefined" !== typeof (response.delete) && response.delete === "finished") {
894
+ // Load overview
895
+ loadOverview();
896
+ return;
897
+ }
898
+
899
+ if (true !== response) {
900
+ // continue
901
+ cancelCloningUpdate();
902
+ return;
903
+ }
904
+
905
  // Load overview
906
  loadOverview();
 
907
  }
 
 
 
 
 
 
 
 
 
 
 
908
  );
909
  };
910
 
911
  /**
912
  * Cancel Cloning Process
913
  */
914
+ var restart = function () {
915
+ if (true === that.isFinished) {
 
 
916
  return true;
917
  }
918
 
919
  ajax(
920
+ {
921
+ action: "wpstg_restart",
922
+ //clone: that.data.cloneID,
923
+ nonce: wpstg.nonce
924
+ },
925
+ function (response) {
926
+
927
 
928
+ if (response && "undefined" !== typeof (response.delete) && response.delete === "finished") {
929
+ // Load overview
930
+ loadOverview();
931
+ return;
932
+ }
933
+
934
+ if (true !== response) {
935
+ // continue
936
+ cancelCloningUpdate();
937
+ return;
938
+ }
939
 
 
940
  // Load overview
941
  loadOverview();
 
 
 
 
 
 
 
 
942
  }
 
 
 
 
943
  );
944
  };
945
 
948
  * @returns void
949
  */
950
  var logscroll = function () {
951
+ var $div = cache.get(".wpstg-log-details");
952
  if ("undefined" !== typeof ($div[0])) {
953
  $div.scrollTop($div[0].scrollHeight);
954
  }
959
  * @param string log
960
  * @returns void
961
  */
962
+ var getLogs = function (log) {
 
963
  if (log != null && "undefined" !== typeof (log)) {
964
  if (log.constructor === Array) {
965
  $.each(log, function (index, value) {
967
  return;
968
  }
969
  if (value.type === 'ERROR') {
970
+ cache.get(".wpstg-log-details").append('<span style="color:red;">[' + value.type + ']</span>-' + '[' + value.date + '] ' + value.message + '</br>');
971
  } else {
972
+ cache.get(".wpstg-log-details").append('[' + value.type + ']-' + '[' + value.date + '] ' + value.message + '</br>');
973
  }
974
  })
975
  } else {
976
+ cache.get(".wpstg-log-details").append('[' + log.type + ']-' + '[' + log.date + '] ' + log.message + '</br>');
977
  }
978
  }
979
  logscroll();
986
  */
987
  var checkDiskSpace = function () {
988
  cache.get("#wpstg-check-space").on("click", function (e) {
989
+ cache.get(".wpstg-loader").show();
990
  console.log("check disk space");
991
  ajax(
 
 
 
 
 
 
 
992
  {
993
+ action: "wpstg_check_disk_space",
994
+ nonce: wpstg.nonce
995
+ },
996
+ function (response) {
997
+ if (false === response) {
998
+ cache.get("#wpstg-clone-id-error").text('Can not detect required disk space').show();
999
+ cache.get(".wpstg-loader").hide();
1000
+ return;
1001
+ }
1002
 
1003
+ // Show required disk space
1004
+ cache.get("#wpstg-clone-id-error").html('Estimated necessary disk space: ' + response.usedspace + '<br> <span style="color:#444;">Before you proceed ensure your account has enough free disk space to hold the entire instance of the production site. You can check the available space from your hosting account (cPanel or similar).</span>').show();
1005
+ cache.get(".wpstg-loader").hide();
1006
+ },
1007
+ "json",
1008
+ false
1009
+ );
 
1010
  });
1011
  }
1012
 
1013
+ var mainTabs = function () {
1014
+ $('.wpstg--tab--header a[data-target]').on('click', function () {
1015
+ var $this = $(this);
1016
+ var target = $this.attr('data-target');
1017
+ var $wrapper = $this.parents('.wpstg--tab--wrapper');
1018
+ var $menuItems = $wrapper.find('.wpstg--tab--header a[data-target]');
1019
+ var $contents = $wrapper.find('.wpstg--tab--contents > .wpstg--tab--content');
1020
+
1021
+ $contents.filter('.wpstg--tab--active:not(.wpstg--tab--active' + target + ')').removeClass('wpstg--tab--active');
1022
+ $menuItems.not($this).removeClass('wpstg--tab--active');
1023
+ $this.addClass('wpstg--tab--active');
1024
+ $(target).addClass('wpstg--tab--active');
1025
+
1026
+ if ('#wpstg--tab--snapshot' === target) {
1027
+ that.snapshots.init();
1028
+ }
1029
+ });
1030
+ };
1031
+
1032
+ /**
1033
+ * Show or hide animated loading icon
1034
+ * @param isLoading bool
1035
+ */
1036
+ var isLoading = function (isLoading) {
1037
+ if (!isLoading || isLoading === false) {
1038
+ cache.get(".wpstg-loader").hide();
1039
+ } else {
1040
+ cache.get(".wpstg-loader").show();
1041
+ }
1042
+ };
1043
 
1044
  /**
1045
  * Count up processing execution time
1085
  */
1086
  that.startCloning = (function () {
1087
 
1088
+ resetErrors();
1089
+
1090
  // Register function for checking disk space
1091
  checkDiskSpace();
1092
 
1093
+ if ("wpstg_cloning" !== that.data.action && "wpstg_update" !== that.data.action) {
 
1094
  return;
1095
  }
1096
 
1101
 
1102
  // Functions
1103
  // Start
1104
+ function start() {
 
1105
 
1106
  console.log("Starting cloning process...");
1107
 
1108
+ cache.get(".wpstg-loader").show();
1109
  cache.get("#wpstg-cancel-cloning").text('Cancel');
1110
  cache.get("#wpstg-resume-cloning").hide();
1111
  cache.get("#wpstg-error-details").hide();
1122
  }
1123
 
1124
 
 
1125
  /**
1126
  * Start ajax processing
1127
  * @returns string
1128
  */
1129
  var processing = function () {
1130
 
1131
+ if (true === that.isCancelled) {
 
1132
  return false;
1133
  }
1134
 
1135
+ isLoading(true);
 
 
 
 
 
 
1136
 
1137
  // Show logging window
1138
+ cache.get('.wpstg-log-details').show();
1139
 
1140
  WPStaging.ajax(
 
 
 
 
 
 
 
 
 
 
 
 
1141
  {
1142
+ action: "wpstg_processing",
1143
+ nonce: wpstg.nonce,
1144
+ excludedTables: getExcludedTables(),
1145
+ includedDirectories: getIncludedDirectories(),
1146
+ excludedDirectories: getExcludedDirectories(),
1147
+ extraDirectories: getIncludedExtraDirectories()
1148
+ },
1149
+ function (response) {
 
 
 
 
 
 
1150
 
1151
+ showAjaxFatalError(response);
 
1152
 
1153
+ // Add Log messages
1154
+ if ("undefined" !== typeof (response.last_msg) && response.last_msg) {
1155
+ getLogs(response.last_msg);
1156
+ }
1157
+ // Continue processing
1158
+ if (false === response.status) {
1159
+ progressBar(response);
1160
+
1161
+ setTimeout(function () {
1162
+ cache.get(".wpstg-loader").show();
1163
+ processing();
1164
+ }, wpstg.delayReq);
1165
+
1166
+ } else if (true === response.status && 'finished' !== response.status) {
1167
+ cache.get("#wpstg-error-details").hide();
1168
+ cache.get("#wpstg-error-wrapper").hide();
1169
+ progressBar(response, true);
1170
  processing();
1171
+ } else if ('finished' === response.status || ("undefined" !== typeof (response.job_done) && response.job_done)) {
1172
+ finish(response);
1173
+ }
1174
+ ;
1175
+ },
1176
+ "json",
1177
+ false
1178
+ );
 
 
 
 
 
 
 
 
1179
  };
1180
 
1181
  // Finish
1182
+ function finish(response) {
 
1183
 
1184
+ if (true === that.getLogs) {
 
1185
  getLogs();
1186
  }
1187
 
1188
  progressBar(response);
1189
 
1190
  // Add Log
1191
+ if ("undefined" !== typeof (response.last_msg)) {
 
1192
  getLogs(response.last_msg);
1193
  }
1194
 
1195
  console.log("Cloning process finished");
1196
 
1197
+ cache.get(".wpstg-loader").hide();
1198
  cache.get("#wpstg-processing-header").html('Processing Complete');
1199
  $("#wpstg-processing-status").text("Succesfully finished");
1200
 
1217
  that.timer('stop');
1218
 
1219
 
1220
+ cache.get(".wpstg-loader").hide();
1221
  cache.get("#wpstg-processing-header").html('Processing Complete');
1222
 
1223
  return false;
1224
 
1225
  }
1226
+
1227
  /**
1228
  * Add percentage progress bar
1229
  * @param object response
1233
  if ("undefined" === typeof (response.percentage))
1234
  return false;
1235
 
 
 
 
 
1236
  if (response.job === 'database') {
1237
  cache.get("#wpstg-progress-db").width(response.percentage * 0.2 + '%').html(response.percentage + '%');
1238
  cache.get("#wpstg-processing-status").html(response.percentage.toFixed(0) + '%' + ' - Step 1 of 4 Cloning Database Tables...');
1276
  elements();
1277
  stepButtons();
1278
  tabs();
1279
+ mainTabs();
1280
  });
1281
 
1282
  /**
1288
  that.getLogs = getLogs;
1289
  that.loadOverview = loadOverview;
1290
 
1291
+ that.snapshots = {
1292
+ init() {
1293
+ this.fetchListing();
1294
+ this.create();
1295
+ this.delete();
1296
+ this.restore();
1297
+ this.export();
1298
+ this.edit();
1299
+ },
1300
+ fetchListing() {
1301
+ isLoading(true);
1302
+ resetErrors();
1303
+ that.ajax(
1304
+ {
1305
+ action: 'wpstg--snapshots--listing',
1306
+ nonce: wpstg.nonce,
1307
+ },
1308
+ function (response) {
1309
+ showAjaxFatalError(response, '', 'Submit an error report.');
1310
+ cache.get('#wpstg--tab--snapshot').html(response);
1311
+ isLoading(false);
1312
+ },
1313
+ );
1314
+ },
1315
+ delete() {
1316
+ $('#wpstg--tab--snapshot')
1317
+ .off('click', '.wpstg-delete-snapshot[data-id]')
1318
+ .on('click', '.wpstg-delete-snapshot[data-id]', function (e) {
1319
+ e.preventDefault();
1320
+ resetErrors();
1321
+ isLoading(true);
1322
+ cache.get('#wpstg-existing-snapshots').hide();
1323
+ var id = this.getAttribute('data-id');
1324
+ that.ajax(
1325
+ {
1326
+ action: 'wpstg--snapshots--delete--confirm',
1327
+ id: id,
1328
+ nonce: wpstg.nonce,
1329
+ },
1330
+ function (response) {
1331
+ showAjaxFatalError(response, '', ' Please submit an error report by using the REPORT ISSUE button.');
1332
+ isLoading(false);
1333
+ cache.get('#wpstg-delete-confirmation').html(response);
1334
+ },
1335
+ );
1336
+ })
1337
+ // Delete final confirmation page
1338
+ .off('click', '#wpstg-delete-snapshot')
1339
+ .on('click', '#wpstg-delete-snapshot', function (e) {
1340
+ e.preventDefault();
1341
+ resetErrors();
1342
+ isLoading(true);
1343
+ var id = this.getAttribute('data-id');
1344
+ that.ajax(
1345
+ {
1346
+ action: 'wpstg--snapshots--delete',
1347
+ id: id,
1348
+ nonce: wpstg.nonce,
1349
+ },
1350
+ function (response) {
1351
+ showAjaxFatalError(response, '', ' Please submit an error report by using the REPORT ISSUE button.');
1352
+ that.snapshots.fetchListing();
1353
+ isLoading(false);
1354
+ },
1355
+ );
1356
+ })
1357
+ .off('click', '#wpstg-cancel-snapshot-delete')
1358
+ .on('click', '#wpstg-cancel-snapshot-delete', function (e) {
1359
+ e.preventDefault();
1360
+ isLoading(false);
1361
+ var id = this.getAttribute('data-id');
1362
+ that.snapshots.fetchListing();
1363
+ })
1364
+ ;
1365
+ },
1366
+ create() {
1367
+ var createSnapshot = function (name, notes) {
1368
+ isLoading(true);
1369
+ resetErrors();
1370
+ WPStaging.ajax(
1371
+ {
1372
+ action: 'wpstg--snapshots--create',
1373
+ nonce: wpstg.nonce,
1374
+ name,
1375
+ notes,
1376
+ },
1377
+ function (response) {
1378
+ if (typeof response === 'undefined') {
1379
+ setTimeout(function () {
1380
+ createSnapshot(name, notes);
1381
+ }, wpstg.delayReq);
1382
+ return;
1383
+ }
1384
+
1385
+ showAjaxFatalError(response, '', 'Submit an error report and contact us.');
1386
+
1387
+ if (typeof response.last_msg !== 'undefined' && response.last_msg) {
1388
+ getLogs(response.last_msg);
1389
+ }
1390
+
1391
+ if (response.status === false) {
1392
+ createSnapshot(name, notes);
1393
+ } else if (response.status === true) {
1394
+ isLoading(false);
1395
+ $('#wpstg--progress--status').text('Snapshot successfully created!');
1396
+ that.snapshots.fetchListing();
1397
+ } else {
1398
+ setTimeout(function () {
1399
+ createSnapshot(name, notes);
1400
+ }, wpstg.delayReq);
1401
+ }
1402
+ },
1403
+ 'json',
1404
+ false
1405
+ );
1406
+ };
1407
+ // Add snapshot name and notes
1408
+ $('#wpstg--tab--snapshot')
1409
+ .off('click', '#wpstg-new-snapshot')
1410
+ .on('click', '#wpstg-new-snapshot', async function(e) {
1411
+ resetErrors();
1412
+ e.preventDefault();
1413
+
1414
+ const { value: formValues } = await Swal.fire({
1415
+ title: '',
1416
+ html: `
1417
+ <label id="wpstg-snapshot-name">Snapshot Name</label>
1418
+ <input id="wpstg-snapshot-name-input" class="swal2-input" placeholder="Name your snapshot for better distinction">
1419
+ <label>Additional Notes</label>
1420
+ <textarea id="wpstg-snapshot-notes-textarea" class="swal2-textarea" placeholder="Add an optional description e.g.: 'before push of staging site', 'before updating plugin XY'"></textarea>
1421
+ `,
1422
+ focusConfirm: false,
1423
+ confirmButtonText: 'Take New Snapshot',
1424
+ showCancelButton: true,
1425
+ preConfirm: () => ({
1426
+ name: document.getElementById('wpstg-snapshot-name-input').value || null,
1427
+ notes: document.getElementById('wpstg-snapshot-notes-textarea').value || null,
1428
+ }),
1429
+ });
1430
+
1431
+ if (!formValues) {
1432
+ return;
1433
+ }
1434
+
1435
+ that.ajax(
1436
+ {
1437
+ action: 'wpstg--snapshots--create--progress',
1438
+ nonce: wpstg.nonce,
1439
+ },
1440
+ function (response) {
1441
+ showAjaxFatalError(response, '', 'Submit an error report and contact us.');
1442
+ cache.get('#wpstg--tab--snapshot').html(response);
1443
+ createSnapshot(formValues.name, formValues.notes);
1444
+ },
1445
+ );
1446
+ })
1447
+ ;
1448
+ },
1449
+ restore() {
1450
+ var restoreSnapshot = function (id, isReset) {
1451
+ isLoading(true);
1452
+ resetErrors();
1453
+
1454
+ if (typeof isReset === 'undefined') {
1455
+ isReset = false;
1456
+ }
1457
+
1458
+ WPStaging.ajax(
1459
+ {
1460
+ action: 'wpstg--snapshots--restore',
1461
+ nonce: wpstg.nonce,
1462
+ id: id,
1463
+ isReset: isReset,
1464
+ },
1465
+ function (response) {
1466
+ if (typeof response === 'undefined') {
1467
+ setTimeout(function () {
1468
+ restoreSnapshot(id);
1469
+ }, wpstg.delayReq);
1470
+ return;
1471
+ }
1472
+
1473
+ showAjaxFatalError(response, '', 'Submit an error report and contact us.');
1474
+
1475
+ if (typeof response.last_msg !== 'undefined' && response.last_msg) {
1476
+ getLogs(response.last_msg);
1477
+ }
1478
+
1479
+ if (response.status === false || response.job_done === false) {
1480
+ restoreSnapshot(id);
1481
+ } else if (response.status === true && response.job_done === true) {
1482
+ isLoading(false);
1483
+ $('#wpstg--progress--status').text('Snapshot successfully restored');
1484
+ } else {
1485
+ setTimeout(function () {
1486
+ restoreSnapshot(id);
1487
+ }, wpstg.delayReq);
1488
+ }
1489
+ },
1490
+ 'json',
1491
+ false
1492
+ );
1493
+ };
1494
+
1495
+ // Force delete if snapshot tables do not exist
1496
+ $('#wpstg-error-wrapper')
1497
+ .off('click', '#wpstg-snapshot-force-delete')
1498
+ .on('click', '#wpstg-snapshot-force-delete', function (e) {
1499
+ e.preventDefault();
1500
+ resetErrors();
1501
+ isLoading(true);
1502
+ var id = this.getAttribute('data-id');
1503
+
1504
+ if (!confirm("Do you want to delete this snapshot " + id + " from the listed snapshots?")) {
1505
+ isLoading(false);
1506
+ return false;
1507
+ }
1508
+
1509
+ that.ajax(
1510
+ {
1511
+ action: 'wpstg--snapshots--delete',
1512
+ id: id,
1513
+ force: 1,
1514
+ nonce: wpstg.nonce,
1515
+ },
1516
+ function (response) {
1517
+ showAjaxFatalError(response, '', ' Please submit an error report by using the REPORT ISSUE button.');
1518
+ that.snapshots.fetchListing();
1519
+ isLoading(false);
1520
+ },
1521
+ );
1522
+ })
1523
+
1524
+ $('#wpstg--tab--snapshot')
1525
+ .off('click', '.wpstg--snapshot--restore[data-id]')
1526
+ .on('click', '.wpstg--snapshot--restore[data-id]', function (e) {
1527
+ e.preventDefault();
1528
+ resetErrors();
1529
+ that.ajax(
1530
+ {
1531
+ action: 'wpstg--snapshots--restore--confirm',
1532
+ nonce: wpstg.nonce,
1533
+ id: $(this).data('id'),
1534
+ },
1535
+ function (data) {
1536
+ cache.get('#wpstg--tab--snapshot').html(data);
1537
+ },
1538
+ );
1539
+ })
1540
+ .off('click', '#wpstg--snapshot--restore--cancel')
1541
+ .on('click', '#wpstg--snapshot--restore--cancel', function (e) {
1542
+ resetErrors();
1543
+ e.preventDefault();
1544
+ that.snapshots.fetchListing();
1545
+ })
1546
+ .off('click', '#wpstg--snapshot--restore[data-id]')
1547
+ .on('click', '#wpstg--snapshot--restore[data-id]', function (e) {
1548
+ e.preventDefault();
1549
+ resetErrors();
1550
+ var id = $(this).data('id');
1551
+
1552
+ that.ajax(
1553
+ {
1554
+ action: 'wpstg--snapshots--restore--progress',
1555
+ nonce: wpstg.nonce,
1556
+ id: id,
1557
+ },
1558
+ function (response) {
1559
+ showAjaxFatalError(response, '', 'Submit an error report and contact us.');
1560
+ cache.get('#wpstg--tab--snapshot').html(response);
1561
+ restoreSnapshot(id, true);
1562
+ },
1563
+ );
1564
+ })
1565
+ ;
1566
+ },
1567
+ export() {
1568
+ function download(url) {
1569
+ var a = document.createElement('a');
1570
+ a.style.display = 'none';
1571
+ a.href = url;
1572
+ document.body.appendChild(a);
1573
+ a.click();
1574
+ document.body.removeChild(a);
1575
+ }
1576
+
1577
+ $('#wpstg--tab--snapshot')
1578
+ .off('click', '.wpstg--snapshot--export')
1579
+ .on('click', '.wpstg--snapshot--export', function (e) {
1580
+ e.preventDefault();
1581
+ isLoading(true);
1582
+ that.ajax(
1583
+ {
1584
+ action: 'wpstg--snapshots--export',
1585
+ nonce: wpstg.nonce,
1586
+ id: $(this).data('id'),
1587
+ },
1588
+ function (response) {
1589
+ showAjaxFatalError(response, '', 'Submit an error report and contact us.');
1590
+ isLoading(false);
1591
+ if (response && response.success && response.data && response.data.length > 0) {
1592
+ download(response.data);
1593
+ }
1594
+ },
1595
+ );
1596
+ })
1597
+ ;
1598
+ },
1599
+ // Edit snapshots name and notes
1600
+ edit() {
1601
+ $('#wpstg--tab--snapshot')
1602
+ .off('click', '.wpstg--snapshot--edit[data-id]')
1603
+ .on('click', '.wpstg--snapshot--edit[data-id]', async function(e) {
1604
+ e.preventDefault();
1605
+ console.log('edit');
1606
+
1607
+ const $this = $(this);
1608
+ const name = $this.data('name');
1609
+ const notes = $this.data('notes');
1610
+
1611
+ const { value: formValues } = await Swal.fire({
1612
+ title: '',
1613
+ html: `
1614
+ <label id="wpstg-snapshot-name">Snapshot Name</label>
1615
+ <input id="wpstg-snapshot-name-input" class="swal2-input" value="${name}">
1616
+ <label>Additional Notes</label>
1617
+ <textarea id="wpstg-snapshot-notes-textarea" class="swal2-textarea">${notes}</textarea>
1618
+ `,
1619
+ focusConfirm: false,
1620
+ confirmButtonText: 'Update Snapshot',
1621
+ showCancelButton: true,
1622
+ preConfirm: () => ({
1623
+ name: document.getElementById('wpstg-snapshot-name-input').value || null,
1624
+ notes: document.getElementById('wpstg-snapshot-notes-textarea').value || null,
1625
+ }),
1626
+ });
1627
+
1628
+ if (!formValues) {
1629
+ return;
1630
+ }
1631
+
1632
+ that.ajax(
1633
+ {
1634
+ action: 'wpstg--snapshots--edit',
1635
+ nonce: wpstg.nonce,
1636
+ id: $this.data('id'),
1637
+ name: formValues.name,
1638
+ notes: formValues.notes,
1639
+ },
1640
+ function(response) {
1641
+ showAjaxFatalError(response, '', 'Submit an error report.');
1642
+ that.snapshots.fetchListing();
1643
+ },
1644
+ );
1645
+ })
1646
+ ;
1647
+ },
1648
+ };
1649
+
1650
  return that;
1651
  })(jQuery);
1652
 
1660
  jQuery(document).ready(function ($) {
1661
 
1662
  $('#wpstg-report-issue-button').click(function (e) {
 
1663
  $('.wpstg-report-issue-form').toggleClass('wpstg-report-show');
1664
  e.preventDefault();
1665
  });
1666
 
1667
+ $('body').on('click', '#wpstg-snapshots-report-issue-button', function (e) {
1668
+ $('.wpstg-report-issue-form').toggleClass('wpstg-report-show');
1669
+ console.log('test');
1670
+ e.preventDefault();
1671
+ });
1672
+
1673
  $('#wpstg-report-cancel').click(function (e) {
1674
  $('.wpstg-report-issue-form').removeClass('wpstg-report-show');
1675
  e.preventDefault();
Backend/views/clone/ajax/start.php CHANGED
@@ -39,50 +39,46 @@
39
  echo sprintf( __( 'WP Staging successfully created a staging site in a sub-directory of your main site accessable from:<br><strong><a href="%1$s" target="_blank" id="wpstg-clone-url-1">%1$s</a></strong>', 'wp-staging' ), $url );
40
  ?>
41
  <br>
42
- <?php //echo __('Open and access the staging site: ', 'wp-staging')?>
43
  <br>
44
- <a href="<?php echo $url; ?>" id="wpstg-clone-url" target="_blank" class="wpstg-link-btn button-primary">
45
- Open staging site <span style="font-size: 10px;">(login with your admin credentials)</span>
46
  </a>
47
- <!--<a href="" class="wpstg-link-btn button-primary" id="wpstg-remove-cloning">
48
- <?php //echo __("Remove", "wp-staging")?>
49
- </a>//-->
50
- <a href="" class="wpstg-link-btn button-primary" id="wpstg-home-link">
51
- <?php echo __("Start again", "wp-staging")?>
52
  </a>
53
  <div id="wpstg-success-notice">
54
- <h3 style="margin-top:0px;">
55
- <?php _e("Important Notes:", "wp-staging")?>
56
  </h3>
57
  <ul>
58
  <li>
59
- <strong>1. Search friendly permalinks on your <span style="font-style:italic;">staging site</span> have been disabled as default option for technical reasons. </strong>
60
  <br>
61
- Usually that's perfectly okay for a staging website. In 99% of all cases you do not need to activate permalinks.
62
  <br>
63
  <p>
64
- If Apache runs on your webserver there is a good chance that permalinks still work. Try to activate the permalinks from <br/>
65
  <br>
66
  <strong>Staging Site > wp-admin > Settings > Permalinks</strong></a>
67
  <br/><br/>
68
- If that does not work or you are using Nginx webserver there are modifications needed in the .htaccess (Apache) or *.conf (Nginx).
69
  </p>
70
  <p>
71
- <strong><a href="https://wp-staging.com/docs/activate-permalinks-staging-site/?utm_source=wpstg_admin&utm_medium=finish_screen&utm_campaign=tutorial" target="_blank">Read here</a> to see that modifications and learn how to enable permalinks on the staging site.</strong>
72
  </p>
73
  </li>
74
  <li>
75
- <strong>2. Verify that you are REALLY working on your staging site and NOT on your production site if you are uncertain! </strong>
76
  <br>
77
  Your main and your staging site are both reachable under the same domain so
78
  <br>
79
- it´s easy to get confused.
80
  <p>
81
- To assist you we changed the color of the admin bar:
82
  <br><br>
83
  <img src="<?php echo $this->url . "/img/admin_dashboard.png" ?>">
84
  <br>
85
- On the fronpage the name also changed to <br>
86
  <strong style="font-style:italic;">
87
  "STAGING - <span class="wpstg-clone-name"><?php echo get_bloginfo("name")?></span>"
88
  </strong>.
@@ -96,4 +92,4 @@
96
  <div id="wpstg-error-details"></div>
97
  </div>
98
 
99
- <div id="wpstg-log-details"></div>
39
  echo sprintf( __( 'WP Staging successfully created a staging site in a sub-directory of your main site accessable from:<br><strong><a href="%1$s" target="_blank" id="wpstg-clone-url-1">%1$s</a></strong>', 'wp-staging' ), $url );
40
  ?>
41
  <br>
 
42
  <br>
43
+ <a href="" class="wpstg-link-btn wpstg-blue-primary" id="wpstg-home-link">
44
+ <?php echo __("BACK", "wp-staging")?>
45
  </a>
46
+ <a href="<?php echo $url; ?>" id="wpstg-clone-url" target="_blank" class="wpstg-link-btn wpstg-blue-primary">
47
+ <?php _e('Open Staging Site', 'wp-staging') ?><span style="font-size: 10px;"><?php _e('(Login with your admin credentials)', 'wp-staging') ?></span>
 
 
 
48
  </a>
49
  <div id="wpstg-success-notice">
50
+ <h3>
51
+ <?php _e("Please read this first:", "wp-staging")?>
52
  </h3>
53
  <ul>
54
  <li>
55
+ <strong>1. Post name permalinks on your <span style="font-style:italic;">staging site</span> have been disabled for technical reasons. </strong>
56
  <br>
57
+ Usually this will not affect your staging website. In 99% of all cases you do not need to activate permalinks.
58
  <br>
59
  <p>
60
+ If Apache is the webserver there is a good chance that permalinks can be activated without further modifications. Try to activate them from <br/>
61
  <br>
62
  <strong>Staging Site > wp-admin > Settings > Permalinks</strong></a>
63
  <br/><br/>
64
+ If this does not work or Nginx webserver is used there might be some modifications needed in the files .htaccess (Apache) or *.conf (Nginx).
65
  </p>
66
  <p>
67
+ <strong><a href="https://wp-staging.com/docs/activate-permalinks-staging-site/?utm_source=wpstg_admin&utm_medium=finish_screen&utm_campaign=tutorial" target="_blank">Read this tutorial</a> to learn how to enable permalinks on the staging site.</strong>
68
  </p>
69
  </li>
70
  <li>
71
+ <strong>2. Verify that you are REALLY working on your staging site and NOT on your production site if you are not 100% sure! </strong>
72
  <br>
73
  Your main and your staging site are both reachable under the same domain so
74
  <br>
75
+ this can be confusing.
76
  <p>
77
+ To make it more clear when you work on the staging site WP Staging changed the color of the admin bar:
78
  <br><br>
79
  <img src="<?php echo $this->url . "/img/admin_dashboard.png" ?>">
80
  <br>
81
+ On the fronpage the site name also changed to <br>
82
  <strong style="font-style:italic;">
83
  "STAGING - <span class="wpstg-clone-name"><?php echo get_bloginfo("name")?></span>"
84
  </strong>.
92
  <div id="wpstg-error-details"></div>
93
  </div>
94
 
95
+ <div class="wpstg-log-details"></div>
Backend/views/clone/ajax/update.php CHANGED
@@ -30,4 +30,4 @@
30
  <div id="wpstg-error-details"></div>
31
  </div>
32
 
33
- <div id="wpstg-log-details"></div>
30
  <div id="wpstg-error-details"></div>
31
  </div>
32
 
33
+ <div class="wpstg-log-details"></div>
Backend/views/clone/single-site/index.php CHANGED
@@ -16,9 +16,6 @@
16
  <i class="wpstg-icon-issue"></i><?php echo __( "Report Issue", "wp-staging" ); ?>
17
  </button>
18
  </li>
19
- <li>
20
- <span id="wpstg-loader" style="display:none;"></span>
21
- </li>
22
  </ul>
23
 
24
  <div id="wpstg-workflow"></div>
16
  <i class="wpstg-icon-issue"></i><?php echo __( "Report Issue", "wp-staging" ); ?>
17
  </button>
18
  </li>
 
 
 
19
  </ul>
20
 
21
  <div id="wpstg-workflow"></div>
Backend/views/notices/beta.php CHANGED
@@ -1,3 +1,4 @@
 
1
  <div class="wpstg_beta_notice wpstg-error" style="box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);">
2
  <p>
3
  <?php _e("WP Staging is well tested and we did a lot to catch every possible error but
1
+ <!-- Not used any longer. So can be used for other purposes in the future //-->
2
  <div class="wpstg_beta_notice wpstg-error" style="box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);">
3
  <p>
4
  <?php _e("WP Staging is well tested and we did a lot to catch every possible error but
Backend/views/notices/wp-version-compatible-message.php CHANGED
@@ -10,4 +10,4 @@
10
  );
11
  ?>
12
  </p>
13
- </div>
10
  );
11
  ?>
12
  </p>
13
+ </div>
Core/Utils/Logger.php CHANGED
@@ -1,19 +1,20 @@
1
  <?php
2
- namespace WPStaging\Utils;
3
 
4
- // No Direct Access
5
- if (!defined("WPINC"))
6
- {
7
- die;
8
- }
 
9
 
10
- use WPStaging\WPStaging;
 
11
 
12
  /**
13
  * Class Logger
14
  * @package WPStaging\Utils
15
  */
16
- class Logger
17
  {
18
  const TYPE_ERROR = "ERROR";
19
 
@@ -24,18 +25,18 @@ class Logger
24
  const TYPE_WARNING = "WARNING";
25
 
26
  const TYPE_INFO = "INFO";
27
-
28
  const TYPE_DEBUG = "DEBUG";
29
 
30
  /**
31
  * Log directory (full path)
32
- * @var string
33
  */
34
  private $logDir;
35
 
36
  /**
37
  * Log file extension
38
- * @var string
39
  */
40
  private $logExtension = "log";
41
 
@@ -47,14 +48,16 @@ class Logger
47
 
48
  /**
49
  * Forced filename for the log
50
- * @var null|string
51
  */
52
  private $fileName = null;
53
 
54
  /**
55
  * Logger constructor.
56
- * @param null|string $logDir
57
- * @param null|string $logExtension
 
 
58
  * @throws \Exception
59
  */
60
  public function __construct($logDir = null, $logExtension = null)
@@ -69,7 +72,7 @@ class Logger
69
  {
70
 
71
  $this->logDir = \WPStaging\WPStaging::getContentDir() . "logs" . DIRECTORY_SEPARATOR;
72
-
73
  }
74
 
75
  // Set log extension
@@ -86,31 +89,33 @@ class Logger
86
  }
87
 
88
  /**
89
- * @param string $message
90
- * @param string $type
 
 
 
91
  */
92
- public function log($message, $type = self::TYPE_ERROR)
93
  {
94
- $this->add($message, $type);
95
  $this->commit();
96
  }
97
 
98
  /**
99
- * @param string $message
100
- * @param string $type
101
  */
102
  public function add($message, $type = self::TYPE_ERROR)
103
- {
104
-
105
  $this->messages[] = array(
106
  "type" => $type,
107
  "date" => date("Y/m/d H:i:s"),
108
  "message" => $message
109
- );
110
  }
111
 
112
  /**
113
- * @return null|string
114
  */
115
  public function getFileName()
116
  {
@@ -118,7 +123,7 @@ class Logger
118
  }
119
 
120
  /**
121
- * @param string $fileName
122
  */
123
  public function setFileName($fileName)
124
  {
@@ -151,8 +156,9 @@ class Logger
151
  }
152
 
153
  /**
154
- * @param null|string $file
155
- * @return string
 
156
  */
157
  public function read($file = null)
158
  {
@@ -160,8 +166,9 @@ class Logger
160
  }
161
 
162
  /**
163
- * @param null|string $fileName
164
- * @return string
 
165
  */
166
  public function getLogFile($fileName = null)
167
  {
@@ -176,7 +183,9 @@ class Logger
176
 
177
  /**
178
  * Delete a log file
179
- * @param string $logFileName
 
 
180
  * @return bool
181
  * @throws \Exception
182
  */
@@ -193,7 +202,7 @@ class Logger
193
  }
194
 
195
  /**
196
- * @return string
197
  */
198
  public function getLogDir()
199
  {
@@ -201,13 +210,13 @@ class Logger
201
  }
202
 
203
  /**
204
- * @return string
205
  */
206
  public function getLogExtension()
207
  {
208
  return $this->logExtension;
209
  }
210
-
211
  /**
212
  * Get last element of logging data array
213
  * @return string
@@ -222,13 +231,78 @@ class Logger
222
  return $this->messages[]=array_pop($this->messages);
223
  }
224
  }
225
-
226
  /**
227
  * Get running time in seconds
228
  * @return int
229
  */
230
- public function getRunningTime(){
 
231
  $str_time = $this->messages[0]["date"];
232
  return $str_time;
233
  }
234
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <?php
 
2
 
3
+ /**
4
+ * This class is not PSR-3 compliant. Currently just added the basic functionality to make the "change" easier
5
+ * in the future. For now, there are just few things to make transition easy.
6
+ */
7
+
8
+ namespace WPStaging\Utils;
9
 
10
+ use Psr\Log\LoggerInterface;
11
+ use Psr\Log\LogLevel;
12
 
13
  /**
14
  * Class Logger
15
  * @package WPStaging\Utils
16
  */
17
+ class Logger implements LoggerInterface
18
  {
19
  const TYPE_ERROR = "ERROR";
20
 
25
  const TYPE_WARNING = "WARNING";
26
 
27
  const TYPE_INFO = "INFO";
28
+
29
  const TYPE_DEBUG = "DEBUG";
30
 
31
  /**
32
  * Log directory (full path)
33
+ * @var Strings
34
  */
35
  private $logDir;
36
 
37
  /**
38
  * Log file extension
39
+ * @var Strings
40
  */
41
  private $logExtension = "log";
42
 
48
 
49
  /**
50
  * Forced filename for the log
51
+ * @var null|Strings
52
  */
53
  private $fileName = null;
54
 
55
  /**
56
  * Logger constructor.
57
+ *
58
+ * @param null|Strings $logDir
59
+ * @param null|Strings $logExtension
60
+ *
61
  * @throws \Exception
62
  */
63
  public function __construct($logDir = null, $logExtension = null)
72
  {
73
 
74
  $this->logDir = \WPStaging\WPStaging::getContentDir() . "logs" . DIRECTORY_SEPARATOR;
75
+
76
  }
77
 
78
  // Set log extension
89
  }
90
 
91
  /**
92
+ * @param Strings $level
93
+ * @param Strings $message
94
+ * @param array $context
95
+ *
96
+ * @return void
97
  */
98
+ public function log($level, $message, array $context = [])
99
  {
100
+ $this->add($message, $level);
101
  $this->commit();
102
  }
103
 
104
  /**
105
+ * @param Strings $message
106
+ * @param Strings $type
107
  */
108
  public function add($message, $type = self::TYPE_ERROR)
109
+ {
 
110
  $this->messages[] = array(
111
  "type" => $type,
112
  "date" => date("Y/m/d H:i:s"),
113
  "message" => $message
114
+ );
115
  }
116
 
117
  /**
118
+ * @return null|Strings
119
  */
120
  public function getFileName()
121
  {
123
  }
124
 
125
  /**
126
+ * @param Strings $fileName
127
  */
128
  public function setFileName($fileName)
129
  {
156
  }
157
 
158
  /**
159
+ * @param null|Strings $file
160
+ *
161
+ * @return Strings
162
  */
163
  public function read($file = null)
164
  {
166
  }
167
 
168
  /**
169
+ * @param null|Strings $fileName
170
+ *
171
+ * @return Strings
172
  */
173
  public function getLogFile($fileName = null)
174
  {
183
 
184
  /**
185
  * Delete a log file
186
+ *
187
+ * @param Strings $logFileName
188
+ *
189
  * @return bool
190
  * @throws \Exception
191
  */
202
  }
203
 
204
  /**
205
+ * @return Strings
206
  */
207
  public function getLogDir()
208
  {
210
  }
211
 
212
  /**
213
+ * @return Strings
214
  */
215
  public function getLogExtension()
216
  {
217
  return $this->logExtension;
218
  }
219
+
220
  /**
221
  * Get last element of logging data array
222
  * @return string
231
  return $this->messages[]=array_pop($this->messages);
232
  }
233
  }
234
+
235
  /**
236
  * Get running time in seconds
237
  * @return int
238
  */
239
+ public function getRunningTime()
240
+ {
241
  $str_time = $this->messages[0]["date"];
242
  return $str_time;
243
  }
244
+
245
+ /**
246
+ * @inheritDoc
247
+ */
248
+ public function emergency($message, array $context = [])
249
+ {
250
+ $this->add($message, LogLevel::EMERGENCY);
251
+ }
252
+
253
+ /**
254
+ * @inheritDoc
255
+ */
256
+ public function alert($message, array $context = [])
257
+ {
258
+ $this->add($message, LogLevel::ALERT);
259
+ }
260
+
261
+ /**
262
+ * @inheritDoc
263
+ */
264
+ public function critical($message, array $context = [])
265
+ {
266
+ $this->add($message, LogLevel::CRITICAL);
267
+ }
268
+
269
+ /**
270
+ * @inheritDoc
271
+ */
272
+ public function error($message, array $context = [])
273
+ {
274
+ $this->add($message, LogLevel::ERROR);
275
+ }
276
+
277
+ /**
278
+ * @inheritDoc
279
+ */
280
+ public function warning($message, array $context = [])
281
+ {
282
+ $this->add($message, LogLevel::WARNING);
283
+ }
284
+
285
+ /**
286
+ * @inheritDoc
287
+ */
288
+ public function notice($message, array $context = [])
289
+ {
290
+ $this->add($message, LogLevel::NOTICE);
291
+ }
292
+
293
+ /**
294
+ * @inheritDoc
295
+ */
296
+ public function info($message, array $context = [])
297
+ {
298
+ $this->add($message, LogLevel::INFO);
299
+ }
300
+
301
+ /**
302
+ * @inheritDoc
303
+ */
304
+ public function debug($message, array $context = [])
305
+ {
306
+ $this->add($message, LogLevel::DEBUG);
307
+ }
308
+ }
Core/Utils/functions.php CHANGED
@@ -523,52 +523,6 @@ function wpstg_chown($file, $owner)
523
  return true;
524
  }
525
 
526
- /**
527
- * Checks if the passed string would match the given shell wildcard pattern.
528
- * This function emulates [[fnmatch()]], which may be unavailable at certain environment, using PCRE.
529
- * @param string $pattern the shell wildcard pattern.
530
- * @param string $string the tested string.
531
- * @param array $options options for matching. Valid options are:
532
- *
533
- * - caseSensitive: bool, whether pattern should be case sensitive. Defaults to `true`.
534
- * - escape: bool, whether backslash escaping is enabled. Defaults to `true`.
535
- * - filePath: bool, whether slashes in string only matches slashes in the given pattern. Defaults to `false`.
536
- *
537
- * @return bool whether the string matches pattern or not.
538
- */
539
- function wpstg_fnmatch($pattern, $string, $options = array())
540
- {
541
- if ($pattern === '*' && empty($options['filePath'])) {
542
- return true;
543
- }
544
- $replacements = array(
545
- '\\\\\\\\' => '\\\\',
546
- '\\\\\\*' => '[*]',
547
- '\\\\\\?' => '[?]',
548
- '\*' => '.*',
549
- '\?' => '.',
550
- '\[\!' => '[^',
551
- '\[' => '[',
552
- '\]' => ']',
553
- '\-' => '-',
554
- );
555
- if (isset($options['escape']) && !$options['escape']) {
556
- unset($replacements['\\\\\\\\']);
557
- unset($replacements['\\\\\\*']);
558
- unset($replacements['\\\\\\?']);
559
- }
560
- if (!empty($options['filePath'])) {
561
- $replacements['\*'] = '[^/\\\\]*';
562
- $replacements['\?'] = '[^/\\\\]';
563
- }
564
- $pattern = strtr(preg_quote($pattern, '#'), $replacements);
565
- $pattern = '#^' . $pattern . '$#us';
566
- if (isset($options['caseSensitive']) && !$options['caseSensitive']) {
567
- $pattern .= 'i';
568
- }
569
- return preg_match($pattern, $string) === 1;
570
- }
571
-
572
  /*
573
  * Check if website is installed locally
574
  * @return boolean
@@ -589,7 +543,7 @@ function wpstg_is_local()
589
  /**
590
  * Get absolute path to plugins dir.
591
  * Take into account custom user made path modifications
592
- * A function with the name wpstg_get_plugins_dir() already exists in
593
  * must-use plugin wp-staging-optimizer.php so we've created this one that does the same job.
594
  *
595
  * @return string
523
  return true;
524
  }
525
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
  /*
527
  * Check if website is installed locally
528
  * @return boolean
543
  /**
544
  * Get absolute path to plugins dir.
545
  * Take into account custom user made path modifications
546
+ * A function with the name wpstg_get_plugins_dir() already exists in
547
  * must-use plugin wp-staging-optimizer.php so we've created this one that does the same job.
548
  *
549
  * @return string
Core/WPStaging.php CHANGED
@@ -13,12 +13,12 @@ require_once __DIR__ . DIRECTORY_SEPARATOR . "Utils" . DIRECTORY_SEPARATOR . "Au
13
  use WPStaging\Backend\Administrator;
14
  use WPStaging\DTO\Settings;
15
  use WPStaging\Frontend\Frontend;
 
16
  use WPStaging\Utils\Autoloader;
17
  use WPStaging\Utils\Cache;
18
  use WPStaging\Utils\Loader;
19
  use WPStaging\Utils\Logger;
20
- use WPStaging\DI\InjectionAware;
21
- use WPStaging\Cron\Cron;
22
 
23
  /**
24
  * Class WPStaging
@@ -62,7 +62,6 @@ final class WPStaging {
62
 
63
  $this->registerMain();
64
  $this->registerNamespaces();
65
- $this->loadLanguages();
66
  $this->loadDependencies();
67
  $this->defineHooks();
68
  $this->initCron();
@@ -184,6 +183,22 @@ final class WPStaging {
184
  wp_enqueue_script(
185
  "wpstg-admin-pro-script", $this->url . "Backend/Pro/public/js/wpstg-admin-pro.js", array("jquery"), $this->getVersion(), false
186
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  }
188
 
189
  // Load admin css files
@@ -318,6 +333,12 @@ final class WPStaging {
318
 
319
  $this->set( "settings", new Settings() );
320
 
 
 
 
 
 
 
321
  if( is_admin() ) {
322
  new Administrator( $this );
323
  } else {
@@ -358,6 +379,15 @@ final class WPStaging {
358
  return (isset( $this->services[$name] )) ? $this->services[$name] : null;
359
  }
360
 
 
 
 
 
 
 
 
 
 
361
  /**
362
  * @return string
363
  */
@@ -439,37 +469,6 @@ final class WPStaging {
439
  return $delay;
440
  }
441
 
442
- /**
443
- * Load language file
444
- */
445
- public function loadLanguages() {
446
- $languagesDirectory = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $this->slug . DIRECTORY_SEPARATOR . "languages" . DIRECTORY_SEPARATOR;
447
-
448
- // Set filter for plugins languages directory
449
- $languagesDirectory = apply_filters( "wpstg_languages_directory", $languagesDirectory );
450
-
451
- // Traditional WP plugin locale filter
452
- $locale = apply_filters( "plugin_locale", get_locale(), "wp-staging" );
453
- $moFile = sprintf( '%1$s-%2$s.mo', "wp-staging", $locale );
454
-
455
- // Setup paths to current locale file
456
- $moFileLocal = $languagesDirectory . $moFile;
457
- $moFileGlobal = WP_LANG_DIR . DIRECTORY_SEPARATOR . "wp-staging" . DIRECTORY_SEPARATOR . $moFile;
458
-
459
- // Global file (/wp-content/languages/wp-staging/wpstg)
460
- if( file_exists( $moFileGlobal ) ) {
461
- load_textdomain( "wp-staging", $moFileGlobal );
462
- }
463
- // Local file (/wp-content/plugins/wp-staging/languages/)
464
- elseif( file_exists( $moFileLocal ) ) {
465
- load_textdomain( "wp-staging", $moFileLocal );
466
- }
467
- // Default file
468
- else {
469
- load_plugin_textdomain( "wp-staging", false, $languagesDirectory );
470
- }
471
- }
472
-
473
  /**
474
  * Initialize licensing functions
475
  * @return boolean
13
  use WPStaging\Backend\Administrator;
14
  use WPStaging\DTO\Settings;
15
  use WPStaging\Frontend\Frontend;
16
+ use WPStaging\Service\Container\Container;
17
  use WPStaging\Utils\Autoloader;
18
  use WPStaging\Utils\Cache;
19
  use WPStaging\Utils\Loader;
20
  use WPStaging\Utils\Logger;
21
+ use WPStaging\Service\PluginFactory;
 
22
 
23
  /**
24
  * Class WPStaging
62
 
63
  $this->registerMain();
64
  $this->registerNamespaces();
 
65
  $this->loadDependencies();
66
  $this->defineHooks();
67
  $this->initCron();
183
  wp_enqueue_script(
184
  "wpstg-admin-pro-script", $this->url . "Backend/Pro/public/js/wpstg-admin-pro.js", array("jquery"), $this->getVersion(), false
185
  );
186
+
187
+ // Sweet Alert
188
+ wp_enqueue_script(
189
+ 'wpstg-admin-pro-sweetalerts',
190
+ $this->url . 'Backend/Pro/public/vendor/sweetalert2/sweetalert2.all.min.js',
191
+ [],
192
+ $this->getVersion(),
193
+ true
194
+ );
195
+
196
+ wp_enqueue_style(
197
+ 'wpstg-admin-pro-sweetalerts',
198
+ $this->url . 'Backend/Pro/public/vendor/sweetalert2/wordpress-admin.min.css',
199
+ [],
200
+ $this->getVersion()
201
+ );
202
  }
203
 
204
  // Load admin css files
333
 
334
  $this->set( "settings", new Settings() );
335
 
336
+ /** @noinspection PhpUnhandledExceptionInspection */
337
+ $plugin = PluginFactory::make(Plugin::class);
338
+ $plugin->init();
339
+ $this->set(Plugin::class, $plugin);
340
+
341
+ // Set Administrator
342
  if( is_admin() ) {
343
  new Administrator( $this );
344
  } else {
379
  return (isset( $this->services[$name] )) ? $this->services[$name] : null;
380
  }
381
 
382
+ /**
383
+ * @return Container
384
+ */
385
+ public static function getContainer()
386
+ {
387
+ /** @noinspection NullPointerExceptionInspection */
388
+ return self::$instance->get(Plugin::class)->getContainer();
389
+ }
390
+
391
  /**
392
  * @return string
393
  */
469
  return $delay;
470
  }
471
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
472
  /**
473
  * Initialize licensing functions
474
  * @return boolean
Frontend/Frontend.php CHANGED
@@ -9,80 +9,78 @@ use WPStaging\Frontend\loginForm;
9
  * Class Frontend
10
  * @package WPStaging\Frontend
11
  */
12
- class Frontend extends InjectionAware {
 
13
 
14
  /**
15
  * @var object
16
  */
17
  private $settings;
18
 
19
- /**
20
- *
21
- * @var string
22
- */
23
- private $loginSlug;
24
-
25
  /**
26
  * Frontend initialization.
27
  */
28
- public function initialize() {
 
29
  $this->defineHooks();
30
 
31
- $this->settings = json_decode( json_encode( get_option( "wpstg_settings", array() ) ) );
32
 
33
- $this->loginSlug = isset( $this->settings->loginSlug ) ? $this->settings->loginSlug : '';
34
  }
35
 
36
  /**
37
  * Define Hooks
38
  */
39
- private function defineHooks() {
 
40
  // Get loader
41
- $loader = $this->di->get( "loader" );
42
- $loader->addAction( "init", $this, "checkPermissions" );
43
- $loader->addFilter( "wp_before_admin_bar_render", $this, "changeSiteName" );
44
  }
45
 
46
  /**
47
  * Change admin_bar site_name
48
  *
49
- * @global object $wp_admin_bar
50
  * @return void
 
51
  */
52
- public function changeSiteName() {
 
53
  global $wp_admin_bar;
54
- if( $this->isStagingSite() ) {
55
  // Main Title
56
- $wp_admin_bar->add_menu( array(
57
- 'id' => 'site-name',
58
- 'title' => is_admin() ? ('STAGING - ' . get_bloginfo( 'name' ) ) : ( 'STAGING - ' . get_bloginfo( 'name' ) . ' Dashboard' ),
59
- 'href' => is_admin() ? home_url( '/' ) : admin_url(),
60
- ) );
61
  }
62
  }
63
 
64
  /**
65
  * Check permissions for the page to decide whether or not to disable the page
66
  */
67
- public function checkPermissions() {
 
68
  $this->resetPermaLinks();
69
 
70
- if( $this->disableLogin() ) {
71
 
72
  $args = array(
73
- 'echo' => true,
74
  // Default 'redirect' value takes the user back to the request URI.
75
- 'redirect' => ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
76
- 'form_id' => 'loginform',
77
- 'label_username' => __( 'Username or Email Address' ),
78
- 'label_password' => __( 'Password' ),
79
- 'label_remember' => __( 'Remember Me' ),
80
- 'label_log_in' => __( 'Log In' ),
81
- 'id_username' => 'user_login',
82
- 'id_password' => 'user_pass',
83
- 'id_remember' => 'rememberme',
84
- 'id_submit' => 'wp-submit',
85
- 'remember' => true,
86
  'value_username' => '',
87
  // Set 'value_remember' to true to default the "Remember me" checkbox to checked.
88
  'value_remember' => false,
@@ -93,7 +91,7 @@ class Frontend extends InjectionAware {
93
  * Lines below are not used at the moment but are fully functional
94
  */
95
  $login = new loginForm();
96
- $login->renderForm( $args );
97
  die();
98
  }
99
  }
@@ -102,7 +100,8 @@ class Frontend extends InjectionAware {
102
  * Get path to wp-login.php
103
  * @return string
104
  */
105
- private function getLoginUrl() {
 
106
  return get_site_url() . '/wp-login.php';
107
  }
108
 
@@ -110,38 +109,34 @@ class Frontend extends InjectionAware {
110
  * Check if the page should be blocked
111
  * @return bool
112
  */
113
- private function disableLogin()
114
  {
115
- // Is not staging site
 
 
 
 
116
  if (!wpstg_is_stagingsite()) {
117
  return false;
118
  }
119
 
120
- // Allow access for user role administrator in any case
121
  if (current_user_can('manage_options')) {
122
  return false;
123
  }
124
 
125
- // Simple check for free version only
126
  if (!defined('WPSTGPRO_VERSION')) {
127
- return (
128
- (!isset($this->settings->disableAdminLogin) || '1' !== $this->settings->disableAdminLogin) &&
129
- (!current_user_can("manage_options") && !$this->isLoginPage() && !is_admin())
130
- );
131
  }
132
 
133
- // Allow access for special wp staging role "all"
134
- if (!empty($this->settings->userRoles) &&
135
- (in_array('all', $this->settings->userRoles) && !$this->isLoginPage() && !is_admin())
136
- ) {
137
  return false;
138
  }
139
 
140
- // If user roles are not defined, disable login for all user except administrator roles
141
- if (
142
- (!isset($this->settings->userRoles) || !is_array($this->settings->userRoles)) &&
143
- (!current_user_can('manage_options') && !$this->isLoginPage() && !is_admin())
144
- ) {
145
  return true;
146
  }
147
 
@@ -161,46 +156,45 @@ class Frontend extends InjectionAware {
161
  * Check if it is a staging site
162
  * @return bool
163
  */
164
- private function isStagingSite() {
165
- return ("true" === get_option( "wpstg_is_staging_site" ));
 
166
  }
167
 
168
  /**
169
  * Check if it is the login page
170
  * @return bool
171
  */
172
- private function isLoginPage() {
 
173
 
174
- return (
175
- in_array( $GLOBALS["pagenow"], array("wp-login.php") ) ||
176
- in_array( $this->loginSlug, $_GET ) ||
177
- array_key_exists( $this->loginSlug, $_GET )
178
- );
179
  }
180
 
181
  /**
182
  * Reset permalink structure of the clone to default; index.php?p=123
183
  */
184
- private function resetPermaLinks() {
185
- // Do nothing
186
- if( !$this->isStagingSite() || "true" === get_option( "wpstg_rmpermalinks_executed" ) ) {
 
187
  return;
188
  }
189
 
190
  // Do nothing
191
- if(defined('WPSTGPRO_VERSION')) {
192
  if (isset($this->settings->keepPermalinks) && $this->settings->keepPermalinks === "1") {
193
  return;
194
  }
195
  }
196
 
197
- // $wp_rewrite is not available before the init hook. So we need to use the global declaration
198
  global $wp_rewrite;
199
- $wp_rewrite->set_permalink_structure( null );
200
 
201
  flush_rewrite_rules();
202
 
203
- update_option( "wpstg_rmpermalinks_executed", "true" );
204
  }
205
 
206
- }
9
  * Class Frontend
10
  * @package WPStaging\Frontend
11
  */
12
+ class Frontend extends InjectionAware
13
+ {
14
 
15
  /**
16
  * @var object
17
  */
18
  private $settings;
19
 
 
 
 
 
 
 
20
  /**
21
  * Frontend initialization.
22
  */
23
+ public function initialize()
24
+ {
25
  $this->defineHooks();
26
 
27
+ $this->settings = json_decode(json_encode(get_option("wpstg_settings", array())));
28
 
 
29
  }
30
 
31
  /**
32
  * Define Hooks
33
  */
34
+ private function defineHooks()
35
+ {
36
  // Get loader
37
+ $loader = $this->di->get("loader");
38
+ $loader->addAction("init", $this, "checkPermissions");
39
+ $loader->addFilter("wp_before_admin_bar_render", $this, "changeSiteName");
40
  }
41
 
42
  /**
43
  * Change admin_bar site_name
44
  *
 
45
  * @return void
46
+ * @global object $wp_admin_bar
47
  */
48
+ public function changeSiteName()
49
+ {
50
  global $wp_admin_bar;
51
+ if ($this->isStagingSite()) {
52
  // Main Title
53
+ $wp_admin_bar->add_menu(array(
54
+ 'id' => 'site-name',
55
+ 'title' => is_admin() ? ('STAGING - ' . get_bloginfo('name')) : ('STAGING - ' . get_bloginfo('name') . ' Dashboard'),
56
+ 'href' => is_admin() ? home_url('/') : admin_url(),
57
+ ));
58
  }
59
  }
60
 
61
  /**
62
  * Check permissions for the page to decide whether or not to disable the page
63
  */
64
+ public function checkPermissions()
65
+ {
66
  $this->resetPermaLinks();
67
 
68
+ if ($this->isLoginRequired()) {
69
 
70
  $args = array(
71
+ 'echo' => true,
72
  // Default 'redirect' value takes the user back to the request URI.
73
+ 'redirect' => (is_ssl() ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
74
+ 'form_id' => 'loginform',
75
+ 'label_username' => __('Username or Email Address'),
76
+ 'label_password' => __('Password'),
77
+ 'label_remember' => __('Remember Me'),
78
+ 'label_log_in' => __('Log In'),
79
+ 'id_username' => 'user_login',
80
+ 'id_password' => 'user_pass',
81
+ 'id_remember' => 'rememberme',
82
+ 'id_submit' => 'wp-submit',
83
+ 'remember' => true,
84
  'value_username' => '',
85
  // Set 'value_remember' to true to default the "Remember me" checkbox to checked.
86
  'value_remember' => false,
91
  * Lines below are not used at the moment but are fully functional
92
  */
93
  $login = new loginForm();
94
+ $login->renderForm($args);
95
  die();
96
  }
97
  }
100
  * Get path to wp-login.php
101
  * @return string
102
  */
103
+ private function getLoginUrl()
104
+ {
105
  return get_site_url() . '/wp-login.php';
106
  }
107
 
109
  * Check if the page should be blocked
110
  * @return bool
111
  */
112
+ private function isLoginRequired()
113
  {
114
+
115
+ if ($this->isLoginPage() || is_admin()) {
116
+ return false;
117
+ }
118
+
119
  if (!wpstg_is_stagingsite()) {
120
  return false;
121
  }
122
 
123
+ // Allow access for administrator
124
  if (current_user_can('manage_options')) {
125
  return false;
126
  }
127
 
128
+ // Simple check (free version only)
129
  if (!defined('WPSTGPRO_VERSION')) {
130
+ return (!isset($this->settings->disableAdminLogin) || '1' !== $this->settings->disableAdminLogin);
 
 
 
131
  }
132
 
133
+ // Allow access for wp staging user role "all"
134
+ if (!empty($this->settings->userRoles) && in_array('all', $this->settings->userRoles)) {
 
 
135
  return false;
136
  }
137
 
138
+ // Allow access only for administratorss if no user roles are defined
139
+ if (!isset($this->settings->userRoles) || !is_array($this->settings->userRoles)) {
 
 
 
140
  return true;
141
  }
142
 
156
  * Check if it is a staging site
157
  * @return bool
158
  */
159
+ private function isStagingSite()
160
+ {
161
+ return ("true" === get_option("wpstg_is_staging_site"));
162
  }
163
 
164
  /**
165
  * Check if it is the login page
166
  * @return bool
167
  */
168
+ private function isLoginPage()
169
+ {
170
 
171
+ return (in_array($GLOBALS["pagenow"], array("wp-login.php")));
 
 
 
 
172
  }
173
 
174
  /**
175
  * Reset permalink structure of the clone to default; index.php?p=123
176
  */
177
+ private function resetPermaLinks()
178
+ {
179
+ // Do nothing
180
+ if (!$this->isStagingSite() || "true" === get_option("wpstg_rmpermalinks_executed")) {
181
  return;
182
  }
183
 
184
  // Do nothing
185
+ if (defined('WPSTGPRO_VERSION')) {
186
  if (isset($this->settings->keepPermalinks) && $this->settings->keepPermalinks === "1") {
187
  return;
188
  }
189
  }
190
 
191
+ // $wp_rewrite is not available before the init hook. So we need to use the global variable
192
  global $wp_rewrite;
193
+ $wp_rewrite->set_permalink_structure(null);
194
 
195
  flush_rewrite_rules();
196
 
197
+ update_option("wpstg_rmpermalinks_executed", "true");
198
  }
199
 
200
+ }
Manager/Database/TableDto.php ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+ // TODO PHP7.x; type-hints & return types
5
+
6
+ namespace WPStaging\Manager\Database;
7
+
8
+ use DateTime;
9
+ use WPStaging\Service\Interfaces\HydrateableInterface;
10
+ use WPStaging\Service\Utils\FileSize;
11
+
12
+ class TableDto implements HydrateableInterface
13
+ {
14
+ /** @var string */
15
+ private $name;
16
+
17
+ /** @var int */
18
+ private $rows;
19
+
20
+ /** @var int */
21
+ private $size;
22
+
23
+ /** @var int */
24
+ private $autoIncrement;
25
+
26
+ /** @var DateTime */
27
+ private $createdAt;
28
+
29
+ /** @var DateTime */
30
+ private $updatedAt;
31
+
32
+ public function hydrate(array $data = [])
33
+ {
34
+ $this->setName($data['Name']);
35
+
36
+ $this->setRows(isset($data['Rows'])? (int) $data['Rows'] : 0);
37
+ $this->setAutoIncrement(isset($data['Auto_increment'])? $data['Auto_increment'] : null);
38
+ /** @noinspection PhpUnhandledExceptionInspection */
39
+ $this->setCreatedAt(new DateTime(isset($data['Create_time'])? $data['Create_time'] : ''));
40
+ if (isset($data['Update_time']) && $data['Update_time']) {
41
+ /** @noinspection PhpUnhandledExceptionInspection */
42
+ $this->setUpdatedAt(new DateTime($data['Update_time']));
43
+ }
44
+
45
+ if (isset($data['Data_length'], $data['Index_length'])) {
46
+ $size = (int) $data['Data_length'] + (int) $data['Index_length'];
47
+ $this->setSize($size);
48
+ }
49
+
50
+ return $this;
51
+ }
52
+
53
+ /**
54
+ * @return string
55
+ */
56
+ public function getName()
57
+ {
58
+ return $this->name;
59
+ }
60
+
61
+ /**
62
+ * @param string $name
63
+ */
64
+ public function setName($name)
65
+ {
66
+ $this->name = $name;
67
+ }
68
+
69
+ /**
70
+ * @return int
71
+ */
72
+ public function getRows()
73
+ {
74
+ return $this->rows;
75
+ }
76
+
77
+ /**
78
+ * @param int $rows
79
+ */
80
+ public function setRows($rows)
81
+ {
82
+ $this->rows = $rows;
83
+ }
84
+
85
+ /**
86
+ * @return int
87
+ */
88
+ public function getSize()
89
+ {
90
+ return $this->size;
91
+ }
92
+
93
+ /**
94
+ * @param int $size
95
+ */
96
+ public function setSize($size)
97
+ {
98
+ $this->size = $size;
99
+ }
100
+
101
+ /**
102
+ * @return int|null
103
+ */
104
+ public function getAutoIncrement()
105
+ {
106
+ return $this->autoIncrement;
107
+ }
108
+
109
+ /**
110
+ * @param int|null $autoIncrement
111
+ */
112
+ public function setAutoIncrement($autoIncrement)
113
+ {
114
+ $this->autoIncrement = $autoIncrement;
115
+ }
116
+
117
+ /**
118
+ * @return DateTime
119
+ */
120
+ public function getCreatedAt()
121
+ {
122
+ return $this->createdAt;
123
+ }
124
+
125
+ /**
126
+ * @param DateTime $createdAt
127
+ */
128
+ public function setCreatedAt($createdAt)
129
+ {
130
+ $this->createdAt = $createdAt;
131
+ }
132
+
133
+ /**
134
+ * @return DateTime|null
135
+ */
136
+ public function getUpdatedAt()
137
+ {
138
+ return $this->updatedAt;
139
+ }
140
+
141
+ /**
142
+ * @param DateTime $updatedAt
143
+ */
144
+ public function setUpdatedAt($updatedAt)
145
+ {
146
+ $this->updatedAt = $updatedAt;
147
+ }
148
+
149
+ /**
150
+ * @return string
151
+ */
152
+ public function getHumanReadableSize()
153
+ {
154
+ return (new FileSize)->humanReadable($this->size);
155
+ }
156
+ }
Manager/Database/TableManager.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+ // TODO PHP7.x type-hints & return types
5
+
6
+ namespace WPStaging\Manager\Database;
7
+
8
+ use WPStaging\Service\Adapter\Database;
9
+ use WPStaging\Service\Collection\Collection;
10
+
11
+ class TableManager
12
+ {
13
+ /** @var Database */
14
+ private $database;
15
+
16
+ public function __construct()
17
+ {
18
+ $this->database = new Database;
19
+ }
20
+
21
+ /**
22
+ * @param string|null $prefix
23
+ *
24
+ * @return TableDto[]|Collection|null
25
+ */
26
+ public function findStartsWith($prefix = null)
27
+ {
28
+ $tables = $this->database->find('SHOW TABLE STATUS LIKE "' . $this->provideSqlPrefix($prefix) . '%"');
29
+ if (!$tables) {
30
+ return null;
31
+ }
32
+
33
+ $collection = new Collection(TableDto::class);
34
+ foreach ($tables as $table) {
35
+ $collection->attach((new TableDto)->hydrate((array) $table));
36
+ }
37
+ return $collection;
38
+ }
39
+
40
+ /**
41
+ * @param string|null $prefix
42
+ * @return string
43
+ */
44
+ private function provideSqlPrefix($prefix = null)
45
+ {
46
+ return $this->database->provideSqlPrefix($prefix);
47
+ }
48
+ }
Manager/FileSystem/FileManager.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+ // TODO PHP7.x; return types && type-hints
5
+
6
+ namespace WPStaging\Manager\FileSystem;
7
+
8
+ class FileManager
9
+ {
10
+
11
+ /**
12
+ * @param string $file full path + filename
13
+ * @param array $excludedFiles List of filenames. Can be wildcard pattern like data.php, data*.php, *.php, .php
14
+ * @return boolean
15
+ */
16
+ public function isFilenameExcluded($file, $excludedFiles)
17
+ {
18
+ $filename = basename($file);
19
+
20
+ // Regular filenames
21
+ if (in_array($filename, $excludedFiles, true)) {
22
+ return true;
23
+ }
24
+
25
+ // Wildcards
26
+ foreach ($excludedFiles as $pattern) {
27
+ if ($this->isPatternMatching($pattern, $filename)) {
28
+ return true;
29
+ }
30
+ }
31
+ return false;
32
+ }
33
+
34
+ /**
35
+ * Checks if the passed string would match the given shell wildcard pattern.
36
+ * This function emulates [[fnmatch()]], which may be unavailable at certain environment, using PCRE.
37
+ * @param string $pattern the shell wildcard pattern.
38
+ * @param string $string the tested string.
39
+ * @param array $options options for matching. Valid options are:
40
+ *
41
+ * - caseSensitive: bool, whether pattern should be case sensitive. Defaults to `true`.
42
+ * - escape: bool, whether backslash escaping is enabled. Defaults to `true`.
43
+ * - filePath: bool, whether slashes in string only matches slashes in the given pattern. Defaults to `false`.
44
+ *
45
+ * @return bool whether the string matches pattern or not.
46
+ */
47
+ protected function isPatternMatching($pattern, $string, $options = [])
48
+ {
49
+ if ($pattern === '*' && empty($options['filePath'])) {
50
+ return true;
51
+ }
52
+
53
+ $replacements = [
54
+ '\\\\\\\\' => '\\\\',
55
+ '\\\\\\*' => '[*]',
56
+ '\\\\\\?' => '[?]',
57
+ '\*' => '.*',
58
+ '\?' => '.',
59
+ '\[\!' => '[^',
60
+ '\[' => '[',
61
+ '\]' => ']',
62
+ '\-' => '-',
63
+ ];
64
+
65
+ if (isset($options['escape']) && !$options['escape']) {
66
+ unset($replacements['\\\\\\\\']);
67
+ unset($replacements['\\\\\\*']);
68
+ unset($replacements['\\\\\\?']);
69
+ }
70
+
71
+ if (!empty($options['filePath'])) {
72
+ $replacements['\*'] = '[^/\\\\]*';
73
+ $replacements['\?'] = '[^/\\\\]';
74
+ }
75
+
76
+ $pattern = strtr(preg_quote($pattern, '#'), $replacements);
77
+ $pattern = '#^' . $pattern . '$#us';
78
+ if (isset($options['caseSensitive']) && !$options['caseSensitive']) {
79
+ $pattern .= 'i';
80
+ }
81
+
82
+ return 1 === preg_match($pattern, $string);
83
+ }
84
+ }
Manager/SnapshotManager.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Manager;
4
+
5
+ use WPStaging\Repository\SnapshotRepository;
6
+
7
+ class SnapshotManager
8
+ {
9
+ /**
10
+ * @param string $prefix
11
+ * @return bool
12
+ */
13
+ public function deleteByPrefix($prefix)
14
+ {
15
+ $repository = new SnapshotRepository;
16
+
17
+ $snapshots = $repository->findAll();
18
+ if (!$snapshots) {
19
+ return true;
20
+ }
21
+
22
+ $snapshots->removeById($prefix);
23
+ if ($repository->save($snapshots)) {
24
+ return true;
25
+ }
26
+
27
+ return false;
28
+ }
29
+ }
Plugin.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging;
4
+
5
+ use WPStaging\Service\AbstractPlugin;
6
+
7
+ class Plugin extends AbstractPlugin
8
+ {
9
+ /**
10
+ * TODO; remove the demonstration
11
+ * @noinspection PhpUnused
12
+ */
13
+ public function onActivation()
14
+ {
15
+ }
16
+
17
+ /**
18
+ * TODO; remove the demonstration
19
+ * @noinspection PhpUnused
20
+ */
21
+ public function onDeactivate()
22
+ {
23
+ }
24
+
25
+ /**
26
+ * TODO; remove the demonstration
27
+ * @noinspection PhpUnused
28
+ * This needs to be static due to how register_uninstall_hook() works
29
+ */
30
+ public static function onUninstall()
31
+ {
32
+ }
33
+ }
Service/AbstractPlugin.php ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service;
4
+
5
+ use WPStaging\Service\Adapter\Directory;
6
+ use WPStaging\Service\Adapter\Hooks;
7
+ use WPStaging\Service\Container\Container;
8
+ use WPStaging\WPStaging;
9
+
10
+ abstract class AbstractPlugin implements PluginInterface
11
+ {
12
+ const APP_DEV = 'dev';
13
+ const APP_PROD = 'prod';
14
+
15
+ /** @var Container */
16
+ protected $container;
17
+
18
+ /** @var array */
19
+ protected $components = [];
20
+
21
+ public function __construct(Container $container = null)
22
+ {
23
+ if ($container) {
24
+ $this->setContainer($container);
25
+ }
26
+ }
27
+
28
+ public function setContainer(Container $container)
29
+ {
30
+ $this->container = $container;
31
+ }
32
+
33
+ public function init()
34
+ {
35
+ if (!$this->container) {
36
+ /** @noinspection PhpUnhandledExceptionInspection */
37
+ throw new InvalidPluginException(static::class);
38
+ }
39
+
40
+ $this->initDependencies();
41
+ $this->registerLifeCycle();
42
+ $this->loadLanguages();
43
+
44
+ /** @var Hooks $hooks */
45
+ $hooks = $this->container->get(Hooks::class);
46
+
47
+ foreach ($this->components as $id => $options) {
48
+ $this->container->setInitialized($id, $options);
49
+ }
50
+
51
+ $hooks->init();
52
+ }
53
+
54
+ /**
55
+ * @noinspection PhpUnused
56
+ * @param string $id
57
+ * @param array $options
58
+ */
59
+ public function addComponent($id, array $options = [])
60
+ {
61
+ if (array_key_exists($id, $this->components)) {
62
+ return;
63
+ }
64
+
65
+ $this->components[$id] = $options;
66
+ }
67
+
68
+ /**
69
+ * @noinspection PhpUnused
70
+ * @param string $id
71
+ */
72
+ public function removeComponent($id)
73
+ {
74
+ $key = array_search($id, $this->components, true);
75
+ if (false === $key) {
76
+ return;
77
+ }
78
+
79
+ unset($this->components[$key]);
80
+ $this->container->remove($id);
81
+ }
82
+
83
+ /**
84
+ * @return string|null
85
+ */
86
+ public function getSlug()
87
+ {
88
+ return $this->container->getParameter('slug');
89
+ }
90
+
91
+ public function getDomain()
92
+ {
93
+ return $this->container->getParameter('domain');
94
+ }
95
+
96
+ /**
97
+ * WP Staging Version Number
98
+ * @return string|null
99
+ */
100
+ public function getVersion()
101
+ {
102
+ if (!function_exists('get_plugin_data')) {
103
+ return null;
104
+ }
105
+
106
+ /** @var Directory|null $directory */
107
+ $directory = $this->container->get(Directory::class);
108
+ $pluginFile = $directory->getPluginDirectory() . $this->getSlug() . '.php';
109
+ $data = get_plugin_data($pluginFile);
110
+
111
+ // TODO PHP7.0; return $data['Version'] ?? WPStaging::getVersion();
112
+ return isset($data['Version']) && $data['Version']? $data['Version'] : WPStaging::getVersion();
113
+ }
114
+
115
+ /**
116
+ * @noinspection PhpUnused
117
+ * @return Container
118
+ */
119
+ public function getContainer()
120
+ {
121
+ return $this->container;
122
+ }
123
+
124
+ private function loadLanguages()
125
+ {
126
+ /** @noinspection NullPointerExceptionInspection */
127
+ $languagesDirectory = $this->container->get(Directory::class)->getPluginDirectory() . 'languages/';
128
+
129
+ // Set filter for plugins languages directory
130
+ $languagesDirectory = apply_filters($this->getSlug() . '_languages_directory', $languagesDirectory);
131
+
132
+ // Traditional WP plugin locale filter
133
+ $locale = apply_filters('plugin_locale', get_user_locale(), 'wp-staging');
134
+ $moFile = sprintf('%1$s-%2$s.mo', 'wp-staging', $locale);
135
+
136
+ // Setup paths to current locale file
137
+ $moFileLocal = $languagesDirectory . $moFile;
138
+ $moFileGlobal = sprintf('%s/wp-staging/%s', WP_LANG_DIR, $moFile);
139
+
140
+ if (file_exists($moFileGlobal)) {
141
+ load_textdomain('wp-staging', $moFileGlobal);
142
+ }
143
+ elseif (file_exists($moFileLocal)) {
144
+ load_textdomain('wp-staging', $moFileLocal);
145
+ }
146
+ else {
147
+ load_plugin_textdomain('wp-staging', false, $languagesDirectory);
148
+ }
149
+ }
150
+
151
+ private function initDependencies()
152
+ {
153
+ $this->container->set(Hooks::class, new Hooks);
154
+ $this->container->set(Directory::class, new Directory($this->getSlug()));
155
+ }
156
+
157
+ private function registerLifeCycle()
158
+ {
159
+ /** @noinspection NullPointerExceptionInspection */
160
+ $file = $this->container->get(Directory::class)->getPluginDirectory() . $this->getSlug() . '.php';
161
+
162
+ if (method_exists($this, 'onActivation')) {
163
+ register_activation_hook($file, [$this, 'onActivation']);
164
+ }
165
+
166
+ if (method_exists($this, 'onDeactivate')) {
167
+ register_deactivation_hook($file, [$this, 'onDeactivate']);
168
+ }
169
+
170
+ if (method_exists($this, 'onUninstall')) {
171
+ // $this does not work hence the usage of get_called_class()
172
+ register_uninstall_hook($file, [static::class, 'onUninstall']);
173
+ }
174
+ }
175
+ }
Service/Adapter/Database.php ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * IMPORTANT use only named queries or WP queries
4
+ */
5
+ /** @noinspection PhpUndefinedClassInspection */
6
+
7
+ namespace WPStaging\Service\Adapter;
8
+
9
+ use wpdb;
10
+ use WPStaging\Service\Adapter\Database\InterfaceDatabase;
11
+ use WPStaging\Service\Adapter\Database\WpDbAdapter;
12
+ use SplObjectStorage;
13
+
14
+ class Database
15
+ {
16
+ /** @var InterfaceDatabase */
17
+ private $client;
18
+
19
+ /** @var WpDbAdapter */
20
+ private $wpdba;
21
+
22
+ /** @var wpdb */
23
+ private $wpdb;
24
+
25
+ public function __construct()
26
+ {
27
+ global $wpdb;
28
+ $this->wpdb = $wpdb;
29
+ $this->wpdba = new WpDbAdapter($this->wpdb);
30
+ $this->client = $this->wpdba;
31
+ }
32
+
33
+ /**
34
+ * @return InterfaceDatabase|null
35
+ */
36
+ public function getClient()
37
+ {
38
+ return $this->client;
39
+ }
40
+
41
+ /**
42
+ * @return WpDbAdapter
43
+ * @noinspection PhpUnused
44
+ */
45
+ public function getWpdba()
46
+ {
47
+ return $this->wpdba;
48
+ }
49
+
50
+ /**
51
+ * @return string
52
+ */
53
+ public function getPrefix()
54
+ {
55
+ return $this->wpdb->prefix;
56
+ }
57
+
58
+ /**
59
+ * @param string|null $prefix
60
+ *
61
+ * @return string|string[]
62
+ */
63
+ public function provideSqlPrefix($prefix = null)
64
+ {
65
+ if (!$prefix) {
66
+ $prefix = $this->getPrefix();
67
+ }
68
+ return str_replace('_', '\_', $prefix);
69
+ }
70
+
71
+ /**
72
+ * @return string
73
+ * @noinspection PhpUnused
74
+ */
75
+ public function getCharset()
76
+ {
77
+ return $this->wpdb->get_charset_collate();
78
+ }
79
+
80
+ /**
81
+ * @param string $statement
82
+ *
83
+ * @return bool
84
+ */
85
+ public function exec($statement)
86
+ {
87
+ return (bool)$this->client->exec($statement);
88
+ }
89
+
90
+ /**
91
+ * @param string $sql
92
+ * @param array $conditions
93
+ *
94
+ * @return SplObjectStorage|null
95
+ */
96
+ public function find($sql, array $conditions = [])
97
+ {
98
+ return $this->client->find($sql, $conditions);
99
+ }
100
+ }
Service/Adapter/Database/AbstractDatabase.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Adapter\Database;
4
+
5
+ abstract class AbstractDatabase implements InterfaceDatabase
6
+ {
7
+
8
+ }
Service/Adapter/Database/DatabaseException.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Adapter\Database;
4
+
5
+ use Exception;
6
+
7
+ class DatabaseException extends Exception
8
+ {
9
+
10
+ public function __construct($message = '')
11
+ {
12
+ parent::__construct($message);
13
+ }
14
+ }
Service/Adapter/Database/DatabaseQueryDto.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Adapter\Database;
4
+
5
+
6
+ class DatabaseQueryDto
7
+ {
8
+ /** @var string */
9
+ private $tableName;
10
+
11
+ /** @var array */
12
+ private $data = [];
13
+
14
+ /** @var array */
15
+ private $dataValueMap = [];
16
+
17
+ /** @var array */
18
+ private $conditions = [];
19
+
20
+ /** @var array */
21
+ private $conditionsValueMap = [];
22
+
23
+ /**
24
+ * @return string
25
+ */
26
+ public function getTableName()
27
+ {
28
+ return $this->tableName;
29
+ }
30
+
31
+ /**
32
+ * @param string $tableName
33
+ */
34
+ public function setTableName($tableName)
35
+ {
36
+ $this->tableName = $tableName;
37
+ }
38
+
39
+ /**
40
+ * @return array
41
+ */
42
+ public function getData()
43
+ {
44
+ return $this->data;
45
+ }
46
+
47
+ public function setData(array $data = [])
48
+ {
49
+ $this->data = $data;
50
+ }
51
+
52
+ /**
53
+ * @return array
54
+ */
55
+ public function getDataValueMap()
56
+ {
57
+ return $this->dataValueMap;
58
+ }
59
+
60
+ public function setDataValueMap(array $dataValueMap = [])
61
+ {
62
+ $this->dataValueMap = $dataValueMap;
63
+ }
64
+
65
+ /**
66
+ * @return array
67
+ */
68
+ public function getConditions()
69
+ {
70
+ return $this->conditions;
71
+ }
72
+
73
+ public function setConditions(array $conditions = [])
74
+ {
75
+ $this->conditions = $conditions;
76
+ }
77
+
78
+ /**
79
+ * @return array
80
+ */
81
+ public function getConditionsValueMap()
82
+ {
83
+ return $this->conditionsValueMap;
84
+ }
85
+
86
+ public function setConditionsValueMap(array $conditionsValueMap = [])
87
+ {
88
+ $this->conditionsValueMap = $conditionsValueMap;
89
+ }
90
+ }
Service/Adapter/Database/InterfaceDatabase.php ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Adapter\Database;
4
+
5
+ use SplObjectStorage;
6
+
7
+ interface InterfaceDatabase
8
+ {
9
+ /**
10
+ * @return object
11
+ */
12
+ public function getClient();
13
+
14
+ /**
15
+ * @param string $sql
16
+ * @param array $conditions
17
+ *
18
+ * @return SplObjectStorage|null
19
+ */
20
+ public function find($sql, array $conditions = []);
21
+
22
+ /**
23
+ * @param string $sql
24
+ * @param array $conditions
25
+ *
26
+ * @return object|null
27
+ */
28
+ public function findOne($sql, array $conditions = []);
29
+
30
+ /**
31
+ * @param DatabaseQueryDto $queryDto
32
+ *
33
+ * @return bool
34
+ */
35
+ public function insert(DatabaseQueryDto $queryDto);
36
+
37
+ /**
38
+ * @param DatabaseQueryDto $queryDto
39
+ *
40
+ * @return bool
41
+ */
42
+ public function update(DatabaseQueryDto $queryDto);
43
+
44
+ /**
45
+ * @param string $tableName
46
+ * @param array $condition
47
+ *
48
+ * @return bool|int
49
+ */
50
+ public function delete($tableName, array $condition = []);
51
+
52
+ /**
53
+ * @param string $sql
54
+ *
55
+ * @return bool|int
56
+ */
57
+ public function exec($sql);
58
+
59
+ }
Service/Adapter/Database/WpDbAdapter.php ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Adapter\Database;
4
+
5
+ use SplObjectStorage;
6
+ use wpdb;
7
+
8
+ class WpDbAdapter extends AbstractDatabase
9
+ {
10
+ /** @var wpdb */
11
+ private $client;
12
+
13
+ public function __construct(wpdb $wpdb)
14
+ {
15
+ $this->client = $wpdb;
16
+ }
17
+
18
+ /**
19
+ * @inheritDoc
20
+ */
21
+ public function getClient()
22
+ {
23
+ return $this->client;
24
+ }
25
+
26
+ /**
27
+ * @inheritDoc
28
+ */
29
+ public function find($sql, array $conditions = [])
30
+ {
31
+ $records = $this->getResults($sql, $conditions);
32
+
33
+ if (!$records) {
34
+ return null;
35
+ }
36
+
37
+ $collection = new SplObjectStorage;
38
+ foreach($records as $record) {
39
+ $collection->attach($record);
40
+ }
41
+
42
+ return $collection;
43
+ }
44
+
45
+ /**
46
+ * @inheritDoc
47
+ */
48
+ public function findOne($sql, array $conditions = [])
49
+ {
50
+ $records = $this->getResults($sql, $conditions);
51
+
52
+ if (!$records) {
53
+ return null;
54
+ }
55
+
56
+ return reset($records);
57
+ }
58
+
59
+ /**
60
+ * @inheritDoc
61
+ */
62
+ public function insert(DatabaseQueryDto $queryDto)
63
+ {
64
+ // TODO: Implement insert() method.
65
+ }
66
+
67
+ /**
68
+ * @inheritDoc
69
+ */
70
+ public function update(DatabaseQueryDto $queryDto)
71
+ {
72
+ // TODO: Implement update() method.
73
+ }
74
+
75
+ /**
76
+ * @inheritDoc
77
+ */
78
+ public function delete($tableName, array $condition = [])
79
+ {
80
+ // TODO: Implement delete() method.
81
+ }
82
+
83
+ /**
84
+ * @inheritDoc
85
+ */
86
+ public function exec($sql)
87
+ {
88
+ return $this->client->query($sql);
89
+ }
90
+
91
+ private function getResults($sql, array $conditions = [])
92
+ {
93
+ if (!$conditions) {
94
+ $response = $this->client->get_results($sql);
95
+ }
96
+ else {
97
+ $response = $this->client->get_results($this->client->prepare($sql, $conditions));
98
+ }
99
+
100
+ return $response ? array_values((array)$response) : null;
101
+ }
102
+ }
Service/Adapter/DateTimeAdapter.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ //TODO PHP7.x; declare(strict_types=1);
4
+
5
+ namespace WPStaging\Service\Adapter;
6
+
7
+ use DateTime as CoreDateTime;
8
+
9
+ class DateTimeAdapter
10
+ {
11
+ public function getDateTimeFormat()
12
+ {
13
+ $dateFormat = get_option('date_format');
14
+ $timeFormat = 'H:i:s';
15
+
16
+ if (!$dateFormat) {
17
+ $dateFormat = 'Y/m/d';
18
+ }
19
+
20
+ $dateFormat = str_replace('F', 'M', $dateFormat);
21
+
22
+ return $dateFormat . ' ' . $timeFormat;
23
+ }
24
+
25
+ // TODO PHP7.0; public function transformWpDateTimeFormat(DateTime $dateTime): string
26
+ /**
27
+ * @param CoreDateTime $dateTime
28
+ * @return string
29
+ */
30
+ public function transformToWpFormat(CoreDateTime $dateTime)
31
+ {
32
+ return $dateTime->format($this->getDateTimeFormat());
33
+ }
34
+ }
Service/Adapter/Directory.php ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Adapter;
4
+
5
+ use RuntimeException;
6
+
7
+ class Directory
8
+ {
9
+ /** @var string */
10
+ private $slug;
11
+
12
+ /** @var string|null */
13
+ private $uploadDir;
14
+
15
+ /**
16
+ * @param string $slug
17
+ */
18
+ public function __construct($slug)
19
+ {
20
+ $this->slug = $slug;
21
+ }
22
+
23
+ /**
24
+ * @return string
25
+ */
26
+ public function getPluginDirectory()
27
+ {
28
+ return sprintf('%s/%s/', WP_PLUGIN_DIR, $this->slug);
29
+ }
30
+
31
+ /**
32
+ * @noinspection PhpUnused
33
+ * @return string
34
+ */
35
+ public function getCacheDirectory()
36
+ {
37
+ // TODO implement
38
+ }
39
+
40
+ /**
41
+ * @noinspection PhpUnused
42
+ * @return string
43
+ */
44
+ public function getLogDirectory()
45
+ {
46
+ // TODO implement
47
+ }
48
+
49
+ /**
50
+ * Relative Path
51
+ * @noinspection PhpUnused
52
+ * @return string
53
+ */
54
+ public function getUploadsDirectory()
55
+ {
56
+ if ($this->uploadDir) {
57
+ return $this->uploadDir;
58
+ }
59
+
60
+ // Get upload directory information. Default is ABSPATH . 'wp-content/uploads'
61
+ // Can be customized by populating the db option upload_path or the constant UPLOADS
62
+ // If both are defined WordPress will uses the value of the UPLOADS constant
63
+ $dir = wp_upload_dir();
64
+
65
+ // TODO RPoC
66
+ if ($dir['error']) {
67
+ throw new RuntimeException($dir['error']);
68
+ }
69
+
70
+ // Get absolute path to wordpress uploads directory e.g /var/www/wp-content/uploads/
71
+ $this->uploadDir = trailingslashit($dir['basedir']);
72
+ return $this->uploadDir;
73
+ }
74
+ }
Service/Adapter/Dto/HookDto.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Adapter\Dto;
4
+
5
+ class HookDto
6
+ {
7
+ const DEFAULT_PRIORITY = 10;
8
+
9
+ /** @var string */
10
+ private $hook;
11
+
12
+ /** @var object */
13
+ private $component;
14
+
15
+ /** @var string */
16
+ private $callback;
17
+
18
+ /** @var int */
19
+ private $priority = 10;
20
+
21
+ /** @var int */
22
+ private $acceptedArgs = 0;
23
+
24
+ /**
25
+ * @return string|null
26
+ */
27
+ public function getHook()
28
+ {
29
+ return $this->hook;
30
+ }
31
+
32
+ /**
33
+ * @param string $hook
34
+ */
35
+ public function setHook($hook)
36
+ {
37
+ $this->hook = $hook;
38
+ }
39
+
40
+ /**
41
+ * @return object|null
42
+ */
43
+ public function getComponent()
44
+ {
45
+ return $this->component;
46
+ }
47
+
48
+ /**
49
+ * @param object $component
50
+ */
51
+ public function setComponent($component)
52
+ {
53
+ $this->component = $component;
54
+ }
55
+
56
+ /**
57
+ * @return string|null
58
+ */
59
+ public function getCallback()
60
+ {
61
+ return $this->callback;
62
+ }
63
+
64
+ /**
65
+ * @param string $callback
66
+ */
67
+ public function setCallback($callback)
68
+ {
69
+ $this->callback = $callback;
70
+ }
71
+
72
+ /**
73
+ * @return int
74
+ */
75
+ public function getPriority()
76
+ {
77
+ return $this->priority?: self::DEFAULT_PRIORITY;
78
+ }
79
+
80
+ /**
81
+ * @param int $priority
82
+ */
83
+ public function setPriority($priority)
84
+ {
85
+ $this->priority = $priority;
86
+ }
87
+
88
+ /**
89
+ * @return int
90
+ */
91
+ public function getAcceptedArgs()
92
+ {
93
+ return (int)$this->acceptedArgs;
94
+ }
95
+
96
+ /**
97
+ * @param $acceptedArgs
98
+ */
99
+ public function setAcceptedArgs($acceptedArgs)
100
+ {
101
+ $this->acceptedArgs = $acceptedArgs;
102
+ }
103
+ }
Service/Adapter/Dto/JedDto.php ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+
5
+ namespace WPStaging\Service\Adapter\Dto;
6
+
7
+ use JsonSerializable;
8
+
9
+ class JedDto implements JsonSerializable
10
+ {
11
+ /** @var string */
12
+ private $domain;
13
+
14
+ /** @var string */
15
+ private $language;
16
+
17
+ /** @var string */
18
+ private $pluralForms;
19
+
20
+ /** @var array */
21
+ private $translations = [];
22
+
23
+ /**
24
+ * @return array
25
+ */
26
+ public function toArray()
27
+ {
28
+ $settings = [
29
+ '' => [
30
+ 'domain' => $this->domain,
31
+ 'lang' => $this->language,
32
+ 'plural_forms' => $this->pluralForms,
33
+ ],
34
+ ];
35
+
36
+ /** @noinspection AdditionOperationOnArraysInspection */
37
+ return $settings + $this->translations;
38
+ }
39
+
40
+ /**
41
+ * @return array
42
+ */
43
+ public function jsonSerialize()
44
+ {
45
+ return $this->toArray();
46
+ }
47
+
48
+ /**
49
+ * @return string
50
+ */
51
+ public function getDomain()
52
+ {
53
+ return $this->domain;
54
+ }
55
+
56
+ /**
57
+ * @param string $domain
58
+ */
59
+ public function setDomain($domain)
60
+ {
61
+ $this->domain = $domain;
62
+ }
63
+
64
+ /**
65
+ * @return string
66
+ */
67
+ public function getLanguage()
68
+ {
69
+ return $this->language;
70
+ }
71
+
72
+ /**
73
+ * @param string $language
74
+ */
75
+ public function setLanguage($language)
76
+ {
77
+ $this->language = $language;
78
+ }
79
+
80
+ /**
81
+ * @return string
82
+ */
83
+ public function getPluralForms()
84
+ {
85
+ return $this->pluralForms;
86
+ }
87
+
88
+ /**
89
+ * @param string $pluralForms
90
+ */
91
+ public function setPluralForms($pluralForms)
92
+ {
93
+ $this->pluralForms = $pluralForms;
94
+ }
95
+
96
+ /**
97
+ * @return array
98
+ */
99
+ public function getTranslations()
100
+ {
101
+ return $this->translations;
102
+ }
103
+
104
+ /**
105
+ * @param array $translations
106
+ */
107
+ public function setTranslations(array $translations = [])
108
+ {
109
+ $this->translations = $translations;
110
+ }
111
+
112
+ /**
113
+ * @param string $key
114
+ * @param array $translations
115
+ */
116
+ public function addTranslations($key, array $translations)
117
+ {
118
+ $this->translations[$key] = $translations;
119
+ }
120
+ }
Service/Adapter/Hooks.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Adapter;
4
+
5
+ use WPStaging\Service\Adapter\Dto\HookDto;
6
+
7
+ final class Hooks
8
+ {
9
+ /** @var HookDto[]|array */
10
+ private $actions = [];
11
+
12
+ /** @var HookDto[]|array */
13
+ private $filters = [];
14
+
15
+ /** @noinspection PhpUnused */
16
+ public function addAction(HookDto $dto)
17
+ {
18
+ $this->actions[] = $dto;
19
+ }
20
+
21
+ /** @noinspection PhpUnused */
22
+ public function addFilter(HookDto $dto)
23
+ {
24
+ $this->filters[] = $dto;
25
+ }
26
+
27
+ public function init()
28
+ {
29
+ foreach ($this->filters as $filter) {
30
+ add_filter(
31
+ $filter->getHook(),
32
+ [$filter->getComponent(), $filter->getCallback()],
33
+ $filter->getPriority(),
34
+ $filter->getAcceptedArgs()
35
+ );
36
+ }
37
+
38
+ foreach ($this->actions as $action) {
39
+ add_action(
40
+ $action->getHook(),
41
+ [$action->getComponent(), $action->getCallback()],
42
+ $action->getPriority(),
43
+ $action->getAcceptedArgs()
44
+ );
45
+ }
46
+ }
47
+ }
Service/Adapter/Translation.php ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+
5
+ namespace WPStaging\Service\Adapter;
6
+
7
+ use Translations;
8
+ use NOOP_Translations;
9
+ use Translation_Entry;
10
+ use WPStaging\Service\Adapter\Dto\JedDto;
11
+
12
+ class Translation
13
+ {
14
+ const DEFAULT_LANGUAGE = 'en';
15
+
16
+ const DEFAULT_PLURAL_FORMS = 'nplurals=2; plural=n != 1;';
17
+
18
+ /** @var string */
19
+ private $domain;
20
+
21
+ /**
22
+ * @param string $domain
23
+ */
24
+ public function __construct($domain)
25
+ {
26
+ $this->domain = $domain;
27
+ }
28
+
29
+ /**
30
+ * @return JedDto
31
+ */
32
+ public function toJed()
33
+ {
34
+ $translations = $this->provideTranslations();
35
+
36
+ $jed = new JedDto;
37
+ $jed->setDomain($this->domain);
38
+ $jed->setLanguage($this->getLanguage($translations->headers));
39
+ $jed->setPluralForms($this->getPluralForms($translations->headers));
40
+
41
+ /**
42
+ * @var string $key
43
+ * @var Translation_Entry $value
44
+ */
45
+ foreach($translations->entries as $key => $value) {
46
+ $jed->addTranslations($key, $value->translations);
47
+ }
48
+
49
+ return $jed;
50
+ }
51
+
52
+ /**
53
+ * @return Translations|NOOP_Translations
54
+ */
55
+ private function provideTranslations()
56
+ {
57
+ return get_translations_for_domain($this->domain);
58
+ }
59
+
60
+ /**
61
+ * @param array $headers
62
+ * @return string
63
+ */
64
+ private function getLanguage(array $headers = [])
65
+ {
66
+ if (isset($headers['Language']) && $headers['Language']) {
67
+ return strtolower($headers['Language']);
68
+ }
69
+
70
+ return self::DEFAULT_LANGUAGE;
71
+ }
72
+
73
+ /**
74
+ * @param array $headers
75
+ * @return string
76
+ */
77
+ private function getPluralForms(array $headers = [])
78
+ {
79
+ if (isset($headers['Plural-Forms']) && $headers['Plural-Forms']) {
80
+ return (string) $headers['Plural-Forms'];
81
+ }
82
+
83
+ return self::DEFAULT_PLURAL_FORMS;
84
+ }
85
+ }
Service/Collection/Collection.php ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ //TODO PHP7.x; declare(strict_types=1);
4
+
5
+ namespace WPStaging\Service\Collection;
6
+
7
+ use SplObjectStorage;
8
+ use JsonSerializable;
9
+ use WPStaging\Service\Interfaces\ArrayableInterface;
10
+ use WPStaging\Service\Interfaces\HydrateableInterface;
11
+
12
+ class Collection extends SplObjectStorage implements JsonSerializable
13
+ {
14
+ /** @var string */
15
+ protected $storedClass;
16
+
17
+ /**
18
+ * @param string $storedClass
19
+ */
20
+ public function __construct($storedClass)
21
+ {
22
+ $this->storedClass = $storedClass;
23
+ }
24
+
25
+ public function toArray()
26
+ {
27
+ $collection = [];
28
+ /** @var ArrayableInterface $item */
29
+ foreach ($this as $item) {
30
+ if (method_exists($item, 'toArray')) {
31
+ $collection[] = $item->toArray();
32
+ } else {
33
+ $collection[] = $item;
34
+ }
35
+ }
36
+
37
+ return $collection;
38
+ }
39
+
40
+ public function attachAllByArray(array $data = [])
41
+ {
42
+ foreach ($data as $item) {
43
+ if ($item instanceof $this->storedClass) {
44
+ $this->attach($item);
45
+ continue;
46
+ }
47
+
48
+ /** @var HydrateableInterface $object */
49
+ $object = new $this->storedClass;
50
+ $object->hydrate((array) $item);
51
+ $this->attach($object);
52
+ }
53
+ }
54
+
55
+ public function jsonSerialize()
56
+ {
57
+ return $this->toArray();
58
+ }
59
+ }
Service/Collection/OptionCollection.php ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php /** @noinspection PhpUndefinedClassInspection */
2
+
3
+ //TODO PHP7.x; declare(strict_types=1);
4
+ //TODO PHP7.x type-hints & return types
5
+
6
+ namespace WPStaging\Service\Collection;
7
+
8
+ use JsonSerializable;
9
+ use WPStaging\Pro\Component\Job\Database\JobCreateSnapshot;
10
+ use WPStaging\Service\Entity\AbstractEntity;
11
+ use WPStaging\Service\Entity\IdentifyableEntityInterface;
12
+
13
+ class OptionCollection extends Collection implements JsonSerializable
14
+ {
15
+
16
+ /**
17
+ * @param string $id
18
+ *
19
+ * @return bool
20
+ */
21
+ public function doesIncludeId($id)
22
+ {
23
+ // We could use following for simplicity but not that performable
24
+ // return array_key_exists($id, $this->toArray());
25
+ /** @var IdentifyableEntityInterface $item */
26
+ foreach ($this as $item) {
27
+ if ($id === $item->getId()) {
28
+ return true;
29
+ }
30
+ }
31
+
32
+ return false;
33
+ }
34
+
35
+ /**
36
+ * @param string $id
37
+ *
38
+ * @return AbstractEntity|null
39
+ * @noinspection PhpUnused
40
+ */
41
+ public function findById($id)
42
+ {
43
+ /** @var AbstractEntity $item */
44
+ foreach ($this as $item) {
45
+ if ($id === $item->getId()) {
46
+ return $item;
47
+ }
48
+ }
49
+
50
+ return null;
51
+ }
52
+
53
+ /**
54
+ * @param string $id
55
+ */
56
+ public function removeById($id)
57
+ {
58
+ $item = $this->findById($id);
59
+ if (!$item) {
60
+ return;
61
+ }
62
+ // TODO RPoC
63
+ delete_transient(JobCreateSnapshot::TRANSIENT_PREFIX . $id);
64
+ $this->detach($item);
65
+ }
66
+
67
+ public function filterByPrefix($prefix)
68
+ {
69
+ /** @var AbstractEntity $item */
70
+ foreach ($this as $item) {
71
+ if (0 !== strpos($item->getId(), $prefix)) {
72
+ $this->detach($item);
73
+ }
74
+ }
75
+ }
76
+
77
+ public function sortBy($key, $sort = SORT_DESC)
78
+ {
79
+ $array = $this->toArray();
80
+ $columns = array_column($array, $key);
81
+ array_multisort($columns, $sort, $array);
82
+ $this->removeAll($this);
83
+ $this->attachAllByArray($array);
84
+ }
85
+ }
Service/Command/CommandInterface.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Command;
4
+
5
+ interface CommandInterface
6
+ {
7
+ /**
8
+ * @return void
9
+ */
10
+ public function execute();
11
+ }
Service/Command/HandlerInterface.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Command;
4
+
5
+ interface HandlerInterface
6
+ {
7
+ public function addCommand(CommandInterface $command);
8
+
9
+ public function handle();
10
+ }
Service/Component/AbstractComponent.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // TODO PHP7.x; declare(strict_types=1);
3
+ // TODO PHP7.x; return type hints
4
+
5
+ namespace WPStaging\Service\Component;
6
+
7
+ use WPStaging\Service\Adapter\Dto\HookDto;
8
+ use WPStaging\Service\Adapter\Hooks;
9
+
10
+ abstract class AbstractComponent implements ComponentInterface
11
+ {
12
+ /** @var Hooks */
13
+ private $hooks;
14
+
15
+ public function __construct(Hooks $hooks)
16
+ {
17
+ $this->hooks = $hooks;
18
+ $this->registerHooks();
19
+ }
20
+
21
+ /**
22
+ * @param string $action
23
+ * @param string $method
24
+ * @param int $acceptedArgs
25
+ * @param int $priority
26
+ * @noinspection PhpUnused
27
+ */
28
+ public function addAction($action, $method, $acceptedArgs = 0, $priority = 10)
29
+ {
30
+ $dto = $this->generateDto($action, $method, $acceptedArgs, $priority);
31
+ $this->hooks->addAction($dto);
32
+ }
33
+
34
+ /**
35
+ * @param string $action
36
+ * @param string $method
37
+ * @param int $acceptedArgs
38
+ * @param int $priority
39
+ * @noinspection PhpUnused
40
+ */
41
+ public function addFilter($action, $method, $acceptedArgs = 0, $priority = 10)
42
+ {
43
+ $dto = $this->generateDto($action, $method, $acceptedArgs, $priority);
44
+ $this->hooks->addFilter($dto);
45
+ }
46
+
47
+ /**
48
+ * @param string $action
49
+ * @param string $method
50
+ * @param int $acceptedArgs
51
+ * @param int $priority
52
+ *
53
+ * @return HookDto
54
+ */
55
+ private function generateDto($action, $method, $acceptedArgs = 0, $priority = 10)
56
+ {
57
+ $dto = new HookDto;
58
+ $dto->setHook($action);
59
+ $dto->setComponent($this);
60
+ $dto->setCallback($method);
61
+ $dto->setAcceptedArgs($acceptedArgs);
62
+ $dto->setPriority($priority);
63
+
64
+ return $dto;
65
+ }
66
+ }
Service/Component/AbstractTemplateComponent.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+ // TODO PHP7.x; type-hints && return types
5
+
6
+ namespace WPStaging\Service\Component;
7
+
8
+ use WPStaging\Service\Adapter\Hooks;
9
+ use WPStaging\Service\TemplateEngine\TemplateEngine;
10
+
11
+ abstract class AbstractTemplateComponent extends AbstractComponent implements RenderableComponentInterface
12
+ {
13
+ use AjaxTrait;
14
+
15
+ /** @var TemplateEngine */
16
+ protected $templateEngine;
17
+
18
+ public function __construct(Hooks $hooks, TemplateEngine $templateEngine)
19
+ {
20
+ parent::__construct($hooks);
21
+ $this->templateEngine = $templateEngine;
22
+ }
23
+
24
+ /**
25
+ * @param string $path
26
+ * @param array $params
27
+ *
28
+ * @return string
29
+ */
30
+ public function renderTemplate($path, array $params = [])
31
+ {
32
+ return $this->templateEngine->render($path, $params);
33
+ }
34
+
35
+ /**
36
+ * @return string
37
+ */
38
+ public function getSlug()
39
+ {
40
+ return $this->templateEngine->getSlug();
41
+ }
42
+ }
Service/Component/AjaxTrait.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+
5
+ namespace WPStaging\Service\Component;
6
+
7
+ trait AjaxTrait
8
+ {
9
+ public function isAjax()
10
+ {
11
+ return defined('DOING_AJAX') && DOING_AJAX;
12
+ }
13
+
14
+ /**
15
+ * @param string|null $action
16
+ * @param string|null $key
17
+ *
18
+ * @return bool
19
+ */
20
+ public function isSecureAjax($action = null, $key = null)
21
+ {
22
+ $_action = $action;
23
+ if (null === $_action) {
24
+ $_action = -1;
25
+ }
26
+
27
+ $_key = $key;
28
+ if (null === $_key) {
29
+ $_key = false;
30
+ }
31
+
32
+ return $this->isAjax() && (bool) check_ajax_referer($_action, $_key, false);
33
+ }
34
+ }
Service/Component/ComponentInterface.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // TODO PHP7.x; declare(strict_types=1);
3
+ // TODO PHP7.x; return type hints
4
+
5
+ namespace WPStaging\Service\Component;
6
+
7
+ interface ComponentInterface
8
+ {
9
+ public function registerHooks();
10
+ }
Service/Component/RenderableComponentInterface.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // TODO PHP7.x; declare(strict_types=1);
3
+ // TODO PHP7.x; return type hints
4
+
5
+ namespace WPStaging\Service\Component;
6
+
7
+ interface RenderableComponentInterface
8
+ {
9
+ public function render();
10
+ }
Service/Container/AbstractContainerAware.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Component;
4
+
5
+ use WPStaging\Service\Container\Container;
6
+
7
+ abstract class AbstractContainerAware
8
+ {
9
+ /** @var Container */
10
+ protected $container;
11
+
12
+ public function __construct(Container $container)
13
+ {
14
+ $this->container = $container;
15
+ }
16
+
17
+ /**
18
+ * @param string $id
19
+ *
20
+ * @return null|object
21
+ */
22
+ protected function get($id)
23
+ {
24
+ return $this->container->get($id);
25
+ }
26
+
27
+ /**
28
+ * @return string|null
29
+ */
30
+ protected function getSlug()
31
+ {
32
+ return $this->container->getParameter('slug');
33
+ }
34
+ }
Service/Container/Container.php ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Container;
4
+
5
+ use ReflectionClass;
6
+ use ReflectionException;
7
+ use ReflectionParameter;
8
+
9
+ class Container implements ContainerInterface
10
+ {
11
+ /** @var array */
12
+ private $container = [];
13
+
14
+ /** @var array */
15
+ private $parameters = [];
16
+
17
+ /** @var array */
18
+ private $mapping = [];
19
+
20
+ public function __construct(array $config = [])
21
+ {
22
+ $this->setContainerByConfig($config);
23
+ $this->setParametersByConfig($config);
24
+ $this->setMappingByConfig($config);
25
+ }
26
+
27
+ /**
28
+ * @noinspection PhpDocMissingThrowsInspection
29
+ * @param string $id
30
+ *
31
+ * @return $this|mixed|object|null
32
+ */
33
+ public function get($id)
34
+ {
35
+ if (self::class === $id) {
36
+ return $this;
37
+ }
38
+
39
+ if (!$this->has($id)) {
40
+ /** @noinspection PhpUnhandledExceptionInspection */
41
+ return $this->loadClass($id);
42
+ }
43
+
44
+ if (is_object($this->container[$id])) {
45
+ return $this->container[$id];
46
+ }
47
+
48
+ if (is_callable($this->container[$id])) {
49
+ $this->container[$id] = $this->container[$id]();
50
+ }
51
+
52
+ /** @noinspection PhpUnhandledExceptionInspection */
53
+ $this->loadClass($id);
54
+
55
+ return $this->container[$id];
56
+ }
57
+
58
+ /**
59
+ * @param string $id
60
+ * @param object|callable|null $value
61
+ */
62
+ public function set($id, $value = null)
63
+ {
64
+ $this->container[$id] = $value;
65
+ }
66
+
67
+ /**
68
+ * @param string $id
69
+ */
70
+ public function remove($id)
71
+ {
72
+ if (!$this->has($id)) {
73
+ return;
74
+ }
75
+
76
+ unset($this->container[$id]);
77
+ }
78
+
79
+ /**
80
+ * @param string $id
81
+ *
82
+ * @return bool
83
+ */
84
+ public function has($id)
85
+ {
86
+ return isset($this->container[$id]);
87
+ }
88
+
89
+ /**
90
+ * @noinspection PhpUnused
91
+ * @param string $id
92
+ * @param array $options
93
+ */
94
+ public function setInitialized($id, array $options = [])
95
+ {
96
+ $this->container[$id] = $options;
97
+ $this->get($id);
98
+ }
99
+
100
+ /**
101
+ * @noinspection PhpUnused
102
+ * @param string $key
103
+ * @param mixed $value
104
+ */
105
+ public function setParameter($key, $value)
106
+ {
107
+ $this->parameters[$key] = $value;
108
+ }
109
+
110
+ /**
111
+ * @param string|null $key
112
+ * @param mixed|null $default
113
+ *
114
+ * @return mixed|null
115
+ */
116
+ public function getParameter($key = null, $default = null)
117
+ {
118
+ if (null === $key) {
119
+ return $this->parameters;
120
+ }
121
+
122
+ if ($this->hasParameter($key)) {
123
+ return $this->parameters[$key];
124
+ }
125
+
126
+ if (false === strpos($key, '.')) {
127
+ return $default;
128
+ }
129
+
130
+ $params = $this->parameters;
131
+ foreach (explode('.', $key) as $segment) {
132
+ if (!is_array($params) || !$this->hasParameter($segment)) {
133
+ return $default;
134
+ }
135
+
136
+ $items = &$items[$segment];
137
+ }
138
+
139
+ return $params;
140
+ }
141
+
142
+ /**
143
+ * @param string $key
144
+ *
145
+ * @return bool
146
+ */
147
+ public function hasParameter($key)
148
+ {
149
+ return isset($this->parameters[$key]);
150
+ }
151
+
152
+ private function setContainerByConfig(array $config = [])
153
+ {
154
+ if (!isset($config['services'])) {
155
+ return;
156
+ }
157
+
158
+ foreach ($config['services'] as $key => $value) {
159
+ if (is_int($key) && is_string($value)) {
160
+ $key = $value;
161
+ }
162
+ $this->container[$key] = $value;
163
+ }
164
+ }
165
+
166
+ private function setParametersByConfig(array $config = [])
167
+ {
168
+ if (!isset($config['params'])) {
169
+ return;
170
+ }
171
+
172
+ foreach ($config['params'] as $key => $value) {
173
+ $this->parameters[$key] = $value;
174
+ }
175
+ }
176
+
177
+ private function setMappingByConfig(array $config = [])
178
+ {
179
+ if (!isset($config['mapping'])) {
180
+ return;
181
+ }
182
+
183
+ foreach ($config['mapping'] as $key => $value) {
184
+ if (is_scalar($value)) {
185
+ $this->mapping[$key] = $value;
186
+ }
187
+ }
188
+ }
189
+
190
+ /**
191
+ * @param string $id
192
+ *
193
+ * @return object|string
194
+ * @throws InvalidConstructorParamException
195
+ * @throws ReflectionException
196
+ */
197
+ private function resolve($id)
198
+ {
199
+ if (!class_exists($id)) {
200
+ return $id;
201
+ }
202
+
203
+ $reflection = $this->getReflection($id);
204
+
205
+ if (!$reflection) {
206
+ return $id;
207
+ }
208
+
209
+ return $this->newInstance($reflection);
210
+ }
211
+
212
+ private function getReflection($class)
213
+ {
214
+ try {
215
+ return new ReflectionClass($class);
216
+ }
217
+ catch (ReflectionException $e) {
218
+ return null;
219
+ }
220
+ }
221
+
222
+ /**
223
+ * @param ReflectionClass|null $reflection
224
+ *
225
+ * @return null|object
226
+ * @throws InvalidConstructorParamException
227
+ * @throws ReflectionException
228
+ */
229
+ private function newInstance($reflection = null)
230
+ {
231
+ if (!$reflection) {
232
+ return null;
233
+ }
234
+
235
+ if (!$reflection->getConstructor()) {
236
+ return $reflection->newInstance();
237
+ }
238
+
239
+ $params = $reflection->getConstructor()->getParameters();
240
+ // TODO PHP7.0; $this->container[$reflection->getName()] ?? [];
241
+ if (isset($this->container[$reflection->getName()]) && is_array($this->container[$reflection->getName()])) {
242
+ $options = $this->container[$reflection->getName()];
243
+ }
244
+ else {
245
+ $options = [];
246
+ }
247
+
248
+ $args = [];
249
+ foreach ($params as $param) {
250
+ $args[] = $this->getConstructorParam($param, $options);
251
+ }
252
+
253
+ return $reflection->newInstanceArgs($args);
254
+ }
255
+
256
+ /**
257
+ * @param ReflectionParameter $param
258
+ * @param array $options
259
+ *
260
+ * @return mixed|object|null
261
+ * @throws InvalidConstructorParamException
262
+ * @throws ReflectionException
263
+ */
264
+ private function getConstructorParam(ReflectionParameter $param, array $options = [])
265
+ {
266
+ if ($param->getClass()) {
267
+ return $this->getConstructorParamClass($param->getClass());
268
+ }
269
+
270
+ // Check param values
271
+ if (isset($options[$param->getName()])) {
272
+ return $this->getConstructorParamValue($options[$param->getName()]);
273
+ }
274
+
275
+ if (isset($this->parameters[$param->getName()])) {
276
+ return $this->getConstructorParamValue($this->parameters[$param->getName()]);
277
+ }
278
+
279
+
280
+ if ($param->isDefaultValueAvailable()) {
281
+ return $param->getDefaultValue();
282
+ }
283
+
284
+ if ($param->getType() && $param->getType()->allowsNull()) {
285
+ return null;
286
+ }
287
+
288
+ throw new InvalidConstructorParamException($param->getName());
289
+ }
290
+
291
+ private function getConstructorParamClass(ReflectionClass $class)
292
+ {
293
+ if (isset($this->mapping[$class->getName()])) {
294
+ return $this->get($this->mapping[$class->getName()]);
295
+ }
296
+
297
+ return $this->get($class->getName());
298
+ }
299
+
300
+ /**
301
+ * @param null|array|string $value
302
+ *
303
+ * @return null|object|array|string
304
+ */
305
+ private function getConstructorParamValue($value = null)
306
+ {
307
+ if (!is_string($value)) {
308
+ return $value;
309
+ }
310
+
311
+ if (class_exists($value)) {
312
+ return $this->get($value);
313
+ }
314
+
315
+ if (!preg_match_all('#{{(.*?)}}#', $value, $matches)) {
316
+ return $value;
317
+ }
318
+
319
+ $replace = [];
320
+
321
+ foreach ($matches[1] as $key) {
322
+ $replace[] = $this->getParameter($key);
323
+ }
324
+
325
+ return str_replace($matches[0], $replace, $value);
326
+ }
327
+
328
+ /**
329
+ * @param string $id
330
+ *
331
+ * @return object|null
332
+ * @throws InvalidConstructorParamException
333
+ * @throws ReflectionException
334
+ */
335
+ private function loadClass($id)
336
+ {
337
+ if (isset($this->mapping[$id])) {
338
+ $id = $this->mapping[$id];
339
+ }
340
+
341
+ if (!class_exists($id)) {
342
+ return null;
343
+ }
344
+
345
+ /** @noinspection PhpUnhandledExceptionInspection */
346
+ $this->container[$id] = $this->resolve($id);
347
+
348
+ return $this->container[$id];
349
+ }
350
+ }
Service/Container/ContainerInterface.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Container;
4
+
5
+ interface ContainerInterface
6
+ {
7
+ /**
8
+ * @param string $id
9
+ *
10
+ * @return object|null
11
+ */
12
+ public function get($id);
13
+
14
+ /**
15
+ * @param string $id
16
+ * @param null|string|array|object|int|float|bool $value
17
+ *
18
+ * @return void
19
+ */
20
+ public function set($id, $value = null);
21
+
22
+ /**
23
+ * @param string $id
24
+ *
25
+ * @return bool
26
+ */
27
+ public function has($id);
28
+ }
Service/Container/InvalidConstructorParamException.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Container;
4
+
5
+ use Exception;
6
+ use Throwable;
7
+
8
+ class InvalidConstructorParamException extends Exception
9
+ {
10
+
11
+ public function __construct($message = '', $code = 0, Throwable $previous = null)
12
+ {
13
+ $message = sprintf('Invalid Constructor Parameter $%s', $message);
14
+ parent::__construct($message, $code, $previous);
15
+ }
16
+ }
Service/Dto/AbstractDto.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ //TODO PHP7.x; declare(strict_types=1);
4
+
5
+
6
+ namespace WPStaging\Service\Dto;
7
+
8
+
9
+ use JsonSerializable;
10
+ use WPStaging\Service\Traits\ArrayableTrait;
11
+
12
+ abstract class AbstractDto implements JsonSerializable
13
+ {
14
+ use ArrayableTrait;
15
+
16
+ /**
17
+ * @inheritDoc
18
+ */
19
+ public function jsonSerialize()
20
+ {
21
+ return $this->toArray();
22
+ }
23
+ }
Service/Entity/AbstractEntity.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ //TODO PHP7.x; declare(strict_types=1);
4
+
5
+ namespace WPStaging\Service\Entity;
6
+
7
+ use Serializable;
8
+ use JsonSerializable;
9
+ use WPStaging\Service\Interfaces\ArrayableInterface;
10
+ use WPStaging\Service\Interfaces\HydrateableInterface;
11
+ use WPStaging\Service\Traits\ArrayableTrait;
12
+ use WPStaging\Service\Traits\HydrateTrait;
13
+
14
+ abstract class AbstractEntity implements Serializable, JsonSerializable, ArrayableInterface, HydrateableInterface
15
+ {
16
+ use ArrayableTrait;
17
+ use HydrateTrait;
18
+
19
+ /**
20
+ * @inheritDoc
21
+ */
22
+ public function serialize()
23
+ {
24
+ return serialize($this->toArray());
25
+ }
26
+
27
+ /**
28
+ * @inheritDoc
29
+ */
30
+ public function unserialize($serialized)
31
+ {
32
+ $this->hydrate(unserialize($serialized));
33
+ }
34
+
35
+ /**
36
+ * @inheritDoc
37
+ */
38
+ public function jsonSerialize()
39
+ {
40
+ return $this->toArray();
41
+ }
42
+ }
Service/Entity/EntityException.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+
5
+ namespace WPStaging\Service\Entity;
6
+
7
+ use RuntimeException;
8
+
9
+ class EntityException extends RuntimeException
10
+ {
11
+
12
+ }
Service/Entity/IdentifyableEntityInterface.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+
5
+ namespace WPStaging\Service\Entity;
6
+
7
+ interface IdentifyableEntityInterface
8
+ {
9
+ /**
10
+ * @return string
11
+ */
12
+ public function getId();
13
+ }
Service/Interfaces/ArrayableInterface.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+ // TODO PHP7.x; type-hints & return types
5
+
6
+ namespace WPStaging\Service\Interfaces;
7
+
8
+ interface ArrayableInterface
9
+ {
10
+ /**
11
+ * @return array
12
+ */
13
+ public function toArray();
14
+ }
Service/Interfaces/HydrateableInterface.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+ // TODO PHP7.x; type-hints & return types
5
+
6
+ namespace WPStaging\Service\Interfaces;
7
+
8
+ interface HydrateableInterface
9
+ {
10
+ /**
11
+ * @param array $data
12
+ *
13
+ * @return self
14
+ */
15
+ public function hydrate(array $data = []);
16
+ }
Service/InvalidPluginException.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service;
4
+
5
+ use Exception;
6
+ use Throwable;
7
+
8
+ class InvalidPluginException extends Exception
9
+ {
10
+
11
+ public function __construct($message = '', $code = 0, Throwable $previous = null)
12
+ {
13
+ $message = sprintf('Plugin %s must implement PluginInterface', $message);
14
+ parent::__construct($message, $code, $previous);
15
+ }
16
+ }
Service/PluginFactory.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service;
4
+
5
+ use WPStaging\Service\Container\Container;
6
+
7
+ class PluginFactory implements PluginFactoryInterface
8
+ {
9
+ /**
10
+ * @param string $pluginClass
11
+ *
12
+ * @return PluginInterface
13
+ * @throws InvalidPluginException
14
+ */
15
+ public static function make($pluginClass)
16
+ {
17
+ /** @noinspection PhpIncludeInspection */
18
+ $config = require plugin_dir_path(__FILE__) . '../config.php';
19
+ $container = new Container($config?: []);
20
+
21
+ /** @var PluginInterface $plugin */
22
+ $plugin = new $pluginClass($container);
23
+
24
+ if (!$plugin instanceof PluginInterface) {
25
+ throw new InvalidPluginException($pluginClass);
26
+ }
27
+
28
+ if (!isset($config['components'])) {
29
+ return $plugin;
30
+ }
31
+
32
+ foreach ($config['components'] as $id => $options) {
33
+ if (is_string($id) && is_array($options)) {
34
+ $plugin->addComponent($id, $options);
35
+ continue;
36
+ }
37
+
38
+ $plugin->addComponent($options);
39
+ }
40
+
41
+ return $plugin;
42
+ }
43
+ }
Service/PluginFactoryInterface.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service;
4
+
5
+ interface PluginFactoryInterface
6
+ {
7
+ /**
8
+ * @param string $pluginClass
9
+ *
10
+ * @return AbstractPlugin
11
+ */
12
+ public static function make($pluginClass);
13
+ }
Service/PluginInterface.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service;
4
+
5
+ use WPStaging\Service\Container\Container;
6
+
7
+ interface PluginInterface
8
+ {
9
+ public function setContainer(Container $container);
10
+
11
+ public function init();
12
+
13
+ /**
14
+ * @param string $id
15
+ * @param array $options
16
+ */
17
+ public function addComponent($id, array $options = []);
18
+
19
+ public function removeComponent($id);
20
+
21
+ /**
22
+ * @return string|null
23
+ */
24
+ public function getSlug();
25
+
26
+ /**
27
+ * @return Container
28
+ */
29
+ public function getContainer();
30
+ }
Service/TemplateEngine/TemplateEngine.php ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // TODO PHP7.x; declare(strict_types=1);
3
+ // TODO PHP7.x; type-hints & return types;
4
+
5
+ namespace WPStaging\Service\TemplateEngine;
6
+
7
+ use DateTime;
8
+ use WPStaging\Service\Adapter\DateTimeAdapter;
9
+ use WPStaging\Service\Adapter\Directory;
10
+
11
+ class TemplateEngine implements TemplateEngineInterface
12
+ {
13
+
14
+ /** @var string */
15
+ private $slug;
16
+
17
+ /** @var string */
18
+ private $path;
19
+
20
+ /** @var string */
21
+ private $url;
22
+
23
+ /** @var string */
24
+ private $domain;
25
+
26
+ /**
27
+ * TemplateEngine constructor.
28
+ *
29
+ * @param Directory $directory
30
+ * @param string $slug
31
+ * @param string $domain
32
+ */
33
+ public function __construct(Directory $directory, $slug, $domain)
34
+ {
35
+ $this->slug = $slug;
36
+ $this->domain = $domain;
37
+ $this->path = $directory->getPluginDirectory() . 'template/';
38
+ $this->url = plugin_dir_url($directory->getPluginDirectory() . $slug . '.php') . 'public/';
39
+ }
40
+
41
+ /**
42
+ * @return string
43
+ * @noinspection PhpUnused
44
+ */
45
+ public function getSlug()
46
+ {
47
+ return $this->slug;
48
+ }
49
+
50
+ /**
51
+ * @return string
52
+ */
53
+ public function getDomain()
54
+ {
55
+ return $this->domain;
56
+ }
57
+
58
+ /**
59
+ * @param string $domain
60
+ */
61
+ public function setDomain($domain)
62
+ {
63
+ $this->domain = $domain;
64
+ }
65
+
66
+ /**
67
+ * @return string
68
+ * @noinspection PhpUnused
69
+ */
70
+ public function getPath()
71
+ {
72
+ return $this->path;
73
+ }
74
+
75
+ /**
76
+ * @param string $path
77
+ * @noinspection PhpUnused
78
+ */
79
+ public function setPath($path)
80
+ {
81
+ $this->path = $path;
82
+ }
83
+
84
+ /**
85
+ * @return string
86
+ * @noinspection PhpUnused
87
+ */
88
+ public function getUrl()
89
+ {
90
+ return $this->url;
91
+ }
92
+
93
+ /**
94
+ * @param string $url
95
+ * @noinspection PhpUnused
96
+ */
97
+ public function setUrl($url)
98
+ {
99
+ $this->url = $url;
100
+ }
101
+
102
+ /**
103
+ * @param string $path
104
+ * @param array $params
105
+ *
106
+ * @return string
107
+ */
108
+ public function render($path, array $params = [])
109
+ {
110
+ $fullPath = $this->path . $path;
111
+ if (!file_exists($fullPath)) {
112
+ throw new TemplateEngineException('Template not found: ' . $fullPath);
113
+ }
114
+
115
+ extract($params, EXTR_SKIP);
116
+ ob_start();
117
+
118
+ /** @noinspection PhpIncludeInspection */
119
+ require $fullPath;
120
+ $result = ob_get_clean();
121
+
122
+ return (string)$result;
123
+ }
124
+
125
+ /**
126
+ * @return string
127
+ * @noinspection PhpUnused
128
+ */
129
+ protected function getDateTimeFormat()
130
+ {
131
+ return (new DateTimeAdapter)->getDateTimeFormat();
132
+ }
133
+
134
+ /**
135
+ * @param DateTime|null $dateTime
136
+ *
137
+ * @return string
138
+ */
139
+ protected function transformToWpFormat(DateTime $dateTime = null)
140
+ {
141
+ if (!$dateTime) {
142
+ return '';
143
+ }
144
+ return (new DateTimeAdapter)->transformToWpFormat($dateTime);
145
+ }
146
+ }
Service/TemplateEngine/TemplateEngineException.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // TODO PHP7.x; declare(strict_types=1);
3
+
4
+ namespace WPStaging\Service\TemplateEngine;
5
+
6
+ use RuntimeException;
7
+
8
+ class TemplateEngineException extends RuntimeException
9
+ {
10
+
11
+ }
Service/TemplateEngine/TemplateEngineInterface.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // TODO PHP7.x declare(strict_types=1);
3
+ // TODO PHP7.x; type-hints & return types;
4
+
5
+ namespace WPStaging\Service\TemplateEngine;
6
+
7
+ interface TemplateEngineInterface
8
+ {
9
+
10
+ public function render($path, array $params = []);
11
+ }
Service/Traits/ArrayableTrait.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+
5
+ namespace WPStaging\Service\Traits;
6
+
7
+ use DateTime;
8
+ use ReflectionClass;
9
+ use ReflectionProperty;
10
+ use WPStaging\Service\Adapter\DateTimeAdapter;
11
+
12
+ trait ArrayableTrait
13
+ {
14
+ /**
15
+ * @return array
16
+ * @noinspection PhpDocMissingThrowsInspection
17
+ */
18
+ public function toArray()
19
+ {
20
+ /** @noinspection PhpUnhandledExceptionInspection */
21
+ $reflection = new ReflectionClass($this);
22
+ $props = $reflection->getProperties(
23
+ ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE
24
+ );
25
+
26
+ $data = [];
27
+ /** @var ReflectionProperty $prop */
28
+ foreach($props as $prop) {
29
+ $prop->setAccessible(true);
30
+ $value = $prop->getValue($this);
31
+
32
+ if ($value instanceof DateTime) {
33
+ $value = (new DateTimeAdapter)->transformToWpFormat($value);
34
+ }
35
+
36
+ $data[$prop->getName()] = $value;
37
+ }
38
+
39
+ return $data;
40
+ }
41
+ }
Service/Traits/HydrateTrait.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+ // TODO PHP7.x type-hints & return types
5
+
6
+ namespace WPStaging\Service\Traits;
7
+
8
+ use DateTime;
9
+ use ReflectionException;
10
+ use ReflectionMethod;
11
+ use WPStaging\Service\Entity\EntityException;
12
+
13
+ trait HydrateTrait
14
+ {
15
+
16
+ /**
17
+ * @param array $data
18
+ * @return $this
19
+ * @noinspection PhpDocMissingThrowsInspection
20
+ */
21
+ public function hydrate(array $data = [])
22
+ {
23
+ foreach ($data as $key => $value) {
24
+ /** @noinspection PhpUnhandledExceptionInspection */
25
+ $this->hydrateByMethod('set' . ucfirst($key), $value);
26
+ }
27
+
28
+ return $this;
29
+ }
30
+
31
+ /**
32
+ * @param string $method
33
+ * @param mixed $value
34
+ *
35
+ * @throws ReflectionException
36
+ */
37
+ private function hydrateByMethod($method, $value)
38
+ {
39
+ if (!method_exists($this, $method)) {
40
+ return;
41
+ }
42
+
43
+ /** @noinspection CallableParameterUseCaseInTypeContextInspection */
44
+ $method = new ReflectionMethod($this, $method);
45
+
46
+ $params = $method->getParameters();
47
+
48
+ if (!isset($params[0]) || count($params) > 1) {
49
+ throw new EntityException(sprintf(
50
+ 'Class %s setter method %s does not have a first parameter or has more than one parameter',
51
+ static::class,
52
+ $method
53
+ ));
54
+ }
55
+
56
+ $param = $params[0];
57
+
58
+ if ($value && !$value instanceof DateTime && $param->getClass() && 'DateTime' === $param->getClass()->getName()) {
59
+ /** @noinspection PhpUnhandledExceptionInspection */
60
+ $value = new DateTime(str_replace(',', null, $value));
61
+ }
62
+
63
+ $method->invoke($this, $value);
64
+ }
65
+ }
Service/Utils/FileSize.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+
5
+ namespace WPStaging\Service\Utils;
6
+
7
+ class FileSize
8
+ {
9
+ public function humanReadable($bytes)
10
+ {
11
+ $value = floor(log($bytes) / log(1024));
12
+ $sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
13
+ return sprintf('%.02F', $bytes / pow(1024, $value)) * 1 . ' ' . $sizes[$value];
14
+ }
15
+ }
Service/Utils/FileSystem.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+
5
+ namespace WPStaging\Service\Utils;
6
+
7
+ class FileSystem
8
+ {
9
+ /*
10
+ * Makes sure all paths contain linux separator (/) which works fine on all windows systems, too
11
+ * Windows understands both / and \
12
+ */
13
+ public function compatiblePath($path)
14
+ {
15
+ if ('/' === DIRECTORY_SEPARATOR) {
16
+ return $path;
17
+ }
18
+
19
+ return str_replace('/', DIRECTORY_SEPARATOR, $path);
20
+ }
21
+
22
+ public function replaceWindowsDirSeparator($path)
23
+ {
24
+ return preg_replace('/[\\\\]+/', '/', $path);
25
+ }
26
+
27
+ /**
28
+ * @param string $dir
29
+ * @return bool True if directory is empty. False if empty or does not exist
30
+ */
31
+ public function isEmptyDir($dir)
32
+ {
33
+ if (!is_dir($dir)) {
34
+ return false;
35
+ }
36
+
37
+ return false === (new FilesystemIterator($dir))->valid();
38
+ }
39
+
40
+ /**
41
+ * Delete files and folder. Deals with directories recursively
42
+ *
43
+ * @param string $fullPath
44
+ */
45
+ public function deleteFiles($fullPath)
46
+ {
47
+ if (is_file($fullPath)) {
48
+ unlink($fullPath);
49
+ return;
50
+ }
51
+
52
+ if (!is_dir($fullPath) || $this->isEmptyDir($fullPath)) {
53
+ return;
54
+ }
55
+
56
+ $di = new RecursiveDirectoryIterator($fullPath, FilesystemIterator::SKIP_DOTS);
57
+ $ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
58
+ foreach ($ri as $file) {
59
+ $this->deleteFiles($file);
60
+ }
61
+
62
+ rmdir($fullPath);
63
+ }
64
+ }
Service/Utils/WpDefaultDirectories.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+
5
+ namespace WPStaging\Service\Utils;
6
+
7
+ // TODO PHP7.1; constant visibility
8
+ class WpDefaultDirectories
9
+ {
10
+ const WP_ADMIN = 'wp-admin';
11
+ const WP_INCLUDES = 'wp-includes';
12
+ const WP_CONTENT = 'wp-content';
13
+ const SITES = 'sites';
14
+ const MULTI_OLD_UPLOADS_DIR = 'blogs.dir';
15
+ const MULTI_UPLOADS_DIR = 'sites';
16
+ }
config.php ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @example 'services' or 'components' key can have parameters such as;
4
+ * `Foo::class => ['slug' => '{{slug}}'],`
5
+ * `slug` key matches the class constructor variable name (order does not matter) such as `__construct($slug)`
6
+ * `{{slug}}` value matches the `params.slug` of this file.
7
+ *
8
+ * **IMPORTANT TOPICS**
9
+ * 1. Order of params does not matter as we check & match variable names
10
+ * 2. Not all params in constructor needs to be defined, only the ones we want
11
+ * 3. Hand-typed variables are allowed; `Foo::class => ['slug' => 'bar'],`
12
+ */
13
+
14
+ use Psr\Log\LoggerInterface;
15
+ use WPStaging\Component\Snapshot\AjaxConfirmDelete as SnapshotAjaxConfirmDelete;
16
+ use WPStaging\Component\Snapshot\AjaxConfirmRestore as SnapshotAjaxConfirmRestore;
17
+ use WPStaging\Component\Snapshot\AjaxCreate as SnapshotAjaxCreate;
18
+ use WPStaging\Component\Snapshot\AjaxCreateProgress as SnapshotAjaxCreateProgress;
19
+ use WPStaging\Component\Snapshot\AjaxDelete as SnapshotAjaxDelete;
20
+ use WPStaging\Component\Snapshot\AjaxEdit as SnapshotAjaxEdit;
21
+ use WPStaging\Component\Snapshot\AjaxExport as SnapshotAjaxExport;
22
+ use WPStaging\Component\Snapshot\AjaxListing as SnapshotAjaxListing;
23
+ use WPStaging\Component\Snapshot\AjaxRestore as SnapshotAjaxRestore;
24
+ use WPStaging\Component\Snapshot\AjaxRestoreProgress as SnapshotAjaxRestoreProgress;
25
+ use WPStaging\Service\TemplateEngine\TemplateEngine;
26
+ use WPStaging\Utils\Logger;
27
+
28
+ return [
29
+ // Params we can use all around the application with easy access and without duplication / WET; keep it DRY!
30
+ 'params' => [
31
+ 'slug' => 'wp-staging',
32
+ 'domain' => 'wp-staging',
33
+ ],
34
+ // Services are not initialized, they are only initialized once when they are requested. If they are already
35
+ // initialized when requested, the same instance would be used.
36
+ 'services' => [
37
+ TemplateEngine::class,
38
+ ],
39
+ // Components are initialized upon plugin init / as soon as the Container is set; such as a class that sets;
40
+ // Ajax Request, Adds a Menu, Form etc. needs to be initialized without being requested hence they go here!
41
+ 'components' => [
42
+ SnapshotAjaxListing::class,
43
+ SnapshotAjaxConfirmDelete::class,
44
+ SnapshotAjaxDelete::class,
45
+ SnapshotAjaxCreateProgress::class,
46
+ SnapshotAjaxCreate::class,
47
+ SnapshotAjaxConfirmRestore::class,
48
+ SnapshotAjaxRestoreProgress::class,
49
+ SnapshotAjaxRestore::class,
50
+ SnapshotAjaxExport::class,
51
+ SnapshotAjaxEdit::class,
52
+ ],
53
+ // Map specific interfaces to specific classes.
54
+ // If you map LoggerInterface::class to Logger::class, when you use LoggerInterface as a dependency,
55
+ // it will load / pass Logger class instead
56
+ 'mapping' => [
57
+ LoggerInterface::class => Logger::class,
58
+ ],
59
+ ];
install.php CHANGED
@@ -2,9 +2,7 @@
2
 
3
  namespace WPStaging;
4
 
5
- use WPStaging\WPStaging;
6
  use WPStaging\Backend\Optimizer\Optimizer;
7
- use WPStaging\Cron\Cron;
8
  use WPStaging\Utils\IISWebConfig;
9
  use WPStaging\Utils\Htaccess;
10
  use WPStaging\Utils\Filesystem;
@@ -13,6 +11,9 @@ use WPStaging\Utils\Filesystem;
13
  if (!defined('ABSPATH'))
14
  exit;
15
 
 
 
 
16
  /**
17
  * Install Class
18
  *
@@ -47,7 +48,7 @@ class Install
47
  private function installOptimizer()
48
  {
49
 
50
- // Install Optimizer
51
  $optimizer = new Optimizer();
52
  $optimizer->installOptimizer();
53
 
2
 
3
  namespace WPStaging;
4
 
 
5
  use WPStaging\Backend\Optimizer\Optimizer;
 
6
  use WPStaging\Utils\IISWebConfig;
7
  use WPStaging\Utils\Htaccess;
8
  use WPStaging\Utils\Filesystem;
11
  if (!defined('ABSPATH'))
12
  exit;
13
 
14
+ // TODO; remove previous auto-loader, use composer based instead!
15
+ require_once __DIR__ . '/vendor/autoload.php';
16
+
17
  /**
18
  * Install Class
19
  *
48
  private function installOptimizer()
49
  {
50
 
51
+ // Install Optimizer
52
  $optimizer = new Optimizer();
53
  $optimizer->installOptimizer();
54
 
readme.txt CHANGED
@@ -9,7 +9,7 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
  Tags: staging, duplication, cloning, clone, migration, sandbox, test site, testing, backup, post, admin, administration, duplicate posts
10
  Requires at least: 3.6+
11
  Tested up to: 5.3
12
- Stable tag: 2.6.8
13
  Requires PHP: 5.3
14
 
15
  A duplicator plugin - clone/move, duplicate & migrate live websites to independent staging and development sites that are accessible​ by authorized users only.
@@ -153,6 +153,14 @@ https://wp-staging.com
153
 
154
  == Changelog ==
155
 
 
 
 
 
 
 
 
 
156
  = 2.6.8 =
157
  * Fix: If server is windows it will result in missing files after cloning and can lead to fatal errors of the staging site
158
 
@@ -234,15 +242,9 @@ Complete changelog: [https://wp-staging.com/wp-staging-changelog](https://wp-sta
234
  == Upgrade Notice ==
235
  * Install this version for supporting latest WordPress version
236
 
237
- * New: Support for WordPress 5.3.1
238
- * New: Refactoring code base and remove app folder
239
- * New: Add french language files
240
- * New: Add WP Staging logo to login form
241
- * New: Set 24 hours expiration date to process lock
242
- * New: Add link URL to staging site title
243
- * Fix: Fatal error: Invalid serialization data for DateTime object #91
244
- * Fix: Add missing string language location
245
- * Fix: Function fnmatch() not available in all systems
246
- * Fix: Warning in staging site after initial cloning in db row rewrite_rules
247
- * Fix: Wrong staging site is selected when delete function is executed and there are more then 10 staging sites
248
- * Fix: Fatal error: Cannot redeclare wpstgpro_overwrite_nonce() and wpstg_overwrite_nonce() after activating pro version on top of this free one
9
  Tags: staging, duplication, cloning, clone, migration, sandbox, test site, testing, backup, post, admin, administration, duplicate posts
10
  Requires at least: 3.6+
11
  Tested up to: 5.3
12
+ Stable tag: 2.6.9
13
  Requires PHP: 5.3
14
 
15
  A duplicator plugin - clone/move, duplicate & migrate live websites to independent staging and development sites that are accessible​ by authorized users only.
153
 
154
  == Changelog ==
155
 
156
+ = 2.6.9 =
157
+ * Fix: Can not login to staging site under certain circumstances
158
+ * Fix: Use user selected language setting instead global site based one
159
+ * Fix: Fatal Error: curl_version() not defined in SystemInfo.php
160
+ * New: Refactored structure for easier maintenance
161
+ * New: Core support for WP Staging snapshots
162
+ * New: Implementing of UnitTests
163
+
164
  = 2.6.8 =
165
  * Fix: If server is windows it will result in missing files after cloning and can lead to fatal errors of the staging site
166
 
242
  == Upgrade Notice ==
243
  * Install this version for supporting latest WordPress version
244
 
245
+ * Fix: Can not login to staging site under certain circumstances
246
+ * Fix: Use user selected language setting instead global site based one
247
+ * Fix: Fatal Error: curl_version() not defined in SystemInfo.php
248
+ * New: Refactored structure for easier maintenance
249
+ * New: Core support for WP Staging snapshots
250
+ * New: Implementing of UnitTests
 
 
 
 
 
 
uninstall.php CHANGED
@@ -18,6 +18,9 @@ if (!defined('WP_UNINSTALL_PLUGIN')) {
18
  exit;
19
  }
20
 
 
 
 
21
  class uninstall
22
  {
23
 
18
  exit;
19
  }
20
 
21
+ // TODO; remove previous auto-loader, use composer based instead!
22
+ require_once __DIR__ . '/vendor/autoload.php';
23
+
24
  class uninstall
25
  {
26
 
vendor/autoload.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload.php @generated by Composer
4
+
5
+ require_once __DIR__ . '/composer/autoload_real.php';
6
+
7
+ return ComposerAutoloaderInitc2c8fce0bd7d21d1b2bc76515b028e91::getLoader();
vendor/composer/ClassLoader.php ADDED
@@ -0,0 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Composer.
5
+ *
6
+ * (c) Nils Adermann <naderman@naderman.de>
7
+ * Jordi Boggiano <j.boggiano@seld.be>
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ namespace Composer\Autoload;
14
+
15
+ /**
16
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17
+ *
18
+ * $loader = new \Composer\Autoload\ClassLoader();
19
+ *
20
+ * // register classes with namespaces
21
+ * $loader->add('Symfony\Component', __DIR__.'/component');
22
+ * $loader->add('Symfony', __DIR__.'/framework');
23
+ *
24
+ * // activate the autoloader
25
+ * $loader->register();
26
+ *
27
+ * // to enable searching the include path (eg. for PEAR packages)
28
+ * $loader->setUseIncludePath(true);
29
+ *
30
+ * In this example, if you try to use a class in the Symfony\Component
31
+ * namespace or one of its children (Symfony\Component\Console for instance),
32
+ * the autoloader will first look for the class under the component/
33
+ * directory, and it will then fallback to the framework/ directory if not
34
+ * found before giving up.
35
+ *
36
+ * This class is loosely based on the Symfony UniversalClassLoader.
37
+ *
38
+ * @author Fabien Potencier <fabien@symfony.com>
39
+ * @author Jordi Boggiano <j.boggiano@seld.be>
40
+ * @see http://www.php-fig.org/psr/psr-0/
41
+ * @see http://www.php-fig.org/psr/psr-4/
42
+ */
43
+ class ClassLoader
44
+ {
45
+ // PSR-4
46
+ private $prefixLengthsPsr4 = array();
47
+ private $prefixDirsPsr4 = array();
48
+ private $fallbackDirsPsr4 = array();
49
+
50
+ // PSR-0
51
+ private $prefixesPsr0 = array();
52
+ private $fallbackDirsPsr0 = array();
53
+
54
+ private $useIncludePath = false;
55
+ private $classMap = array();
56
+ private $classMapAuthoritative = false;
57
+ private $missingClasses = array();
58
+ private $apcuPrefix;
59
+
60
+ public function getPrefixes()
61
+ {
62
+ if (!empty($this->prefixesPsr0)) {
63
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
64
+ }
65
+
66
+ return array();
67
+ }
68
+
69
+ public function getPrefixesPsr4()
70
+ {
71
+ return $this->prefixDirsPsr4;
72
+ }
73
+
74
+ public function getFallbackDirs()
75
+ {
76
+ return $this->fallbackDirsPsr0;
77
+ }
78
+
79
+ public function getFallbackDirsPsr4()
80
+ {
81
+ return $this->fallbackDirsPsr4;
82
+ }
83
+
84
+ public function getClassMap()
85
+ {
86
+ return $this->classMap;
87
+ }
88
+
89
+ /**
90
+ * @param array $classMap Class to filename map
91
+ */
92
+ public function addClassMap(array $classMap)
93
+ {
94
+ if ($this->classMap) {
95
+ $this->classMap = array_merge($this->classMap, $classMap);
96
+ } else {
97
+ $this->classMap = $classMap;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Registers a set of PSR-0 directories for a given prefix, either
103
+ * appending or prepending to the ones previously set for this prefix.
104
+ *
105
+ * @param string $prefix The prefix
106
+ * @param array|string $paths The PSR-0 root directories
107
+ * @param bool $prepend Whether to prepend the directories
108
+ */
109
+ public function add($prefix, $paths, $prepend = false)
110
+ {
111
+ if (!$prefix) {
112
+ if ($prepend) {
113
+ $this->fallbackDirsPsr0 = array_merge(
114
+ (array) $paths,
115
+ $this->fallbackDirsPsr0
116
+ );
117
+ } else {
118
+ $this->fallbackDirsPsr0 = array_merge(
119
+ $this->fallbackDirsPsr0,
120
+ (array) $paths
121
+ );
122
+ }
123
+
124
+ return;
125
+ }
126
+
127
+ $first = $prefix[0];
128
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
129
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
130
+
131
+ return;
132
+ }
133
+ if ($prepend) {
134
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
135
+ (array) $paths,
136
+ $this->prefixesPsr0[$first][$prefix]
137
+ );
138
+ } else {
139
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
140
+ $this->prefixesPsr0[$first][$prefix],
141
+ (array) $paths
142
+ );
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Registers a set of PSR-4 directories for a given namespace, either
148
+ * appending or prepending to the ones previously set for this namespace.
149
+ *
150
+ * @param string $prefix The prefix/namespace, with trailing '\\'
151
+ * @param array|string $paths The PSR-4 base directories
152
+ * @param bool $prepend Whether to prepend the directories
153
+ *
154
+ * @throws \InvalidArgumentException
155
+ */
156
+ public function addPsr4($prefix, $paths, $prepend = false)
157
+ {
158
+ if (!$prefix) {
159
+ // Register directories for the root namespace.
160
+ if ($prepend) {
161
+ $this->fallbackDirsPsr4 = array_merge(
162
+ (array) $paths,
163
+ $this->fallbackDirsPsr4
164
+ );
165
+ } else {
166
+ $this->fallbackDirsPsr4 = array_merge(
167
+ $this->fallbackDirsPsr4,
168
+ (array) $paths
169
+ );
170
+ }
171
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
172
+ // Register directories for a new namespace.
173
+ $length = strlen($prefix);
174
+ if ('\\' !== $prefix[$length - 1]) {
175
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
176
+ }
177
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
178
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
179
+ } elseif ($prepend) {
180
+ // Prepend directories for an already registered namespace.
181
+ $this->prefixDirsPsr4[$prefix] = array_merge(
182
+ (array) $paths,
183
+ $this->prefixDirsPsr4[$prefix]
184
+ );
185
+ } else {
186
+ // Append directories for an already registered namespace.
187
+ $this->prefixDirsPsr4[$prefix] = array_merge(
188
+ $this->prefixDirsPsr4[$prefix],
189
+ (array) $paths
190
+ );
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Registers a set of PSR-0 directories for a given prefix,
196
+ * replacing any others previously set for this prefix.
197
+ *
198
+ * @param string $prefix The prefix
199
+ * @param array|string $paths The PSR-0 base directories
200
+ */
201
+ public function set($prefix, $paths)
202
+ {
203
+ if (!$prefix) {
204
+ $this->fallbackDirsPsr0 = (array) $paths;
205
+ } else {
206
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Registers a set of PSR-4 directories for a given namespace,
212
+ * replacing any others previously set for this namespace.
213
+ *
214
+ * @param string $prefix The prefix/namespace, with trailing '\\'
215
+ * @param array|string $paths The PSR-4 base directories
216
+ *
217
+ * @throws \InvalidArgumentException
218
+ */
219
+ public function setPsr4($prefix, $paths)
220
+ {
221
+ if (!$prefix) {
222
+ $this->fallbackDirsPsr4 = (array) $paths;
223
+ } else {
224
+ $length = strlen($prefix);
225
+ if ('\\' !== $prefix[$length - 1]) {
226
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
227
+ }
228
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
229
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Turns on searching the include path for class files.
235
+ *
236
+ * @param bool $useIncludePath
237
+ */
238
+ public function setUseIncludePath($useIncludePath)
239
+ {
240
+ $this->useIncludePath = $useIncludePath;
241
+ }
242
+
243
+ /**
244
+ * Can be used to check if the autoloader uses the include path to check
245
+ * for classes.
246
+ *
247
+ * @return bool
248
+ */
249
+ public function getUseIncludePath()
250
+ {
251
+ return $this->useIncludePath;
252
+ }
253
+
254
+ /**
255
+ * Turns off searching the prefix and fallback directories for classes
256
+ * that have not been registered with the class map.
257
+ *
258
+ * @param bool $classMapAuthoritative
259
+ */
260
+ public function setClassMapAuthoritative($classMapAuthoritative)
261
+ {
262
+ $this->classMapAuthoritative = $classMapAuthoritative;
263
+ }
264
+
265
+ /**
266
+ * Should class lookup fail if not found in the current class map?
267
+ *
268
+ * @return bool
269
+ */
270
+ public function isClassMapAuthoritative()
271
+ {
272
+ return $this->classMapAuthoritative;
273
+ }
274
+
275
+ /**
276
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
277
+ *
278
+ * @param string|null $apcuPrefix
279
+ */
280
+ public function setApcuPrefix($apcuPrefix)
281
+ {
282
+ $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
283
+ }
284
+
285
+ /**
286
+ * The APCu prefix in use, or null if APCu caching is not enabled.
287
+ *
288
+ * @return string|null
289
+ */
290
+ public function getApcuPrefix()
291
+ {
292
+ return $this->apcuPrefix;
293
+ }
294
+
295
+ /**
296
+ * Registers this instance as an autoloader.
297
+ *
298
+ * @param bool $prepend Whether to prepend the autoloader or not
299
+ */
300
+ public function register($prepend = false)
301
+ {
302
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
303
+ }
304
+
305
+ /**
306
+ * Unregisters this instance as an autoloader.
307
+ */
308
+ public function unregister()
309
+ {
310
+ spl_autoload_unregister(array($this, 'loadClass'));
311
+ }
312
+
313
+ /**
314
+ * Loads the given class or interface.
315
+ *
316
+ * @param string $class The name of the class
317
+ * @return bool|null True if loaded, null otherwise
318
+ */
319
+ public function loadClass($class)
320
+ {
321
+ if ($file = $this->findFile($class)) {
322
+ includeFile($file);
323
+
324
+ return true;
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Finds the path to the file where the class is defined.
330
+ *
331
+ * @param string $class The name of the class
332
+ *
333
+ * @return string|false The path if found, false otherwise
334
+ */
335
+ public function findFile($class)
336
+ {
337
+ // class map lookup
338
+ if (isset($this->classMap[$class])) {
339
+ return $this->classMap[$class];
340
+ }
341
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
342
+ return false;
343
+ }
344
+ if (null !== $this->apcuPrefix) {
345
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
346
+ if ($hit) {
347
+ return $file;
348
+ }
349
+ }
350
+
351
+ $file = $this->findFileWithExtension($class, '.php');
352
+
353
+ // Search for Hack files if we are running on HHVM
354
+ if (false === $file && defined('HHVM_VERSION')) {
355
+ $file = $this->findFileWithExtension($class, '.hh');
356
+ }
357
+
358
+ if (null !== $this->apcuPrefix) {
359
+ apcu_add($this->apcuPrefix.$class, $file);
360
+ }
361
+
362
+ if (false === $file) {
363
+ // Remember that this class does not exist.
364
+ $this->missingClasses[$class] = true;
365
+ }
366
+
367
+ return $file;
368
+ }
369
+
370
+ private function findFileWithExtension($class, $ext)
371
+ {
372
+ // PSR-4 lookup
373
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
374
+
375
+ $first = $class[0];
376
+ if (isset($this->prefixLengthsPsr4[$first])) {
377
+ $subPath = $class;
378
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
379
+ $subPath = substr($subPath, 0, $lastPos);
380
+ $search = $subPath.'\\';
381
+ if (isset($this->prefixDirsPsr4[$search])) {
382
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
383
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
384
+ if (file_exists($file = $dir . $pathEnd)) {
385
+ return $file;
386
+ }
387
+ }
388
+ }
389
+ }
390
+ }
391
+
392
+ // PSR-4 fallback dirs
393
+ foreach ($this->fallbackDirsPsr4 as $dir) {
394
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
395
+ return $file;
396
+ }
397
+ }
398
+
399
+ // PSR-0 lookup
400
+ if (false !== $pos = strrpos($class, '\\')) {
401
+ // namespaced class name
402
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
403
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
404
+ } else {
405
+ // PEAR-like class name
406
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
407
+ }
408
+
409
+ if (isset($this->prefixesPsr0[$first])) {
410
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
411
+ if (0 === strpos($class, $prefix)) {
412
+ foreach ($dirs as $dir) {
413
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
414
+ return $file;
415
+ }
416
+ }
417
+ }
418
+ }
419
+ }
420
+
421
+ // PSR-0 fallback dirs
422
+ foreach ($this->fallbackDirsPsr0 as $dir) {
423
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
424
+ return $file;
425
+ }
426
+ }
427
+
428
+ // PSR-0 include paths.
429
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
430
+ return $file;
431
+ }
432
+
433
+ return false;
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Scope isolated include.
439
+ *
440
+ * Prevents access to $this/self from included files.
441
+ */
442
+ function includeFile($file)
443
+ {
444
+ include $file;
445
+ }
vendor/composer/LICENSE ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2
+ Upstream-Name: Composer
3
+ Upstream-Contact: Jordi Boggiano <j.boggiano@seld.be>
4
+ Source: https://github.com/composer/composer
5
+
6
+ Files: *
7
+ Copyright: 2016, Nils Adermann <naderman@naderman.de>
8
+ 2016, Jordi Boggiano <j.boggiano@seld.be>
9
+ License: Expat
10
+
11
+ Files: src/Composer/Util/TlsHelper.php
12
+ Copyright: 2016, Nils Adermann <naderman@naderman.de>
13
+ 2016, Jordi Boggiano <j.boggiano@seld.be>
14
+ 2013, Evan Coury <me@evancoury.com>
15
+ License: Expat and BSD-2-Clause
16
+
17
+ License: BSD-2-Clause
18
+ Redistribution and use in source and binary forms, with or without modification,
19
+ are permitted provided that the following conditions are met:
20
+ .
21
+ * Redistributions of source code must retain the above copyright notice,
22
+ this list of conditions and the following disclaimer.
23
+ .
24
+ * Redistributions in binary form must reproduce the above copyright notice,
25
+ this list of conditions and the following disclaimer in the documentation
26
+ and/or other materials provided with the distribution.
27
+ .
28
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
29
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
30
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
32
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
33
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
34
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
35
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
37
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
+
39
+ License: Expat
40
+ Permission is hereby granted, free of charge, to any person obtaining a copy
41
+ of this software and associated documentation files (the "Software"), to deal
42
+ in the Software without restriction, including without limitation the rights
43
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
44
+ copies of the Software, and to permit persons to whom the Software is furnished
45
+ to do so, subject to the following conditions:
46
+ .
47
+ The above copyright notice and this permission notice shall be included in all
48
+ copies or substantial portions of the Software.
49
+ .
50
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
51
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
52
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
53
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
54
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
55
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
56
+ THE SOFTWARE.
vendor/composer/autoload_classmap.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_classmap.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ );
vendor/composer/autoload_namespaces.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_namespaces.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ );
vendor/composer/autoload_psr4.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_psr4.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ 'WPStaging\\Test\\' => array($baseDir . '/../tests/unit'),
10
+ 'WPStaging\\' => array($baseDir . '/'),
11
+ 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
12
+ );
vendor/composer/autoload_real.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_real.php @generated by Composer
4
+
5
+ class ComposerAutoloaderInitc2c8fce0bd7d21d1b2bc76515b028e91
6
+ {
7
+ private static $loader;
8
+
9
+ public static function loadClassLoader($class)
10
+ {
11
+ if ('Composer\Autoload\ClassLoader' === $class) {
12
+ require __DIR__ . '/ClassLoader.php';
13
+ }
14
+ }
15
+
16
+ public static function getLoader()
17
+ {
18
+ if (null !== self::$loader) {
19
+ return self::$loader;
20
+ }
21
+
22
+ spl_autoload_register(array('ComposerAutoloaderInitc2c8fce0bd7d21d1b2bc76515b028e91', 'loadClassLoader'), true, true);
23
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
+ spl_autoload_unregister(array('ComposerAutoloaderInitc2c8fce0bd7d21d1b2bc76515b028e91', 'loadClassLoader'));
25
+
26
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
27
+ if ($useStaticLoader) {
28
+ require_once __DIR__ . '/autoload_static.php';
29
+
30
+ call_user_func(\Composer\Autoload\ComposerStaticInitc2c8fce0bd7d21d1b2bc76515b028e91::getInitializer($loader));
31
+ } else {
32
+ $map = require __DIR__ . '/autoload_namespaces.php';
33
+ foreach ($map as $namespace => $path) {
34
+ $loader->set($namespace, $path);
35
+ }
36
+
37
+ $map = require __DIR__ . '/autoload_psr4.php';
38
+ foreach ($map as $namespace => $path) {
39
+ $loader->setPsr4($namespace, $path);
40
+ }
41
+
42
+ $classMap = require __DIR__ . '/autoload_classmap.php';
43
+ if ($classMap) {
44
+ $loader->addClassMap($classMap);
45
+ }
46
+ }
47
+
48
+ $loader->register(true);
49
+
50
+ return $loader;
51
+ }
52
+ }
vendor/composer/autoload_static.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_static.php @generated by Composer
4
+
5
+ namespace Composer\Autoload;
6
+
7
+ class ComposerStaticInitc2c8fce0bd7d21d1b2bc76515b028e91
8
+ {
9
+ public static $prefixLengthsPsr4 = array (
10
+ 'W' =>
11
+ array (
12
+ 'WPStaging\\' => 10,
13
+ ),
14
+ 'P' =>
15
+ array (
16
+ 'Psr\\Log\\' => 8,
17
+ ),
18
+ );
19
+
20
+ public static $prefixDirsPsr4 = array (
21
+ 'WPStaging\\Test\\' =>
22
+ array (
23
+ 0 => __DIR__ . '/../..' . '/../tests/unit',
24
+ ),
25
+ 'WPStaging\\' =>
26
+ array (
27
+ 0 => __DIR__ . '/../..' . '/',
28
+ ),
29
+ 'Psr\\Log\\' =>
30
+ array (
31
+ 0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
32
+ ),
33
+ );
34
+
35
+ public static function getInitializer(ClassLoader $loader)
36
+ {
37
+ return \Closure::bind(function () use ($loader) {
38
+ $loader->prefixLengthsPsr4 = ComposerStaticInitc2c8fce0bd7d21d1b2bc76515b028e91::$prefixLengthsPsr4;
39
+ $loader->prefixDirsPsr4 = ComposerStaticInitc2c8fce0bd7d21d1b2bc76515b028e91::$prefixDirsPsr4;
40
+
41
+ }, null, ClassLoader::class);
42
+ }
43
+ }
vendor/composer/installed.json ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "name": "psr/log",
4
+ "version": "1.1.2",
5
+ "version_normalized": "1.1.2.0",
6
+ "source": {
7
+ "type": "git",
8
+ "url": "https://github.com/php-fig/log.git",
9
+ "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801"
10
+ },
11
+ "dist": {
12
+ "type": "zip",
13
+ "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801",
14
+ "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801",
15
+ "shasum": ""
16
+ },
17
+ "require": {
18
+ "php": ">=5.3.0"
19
+ },
20
+ "time": "2019-11-01T11:05:21+00:00",
21
+ "type": "library",
22
+ "extra": {
23
+ "branch-alias": {
24
+ "dev-master": "1.1.x-dev"
25
+ }
26
+ },
27
+ "installation-source": "dist",
28
+ "autoload": {
29
+ "psr-4": {
30
+ "Psr\\Log\\": "Psr/Log/"
31
+ }
32
+ },
33
+ "notification-url": "https://packagist.org/downloads/",
34
+ "license": [
35
+ "MIT"
36
+ ],
37
+ "authors": [
38
+ {
39
+ "name": "PHP-FIG",
40
+ "homepage": "http://www.php-fig.org/"
41
+ }
42
+ ],
43
+ "description": "Common interface for logging libraries",
44
+ "homepage": "https://github.com/php-fig/log",
45
+ "keywords": [
46
+ "log",
47
+ "psr",
48
+ "psr-3"
49
+ ]
50
+ }
51
+ ]
vendor/psr/log/LICENSE ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2012 PHP Framework Interoperability Group
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
vendor/psr/log/Psr/Log/AbstractLogger.php ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Log;
4
+
5
+ /**
6
+ * This is a simple Logger implementation that other Loggers can inherit from.
7
+ *
8
+ * It simply delegates all log-level-specific methods to the `log` method to
9
+ * reduce boilerplate code that a simple Logger that does the same thing with
10
+ * messages regardless of the error level has to implement.
11
+ */
12
+ abstract class AbstractLogger implements LoggerInterface
13
+ {
14
+ /**
15
+ * System is unusable.
16
+ *
17
+ * @param string $message
18
+ * @param array $context
19
+ *
20
+ * @return void
21
+ */
22
+ public function emergency($message, array $context = array())
23
+ {
24
+ $this->log(LogLevel::EMERGENCY, $message, $context);
25
+ }
26
+
27
+ /**
28
+ * Action must be taken immediately.
29
+ *
30
+ * Example: Entire website down, database unavailable, etc. This should
31
+ * trigger the SMS alerts and wake you up.
32
+ *
33
+ * @param string $message
34
+ * @param array $context
35
+ *
36
+ * @return void
37
+ */
38
+ public function alert($message, array $context = array())
39
+ {
40
+ $this->log(LogLevel::ALERT, $message, $context);
41
+ }
42
+
43
+ /**
44
+ * Critical conditions.
45
+ *
46
+ * Example: Application component unavailable, unexpected exception.
47
+ *
48
+ * @param string $message
49
+ * @param array $context
50
+ *
51
+ * @return void
52
+ */
53
+ public function critical($message, array $context = array())
54
+ {
55
+ $this->log(LogLevel::CRITICAL, $message, $context);
56
+ }
57
+
58
+ /**
59
+ * Runtime errors that do not require immediate action but should typically
60
+ * be logged and monitored.
61
+ *
62
+ * @param string $message
63
+ * @param array $context
64
+ *
65
+ * @return void
66
+ */
67
+ public function error($message, array $context = array())
68
+ {
69
+ $this->log(LogLevel::ERROR, $message, $context);
70
+ }
71
+
72
+ /**
73
+ * Exceptional occurrences that are not errors.
74
+ *
75
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
76
+ * that are not necessarily wrong.
77
+ *
78
+ * @param string $message
79
+ * @param array $context
80
+ *
81
+ * @return void
82
+ */
83
+ public function warning($message, array $context = array())
84
+ {
85
+ $this->log(LogLevel::WARNING, $message, $context);
86
+ }
87
+
88
+ /**
89
+ * Normal but significant events.
90
+ *
91
+ * @param string $message
92
+ * @param array $context
93
+ *
94
+ * @return void
95
+ */
96
+ public function notice($message, array $context = array())
97
+ {
98
+ $this->log(LogLevel::NOTICE, $message, $context);
99
+ }
100
+
101
+ /**
102
+ * Interesting events.
103
+ *
104
+ * Example: User logs in, SQL logs.
105
+ *
106
+ * @param string $message
107
+ * @param array $context
108
+ *
109
+ * @return void
110
+ */
111
+ public function info($message, array $context = array())
112
+ {
113
+ $this->log(LogLevel::INFO, $message, $context);
114
+ }
115
+
116
+ /**
117
+ * Detailed debug information.
118
+ *
119
+ * @param string $message
120
+ * @param array $context
121
+ *
122
+ * @return void
123
+ */
124
+ public function debug($message, array $context = array())
125
+ {
126
+ $this->log(LogLevel::DEBUG, $message, $context);
127
+ }
128
+ }
vendor/psr/log/Psr/Log/InvalidArgumentException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Log;
4
+
5
+ class InvalidArgumentException extends \InvalidArgumentException
6
+ {
7
+ }
vendor/psr/log/Psr/Log/LogLevel.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Log;
4
+
5
+ /**
6
+ * Describes log levels.
7
+ */
8
+ class LogLevel
9
+ {
10
+ const EMERGENCY = 'emergency';
11
+ const ALERT = 'alert';
12
+ const CRITICAL = 'critical';
13
+ const ERROR = 'error';
14
+ const WARNING = 'warning';
15
+ const NOTICE = 'notice';
16
+ const INFO = 'info';
17
+ const DEBUG = 'debug';
18
+ }
vendor/psr/log/Psr/Log/LoggerAwareInterface.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Log;
4
+
5
+ /**
6
+ * Describes a logger-aware instance.
7
+ */
8
+ interface LoggerAwareInterface
9
+ {
10
+ /**
11
+ * Sets a logger instance on the object.
12
+ *
13
+ * @param LoggerInterface $logger
14
+ *
15
+ * @return void
16
+ */
17
+ public function setLogger(LoggerInterface $logger);
18
+ }
vendor/psr/log/Psr/Log/LoggerAwareTrait.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Log;
4
+
5
+ /**
6
+ * Basic Implementation of LoggerAwareInterface.
7
+ */
8
+ trait LoggerAwareTrait
9
+ {
10
+ /**
11
+ * The logger instance.
12
+ *
13
+ * @var LoggerInterface
14
+ */
15
+ protected $logger;
16
+
17
+ /**
18
+ * Sets a logger.
19
+ *
20
+ * @param LoggerInterface $logger
21
+ */
22
+ public function setLogger(LoggerInterface $logger)
23
+ {
24
+ $this->logger = $logger;
25
+ }
26
+ }
vendor/psr/log/Psr/Log/LoggerInterface.php ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Log;
4
+
5
+ /**
6
+ * Describes a logger instance.
7
+ *
8
+ * The message MUST be a string or object implementing __toString().
9
+ *
10
+ * The message MAY contain placeholders in the form: {foo} where foo
11
+ * will be replaced by the context data in key "foo".
12
+ *
13
+ * The context array can contain arbitrary data. The only assumption that
14
+ * can be made by implementors is that if an Exception instance is given
15
+ * to produce a stack trace, it MUST be in a key named "exception".
16
+ *
17
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
18
+ * for the full interface specification.
19
+ */
20
+ interface LoggerInterface
21
+ {
22
+ /**
23
+ * System is unusable.
24
+ *
25
+ * @param string $message
26
+ * @param array $context
27
+ *
28
+ * @return void
29
+ */
30
+ public function emergency($message, array $context = array());
31
+
32
+ /**
33
+ * Action must be taken immediately.
34
+ *
35
+ * Example: Entire website down, database unavailable, etc. This should
36
+ * trigger the SMS alerts and wake you up.
37
+ *
38
+ * @param string $message
39
+ * @param array $context
40
+ *
41
+ * @return void
42
+ */
43
+ public function alert($message, array $context = array());
44
+
45
+ /**
46
+ * Critical conditions.
47
+ *
48
+ * Example: Application component unavailable, unexpected exception.
49
+ *
50
+ * @param string $message
51
+ * @param array $context
52
+ *
53
+ * @return void
54
+ */
55
+ public function critical($message, array $context = array());
56
+
57
+ /**
58
+ * Runtime errors that do not require immediate action but should typically
59
+ * be logged and monitored.
60
+ *
61
+ * @param string $message
62
+ * @param array $context
63
+ *
64
+ * @return void
65
+ */
66
+ public function error($message, array $context = array());
67
+
68
+ /**
69
+ * Exceptional occurrences that are not errors.
70
+ *
71
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
72
+ * that are not necessarily wrong.
73
+ *
74
+ * @param string $message
75
+ * @param array $context
76
+ *
77
+ * @return void
78
+ */
79
+ public function warning($message, array $context = array());
80
+
81
+ /**
82
+ * Normal but significant events.
83
+ *
84
+ * @param string $message
85
+ * @param array $context
86
+ *
87
+ * @return void
88
+ */
89
+ public function notice($message, array $context = array());
90
+
91
+ /**
92
+ * Interesting events.
93
+ *
94
+ * Example: User logs in, SQL logs.
95
+ *
96
+ * @param string $message
97
+ * @param array $context
98
+ *
99
+ * @return void
100
+ */
101
+ public function info($message, array $context = array());
102
+
103
+ /**
104
+ * Detailed debug information.
105
+ *
106
+ * @param string $message
107
+ * @param array $context
108
+ *
109
+ * @return void
110
+ */
111
+ public function debug($message, array $context = array());
112
+
113
+ /**
114
+ * Logs with an arbitrary level.
115
+ *
116
+ * @param mixed $level
117
+ * @param string $message
118
+ * @param array $context
119
+ *
120
+ * @return void
121
+ *
122
+ * @throws \Psr\Log\InvalidArgumentException
123
+ */
124
+ public function log($level, $message, array $context = array());
125
+ }
vendor/psr/log/Psr/Log/LoggerTrait.php ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Log;
4
+
5
+ /**
6
+ * This is a simple Logger trait that classes unable to extend AbstractLogger
7
+ * (because they extend another class, etc) can include.
8
+ *
9
+ * It simply delegates all log-level-specific methods to the `log` method to
10
+ * reduce boilerplate code that a simple Logger that does the same thing with
11
+ * messages regardless of the error level has to implement.
12
+ */
13
+ trait LoggerTrait
14
+ {
15
+ /**
16
+ * System is unusable.
17
+ *
18
+ * @param string $message
19
+ * @param array $context
20
+ *
21
+ * @return void
22
+ */
23
+ public function emergency($message, array $context = array())
24
+ {
25
+ $this->log(LogLevel::EMERGENCY, $message, $context);
26
+ }
27
+
28
+ /**
29
+ * Action must be taken immediately.
30
+ *
31
+ * Example: Entire website down, database unavailable, etc. This should
32
+ * trigger the SMS alerts and wake you up.
33
+ *
34
+ * @param string $message
35
+ * @param array $context
36
+ *
37
+ * @return void
38
+ */
39
+ public function alert($message, array $context = array())
40
+ {
41
+ $this->log(LogLevel::ALERT, $message, $context);
42
+ }
43
+
44
+ /**
45
+ * Critical conditions.
46
+ *
47
+ * Example: Application component unavailable, unexpected exception.
48
+ *
49
+ * @param string $message
50
+ * @param array $context
51
+ *
52
+ * @return void
53
+ */
54
+ public function critical($message, array $context = array())
55
+ {
56
+ $this->log(LogLevel::CRITICAL, $message, $context);
57
+ }
58
+
59
+ /**
60
+ * Runtime errors that do not require immediate action but should typically
61
+ * be logged and monitored.
62
+ *
63
+ * @param string $message
64
+ * @param array $context
65
+ *
66
+ * @return void
67
+ */
68
+ public function error($message, array $context = array())
69
+ {
70
+ $this->log(LogLevel::ERROR, $message, $context);
71
+ }
72
+
73
+ /**
74
+ * Exceptional occurrences that are not errors.
75
+ *
76
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
77
+ * that are not necessarily wrong.
78
+ *
79
+ * @param string $message
80
+ * @param array $context
81
+ *
82
+ * @return void
83
+ */
84
+ public function warning($message, array $context = array())
85
+ {
86
+ $this->log(LogLevel::WARNING, $message, $context);
87
+ }
88
+
89
+ /**
90
+ * Normal but significant events.
91
+ *
92
+ * @param string $message
93
+ * @param array $context
94
+ *
95
+ * @return void
96
+ */
97
+ public function notice($message, array $context = array())
98
+ {
99
+ $this->log(LogLevel::NOTICE, $message, $context);
100
+ }
101
+
102
+ /**
103
+ * Interesting events.
104
+ *
105
+ * Example: User logs in, SQL logs.
106
+ *
107
+ * @param string $message
108
+ * @param array $context
109
+ *
110
+ * @return void
111
+ */
112
+ public function info($message, array $context = array())
113
+ {
114
+ $this->log(LogLevel::INFO, $message, $context);
115
+ }
116
+
117
+ /**
118
+ * Detailed debug information.
119
+ *
120
+ * @param string $message
121
+ * @param array $context
122
+ *
123
+ * @return void
124
+ */
125
+ public function debug($message, array $context = array())
126
+ {
127
+ $this->log(LogLevel::DEBUG, $message, $context);
128
+ }
129
+
130
+ /**
131
+ * Logs with an arbitrary level.
132
+ *
133
+ * @param mixed $level
134
+ * @param string $message
135
+ * @param array $context
136
+ *
137
+ * @return void
138
+ *
139
+ * @throws \Psr\Log\InvalidArgumentException
140
+ */
141
+ abstract public function log($level, $message, array $context = array());
142
+ }
vendor/psr/log/Psr/Log/NullLogger.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Log;
4
+
5
+ /**
6
+ * This Logger can be used to avoid conditional log calls.
7
+ *
8
+ * Logging should always be optional, and if no logger is provided to your
9
+ * library creating a NullLogger instance to have something to throw logs at
10
+ * is a good way to avoid littering your code with `if ($this->logger) { }`
11
+ * blocks.
12
+ */
13
+ class NullLogger extends AbstractLogger
14
+ {
15
+ /**
16
+ * Logs with an arbitrary level.
17
+ *
18
+ * @param mixed $level
19
+ * @param string $message
20
+ * @param array $context
21
+ *
22
+ * @return void
23
+ *
24
+ * @throws \Psr\Log\InvalidArgumentException
25
+ */
26
+ public function log($level, $message, array $context = array())
27
+ {
28
+ // noop
29
+ }
30
+ }
vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Log\Test;
4
+
5
+ use Psr\Log\LoggerInterface;
6
+ use Psr\Log\LogLevel;
7
+ use PHPUnit\Framework\TestCase;
8
+
9
+ /**
10
+ * Provides a base test class for ensuring compliance with the LoggerInterface.
11
+ *
12
+ * Implementors can extend the class and implement abstract methods to run this
13
+ * as part of their test suite.
14
+ */
15
+ abstract class LoggerInterfaceTest extends TestCase
16
+ {
17
+ /**
18
+ * @return LoggerInterface
19
+ */
20
+ abstract public function getLogger();
21
+
22
+ /**
23
+ * This must return the log messages in order.
24
+ *
25
+ * The simple formatting of the messages is: "<LOG LEVEL> <MESSAGE>".
26
+ *
27
+ * Example ->error('Foo') would yield "error Foo".
28
+ *
29
+ * @return string[]
30
+ */
31
+ abstract public function getLogs();
32
+
33
+ public function testImplements()
34
+ {
35
+ $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
36
+ }
37
+
38
+ /**
39
+ * @dataProvider provideLevelsAndMessages
40
+ */
41
+ public function testLogsAtAllLevels($level, $message)
42
+ {
43
+ $logger = $this->getLogger();
44
+ $logger->{$level}($message, array('user' => 'Bob'));
45
+ $logger->log($level, $message, array('user' => 'Bob'));
46
+
47
+ $expected = array(
48
+ $level.' message of level '.$level.' with context: Bob',
49
+ $level.' message of level '.$level.' with context: Bob',
50
+ );
51
+ $this->assertEquals($expected, $this->getLogs());
52
+ }
53
+
54
+ public function provideLevelsAndMessages()
55
+ {
56
+ return array(
57
+ LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
58
+ LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
59
+ LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
60
+ LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
61
+ LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
62
+ LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
63
+ LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
64
+ LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
65
+ );
66
+ }
67
+
68
+ /**
69
+ * @expectedException \Psr\Log\InvalidArgumentException
70
+ */
71
+ public function testThrowsOnInvalidLevel()
72
+ {
73
+ $logger = $this->getLogger();
74
+ $logger->log('invalid level', 'Foo');
75
+ }
76
+
77
+ public function testContextReplacement()
78
+ {
79
+ $logger = $this->getLogger();
80
+ $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
81
+
82
+ $expected = array('info {Message {nothing} Bob Bar a}');
83
+ $this->assertEquals($expected, $this->getLogs());
84
+ }
85
+
86
+ public function testObjectCastToString()
87
+ {
88
+ if (method_exists($this, 'createPartialMock')) {
89
+ $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
90
+ } else {
91
+ $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
92
+ }
93
+ $dummy->expects($this->once())
94
+ ->method('__toString')
95
+ ->will($this->returnValue('DUMMY'));
96
+
97
+ $this->getLogger()->warning($dummy);
98
+
99
+ $expected = array('warning DUMMY');
100
+ $this->assertEquals($expected, $this->getLogs());
101
+ }
102
+
103
+ public function testContextCanContainAnything()
104
+ {
105
+ $closed = fopen('php://memory', 'r');
106
+ fclose($closed);
107
+
108
+ $context = array(
109
+ 'bool' => true,
110
+ 'null' => null,
111
+ 'string' => 'Foo',
112
+ 'int' => 0,
113
+ 'float' => 0.5,
114
+ 'nested' => array('with object' => new DummyTest),
115
+ 'object' => new \DateTime,
116
+ 'resource' => fopen('php://memory', 'r'),
117
+ 'closed' => $closed,
118
+ );
119
+
120
+ $this->getLogger()->warning('Crazy context data', $context);
121
+
122
+ $expected = array('warning Crazy context data');
123
+ $this->assertEquals($expected, $this->getLogs());
124
+ }
125
+
126
+ public function testContextExceptionKeyCanBeExceptionOrOtherValues()
127
+ {
128
+ $logger = $this->getLogger();
129
+ $logger->warning('Random message', array('exception' => 'oops'));
130
+ $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
131
+
132
+ $expected = array(
133
+ 'warning Random message',
134
+ 'critical Uncaught Exception!'
135
+ );
136
+ $this->assertEquals($expected, $this->getLogs());
137
+ }
138
+ }
139
+
140
+ class DummyTest
141
+ {
142
+ public function __toString()
143
+ {
144
+ return 'DummyTest';
145
+ }
146
+ }
vendor/psr/log/Psr/Log/Test/TestLogger.php ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Log\Test;
4
+
5
+ use Psr\Log\AbstractLogger;
6
+
7
+ /**
8
+ * Used for testing purposes.
9
+ *
10
+ * It records all records and gives you access to them for verification.
11
+ *
12
+ * @method bool hasEmergency($record)
13
+ * @method bool hasAlert($record)
14
+ * @method bool hasCritical($record)
15
+ * @method bool hasError($record)
16
+ * @method bool hasWarning($record)
17
+ * @method bool hasNotice($record)
18
+ * @method bool hasInfo($record)
19
+ * @method bool hasDebug($record)
20
+ *
21
+ * @method bool hasEmergencyRecords()
22
+ * @method bool hasAlertRecords()
23
+ * @method bool hasCriticalRecords()
24
+ * @method bool hasErrorRecords()
25
+ * @method bool hasWarningRecords()
26
+ * @method bool hasNoticeRecords()
27
+ * @method bool hasInfoRecords()
28
+ * @method bool hasDebugRecords()
29
+ *
30
+ * @method bool hasEmergencyThatContains($message)
31
+ * @method bool hasAlertThatContains($message)
32
+ * @method bool hasCriticalThatContains($message)
33
+ * @method bool hasErrorThatContains($message)
34
+ * @method bool hasWarningThatContains($message)
35
+ * @method bool hasNoticeThatContains($message)
36
+ * @method bool hasInfoThatContains($message)
37
+ * @method bool hasDebugThatContains($message)
38
+ *
39
+ * @method bool hasEmergencyThatMatches($message)
40
+ * @method bool hasAlertThatMatches($message)
41
+ * @method bool hasCriticalThatMatches($message)
42
+ * @method bool hasErrorThatMatches($message)
43
+ * @method bool hasWarningThatMatches($message)
44
+ * @method bool hasNoticeThatMatches($message)
45
+ * @method bool hasInfoThatMatches($message)
46
+ * @method bool hasDebugThatMatches($message)
47
+ *
48
+ * @method bool hasEmergencyThatPasses($message)
49
+ * @method bool hasAlertThatPasses($message)
50
+ * @method bool hasCriticalThatPasses($message)
51
+ * @method bool hasErrorThatPasses($message)
52
+ * @method bool hasWarningThatPasses($message)
53
+ * @method bool hasNoticeThatPasses($message)
54
+ * @method bool hasInfoThatPasses($message)
55
+ * @method bool hasDebugThatPasses($message)
56
+ */
57
+ class TestLogger extends AbstractLogger
58
+ {
59
+ /**
60
+ * @var array
61
+ */
62
+ public $records = [];
63
+
64
+ public $recordsByLevel = [];
65
+
66
+ /**
67
+ * @inheritdoc
68
+ */
69
+ public function log($level, $message, array $context = [])
70
+ {
71
+ $record = [
72
+ 'level' => $level,
73
+ 'message' => $message,
74
+ 'context' => $context,
75
+ ];
76
+
77
+ $this->recordsByLevel[$record['level']][] = $record;
78
+ $this->records[] = $record;
79
+ }
80
+
81
+ public function hasRecords($level)
82
+ {
83
+ return isset($this->recordsByLevel[$level]);
84
+ }
85
+
86
+ public function hasRecord($record, $level)
87
+ {
88
+ if (is_string($record)) {
89
+ $record = ['message' => $record];
90
+ }
91
+ return $this->hasRecordThatPasses(function ($rec) use ($record) {
92
+ if ($rec['message'] !== $record['message']) {
93
+ return false;
94
+ }
95
+ if (isset($record['context']) && $rec['context'] !== $record['context']) {
96
+ return false;
97
+ }
98
+ return true;
99
+ }, $level);
100
+ }
101
+
102
+ public function hasRecordThatContains($message, $level)
103
+ {
104
+ return $this->hasRecordThatPasses(function ($rec) use ($message) {
105
+ return strpos($rec['message'], $message) !== false;
106
+ }, $level);
107
+ }
108
+
109
+ public function hasRecordThatMatches($regex, $level)
110
+ {
111
+ return $this->hasRecordThatPasses(function ($rec) use ($regex) {
112
+ return preg_match($regex, $rec['message']) > 0;
113
+ }, $level);
114
+ }
115
+
116
+ public function hasRecordThatPasses(callable $predicate, $level)
117
+ {
118
+ if (!isset($this->recordsByLevel[$level])) {
119
+ return false;
120
+ }
121
+ foreach ($this->recordsByLevel[$level] as $i => $rec) {
122
+ if (call_user_func($predicate, $rec, $i)) {
123
+ return true;
124
+ }
125
+ }
126
+ return false;
127
+ }
128
+
129
+ public function __call($method, $args)
130
+ {
131
+ if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
132
+ $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
133
+ $level = strtolower($matches[2]);
134
+ if (method_exists($this, $genericMethod)) {
135
+ $args[] = $level;
136
+ return call_user_func_array([$this, $genericMethod], $args);
137
+ }
138
+ }
139
+ throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
140
+ }
141
+
142
+ public function reset()
143
+ {
144
+ $this->records = [];
145
+ $this->recordsByLevel = [];
146
+ }
147
+ }
vendor/psr/log/README.md ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ PSR Log
2
+ =======
3
+
4
+ This repository holds all interfaces/classes/traits related to
5
+ [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
6
+
7
+ Note that this is not a logger of its own. It is merely an interface that
8
+ describes a logger. See the specification for more details.
9
+
10
+ Installation
11
+ ------------
12
+
13
+ ```bash
14
+ composer require psr/log
15
+ ```
16
+
17
+ Usage
18
+ -----
19
+
20
+ If you need a logger, you can use the interface like this:
21
+
22
+ ```php
23
+ <?php
24
+
25
+ use Psr\Log\LoggerInterface;
26
+
27
+ class Foo
28
+ {
29
+ private $logger;
30
+
31
+ public function __construct(LoggerInterface $logger = null)
32
+ {
33
+ $this->logger = $logger;
34
+ }
35
+
36
+ public function doSomething()
37
+ {
38
+ if ($this->logger) {
39
+ $this->logger->info('Doing work');
40
+ }
41
+
42
+ try {
43
+ $this->doSomethingElse();
44
+ } catch (Exception $exception) {
45
+ $this->logger->error('Oh no!', array('exception' => $exception));
46
+ }
47
+
48
+ // do something useful
49
+ }
50
+ }
51
+ ```
52
+
53
+ You can then pick one of the implementations of the interface to get a logger.
54
+
55
+ If you want to implement the interface, you can require this package and
56
+ implement `Psr\Log\LoggerInterface` in your code. Please read the
57
+ [specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
58
+ for details.
vendor/psr/log/composer.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "psr/log",
3
+ "description": "Common interface for logging libraries",
4
+ "keywords": ["psr", "psr-3", "log"],
5
+ "homepage": "https://github.com/php-fig/log",
6
+ "license": "MIT",
7
+ "authors": [
8
+ {
9
+ "name": "PHP-FIG",
10
+ "homepage": "http://www.php-fig.org/"
11
+ }
12
+ ],
13
+ "require": {
14
+ "php": ">=5.3.0"
15
+ },
16
+ "autoload": {
17
+ "psr-4": {
18
+ "Psr\\Log\\": "Psr/Log/"
19
+ }
20
+ },
21
+ "extra": {
22
+ "branch-alias": {
23
+ "dev-master": "1.1.x-dev"
24
+ }
25
+ }
26
+ }
wp-staging.php CHANGED
@@ -7,7 +7,7 @@
7
  * Author: WP-Staging
8
  * Author URI: https://wp-staging.com
9
  * Contributors: ReneHermi, ilgityildirim
10
- * Version: 2.6.8
11
  * Text Domain: wp-staging
12
  * Domain Path: /languages/
13
  *
@@ -39,7 +39,7 @@ if (!defined('WPSTG_PLUGIN_SLUG')) {
39
 
40
  // Plugin Version
41
  if (!defined('WPSTG_VERSION')) {
42
- define('WPSTG_VERSION', '2.6.8');
43
  }
44
 
45
  // Compatible up to WordPress Version
@@ -47,7 +47,7 @@ if (!defined('WPSTG_COMPATIBLE')) {
47
  define('WPSTG_COMPATIBLE', '5.3.2');
48
  }
49
 
50
- // Folder Path
51
  if (!defined('WPSTG_PLUGIN_DIR')) {
52
  define('WPSTG_PLUGIN_DIR', plugin_dir_path(__FILE__));
53
  }
@@ -118,6 +118,9 @@ $plugin_requirements = new Wpstg_Requirements_Check(array(
118
 
119
  if ($plugin_requirements->passes()) {
120
 
 
 
 
121
  $wpStaging = \WPStaging\WPStaging::getInstance();
122
 
123
  /**
7
  * Author: WP-Staging
8
  * Author URI: https://wp-staging.com
9
  * Contributors: ReneHermi, ilgityildirim
10
+ * Version: 2.6.9
11
  * Text Domain: wp-staging
12
  * Domain Path: /languages/
13
  *
39
 
40
  // Plugin Version
41
  if (!defined('WPSTG_VERSION')) {
42
+ define('WPSTG_VERSION', '2.6.9');
43
  }
44
 
45
  // Compatible up to WordPress Version
47
  define('WPSTG_COMPATIBLE', '5.3.2');
48
  }
49
 
50
+ // Absolute path to plugin
51
  if (!defined('WPSTG_PLUGIN_DIR')) {
52
  define('WPSTG_PLUGIN_DIR', plugin_dir_path(__FILE__));
53
  }
118
 
119
  if ($plugin_requirements->passes()) {
120
 
121
+ // TODO; remove previous auto-loader, use composer based instead!
122
+ require_once __DIR__ .'/vendor/autoload.php';
123
+
124
  $wpStaging = \WPStaging\WPStaging::getInstance();
125
 
126
  /**