App-WordPressTools
view release on metacpan or search on metacpan
script/wp-tools view on Meta::CPAN
}
};
my @paths = map { -e "$nice_path/$_" ? shell_quote("$nice_path/$_") : () } @backup_whitelist;
my $paths = join(' ', @paths);
my $size = eval { `du -msc $paths` } || '';
$size =~ s/.*?(\d+)\s+total.*/$1/s;
if (!$size || $size !~ /^\d+$/) {
die 'Cannot determine size of WordPress installation';
}
if ($args->{max_size} < $size) {
# try to reduce content that is included in the backup
my $backupsize = $size;
for my $type (sort { $skippable->{$b} <=> $skippable->{$a} } keys %$skippable) {
my $skip_path = "$args->{'path'}/wp-content/$type";
my $skip_pathq = shell_quote($skip_path);
next if !-d $skip_path;
$skips->{$type} = $skippable->{$type} || eval { `du -ms $skip_pathq` } || 0;
$skips->{$type} =~ s/^(\d+).*/$1/s;
$backupsize -= $skips->{$type};
last if $backupsize <= $args->{max_size};
}
if ($args->{max_size} < $backupsize) {
die "Cannot backup this WordPress installation because it is too large ($size/$backupsize)";
}
}
}
### backup database
my $result = '';
my $dump_command = '';
# we cannot check the database without wp-config.php (credential storage location)
if (!-f "$nice_path/wp-config.php") {
$args->{'__skip_database'} = 1;
}
else {
#acceptable failure states for databaseless accounts
my $no_db_regex = qr/(?:Access denied for user|Unknown MySQL server host|Unknown database.*when selecting the database|is marked as crashed and last \(automatic\?\) repair failed when using LOCK TABLES|Got error: 130: Incorrect file format|Go...
if ($plus3713) {
$dump_command = "db export $db_file --add-drop-table 2>&1";
$result = _run_wpcli($nice_path, $dump_command);
}
if ($result =~ $no_db_regex) {
$args->{'__skip_database'} = 1;
}
elsif ($result !~ /^Success/) {
# wp-cli backup can fail for wp <3.7.13, so try mysqldump instead
my $wp_configq = shell_quote("$args->{'path'}/wp-config.php");
my $username = `grep DB_USER <$wp_configq | grep -v '$definition_check_string' | cut -d \\' -f 4`;
my $password = `grep DB_PASSWORD <$wp_configq | grep -v '$definition_check_string' | cut -d \\' -f 4`;
my $database = `grep DB_NAME <$wp_configq | grep -v '$definition_check_string' | cut -d \\' -f 4`;
#disallow starting whitespace
$username =~ s/^[\r\n]+//;
$password =~ s/^[\r\n]+//;
$database =~ s/^[\r\n]+//;
chomp $username;
chomp $password;
chomp $database;
if ($username =~ /[\r\n]/ || $password =~ /[\r\n]/ || $database =~ /[\r\n]/) {
die "Multiple credentials found in $args->{'path'}/wp-config.php. Cannot determine which to use. Backup operation halted.";
}
open (my $fh, '>', $defaults_file) or die "Cannot write to $defaults_file: $!";
close $fh;
chmod(0600, $defaults_file) or die "Cannot chmod $defaults_file: $!";
write_text($defaults_file,"[client]\nuser=$username\npassword=$password");
my $databaseq = shell_quote($database);
$dump_command = "mysqldump --defaults-file=$defaults_file $databaseq 2>&1 >$db_file";
my $result = `$dump_command`;
#acceptable failure states for databaseless accounts
if ($result =~ $no_db_regex) {
$args->{'__skip_database'} = 1;
$db_file = '';
}
elsif ($?) {
die "mysqldump command ($dump_command) failed (exit: $?): $result";
}
}
if (!$args->{'__skip_database'}) {
if (!-f $db_file || -s $db_file < 1024) {
die "Aborting because there doesn't seem to be any data to back up (command: $dump_command)";
}
open my $db_test, '<', $db_file;
my $first_line = <$db_test>;
close $db_test;
if ($first_line =~ /Usage: mysqldump/) {
die "Database dump looks like it failed because it contains usage (command: $dump_command)";
}
}
}
### backup filesystem
my $backup_filename = "$args->{backup_dir}/$backup_file";
mkpath($args->{backup_dir}, {mode => 0750}) if !-d $args->{backup_dir};
my $exclusions = '';
for my $type (keys %$skips) {
if (!$skips->{$type}) {
delete $skips->{$type};
next;
}
my $spath = "$wpdir/wp-content/$type";
$spath = "./$spath" if $spath =~ /^-/;
my $skip_path = shell_quote($spath);
$exclusions .= " --exclude $skip_path";
}
my $inclusions = '';
for my $file (@backup_whitelist) {
next if !-e "$args->{'path'}/$file";
my $apath = "$wpdir/$file";
$apath = "./$apath" if $apath =~ /^-/;
my $add_path = shell_quote($apath);
$inclusions .= " $add_path";
}
open(my $fh, '>', $manifest_file) or die "Cannot write to $manifest_file: $!";
print $fh "version:1\n";
print $fh "path:$nice_path\n";
print $fh "time:$time\n";
print $fh "nodb:1\n" if $args->{'__skip_database'};
if (scalar keys %$skips) {
print $fh "skipped:".join(',', keys %$skips)."\n";
}
script/wp-tools view on Meta::CPAN
our $saved_path = $path;
$saved_path =~ s!/+$!!;
$saved_path = "${saved_path}.restore";
if (-d $path) {
my $saved_pathq = shell_quote($saved_path);
my $err_out = `cp -al $pathq $saved_pathq 2>&1`;
if ($?) {
die "Could not copy $path to $saved_path: $err_out";
}
}
eval {
my $del = _find_sed_delimiter($wpdir, $path);
my $bre = _bre_quote($wpdir);
my $repl = _bre_quote($path, '\&');
if (!$del) {
die 'No available delimiter found to use with tar -x --transform';
}
my $transform = shell_quote("--transform=s${del}^$bre${del}$repl${del}");
my $restore_cmd = "tar -xzf $backup_fileq --recursive-unlink $transform $wpdirq 2>&1";
my $restore = `$restore_cmd`;
if ($?) {
die "File restore command ($restore_cmd) exited non-zero ($?): $restore";
}
if (!-d $path) {
die "File restore command ($restore_cmd) failed to actually restore files";
}
if (!$manifest{'nodb'}) {
my $transform = "--transform='s/^wp_backup${time}\\.sql\$/$db_file/'";
my $db_restore = `tar -xzf $backup_fileq $transform wp_backup${time}.sql 2>&1`;
if ($? || !-f $db_file) {
die "Cannot extract database restore file ($db_file): $db_restore";
}
$wp_cli_path = "$args->{'wp_cli'} --path=".shell_quote($path);
$version = _run_wpcli($path, 'core version');
chomp $version;
$canonversion = _canon_ver($version);
$plus3713 = !!($canonversion ge $canon3713);
if ($plus3713) {
my $dbfile = _run_wpcli($path, "db import $db_file --skip-plugins --skip-themes");
}
if (!$plus3713 || $?) {
my $wp_configq = shell_quote("$path/wp-config.php");
if (!-f "$path/wp-config.php") {
die "Missing wp-config.php";
}
my $username = `grep DB_USER <$wp_configq | grep -v '$definition_check_string' | cut -d \\' -f 4`;
my $password = `grep DB_PASSWORD <$wp_configq | grep -v '$definition_check_string' | cut -d \\' -f 4`;
my $database = `grep DB_NAME <$wp_configq | grep -v '$definition_check_string' | cut -d \\' -f 4`;
#disallow starting whitespace
$username =~ s/^[\r\n]+//;
$password =~ s/^[\r\n]+//;
$database =~ s/^[\r\n]+//;
chomp $username;
chomp $password;
chomp $database;
if ($username =~ /[\r\n]/ || $password =~ /[\r\n]/ || $database =~ /[\r\n]/) {
die "Multiple credentials found in $args->{'path'}/wp-config.php. Cannot determine which to use. Restoration operation halted.";
}
open (my $fh, '>', $defaults_file) or die "Cannot write to $defaults_file: $!";
close $fh;
chmod(0600, $defaults_file) or die "Cannot chmod $defaults_file: $!";
write_text($defaults_file,"[client]\nuser=$username\npassword=$password");
my $databaseq = shell_quote($database);
`mysql --defaults-file=$defaults_file $databaseq <$db_file`;
if ($?) {
die "Failed to restore database";
}
}
}
# if we skipped large directories in the backup procedure
# we must copy them from the installation we are replacing
my @skipped = split(/,/, $manifest{'skipped'} || '');
for my $dir (@skipped) {
my $src = "${saved_path}/wp-content/$dir";
my $dst = "${path}/wp-content/$dir";
if (-d $src) {
rmtree($dst) if -e $dst;
my $srcq = shell_quote($src);
my $dstq = shell_quote($dst);
my $err_out = `cp -al $srcq $dstq 2>&1`;
if ($?) {
die "Could not copy $path to $saved_path: $err_out";
}
}
else {
# TODO - should this condition be fatal?
warn "Expected to restore using $dir in installation being replaced, but it is missing";
}
}
};
if ($@) {
my $err = $@;
if (-d $saved_path) {
my $defunct_path = $path;
$defunct_path =~ s!/+$!!;
$defunct_path .= ".defunct_" . time;
if (rename($path, $defunct_path)) {
if (!rename($saved_path, $path)) {
# this is the sad case
$err .= "; also could not move $saved_path back to $path: $!";
undef $saved_path; # do not wipe out if we couldn't fully fix it
rename($defunct_path, $path);
}
if (-d $defunct_path) {
rmtree($defunct_path);
}
}
}
die $err;
}
### cleanup
END {
unlink($db_file) if $db_file && -e $db_file;
unlink($defaults_file) if $defaults_file && -e $defaults_file;
script/wp-tools view on Meta::CPAN
print "No updates available.\n";
exit;
}
### if not explicitly refused, make a backup first
my $backup = !$args->{'skip_backup'} ? backup() : undef;
die "Unable to make backup." if !$args->{'skip_backup'} && (!$backup || !ref $backup || !$backup->{'success'});
if ($backup && ref $backup && $backup->{'success'}) {
$args->{'backup_file'} = $backup->{'backup_file'};
}
if (!$args->{'skip_backup'} && (length($args->{'backup_file'}) == 0 || !-e $args->{'backup_file'} || !-s $args->{'backup_file'})) {
die "Unable to read backup file - upgrade stopped.";
}
#find all non-executable directories and make the executable (for listing)
my $fix_directories = `find $pathq -type d ! -perm /u+x -exec chmod u+x {} \\; ;`;
#find all non-writable files and make them user writable
my $set_permissions = `find $pathq ! -perm /u+w -exec chmod u+w {} \\; ;`;
#if a backup is made, disable maintenance mode temporarily
if (-e '.maintenance') {
$maintenance = '.not.maintenance';
rename('.maintenance',$maintenance);
}
my $current_status;
my $http = HTTP::Tiny->new(timeout => 30);
if ($plus3713) {
### default wp-cli status check
$current_status->{'wpcli_std'} = _run_wpcli($nice_path, q{eval 'echo "ok";'});
chomp $current_status->{'wpcli_std'} if $current_status->{'wpcli_std'};
### npt "safe mode" status check
$current_status->{'wpcli_npt'} = _run_wpcli($nice_path, q{--skip-plugins --skip-themes eval 'echo "ok";'});
chomp $current_status->{'wpcli_npt'} if $current_status->{'wpcli_npt'};
### get url and current default page size/status code
$current_status->{'siteurl'} = _run_wpcli($nice_path, q{option get siteurl});;
$current_status->{'adminurl'} = "$current_status->{'siteurl'}/wp-admin";
my $resp = $http->head($current_status->{'siteurl'});
$current_status->{'code'} = $resp->{'status'};
$current_status->{'size'} = $resp->{'headers'}{'content-length'} || 1;
my $aresp = $http->head($current_status->{'adminurl'});
$current_status->{'admincode'} = $aresp->{'status'};
$current_status->{'adminsize'} = $aresp->{'headers'}{'content-length'} || 1;
}
elsif (-f "$args->{'path'}/wp-config.php") {
### get url and current default page size/status code
my $wp_configq = shell_quote("$args->{'path'}/wp-config.php");
my $username = `grep DB_USER <$wp_configq | grep -v '$definition_check_string' | cut -d \\' -f 4`;
my $password = `grep DB_PASSWORD <$wp_configq | grep -v '$definition_check_string' | cut -d \\' -f 4`;
my $database = `grep DB_NAME <$wp_configq | grep -v '$definition_check_string' | cut -d \\' -f 4`;
my $prefix = `grep table_prefix <$wp_configq | grep -v '$definition_check_string' | cut -d \\' -f 2` || 'wp_';
$prefix =~ s/[^\w_-]//g;
#disallow starting whitespace
$username =~ s/^[\r\n]+//;
$password =~ s/^[\r\n]+//;
$database =~ s/^[\r\n]+//;
chomp $username;
chomp $password;
chomp $database;
if ($username =~ /[\r\n]/ || $password =~ /[\r\n]/ || $database =~ /[\r\n]/) {
die "Multiple credentials found in $args->{'path'}/wp-config.php. Cannot determine which to use. Upgrade operation halted.";
}
if ($prefix =~ /[\r\n]/) {
die "Multiple database prefixes found in $args->{'path'}/wp-config.php. Cannot determine which to use. Upgrade operation halted.";
}
open (my $fh, '>', $defaults_file) or die "Cannot write to $defaults_file: $!";
close $fh;
chmod(0600, $defaults_file) or die "Cannot chmod $defaults_file: $!";
write_text($defaults_file,"[client]\nuser=$username\npassword=$password");
my $table = "${prefix}options";
my $databaseq = shell_quote($database);
my $siteurl_sql = qq{mysql -N -B -e --defaults-file=$defaults_file $databaseq "SELECT option_value FROM $table WHERE option_name = 'siteurl'"};
$current_status->{'siteurl'} = `$siteurl_sql`;
$current_status->{'adminurl'} = "$current_status->{'siteurl'}/wp-admin";
my $resp = $http->head($current_status->{'siteurl'});
$current_status->{'code'} = $resp->{'status'};
$current_status->{'size'} = $resp->{'headers'}{'content-length'} || 1;
my $aresp = $http->head($current_status->{'adminurl'});
$current_status->{'admincode'} = $aresp->{'status'};
$current_status->{'adminsize'} = $aresp->{'headers'}{'content-length'} || 1;
}
eval {
if ($components{'core'}) {
my $update = _run_wpcli($nice_path, ($args->{'force'} || !$plus3713) ? "core download --force 2>&1" : "core update 2>&1");
#try forcing errors
$update = _run_wpcli($nice_path, q{core download --force 2>&1}) if $?;
die "Upgrading WordPress core files exited with $? ($update)" if $?;
if (!$args->{'__skip_database'}) {
my $updatedb = _run_wpcli($nice_path, q{core update-db 2>&1});
#try harder
if ($?) {
$updatedb = _run_wpcli($nice_path, q{core update-db --skip-plugins --skip-themes 2>&1});
}
#allowable database failures
if ($updatedb !~ /(?:The site you have requested is not installed.)/) {
die "Upgrading WordPress core database exited with $? ($updatedb)" if $?;
}
}
}
my $pt_ok = qr/(?:Warning: Update package not available.|Error establishing a database connection)/;
if ($components{'plugin'}) {
my $plugins = _run_wpcli($nice_path, q{plugin update --all 2>&1});
if ($? && $plugins !~ /$pt_ok/gsm) {
die "Upgrading plugins exited with $? ($plugins)";
}
}
if ($components{'theme'}) {
my $themes = _run_wpcli($nice_path, q{theme update --all 2>&1});
if ($? && $themes !~ /$pt_ok/gsm) {
die "Upgrading themes exited with $? ($themes)" if $?;
}
}
};
if ($@) {
$args->{'failed'} = $@;
}
if (!$args->{'failed'} && $plus3713) {
### default wp-cli status check
if ($current_status->{'wpcli_std'} eq 'ok') {
my $test = _run_wpcli($nice_path, q{eval 'echo "ok";'});
( run in 0.323 second using v1.01-cache-2.11-cpan-eab888a1d7d )