GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#951)
by Tom
04:19
created

Backup::is_safe_mode_active()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 11
rs 9.4286
cc 3
eloc 5
nc 2
nop 1
1
<?php
2
3
namespace HM\BackUpWordPress;
4
use Symfony\Component\Finder\Finder;
5
use Ifsnop\Mysqldump as IMysqldump;
6
7
/**
8
 * Generic file and database backup class
9
 *
10
 * @version 2.3
11
 */
12
class Backup {
13
14
	/**
15
	 * The backup type, must be either complete, file or database
16
	 *
17
	 * @string
18
	 */
19
	private $type = '';
20
21
	/**
22
	 * The filename of the backup file
23
	 *
24
	 * @string
25
	 */
26
	private $archive_filename = '';
27
28
	/**
29
	 * The filename of the database dump
30
	 *
31
	 * @string
32
	 */
33
	private $database_dump_filename = '';
34
35
	/**
36
	 * The path to the zip command
37
	 *
38
	 * @string
39
	 */
40
	private $zip_command_path;
41
42
	/**
43
	 * The path to the mysqldump command
44
	 *
45
	 * @string
46
	 */
47
	private $mysqldump_command_path;
48
49
	/**
50
	 * The filename of the existing backup file
51
	 *
52
	 * @string
53
	 */
54
	private $existing_archive_filepath = '';
55
56
	/**
57
	 * An array of exclude rules
58
	 *
59
	 * @array
60
	 */
61
	private $excludes = array();
62
63
	/**
64
	 * The path that should be backed up
65
	 *
66
	 * @var string
67
	 */
68
	private $root = '';
69
70
	/**
71
	 * An array of all the files in root
72
	 * excluding excludes and unreadable files
73
	 *
74
	 * @var array
75
	 */
76
	private $files = array();
77
78
	/**
79
	 * An array of all the files in root
80
	 * that are unreadable
81
	 *
82
	 * @var array
83
	 */
84
	private $unreadable_files = array();
85
86
	/**
87
	 * An array of all the files in root
88
	 * that will be included in the backup
89
	 *
90
	 * @var array
91
	 */
92
	protected $included_files = array();
93
94
	/**
95
	 * An array of all the files in root
96
	 * that match the exclude rules
97
	 *
98
	 * @var array
99
	 */
100
	private $excluded_files = array();
101
102
	/**
103
	 * Contains an array of errors
104
	 *
105
	 * @var mixed
106
	 */
107
	private $errors = array();
108
109
	/**
110
	 * Contains an array of warnings
111
	 *
112
	 * @var mixed
113
	 */
114
	private $warnings = array();
115
116
	/**
117
	 * The archive method used
118
	 *
119
	 * @var string
120
	 */
121
	private $archive_method = '';
122
123
	/**
124
	 * The mysqldump method used
125
	 *
126
	 * @var string
127
	 */
128
	private $mysqldump_method = '';
129
130
	/**
131
	 * @var bool
132
	 */
133
	protected $mysqldump_verified = false;
134
135
	/**
136
	 * @var bool
137
	 */
138
	protected $archive_verified = false;
139
140
	/**
141
	 * @var string
142
	 */
143
	protected $action_callback = '';
144
145
	/**
146
	 * List of patterns we want to exclude by default.
147
	 * @var array
148
	 */
149
	protected $default_excludes = array(
150
		'.git/',
151
		'.svn/',
152
		'.DS_Store',
153
		'.idea/',
154
		'backwpup-*',
155
		'updraft',
156
		'wp-snapshots',
157
		'backupbuddy_backups',
158
		'pb_backupbuddy',
159
		'backup-db',
160
		'Envato-backups',
161
		'managewp',
162
		'backupwordpress-*-backups',
163
	);
164
165
	/**
166
	 * Returns a filterable array of excluded directories and files.
167
	 *
168
	 * @return mixed|void
169
	 */
170
	public function default_excludes() {
171
		return apply_filters( 'hmbkp_default_excludes', $this->default_excludes );
172
	}
173
174
	/**
175
	 * Check whether safe mode is active or not
176
	 *
177
	 * @param string $ini_get_callback
178
	 *
179
	 * @return bool
180
	 */
181
	public static function is_safe_mode_active( $ini_get_callback = 'ini_get' ) {
182
183
		$safe_mode = @call_user_func( $ini_get_callback, 'safe_mode' );
184
185
		if ( $safe_mode && strtolower( $safe_mode ) != 'off' ) {
1 ignored issue
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $safe_mode && str...r($safe_mode) != 'off';.
Loading history...
186
			return true;
187
		}
188
189
		return false;
190
191
	}
192
193
	/**
194
	 * Check whether shell_exec has been disabled.
195
	 *
196
	 * @return bool
197
	 */
198
	public static function is_shell_exec_available() {
199
200
		// Are we in Safe Mode
201
		if ( self::is_safe_mode_active() ) {
202
			return false;
203
		}
204
205
		// Is shell_exec or escapeshellcmd or escapeshellarg disabled?
206
		if ( self::is_function_disabled( 'suhosin.executor.func.blacklist' ) ) {
207
			return false;
208
		}
209
210
		// Functions can also be disabled via suhosin
211
		if ( self::is_function_disabled( 'disable_functions' ) ) {
212
			return false;
213
		}
214
215
		// Can we issue a simple echo command?
216
		if ( ! @shell_exec( 'echo backupwordpress' ) ) {
1 ignored issue
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return (bool) @shell_exe...echo backupwordpress');.
Loading history...
217
			return false;
218
		}
219
220
		return true;
221
222
	}
223
224
	protected static function is_function_disabled( $ini_setting ) {
225
226
		if ( array_intersect( array(
227
			'shell_exec',
228
			'escapeshellarg',
229
			'escapeshellcmd'
230
		), array_map( 'trim', explode( ',', @ini_get( $ini_setting ) ) ) ) ) {
231
			return false;
232
		}
233
234
	}
235
236
237
	/**
238
	 * Attempt to work out the root directory of the site, that
239
	 * is, the path equivelant of home_url().
240
	 *
241
	 * @return string $home_path
242
	 */
243
	public static function get_home_path() {
244
245
		if ( defined( 'HMBKP_ROOT' ) && HMBKP_ROOT ) {
246
			return wp_normalize_path( HMBKP_ROOT );
247
		}
248
249
		$home_url = home_url();
250
		$site_url = site_url();
251
252
		$home_path = ABSPATH;
253
254
		// If site_url contains home_url and they differ then assume WordPress is installed in a sub directory
255
		if ( $home_url !== $site_url && strpos( $site_url, $home_url ) === 0 ) {
256
			$home_path = trailingslashit( substr( wp_normalize_path( ABSPATH ), 0, strrpos( wp_normalize_path( ABSPATH ), str_replace( $home_url, '', $site_url ) ) ) );
257
		}
258
259
		return wp_normalize_path( $home_path );
260
261
	}
262
263
	/**
264
	 * Sets up the default properties
265
	 */
266
	public function __construct() {
267
268
		// Raise the memory limit and max_execution time
269
		@ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
270
		@set_time_limit( 0 );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
271
272
		// Set a custom error handler so we can track errors
273
		set_error_handler( array( $this, 'error_handler' ) );
274
275
		// Some properties can be overridden with defines
276
		if ( defined( 'HMBKP_EXCLUDE' ) && HMBKP_EXCLUDE ) {
277
			$this->set_excludes( HMBKP_EXCLUDE, true );
278
		}
279
280
		if ( defined( 'HMBKP_MYSQLDUMP_PATH' ) ) {
281
			$this->set_mysqldump_command_path( HMBKP_MYSQLDUMP_PATH );
282
		}
283
284
		if ( defined( 'HMBKP_ZIP_PATH' ) ) {
285
			$this->set_zip_command_path( HMBKP_ZIP_PATH );
286
		}
287
288
	}
289
290
	/**
291
	 * Simple class wrapper for Path::get_path()
292
	 *
293
	 * @return string
294
	 */
295
	private function get_path() {
296
		return Path::get_instance()->get_path();
297
	}
298
299
	/**
300
	 * Get the full filepath to the archive file
301
	 *
302
	 * @return string
303
	 */
304
	public function get_archive_filepath() {
305
		return trailingslashit( $this->get_path() ) . $this->get_archive_filename();
306
	}
307
308
	/**
309
	 * Get the filename of the archive file
310
	 *
311
	 * @return string
312
	 */
313
	public function get_archive_filename() {
314
315
		if ( empty( $this->archive_filename ) ) {
316
			$this->set_archive_filename( implode( '-', array(
317
				sanitize_title( str_ireplace( array(
318
					'http://',
319
					'https://',
320
					'www'
321
				), '', home_url() ) ),
322
				'backup',
323
				current_time( 'Y-m-d-H-i-s' )
324
			) ) . '.zip' );
325
		}
326
327
		return $this->archive_filename;
328
329
	}
330
331
	/**
332
	 * Set the filename of the archive file
333
	 *
334
	 * @param string $filename
335
	 *
336
	 * @return \WP_Error|null
337
	 */
338 View Code Duplication
	public function set_archive_filename( $filename ) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
339
340
		if ( empty( $filename ) || ! is_string( $filename ) ) {
341
			return new \WP_Error( 'invalid_file_name', __( 'archive filename must be a non-empty string', 'backupwordpress' ) );
342
		}
343
344
		if ( pathinfo( $filename, PATHINFO_EXTENSION ) !== 'zip' ) {
345
			return new \WP_Error( 'invalid_file_extension', sprintf( __( 'invalid file extension for archive filename <code>%s</code>', 'backupwordpress' ), $filename ) );
346
		}
347
348
		$this->archive_filename = strtolower( sanitize_file_name( remove_accents( $filename ) ) );
349
350
	}
351
352
	/**
353
	 * Get the full filepath to the database dump file.
354
	 *
355
	 * @return string
356
	 */
357
	public function get_database_dump_filepath() {
358
		return trailingslashit( $this->get_path() ) . $this->get_database_dump_filename();
359
	}
360
361
	/**
362
	 * Get the filename of the database dump file
363
	 *
364
	 * @return string
365
	 */
366
	public function get_database_dump_filename() {
367
368
		if ( empty( $this->database_dump_filename ) ) {
369
			$this->set_database_dump_filename( 'database_' . DB_NAME . '.sql' );
370
		}
371
372
		return $this->database_dump_filename;
373
374
	}
375
376
	/**
377
	 * Set the filename of the database dump file
378
	 *
379
	 * @param string $filename
380
	 *
381
	 * @return \WP_Error|null
382
	 */
383 View Code Duplication
	public function set_database_dump_filename( $filename ) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
384
385
		if ( empty( $filename ) || ! is_string( $filename ) ) {
386
			return new \WP_Error( 'invalid_file_name', __( 'database dump filename must be a non-empty string', 'backupwordpress' ) );
387
		}
388
389
		if ( pathinfo( $filename, PATHINFO_EXTENSION ) !== 'sql' ) {
390
			return new \WP_Error( 'invalid_file_extension', sprintf( __( 'invalid file extension for database dump filename <code>%s</code>', 'backupwordpress' ), $filename ) );
391
		}
392
393
		$this->database_dump_filename = strtolower( sanitize_file_name( remove_accents( $filename ) ) );
394
395
	}
396
397
	/**
398
	 * Get the root directory to backup from
399
	 *
400
	 * Defaults to the root of the path equivalent of your home_url
401
	 *
402
	 * @return string
403
	 */
404
	public function get_root() {
405
406
		if ( empty( $this->root ) ) {
407
			$this->set_root( wp_normalize_path( self::get_home_path() ) );
408
		}
409
410
		return $this->root;
411
412
	}
413
414
	/**
415
	 * Set the root directory to backup from
416
	 *
417
	 * @param string $path
418
	 *
419
	 * @return \WP_Error|null
420
	 */
421 View Code Duplication
	public function set_root( $path ) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
422
423
		if ( empty( $path ) || ! is_string( $path ) || ! is_dir( $path ) ) {
424
			return new \WP_Error( 'invalid_directory_path', sprintf( __( 'Invalid root path <code>%s</code> must be a valid directory path', 'backupwordpress' ), $path ) );
425
		}
426
427
		$this->root = wp_normalize_path( $path );
428
429
	}
430
431
	/**
432
	 * Get the filepath for the existing archive
433
	 *
434
	 * @return string
435
	 */
436
	public function get_existing_archive_filepath() {
437
		return $this->existing_archive_filepath;
438
	}
439
440
	/**
441
	 * Set the filepath for the existing archive
442
	 *
443
	 * @param string $existing_archive_filepath
444
	 *
445
	 * @return null
0 ignored issues
show
Documentation introduced by
Should the return type not be \WP_Error|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
446
	 */
447 View Code Duplication
	public function set_existing_archive_filepath( $existing_archive_filepath ) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
448
449
		if ( empty( $existing_archive_filepath ) || ! is_string( $existing_archive_filepath ) ) {
450
			return new \WP_Error( 'invalid_existing_archive_filepath', sprintf( __( 'Invalid existing archive filepath <code>%s</code> must be a non-empty (string)', 'backupwordpress' ), $existing_archive_filepath ) );
451
		}
452
453
		$this->existing_archive_filepath = wp_normalize_path( $existing_archive_filepath );
454
455
	}
456
457
	/**
458
	 * Get the archive method that was used for the backup
459
	 *
460
	 * Will be either zip, ZipArchive or PclZip
461
	 *
462
	 */
463
	public function get_archive_method() {
464
		return $this->archive_method;
465
	}
466
467
	/**
468
	 * Get the database dump method that was used for the backup
469
	 *
470
	 * Will be either mysqldump or mysqldump_fallback
471
	 *
472
	 */
473
	public function get_mysqldump_method() {
474
		return $this->mysqldump_method;
475
	}
476
477
	/**
478
	 * Get the backup type
479
	 *
480
	 * Defaults to complete
481
	 *
482
	 */
483
	public function get_type() {
484
485
		if ( empty( $this->type ) ) {
486
			$this->set_type( 'complete' );
487
		}
488
489
		return $this->type;
490
491
	}
492
493
	/**
494
	 * Set the backup type
495
	 *
496
	 * $type must be one of complete, database or file
497
	 *
498
	 * @param string $type
499
	 *
500
	 * @return \WP_Error|null
501
	 */
502
	public function set_type( $type ) {
503
504
		if ( ! is_string( $type ) || ! in_array( $type, array( 'file', 'database', 'complete' ) ) ) {
505
			return new \WP_Error( 'invalid_backup_type', sprintf( __( 'Invalid backup type <code>%s</code> must be one of (string) file, database or complete', 'backupwordpress' ), $type ) );
506
		}
507
508
		$this->type = $type;
509
510
	}
511
512
	/**
513
	 * Get the path to the mysqldump bin
514
	 *
515
	 * If not explicitly set will attempt to work
516
	 * it out by checking common locations
517
	 *
518
	 * @return string
519
	 */
520
	public function get_mysqldump_command_path() {
521
522
		// Check shell_exec is available
523
		if ( ! self::is_shell_exec_available() ) {
524
			return '';
525
		}
526
527
		// Return now if it's already been set
528
		if ( isset( $this->mysqldump_command_path ) ) {
529
			return $this->mysqldump_command_path;
530
		}
531
532
		$this->mysqldump_command_path = '';
533
534
		// Does mysqldump work
535
		if ( is_null( shell_exec( 'hash mysqldump 2>&1' ) ) ) {
536
537
			// If so store it for later
538
			$this->set_mysqldump_command_path( 'mysqldump' );
539
540
			// And return now
541
			return $this->mysqldump_command_path;
542
543
		}
544
545
		// List of possible mysqldump locations
546
		$mysqldump_locations = array(
547
			'/usr/local/bin/mysqldump',
548
			'/usr/local/mysql/bin/mysqldump',
549
			'/usr/mysql/bin/mysqldump',
550
			'/usr/bin/mysqldump',
551
			'/opt/local/lib/mysql6/bin/mysqldump',
552
			'/opt/local/lib/mysql5/bin/mysqldump',
553
			'/opt/local/lib/mysql4/bin/mysqldump',
554
			'/xampp/mysql/bin/mysqldump',
555
			'/Program Files/xampp/mysql/bin/mysqldump',
556
			'/Program Files/MySQL/MySQL Server 6.0/bin/mysqldump',
557
			'/Program Files/MySQL/MySQL Server 5.7/bin/mysqldump',
558
			'/Program Files/MySQL/MySQL Server 5.6/bin/mysqldump',
559
			'/Program Files/MySQL/MySQL Server 5.5/bin/mysqldump',
560
			'/Program Files/MySQL/MySQL Server 5.4/bin/mysqldump',
561
			'/Program Files/MySQL/MySQL Server 5.1/bin/mysqldump',
562
			'/Program Files/MySQL/MySQL Server 5.0/bin/mysqldump',
563
			'/Program Files/MySQL/MySQL Server 4.1/bin/mysqldump',
564
			'/opt/local/bin/mysqldump'
565
		);
566
567
		// Find the first one which works
568
		foreach ( $mysqldump_locations as $location ) {
569
			if ( (is_null( shell_exec( 'hash ' . wp_normalize_path( $location ) . ' 2>&1' ) ) ) && @is_executable( wp_normalize_path( $location ) ) ) {
570
				$this->set_mysqldump_command_path( $location );
571
				break;  // Found one
572
			}
573
		}
574
575
		return $this->mysqldump_command_path;
576
577
	}
578
579
	/**
580
	 * Set the path to the mysqldump bin
581
	 *
582
	 * Setting the path to false will cause the database
583
	 * dump to use the php fallback
584
	 *
585
	 * @param mixed $path
586
	 */
587
	public function set_mysqldump_command_path( $path ) {
588
		$this->mysqldump_command_path = $path;
589
	}
590
591
	/**
592
	 * Get the path to the zip bin
593
	 *
594
	 * If not explicitly set will attempt to work
595
	 * it out by checking common locations
596
	 *
597
	 * @return string
598
	 */
599
	public function get_zip_command_path() {
600
601
		// Check shell_exec is available
602
		if ( ! self::is_shell_exec_available() ) {
603
			return '';
604
		}
605
606
		// Return now if it's already been set
607
		if ( isset( $this->zip_command_path ) ) {
608
			return $this->zip_command_path;
609
		}
610
611
		$this->zip_command_path = '';
612
613
		// Does zip work
614
		if ( is_null( shell_exec( 'hash zip 2>&1' ) ) ) {
615
616
			// If so store it for later
617
			$this->set_zip_command_path( 'zip' );
618
619
			// And return now
620
			return $this->zip_command_path;
621
622
		}
623
624
		// List of possible zip locations
625
		$zip_locations = array(
626
			'/usr/bin/zip',
627
			'/opt/local/bin/zip'
628
		);
629
630
		// Find the first one which works
631
		foreach ( $zip_locations as $location ) {
632
			if ( @is_executable( wp_normalize_path( $location ) ) ) {
633
				$this->set_zip_command_path( $location );
634
				break;  // Found one
635
			}
636
		}
637
638
		return $this->zip_command_path;
639
640
	}
641
642
	/**
643
	 * Set the path to the zip bin
644
	 *
645
	 * Setting the path to false will cause the database
646
	 * dump to use the php fallback
647
	 *
648
	 * @param mixed $path
649
	 */
650
	public function set_zip_command_path( $path ) {
651
		$this->zip_command_path = $path;
652
	}
653
654
	/**
655
	 * Fire actions for the various backup stages
656
	 *
657
	 * Callers can register callbacks to be called using `set_action_callback`
658
	 * Both the action and the instance on Backup are then passed to the callback function
659
	 *
660
	 * @see set_action_callback
661
	 *
662
	 * @param string $action The event to fire
663
	 */
664
	protected function do_action( $action ) {
665
666
		// If we have any callbacks then let's fire them
667
		if ( ! empty( $this->action_callback ) ) {
668
669
			// Order them by priority, lowest priority first
670
			ksort( $this->action_callback );
671
672
			foreach ( $this->action_callback as $priority ) {
673
				foreach ( $priority as $callback ) {
674
					call_user_func( $callback, $action, $this );
675
				}
676
			}
677
678
		}
679
680
		// Also fire a global WordPress action
681
		do_action( $action, $this );
682
683
	}
684
685
	/**
686
	 * Allow the caller to set a callback function that will be invoked whenever
687
	 * an action fires
688
	 *
689
	 * @see do_action
690
	 * @see /do_action
691
	 *
692
	 * @param callable $callback The function or method to be called
693
	 * @param int $priority The priority of the callback
694
	 */
695
	public function set_action_callback( $callback, $priority = 10 ) {
696
		$this->action_callback[ $priority ][] = $callback;
697
	}
698
699
	/**
700
	 * Kick off a backup
701
	 *
702
	 * @todo should be renamed so it's not same as class
703
	 * @return null
704
	 */
705
	public function backup() {
706
707
		$this->do_action( 'hmbkp_backup_started' );
708
709
		// Backup database
710
		if ( $this->get_type() !== 'file' ) {
711
			$this->dump_database();
712
		}
713
714
		// Zip everything up
715
		$this->archive();
716
717
		$this->do_action( 'hmbkp_backup_complete' );
718
719
	}
720
721
	/**
722
	 * Create the mysql backup
723
	 *
724
	 * Uses mysqldump if available, falls back to PHP
725
	 * if not.
726
	 *
727
	 */
728
	public function dump_database() {
729
730
		// Attempt to use native mysqldump
731
		if ( self::is_shell_exec_available() && $this->get_mysqldump_command_path() && ! is_wp_error( $this->user_can_connect() ) ) {
732
			$this->mysqldump();
733
		}
734
735
		// If we cannot run mysqldump via CLI, fallback to PHP
736
		if ( empty( $this->mysqldump_verified ) ) {
737
			$this->mysqldump_fallback();
738
		}
739
740
		$this->do_action( 'hmbkp_mysqldump_finished' );
741
742
	}
743
744
	/**
745
	 * Export the database to an .sql file via the command line with mysqldump
746
	 */
747
	public function mysqldump() {
748
749
		$this->mysqldump_method = 'mysqldump';
750
751
		$this->do_action( 'hmbkp_mysqldump_started' );
752
753
		// Guess port or socket connection type
754
		$port_or_socket = strstr( DB_HOST, ':' );
755
756
		$host = DB_HOST;
757
758 View Code Duplication
		if ( ! empty( $port_or_socket ) ) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
759
760
			$host = substr( DB_HOST, 0, strpos( DB_HOST, ':' ) );
761
762
			$port_or_socket = substr( $port_or_socket, 1 );
763
764
			if ( 0 !== strpos( $port_or_socket, '/' ) ) {
765
766
				$port = intval( $port_or_socket );
767
768
				$maybe_socket = strstr( $port_or_socket, ':' );
769
770
				if ( ! empty( $maybe_socket ) ) {
771
772
					$socket = substr( $maybe_socket, 1 );
773
774
				}
775
776
			} else {
777
778
				$socket = $port_or_socket;
779
780
			}
781
		}
782
783
		// Path to the mysqldump executable
784
		$cmd = escapeshellarg( $this->get_mysqldump_command_path() );
785
786
		// We don't want to create a new DB
787
		$cmd .= ' --no-create-db';
788
789
		// Allow lock-tables to be overridden
790
		if ( ! defined( 'HMBKP_MYSQLDUMP_SINGLE_TRANSACTION' ) || false !== HMBKP_MYSQLDUMP_SINGLE_TRANSACTION  ) {
791
			$cmd .= ' --single-transaction';
792
		}
793
794
		// Make sure binary data is exported properly
795
		$cmd .= ' --hex-blob';
796
797
		// Username
798
		$cmd .= ' -u ' . escapeshellarg( DB_USER );
799
800
		// Don't pass the password if it's blank
801
		if ( DB_PASSWORD ) {
802
			$cmd .= ' -p' . escapeshellarg( DB_PASSWORD );
803
		}
804
805
		// Set the host
806
		$cmd .= ' -h ' . escapeshellarg( $host );
807
808
		// Set the port if it was set
809
		if ( ! empty( $port ) && is_numeric( $port ) ) {
810
			$cmd .= ' -P ' . $port;
811
		}
812
813
		// Set the socket path
814
		if ( ! empty( $socket ) && ! is_numeric( $socket ) ) {
815
			$cmd .= ' --protocol=socket -S ' . $socket;
816
		}
817
818
		// The file we're saving too
819
		$cmd .= ' -r ' . escapeshellarg( $this->get_database_dump_filepath() );
820
821
		// The database we're dumping
822
		$cmd .= ' ' . escapeshellarg( DB_NAME );
823
824
		// Pipe STDERR to STDOUT
825
		$cmd .= ' 2>&1';
826
827
		// Store any returned data in an error
828
		$stderr = shell_exec( $cmd );
829
830
		// Skip the new password warning that is output in mysql > 5.6 (@see http://bugs.mysql.com/bug.php?id=66546)
831
		if ( 'Warning: Using a password on the command line interface can be insecure.' === trim( $stderr ) ) {
832
			$stderr = '';
833
		}
834
835
		if ( $stderr ) {
836
			$this->error( $this->get_mysqldump_method(), $stderr );
837
		}
838
839
		$this->verify_mysqldump();
840
841
	}
842
843
	/**
844
	 * PHP mysqldump fallback functions, exports the database to a .sql file
845
	 *
846
	 */
847
	public function mysqldump_fallback() {
848
849
		$this->errors_to_warnings( $this->get_mysqldump_method() );
850
851
		$this->mysqldump_method = 'mysqldump_fallback';
852
853
		$this->do_action( 'hmbkp_mysqldump_started' );
854
855
		// Guess port or socket connection type
856
		$port_or_socket = strstr( DB_HOST, ':' );
857
858
		$host = DB_HOST;
0 ignored issues
show
Unused Code introduced by
$host is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
859
860 View Code Duplication
		if ( ! empty( $port_or_socket ) ) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
861
862
			$host = substr( DB_HOST, 0, strpos( DB_HOST, ':' ) );
0 ignored issues
show
Unused Code introduced by
$host is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
863
864
			$port_or_socket = substr( $port_or_socket, 1 );
865
866
			if ( 0 !== strpos( $port_or_socket, '/' ) ) {
867
868
				$port = intval( $port_or_socket );
869
870
				$maybe_socket = strstr( $port_or_socket, ':' );
871
872
				if ( ! empty( $maybe_socket ) ) {
873
874
					$socket = substr( $maybe_socket, 1 );
875
876
				}
877
878
			} else {
879
880
				$socket = $port_or_socket;
881
882
			}
883
		}
884
885
		// PDO connection string formats:
886
		// mysql:host=localhost;port=3307;dbname=testdb
887
		// mysql:unix_socket=/tmp/mysql.sock;dbname=testdb
888
889
		if ( $port_or_socket ) {
890
			if ( isset( $port ) ) {
891
				$dsn = 'mysql:host=' . DB_HOST . ';port=' . $port . ';dbname=' . DB_NAME;
892
			} elseif ( isset( $socket ) ) {
893
				$dsn = 'mysql:unix_socket=' . $socket . ';dbname=' . DB_NAME;
894
			}
895
		} else {
896
			$dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME;
897
		}
898
899
		// Get character set from constant if it is declared.
900
		if ( defined( 'DB_CHARSET' ) && DB_CHARSET ) {
901
			$charset = DB_CHARSET;
902
		} else {
903
			$charset = 'utf8';
904
		}
905
906
		if ( defined( 'DB_PASSWORD' ) && DB_PASSWORD ) {
907
			$pwd = DB_PASSWORD;
908
		} else {
909
			$pwd = '';
910
		}
911
912
		if ( ! defined( 'HMBKP_MYSQLDUMP_SINGLE_TRANSACTION' ) || false !== HMBKP_MYSQLDUMP_SINGLE_TRANSACTION  ) {
913
			$single_transaction = true;
914
		} else {
915
			$single_transaction = false;
916
		}
917
918
		$dump_settings = array(
919
			'default-character-set' => $charset,
920
			'hex-blob'              => true,
921
			'single-transaction'    => $single_transaction,
922
		);
923
924
		try {
925
926
			// Allow passing custom options to dump process.
927
			$dump_settings = apply_filters( 'hmbkp_mysqldump_fallback_dump_settings', $dump_settings );
928
929
			$dump = new IMysqldump\Mysqldump( $dsn, DB_USER, $pwd, $dump_settings );
0 ignored issues
show
Bug introduced by
The variable $dsn does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
930
931
			$dump->start( $this->get_database_dump_filepath() );
932
933
		} catch ( \Exception $e ) {
934
935
			return new \WP_Error( 'mysql-fallback-error', sprintf( __( 'mysqldump fallback error %s', 'backupwordpress' ), $e->getMessage() ) );
936
937
		}
938
939
	}
940
941
	/**
942
	 * Zip up all the files.
943
	 *
944
	 * Attempts to use the shell zip command, if
945
	 * thats not available then it falls back to
946
	 * PHP ZipArchive.
947
	 *
948
	 */
949
	public function archive() {
950
951
		if ( defined( 'HMBKP_FORCE_ZIP_METHOD' ) ) {
952
			switch ( HMBKP_FORCE_ZIP_METHOD ) {
953
				case 'zip':
954
					if ( $this->get_zip_command_path() ) {
955
						$this->zip();
956
					} else {
957
						$this->warning( $this->get_archive_method(), __( 'Zip command is not available.', 'backupwordpress' ) );
958
					}
959
					break;
960
				case 'ziparchive':
961
					if ( class_exists( 'ZipArchive' ) ) {
962
						$this->zip_archive();
963
					} else {
964
						$this->warning( $this->get_archive_method(), __( 'ZipArchive method is not available.', 'backupwordpress' ) );
965
					}
966
					break;
967
				default:
968
					$this->warning( $this->get_archive_method(), __( 'No valid archive method found.', 'backupwordpress' ) );
969
					break;
970
			}
971
		} else {
972
			// Is zip available
973
			if ( $this->get_zip_command_path() ) {
974
				$this->zip();
975
			} else {
976
				// If the shell zip failed then use ZipArchive
977
				if ( empty( $this->archive_verified ) && class_exists( 'ZipArchive' ) ) {
978
					$this->zip_archive();
979
				} else {
980
					$this->warning( $this->get_archive_method(), __( 'No valid archive method found.', 'backupwordpress' ) );
981
				}
982
			}
983
984
		}
985
986
		// Delete the database dump file
987
		if ( file_exists( $this->get_database_dump_filepath() ) ) {
988
			unlink( $this->get_database_dump_filepath() );
989
		}
990
991
		$this->do_action( 'hmbkp_archive_finished' );
992
993
	}
994
995
	/**
996
	 * Zip using the native zip command
997
	 */
998
	public function zip() {
999
1000
		$this->archive_method = 'zip';
1001
1002
		$this->do_action( 'hmbkp_archive_started' );
1003
1004
		// Add the database dump to the archive
1005
		if ( 'file' !== $this->get_type() && file_exists( $this->get_database_dump_filepath() ) ) {
1006
			$stderr = shell_exec( 'cd ' . escapeshellarg( $this->get_path() ) . ' && ' . escapeshellcmd( $this->get_zip_command_path() ) . ' -q ' . escapeshellarg( $this->get_archive_filepath() ) . ' ' . escapeshellarg( $this->get_database_dump_filename() ) . ' 2>&1' );
1007
1008
			if ( ! empty ( $stderr ) ) {
1009
				$this->warning( $this->get_archive_method(), $stderr );
1010
			}
1011
		}
1012
1013
		// Zip up $this->root
1014
		if ( 'database' !== $this->get_type() ) {
1015
1016
			// cd to the site root
1017
			$command = 'cd ' . escapeshellarg( $this->get_root() );
1018
1019
			// Run the zip command with the recursive and quiet flags
1020
			$command .= ' && ' . escapeshellcmd( $this->get_zip_command_path() ) . ' -rq ';
1021
1022
			if ( defined( 'HMBKP_ENABLE_SYNC' ) && HMBKP_ENABLE_SYNC ) {
1023
1024
				// If the destination zip file already exists then let's just add changed files to save time
1025
				if ( file_exists( $this->get_archive_filepath() ) && $this->get_existing_archive_filepath() ) {
1026
					$command .= ' -FS ';
1027
				}
1028
1029
			}
1030
1031
			// Save the zip file to the correct path
1032
			$command .= escapeshellarg( $this->get_archive_filepath() ) . ' ./';
1033
1034
			// Pass exclude rules in if we have them
1035
			if ( $this->exclude_string( 'zip' ) ) {
1036
				$command .= ' -x ' . $this->exclude_string( 'zip' );
1037
			}
1038
1039
			// Push all output to STDERR
1040
			$command .= ' 2>&1';
1041
1042
			$stderr = shell_exec( $command );
1043
1044
		}
1045
1046
		if ( ! empty( $stderr ) ) {
1047
			$this->warning( $this->get_archive_method(), $stderr );
1048
		}
1049
1050
		$this->verify_archive();
1051
1052
	}
1053
1054
	/**
1055
	 * Fallback for creating zip archives if zip command is
1056
	 * unavailable.
1057
	 */
1058
	public function zip_archive() {
1059
1060
		$this->errors_to_warnings( $this->get_archive_method() );
1061
		$this->archive_method = 'ziparchive';
1062
1063
		$this->do_action( 'hmbkp_archive_started' );
1064
1065
		$zip = new \ZipArchive();
1066
1067
		if ( ! class_exists( 'ZipArchive' ) || ! $zip->open( $this->get_archive_filepath(), \ZIPARCHIVE::CREATE ) ) {
1068
			return;
1069
		}
1070
1071
		$excludes = $this->exclude_string( 'regex' );
1072
1073
		// Add the database
1074
		if ( $this->get_type() !== 'file' && file_exists( $this->get_database_dump_filepath() ) ) {
1075
			$zip->addFile( $this->get_database_dump_filepath(), $this->get_database_dump_filename() );
1076
		}
1077
1078
		if ( $this->get_type() !== 'database' ) {
1079
1080
			$files_added = 0;
1081
1082
			foreach ( $this->get_files() as $file ) {
1083
1084
				// Skip dot files, they should only exist on versions of PHP between 5.2.11 -> 5.3
1085
				if ( method_exists( $file, 'isDot' ) && $file->isDot() ) {
1086
					continue;
1087
				}
1088
1089
				// Skip unreadable files
1090
				if ( ! @realpath( $file->getPathname() ) || ! $file->isReadable() ) {
1091
					continue;
1092
				}
1093
1094
				// Excludes
1095
				if ( $excludes && preg_match( '(' . $excludes . ')', str_ireplace( trailingslashit( $this->get_root() ), '', wp_normalize_path( $file->getPathname() ) ) ) ) {
1096
					continue;
1097
				}
1098
1099
				if ( $file->isDir() ) {
1100
					$zip->addEmptyDir( trailingslashit( str_ireplace( trailingslashit( $this->get_root() ), '', wp_normalize_path( $file->getPathname() ) ) ) );
1101
				} elseif ( $file->isFile() ) {
1102
					$zip->addFile( $file->getPathname(), str_ireplace( trailingslashit( $this->get_root() ), '', wp_normalize_path( $file->getPathname() ) ) );
1103
				}
1104
1105
				if ( ++ $files_added % 500 === 0 ) {
1106
					if ( ! $zip->close() || ! $zip->open( $this->get_archive_filepath(), \ZIPARCHIVE::CREATE ) ) {
1107
						return;
1108
					}
1109
				}
1110
1111
			}
1112
1113
		}
1114
1115
		if ( $zip->status ) {
1116
			$this->warning( $this->get_archive_method(), $zip->status );
1117
		}
1118
1119
		if ( $zip->statusSys ) {
1120
			$this->warning( $this->get_archive_method(), $zip->statusSys );
1121
		}
1122
1123
		$zip->close();
1124
1125
		$this->verify_archive();
1126
1127
	}
1128
1129
	public function verify_mysqldump() {
1130
1131
		$this->do_action( 'hmbkp_mysqldump_verify_started' );
1132
1133
		// If we've already passed then no need to check again
1134
		if ( ! empty( $this->mysqldump_verified ) ) {
1135
			return true;
1136
		}
1137
1138
		// If there are mysqldump errors delete the database dump file as mysqldump will still have written one
1139
		if ( $this->get_errors( $this->get_mysqldump_method() ) && file_exists( $this->get_database_dump_filepath() ) ) {
1140
			unlink( $this->get_database_dump_filepath() );
1141
		}
1142
1143
		// If we have an empty file delete it
1144
		if ( @filesize( $this->get_database_dump_filepath() ) === 0 ) {
1145
			unlink( $this->get_database_dump_filepath() );
1146
		}
1147
1148
		// If the file still exists then it must be good
1149
		if ( file_exists( $this->get_database_dump_filepath() ) ) {
1150
			return $this->mysqldump_verified = true;
1151
		}
1152
1153
		return false;
1154
1155
	}
1156
1157
	/**
1158
	 * Verify that the archive is valid and contains all the files it should contain.
1159
	 *
1160
	 * @return bool
1161
	 */
1162
	public function verify_archive() {
1163
1164
		$this->do_action( 'hmbkp_archive_verify_started' );
1165
1166
		// If we've already passed then no need to check again
1167
		if ( ! empty( $this->archive_verified ) ) {
1168
			return true;
1169
		}
1170
1171
		// If there are errors delete the backup file.
1172
		if ( $this->get_errors( $this->get_archive_method() ) && file_exists( $this->get_archive_filepath() ) ) {
1173
			unlink( $this->get_archive_filepath() );
1174
		}
1175
1176
		// If the archive file still exists assume it's good
1177
		if ( file_exists( $this->get_archive_filepath() ) ) {
1178
			return $this->archive_verified = true;
1179
		}
1180
1181
		return false;
1182
1183
	}
1184
1185
	/**
1186
	 * Return an array of all files in the filesystem.
1187
	 *
1188
	 * @param bool $ignore_default_exclude_rules If true then will return all files under root. Otherwise returns all files except those matching default exclude rules.
1189
	 *
1190
	 * @return array
1191
	 */
1192
	public function get_files( $ignore_default_exclude_rules = false ) {
1193
1194
		if ( ! empty( $this->files ) ) {
1195
			return $this->files;
1196
		}
1197
1198
		$finder = new Finder();
1199
		$finder->followLinks();
1200
		$finder->ignoreDotFiles( false );
1201
		$finder->ignoreUnreadableDirs();
1202
1203
		if ( ! $ignore_default_exclude_rules ) {
1204
			// Skips folders/files that match default exclude patterns
1205
			foreach ( $this->default_excludes() as $exclude ) {
1206
				$finder->notPath( $exclude );
1207
			}
1208
		}
1209
1210
		foreach ( $finder->in( $this->get_root() ) as $entry ) {
1211
			$this->files[] = $entry;
1212
		}
1213
1214
		return $this->files;
1215
1216
	}
1217
1218
	/**
1219
	 * Returns an array of files that will be included in the backup.
1220
	 *
1221
	 * @return array
1222
	 */
1223 View Code Duplication
	public function get_included_files() {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1224
1225
		if ( ! empty( $this->included_files ) ) {
1226
			return $this->included_files;
1227
		}
1228
1229
		$this->included_files = array();
1230
1231
		$excludes = $this->exclude_string( 'regex' );
1232
1233
		foreach ( $this->get_files( true ) as $file ) {
1234
1235
			// Skip dot files, they should only exist on versions of PHP between 5.2.11 -> 5.3
1236
			if ( method_exists( $file, 'isDot' ) && $file->isDot() ) {
1237
				continue;
1238
			}
1239
1240
			// Skip unreadable files
1241
			if ( ! @realpath( $file->getPathname() ) || ! $file->isReadable() ) {
1242
				continue;
1243
			}
1244
1245
			// Excludes
1246
			if ( $excludes && preg_match( '(' . $excludes . ')', str_ireplace( trailingslashit( $this->get_root() ), '', wp_normalize_path( $file->getPathname() ) ) ) ) {
1247
				continue;
1248
			}
1249
1250
			$this->included_files[] = $file;
1251
1252
		}
1253
1254
		return $this->included_files;
1255
1256
	}
1257
1258
	/**
1259
	 * Returns an array of files that match the exclude rules.
1260
	 *
1261
	 * @return array
1262
	 */
1263 View Code Duplication
	public function get_excluded_files() {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1264
1265
		if ( ! empty( $this->excluded_files ) ) {
1266
			return $this->excluded_files;
1267
		}
1268
1269
		$this->excluded_files = array();
1270
1271
		$excludes = $this->exclude_string( 'regex' );
1272
1273
		foreach ( $this->get_files( true ) as $file ) {
1274
1275
			// Skip dot files, they should only exist on versions of PHP between 5.2.11 -> 5.3
1276
			if ( method_exists( $file, 'isDot' ) && $file->isDot() ) {
1277
				continue;
1278
			}
1279
1280
			// Skip unreadable files
1281
			if ( ! @realpath( $file->getPathname() ) || ! $file->isReadable() ) {
1282
				continue;
1283
			}
1284
1285
			// Excludes
1286
			if ( $excludes && preg_match( '(' . $excludes . ')', str_ireplace( trailingslashit( $this->get_root() ), '', wp_normalize_path( $file->getPathname() ) ) ) ) {
1287
				$this->excluded_files[] = $file;
1288
			}
1289
1290
		}
1291
1292
		return $this->excluded_files;
1293
1294
	}
1295
1296
	/**
1297
	 * Returns an array of unreadable files.
1298
	 *
1299
	 * @return array
1300
	 */
1301
	public function get_unreadable_files() {
1302
1303
		if ( ! empty( $this->unreadable_files ) ) {
1304
			return $this->unreadable_files;
1305
		}
1306
1307
		$this->unreadable_files = array();
1308
1309
		foreach ( $this->get_files( true ) as $file ) {
1310
1311
			// Skip dot files, they should only exist on versions of PHP between 5.2.11 -> 5.3
1312
			if ( method_exists( $file, 'isDot' ) && $file->isDot() ) {
1313
				continue;
1314
			}
1315
1316
			if ( ! @realpath( $file->getPathname() ) || ! $file->isReadable() ) {
1317
				$this->unreadable_files[] = $file;
1318
			}
1319
1320
		}
1321
1322
		return $this->unreadable_files;
1323
1324
	}
1325
1326
	/**
1327
	 * Get an array of exclude rules
1328
	 *
1329
	 * The backup path is automatically excluded
1330
	 *
1331
	 * @return array
1332
	 */
1333
	public function get_excludes() {
1334
1335
		$excludes = array();
1336
1337
		if ( isset( $this->excludes ) ) {
1338
			$excludes = $this->excludes;
1339
		}
1340
1341
		// If path() is inside root(), exclude it
1342
		if ( strpos( $this->get_path(), $this->get_root() ) !== false ) {
1343
			array_unshift( $excludes, trailingslashit( $this->get_path() ) );
1344
		}
1345
1346
		return array_unique( $excludes );
1347
1348
	}
1349
1350
	/**
1351
	 * Set the excludes, expects and array
1352
	 *
1353
	 * @param  Array $excludes
1354
	 * @param Bool $append
1355
	 */
1356
	public function set_excludes( $excludes, $append = false ) {
1357
1358
		if ( is_string( $excludes ) ) {
1359
			$excludes = explode( ',', $excludes );
1360
		}
1361
1362
		if ( $append ) {
1363
			$excludes = array_merge( $this->excludes, $excludes );
1364
		}
1365
1366
		$this->excludes = array_filter( array_unique( array_map( 'trim', $excludes ) ) );
1367
1368
	}
1369
1370
	/**
1371
	 * Generate the exclude param string for the zip backup
1372
	 *
1373
	 * Takes the exclude rules and formats them for use with either
1374
	 * the shell zip command or pclzip
1375
	 *
1376
	 * @param string $context . (default: 'zip')
1377
	 *
1378
	 * @return string
1379
	 */
1380
	public function exclude_string( $context = 'zip' ) {
1381
1382
		// Return a comma separated list by default
1383
		$separator = ', ';
1384
		$wildcard  = '';
1385
1386
		// The zip command
1387
		if ( $context === 'zip' ) {
1388
			$wildcard  = '*';
1389
			$separator = ' -x ';
1390
1391
			// The PclZip fallback library
1392
		} elseif ( $context === 'regex' ) {
1393
			$wildcard  = '([\s\S]*?)';
1394
			$separator = '|';
1395
		}
1396
1397
		$excludes = $this->get_excludes();
1398
1399
		foreach ( $excludes as $key => &$rule ) {
1400
1401
			$file = $absolute = $fragment = false;
1402
1403
			// Files don't end with /
1404
			if ( ! in_array( substr( $rule, - 1 ), array( '\\', '/' ) ) ) {
1405
				$file = true;
1406
			} // If rule starts with a / then treat as absolute path
1407 View Code Duplication
			elseif ( in_array( substr( $rule, 0, 1 ), array( '\\', '/' ) ) ) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1408
				$absolute = true;
1409
			} // Otherwise treat as dir fragment
1410
			else {
1411
				$fragment = true;
1412
			}
1413
1414
			// Strip $this->root and conform
1415
			$rule = str_ireplace( $this->get_root(), '', untrailingslashit( wp_normalize_path( $rule ) ) );
1416
1417
			// Strip the preceeding slash
1418 View Code Duplication
			if ( in_array( substr( $rule, 0, 1 ), array( '\\', '/' ) ) ) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1419
				$rule = substr( $rule, 1 );
1420
			}
1421
1422
			// Escape string for regex
1423
			if ( $context === 'regex' ) {
1424
				$rule = str_replace( '.', '\.', $rule );
1425
			}
1426
1427
			// Convert any existing wildcards
1428
			if ( $wildcard !== '*' && false !== strpos( $rule, '*' ) ) {
1429
				$rule = str_replace( '*', $wildcard, $rule );
1430
			}
1431
1432
			// Wrap directory fragments and files in wildcards for zip
1433
			if ( 'zip' === $context && ( $fragment || $file ) ) {
1434
				$rule = $wildcard . $rule . $wildcard;
1435
			}
1436
1437
			// Add a wildcard to the end of absolute url for zips
1438
			if ( 'zip' === $context && $absolute ) {
1439
				$rule .= $wildcard;
1440
			}
1441
1442
			// Add and end carrot to files for pclzip but only if it doesn't end in a wildcard
1443
			if ( $file && 'regex' === $context ) {
1444
				$rule .= '$';
1445
			}
1446
1447
			// Add a start carrot to absolute urls for pclzip
1448
			if ( $absolute && 'regex' === $context ) {
1449
				$rule = '^' . $rule;
1450
			}
1451
1452
		}
1453
1454
		// Escape shell args for zip command
1455
		if ( $context === 'zip' ) {
1456
			$excludes = array_map( 'escapeshellarg', array_unique( $excludes ) );
1457
		}
1458
1459
		return implode( $separator, $excludes );
1460
1461
	}
1462
1463
	/**
1464
	 * Get the errors
1465
	 *
1466
	 */
1467 View Code Duplication
	public function get_errors( $context = null ) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1468
1469
		if ( ! empty( $context ) ) {
1470
			return isset( $this->errors[ $context ] ) ? $this->errors[ $context ] : array();
1471
		}
1472
1473
		return $this->errors;
1474
1475
	}
1476
1477
	/**
1478
	 * Add an error to the errors stack
1479
	 *
1480
	 * @param string $context
1481
	 * @param mixed $error
1482
	 */
1483 View Code Duplication
	public function error( $context, $error ) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1484
1485
		if ( empty( $context ) || empty( $error ) ) {
1486
			return;
1487
		}
1488
1489
		$this->do_action( 'hmbkp_error' );
1490
1491
		$this->errors[ $context ][ $_key = md5( implode( ':', (array) $error ) ) ] = $error;
1492
1493
	}
1494
1495
	/**
1496
	 * Migrate errors to warnings
1497
	 *
1498
	 * @param null $context
1499
	 */
1500
	private function errors_to_warnings( $context = null ) {
1501
1502
		$errors = empty( $context ) ? $this->get_errors() : array( $context => $this->get_errors( $context ) );
1503
1504
		if ( empty( $errors ) ) {
1505
			return;
1506
		}
1507
1508
		foreach ( $errors as $error_context => $context_errors ) {
1509
			foreach ( $context_errors as $error ) {
1510
				$this->warning( $error_context, $error );
1511
			}
1512
		}
1513
1514
		if ( $context ) {
1515
			unset( $this->errors[ $context ] );
1516
		} else {
1517
			$this->errors = array();
1518
		}
1519
1520
	}
1521
1522
	/**
1523
	 * Get the warnings
1524
	 *
1525
	 */
1526 View Code Duplication
	public function get_warnings( $context = null ) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1527
1528
		if ( ! empty( $context ) ) {
1529
			return isset( $this->warnings[ $context ] ) ? $this->warnings[ $context ] : array();
1530
		}
1531
1532
		return $this->warnings;
1533
1534
	}
1535
1536
	/**
1537
	 * Add an warning to the warnings stack
1538
	 *
1539
	 * @param string $context
1540
	 * @param mixed $warning
1541
	 */
1542 View Code Duplication
	private function warning( $context, $warning ) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1543
1544
		if ( empty( $context ) || empty( $warning ) ) {
1545
			return;
1546
		}
1547
1548
		$this->do_action( 'hmbkp_warning' );
1549
1550
		$this->warnings[ $context ][ $_key = md5( implode( ':', (array) $warning ) ) ] = $warning;
1551
1552
	}
1553
1554
	/**
1555
	 * Custom error handler for catching php errors
1556
	 *
1557
	 * @param $type
1558
	 *
1559
	 * @return bool
1560
	 */
1561
	public function error_handler( $type ) {
1562
1563
		// Skip strict & deprecated warnings
1564
		if ( ( defined( 'E_DEPRECATED' ) && $type === E_DEPRECATED ) || ( defined( 'E_STRICT' ) && $type === E_STRICT ) || error_reporting() === 0 ) {
1565
			return false;
1566
		}
1567
1568
		$args = func_get_args();
1569
1570
		array_shift( $args );
1571
1572
		$this->warning( 'php', implode( ', ', array_splice( $args, 0, 3 ) ) );
1573
1574
		return false;
1575
1576
	}
1577
1578
	/**
1579
	 * Determine if user can connect via the CLI
1580
	 *
1581
	 * @return \WP_Error
0 ignored issues
show
Documentation introduced by
Should the return type not be \WP_Error|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1582
	 */
1583
	public function user_can_connect() {
1584
1585
		// mysql --host=localhost --user=myname --password=mypass mydb
1586
1587
		// Guess port or socket connection type
1588
		$port_or_socket = strstr( DB_HOST, ':' );
1589
1590
		$host = DB_HOST;
1591
1592 View Code Duplication
		if ( ! empty( $port_or_socket ) ) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1593
1594
			$host = substr( DB_HOST, 0, strpos( DB_HOST, ':' ) );
1595
1596
			$port_or_socket = substr( $port_or_socket, 1 );
1597
1598
			if ( 0 !== strpos( $port_or_socket, '/' ) ) {
1599
1600
				$port = intval( $port_or_socket );
1601
1602
				$maybe_socket = strstr( $port_or_socket, ':' );
1603
1604
				if ( ! empty( $maybe_socket ) ) {
1605
1606
					$socket = substr( $maybe_socket, 1 );
1607
1608
				}
1609
1610
			} else {
1611
1612
				$socket = $port_or_socket;
1613
1614
			}
1615
		}
1616
1617
		// Path to the mysqldump executable
1618
		$cmd = 'mysql ';
1619
1620
		// Username
1621
		$cmd .= ' -u ' . escapeshellarg( DB_USER );
1622
1623
		// Don't pass the password if it's blank
1624
		if ( DB_PASSWORD ) {
1625
			$cmd .= ' -p' . escapeshellarg( DB_PASSWORD );
1626
		}
1627
1628
		// Set the host
1629
		$cmd .= ' -h ' . escapeshellarg( $host );
1630
1631
		// Set the port if it was set
1632
		if ( ! empty( $port ) && is_numeric( $port ) ) {
1633
			$cmd .= ' -P ' . $port;
1634
		}
1635
1636
		// Set the socket path
1637
		if ( ! empty( $socket ) && ! is_numeric( $socket ) ) {
1638
			$cmd .= ' --protocol=socket -S ' . $socket;
1639
		}
1640
1641
		// The database we're dumping
1642
		$cmd .= ' ' . escapeshellarg( DB_NAME );
1643
1644
		// Quit immediately
1645
		$cmd .= ' --execute="quit"';
1646
1647
		// Pipe STDERR to STDOUT
1648
		$cmd .= ' 2>&1';
1649
1650
		// Store any returned data in an error
1651
		$stderr = shell_exec( $cmd );
1652
1653
		// Skip the new password warning that is output in mysql > 5.6 (@see http://bugs.mysql.com/bug.php?id=66546)
1654
		if ( 'Warning: Using a password on the command line interface can be insecure.' === trim( $stderr ) ) {
1655
			$stderr = '';
1656
		}
1657
1658
		if ( $stderr ) {
1659
			return new \WP_Error( 'mysql-cli-connect-error', __( 'Could not connect to mysql', 'backupwordpress' ) );
1660
		}
1661
	}
1662
1663
}
1664