App-WordPressTools

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

Revision history for App::WordPressTools (wp-tools).

1.03  2018-09-13 21:26:05 MDT

  * Security: Backup directories are now created 0750 (i.e. not
    world-readable) for the benefit of those with trusting umasks. If you use
    wp-tools in a shared environment, you are encouraged to upgrade.
  * Deprecated the use of relative paths as arguments to --path, --backupdir,
    and --backupfile.

1.02  2016-12-12 16:00:29 MST

  * Reduce chance of leaving site directory in an inconsistent state if the
    script is killed at the wrong time.
  * Fix broken logic for applying backup skips.

1.01  2016-02-19 15:51:50 MST

  * Fixed a bug with complete version number not being preserved.
  * Fixed a bug that made --force ineffective against the disk space check.
  * Fixed a bug related to innacurate admin page checking on upgrade.
  * Fixed test scripts to invoke the correct perl.
  * Added documentation for how to contribute.
  * Added Travis CI configuration for automated testing (thanks Jason Hall).
  * Fixed various issues with the dzil config and maintenance scripts.

META.json  view on Meta::CPAN

{
   "abstract" : "tools to backup and upgrade WordPress installations",
   "author" : [
      "Seth Johnson <sj@bluehost.com>",
      "Charles McGarvey <cmcgarvey@bluehost.com>",
      "Garth Mortensen <gmortensen@bluehost.com>"
   ],
   "dynamic_config" : 0,
   "generated_by" : "Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010",
   "license" : [
      "gpl_2"
   ],

META.yml  view on Meta::CPAN

---
abstract: 'tools to backup and upgrade WordPress installations'
author:
  - 'Seth Johnson <sj@bluehost.com>'
  - 'Charles McGarvey <cmcgarvey@bluehost.com>'
  - 'Garth Mortensen <gmortensen@bluehost.com>'
build_requires:
  Capture::Tiny: '0'
  Test::More: '0'
configure_requires:
  ExtUtils::MakeMaker: '0'
dynamic_config: 0

Makefile.PL  view on Meta::CPAN

# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.012.
use strict;
use warnings;

use 5.008001;

use ExtUtils::MakeMaker;

my %WriteMakefileArgs = (
  "ABSTRACT" => "tools to backup and upgrade WordPress installations",
  "AUTHOR" => "Seth Johnson <sj\@bluehost.com>, Charles McGarvey <cmcgarvey\@bluehost.com>, Garth Mortensen <gmortensen\@bluehost.com>",
  "CONFIGURE_REQUIRES" => {
    "ExtUtils::MakeMaker" => 0
  },
  "DISTNAME" => "App-WordPressTools",
  "EXE_FILES" => [
    "script/wp-tools"
  ],
  "LICENSE" => "gpl",
  "MIN_PERL_VERSION" => "5.008001",

README  view on Meta::CPAN

NAME

    wp-tools - tools to backup and upgrade WordPress installations

SYNOPSIS

        # simple installation from CPAN
        cpanm App::WordPressTools
    
        # view usage information
        wp-tools --help
    
        # common usage
        wp-tools upgrade|backup|restore OPTIONS...

DESCRIPTION

    WordPress Tools is a set of tools that allow you to backup, restore,
    and upgrade WordPress sites. The tools are especially useful for
    upgrading very outdated sites and for scripting the backing up and
    upgrading of many sites.

    WordPress Tools was built to make the Internet more secure. A huge
    number of websites on the Internet use WordPress
    <https://wordpress.org/>, because it's awesome, but a large portion of
    those sites do not run the latest version of WordPress which makes them
    susceptible to hacking. WordPress Tools can be used to get those sites
    up-to-date again, and that makes the whole Internet more secure.

README  view on Meta::CPAN


      Show usage information and exit.

    --force

      If there are any resource limits in effect, ignore them and do the
      command anyway.

    --max-count=num

      Delete the oldest backups, keeping the number provided (default: 5).

    --max-dproc=num

      Refuse to execute if the maximum number of defunct processes on the
      system exceeds this number (default: 100).

    --max-load=num

      Refuse to execute if the load average exceeds this number (default:
      200).

README  view on Meta::CPAN

    --username=name

      If you run wp-tools as a root user, it will drop permissions to the
      specified user and chdir to their home directory.

COMMANDS

    upgrade

      The upgrade command will upgrade your WordPress core, themes, and
      plugins to the latest version available. It takes a full backup prior
      to the update. After the update, wp-tools will do a quick check to
      try to determine that nothing broke on your site. If any failures are
      detected, wp-tools will automatically restore to the backup taken
      prior to update.

    backup

      The backup command will create a full backup, including database, of
      your WordPress installation as long as it is under the --max-size
      limit (pre-compression).

    restore

      The restore command will restore a WordPress installation from a
      backup taken using the backup command.

EXAMPLES

        # upgrade a site
        wp-tools upgrade --path=/absolute/path/to/public_html/myblog \
                         --backupdir=/absolute/path/to/myblog_backups
    
        # upgrade only plugins and themes
        wp-tools upgrade --path=/absolute/path/to/public_html/myblog  \
                         --backupdir=/absolute/path/to/myblog_backups \
                         --components=plugin,theme
    
        # backup a site
        wp-tools backup --path=/absolute/path/to/public_html/mysite \
                        --backupdir=/absolute/path/to/backups/mysite
    
        # restore a site to a previous state
        wp-tools restore --backupfile=/absolute/path/to/backups/mysite/wp_backup1428524082.tar.gz

NOTES

    Beginning with version 1.03, passing relative paths to --path,
    --backupdir, and --backupfile is deprecated. You should use absolute
    paths (i.e. that begin with a /). Relative paths are accepted but will
    print a warning to STDERR. This is because using a relative path does
    not behave as you would think (for boring historical reasons).

CONTRIBUTORS

    Many people were involved in the creation of WordPress Tools. In
    particular:

      * Matt Andersen

lib/App/WordPressTools.pm  view on Meta::CPAN

package App::WordPressTools;
our $VERSION = '1.03';

# eventually we should refactor some code out of the script into modules like this

=head1 NAME

App::WordPressTools - tools to backup and upgrade WordPress installations

=head1 DESCRIPTION

This module is part of the WordPress Tools package. For information about how to install and use the command-line
program, see L<wp-tools>.

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2016-2018 by Bluehost Inc.

script/wp-tools  view on Meta::CPAN

#!perl

=head1 NAME

wp-tools - tools to backup and upgrade WordPress installations

=head1 SYNOPSIS

    # simple installation from CPAN
    cpanm App::WordPressTools

    # view usage information
    wp-tools --help

    # common usage
    wp-tools upgrade|backup|restore OPTIONS...

=head1 DESCRIPTION

WordPress Tools is a set of tools that allow you to backup, restore, and upgrade WordPress sites. The tools are
especially useful for upgrading very outdated sites and for scripting the backing up and upgrading of B<many> sites.

WordPress Tools was built to make the Internet more secure. A huge number of websites on the Internet use
L<WordPress|https://wordpress.org/>, because it's awesome, but a large portion of those sites do not run the latest
version of WordPress which makes them susceptible to hacking. WordPress Tools can be used to get those sites up-to-date
again, and that makes the whole Internet more secure.

The code has been used in production and has already upgraded over two million WordPress sites, but it has little
real-world testing outside of Linux at this point so your mileage may vary if you have a different operating system.
Stay tuned as we add support for more platforms, and please contribute if you feel like it.

script/wp-tools  view on Meta::CPAN

=item --help

Show usage information and exit.

=item --force

If there are any resource limits in effect, ignore them and do the command anyway.

=item --max-count=num

Delete the oldest backups, keeping the number provided (default: 5).

=item --max-dproc=num

Refuse to execute if the maximum number of defunct processes on the system exceeds this number (default: 100).

=item --max-load=num

Refuse to execute if the load average exceeds this number (default: 200).

=item --max-size=num

script/wp-tools  view on Meta::CPAN


=back

=head1 COMMANDS

=over 4

=item upgrade

The upgrade command will upgrade your WordPress core, themes, and plugins to the latest version available. It takes
a full backup prior to the update. After the update, wp-tools will do a quick check to try to determine that nothing
broke on your site. If any failures are detected, wp-tools will automatically restore to the backup taken prior to
update.

=item backup

The backup command will create a full backup, including database, of your WordPress installation as long as it is under
the --max-size limit (pre-compression).

=item restore

The restore command will restore a WordPress installation from a backup taken using the backup command.

=back

=head1 EXAMPLES

    # upgrade a site
    wp-tools upgrade --path=/absolute/path/to/public_html/myblog \
                     --backupdir=/absolute/path/to/myblog_backups

    # upgrade only plugins and themes
    wp-tools upgrade --path=/absolute/path/to/public_html/myblog  \
                     --backupdir=/absolute/path/to/myblog_backups \
                     --components=plugin,theme

    # backup a site
    wp-tools backup --path=/absolute/path/to/public_html/mysite \
                    --backupdir=/absolute/path/to/backups/mysite

    # restore a site to a previous state
    wp-tools restore --backupfile=/absolute/path/to/backups/mysite/wp_backup1428524082.tar.gz

=head1 NOTES

Beginning with version 1.03, passing relative paths to C<--path>, C<--backupdir>, and C<--backupfile> is B<deprecated>.
You should use absolute paths (i.e. that begin with a F</>). Relative paths are accepted but will print a warning to
C<STDERR>. This is because using a relative path does not behave as you would think (for boring historical reasons).

=head1 CONTRIBUTORS

Many people were involved in the creation of WordPress Tools. In particular:

=over 4

=item *

script/wp-tools  view on Meta::CPAN

use Fcntl qw(:DEFAULT :flock);
use File::Find;
use File::Path;
use File::Slurper qw(read_text write_text);
use Getopt::Long qw(GetOptions);
use HTTP::Tiny;
use String::ShellQuote;


my $default = {
    backup_dir  => '',
    backup_file => '',
    components  => 'core,plugin,theme',
    force       => '',
    max_await   => 50,
    max_count   => 5,
    max_dproc   => 100,
    max_load    => 200,
    max_run     => 50,
    max_size    => 1024,    #MB
    min_space   => 25600,   #MB
    min_freemem => 1048576,
    path        => '',
    username    => '',
    wp_cli      => 'wp',
    skip_backup => undef,
};

my @backup_whitelist = qw(
.htaccess
favicon.ico
index.php
license.txt
readme.html
wp-activate.php
wp-admin
wp-blog-header.php
wp-comments-post.php
wp-config-sample.php

script/wp-tools  view on Meta::CPAN

wp-settings.php
wp-signup.php
wp-trackback.php
xmlrpc.php
);

sub help {
    my $exit_status = shift;
    my $alert       = shift || '';
    print <<"END";
wp-tools makes backups of, upgrades, and restores backups of WordPress installations (files and databases)
usage:
wp-tools [command] [options]
e.g.
wp-tools backup  --path=WORDPRESSPATH --backupdir=BACKUPDIR ...
wp-tools upgrade --path=WORDPRESSPATH ...
wp-tools restore --backupfile=BACKUPFILE ...

    --help            show this help and exit
    --backupdir=str   absolute path to store WordPress backups (required for backup)
    --backupfile=str  absolute path to WordPress backup file (required for restore)
    --components=list comma-separated list of things to upgrade (default: $default->{components})
    --force           try hard to perform the command, disregarding limits if necessary
    --max-await=num   maximum time (ms) for IO requests on the system (default: $default->{max_await})
    --max-count=num   delete the oldest backups, keeping this many (default: $default->{max_count})
    --max-dproc=num   maximum number of defunct processes on the system (default: $default->{max_dproc})
    --max-load=num    maximum load average (default: $default->{max_load})
    --max-size=num    maximum size (in MB) of the installation (default: $default->{max_size})
    --max-run=num     maximum number of concurrent system-wide executions of this script (default: $default->{max_run})
    --min-freemem=num minimum amount of free memory on the system (default: $default->{min_freemem})
    --min-space=num   minimum amount of free space on the partition (default: $default->{min_space})
    --path=str        absolute path to WordPress installation (required for backup, upgrade, optional for restore)
    --username=str    if root, drop privileges to specified user and chdir to user's home (required)
    --skip-backup     skips backup as part of an upgrade (not recommended, default: off)
    --wp-cli=str      command to execute when calling WP-CLI (default: $default->{wp_cli})
END
    print "\n\n$alert\n" if $alert;
    exit(defined $exit_status ? $exit_status : 1);
}

if (grep { /^--version$/ } @ARGV) {
    print "WordPress Tools, version $VERSION\n";
    exit 0;
}

my $args = {%$default};
GetOptions(
    'backupdir=s'   => \$args->{'backup_dir'},
    'backupfile=s'  => \$args->{'backup_file'},
    'components=s'  => \$args->{'components'},
    'force'         => \$args->{'force'},
    'max-await=s'   => \$args->{'max_await'},
    'max-count=s'   => \$args->{'max_count'},
    'max-dproc=s'   => \$args->{'max_dproc'},
    'max-load=s'    => \$args->{'max_load'},
    'max-run=s'     => \$args->{'max_run'},
    'max-size=s'    => \$args->{'max_size'},
    'min-freemem=s' => \$args->{'min_freemem'},
    'min-space=s'   => \$args->{'min_space'},
    'path=s'        => \$args->{'path'},
    'username=s'    => \$args->{'username'},
    'wp-cli=s'      => \$args->{'wp_cli'},
    'skip-backup'   => \$args->{'skip_backup'},
    'help'          => sub { help(0) },
) or help;

my $command = lc($ARGV[0] || '');
if (   !$command
    || $command !~ /^(?:backup|restore|upgrade)$/
    || (!$args->{'backup_dir'}  && $command =~ /^(?:backup)$/)
    || (!$args->{'backup_dir'}  && $command =~ /^(?:upgrade)$/ && !$args->{'skip_backup'})
    || (!$args->{'path'}        && $command =~ /^(?:backup|upgrade)$/)
    || (!$args->{'backup_file'} && $command =~ /^(?:restore)$/)
    || (!$args->{'components'}  && $command =~ /^(?:upgrade)$/)
    || !$args->{'wp_cli'} ) {
    help(1, "Required parameter missing");
}

if (!$ENV{WP_TOOLS_SILENCE_DEPRECATION_WARNINGS}) {
    # print warnings for deprecated args
    for my $arg (qw{path backup_dir backup_file}) {
        next if !$args->{$arg} || $args->{$arg} =~ m!^/!;
        warn "DEPRECATED use of relative path ($args->{$arg}); use \$PWD/$args->{$arg} instead!\n";
    }
}

### check box load
if ($args->{max_load} && !$args->{force}) {
    open(my $fh, '<', '/proc/loadavg');
    my $loadfile = readline $fh;
    close $fh;

script/wp-tools  view on Meta::CPAN

    $args->{'username'} = $username if !$args->{'username'};
}

### check space and average IO wait time of the home partition
if (my ($homeslash) = $home_dir =~ m{^(/home\d+)/} and !$args->{force}) {
    #get available space on /home for the user in question in POSIX standard
    my $df = `df -P $homeslash | tail -1`;
    my ($device,undef,undef,$available) = split(/\s+/, $df);
    #convert to MB
    my $mbavail  = $available / 1024;
    die "Not enough space available for backup ($mbavail MB)" if $mbavail < $args->{'min_space'};
    if ($args->{max_await} && $device !~ /^(?:rootfs|fakefs)/) {
        my $iostat = `iostat $device -dx 10 2 | tail -2`;
        chomp $iostat;
        my @iostat = split(/\s+/, $iostat);
        my $await = $iostat[9];
        if (!$await || $await !~ /^[0-9.]+$/) {
            warn "ignoring that await is not a number ($await)";
            $await = 0;
        }
        die "Average IO wait time ($await) is too high on $homeslash" if $args->{'max_await'} < $await;

script/wp-tools  view on Meta::CPAN

    return $pid_files{$file} = $fh;
}
sub unlink_pid_file {
    my $file = shift;
    my $fh   = $pid_files{$file} or return;
    close($fh);
    unlink($file);      # explicitly ignore unlink errors since we may not be the owner
}
for my $i (1..$args->{max_run}) {
    my $num  = sprintf('%04d', $i);
    my $file = "/tmp/wp_backup-${num}.pid";
    last if eval { create_pid_file($file) };
}
if (!keys %pid_files && !$args->{force}) {
    die 'Too many concurrent executions; try again later.';
}
sub _lock_path {
    my $path = shift;
    $path =~ s!/*$!!;
    my $hash = md5_hex($path);
    create_pid_file(".wp_backup-${hash}.pid");
    return $hash;
}
END {
    unlink_pid_file($_) for (keys %pid_files);
}

my $nice_path    = $args->{path} || '';
$nice_path       =~ s/^\///;
my @parts        = split '/', $nice_path;
my $wpdir        = $parts[-1] || '';
my $parent       = $nice_path;
$parent          =~ s/\Q$wpdir\E\/?//;
my $parentq      = shell_quote($parent);
my $wpdirq       = shell_quote($wpdir);
my $pathq        = shell_quote($nice_path);
my ($wp_cli_path, $version, $canonversion, $plus3713);
my $canon3713    = _canon_ver('3.7.13');
our $maintenance;
our $mode;
our $definition_check_string = '/*WP-CLI_DEFINITION_CHECK*/';
if ($command =~ /^(backup|upgrade)$/) {
    $wp_cli_path  = "$args->{'wp_cli'} --path=$pathq";
    $version      = _run_wpcli($nice_path, 'core version');
    chomp $version;
    $canonversion = _canon_ver($version);
    $plus3713     = !!($canonversion ge $canon3713);
}

if (my $method = __PACKAGE__->can($command)) {
    my $return = eval{$method->()};
    if ($@) {
        print "Unable to $command: $@";
        exit 1;
    }
    elsif ($return && ref $return) {
        print "$return->{'message'}\n" || "Successfully completed $command operation.\n";
        exit($return->{'success'} ? 0 : 1);
    }
    exit 1;
}

sub backup {
    my $time            = time;
    my $hash            = _lock_path($nice_path);

    my $backup_file     = "wp_backup${time}.tar.gz";
    our $db_file        = "wp_backup${hash}.sql";
    our $manifest_file  = "wp_backup${hash}.MANIFEST";
    our $defaults_file  = "wp_backup${hash}.temporary.my.cnf";

    my $skips = {};

    ### check size of WordPress installation
    if ($args->{max_size} && !$args->{force}) {
        # find directories that may be skipped
        my $skippable = {};
        my $wp_content_path = shell_quote("$args->{'path'}/wp-content");
        my $dums = eval{`du -ms $wp_content_path/*`};
        my $wp_content_size;
        for my $entry (split(/[\r\n]+/, $dums)) {
            my ($size, $path) = $entry =~ m/^(\d+).*wp-content\/(.*)$/;
            $wp_content_size->{$path} = $size;
        }
        eval {
            opendir (my $dh, $wp_content_path) || die;
            while (my $path = readdir $dh) {
                next if $path eq '.' || $path eq '..';
                if ($path =~ /(?:backup|upload)/i) {
                    $skippable->{$path} = $wp_content_size->{$path};
                }
                if ($wp_content_size->{$path} && $wp_content_size->{$path} > 200 && $path !~ /^(?:themes|plugins|mu-plugins|translations|languages)$/) {
                    $skippable->{$path} = $wp_content_size->{$path};
                }
            }
        };

        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;

script/wp-tools  view on Meta::CPAN

            }
            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";
    }
    close $fh;
    my $transform = "--transform='s/^$manifest_file\$/wp_backup.MANIFEST/' --transform='s/^$db_file\$/wp_backup${time}.sql/'";
    my $cd = $parent ? "-C $parentq" : '';
    my $backup = `tar -czf $backup_filename $transform $manifest_file $db_file $cd $exclusions $inclusions`;
    if (!-e $backup_filename) {
        die 'Failed to create backup file';
    }

    ### delete old backups
    if ($args->{max_count} && $args->{max_count} =~ /^\d+$/) {
        my @files;
        File::Find::find({
            no_chdir    => 1,
            wanted      => sub {
                push @files, $_ if -f && m{/wp_backup\d+\.tar\.gz$};
            },
        }, $args->{'backup_dir'});

        my @backups;
        for my $file (@files) {
            my ($time) = $file =~ m{wp_backup(\d+)\.tar\.gz$};
            push @backups, {
                file      => $file,
                timestamp => $time,
            };
        }
        my $count = 0;
        for my $backup (sort { $b->{timestamp} <=> $a->{timestamp} } @backups) {
            $count += 1;
            if ($args->{'max_count'} < $count) {
                unlink $backup->{file};
            }
        }
    }

    ### cleanup
    END {
        unlink($db_file)        if $db_file && -e $db_file;
        unlink($manifest_file)  if $manifest_file && -e $manifest_file;
        unlink($defaults_file)  if $defaults_file && -e $defaults_file;
    }

    return {
        success     => 1,
        path        => $args->{'path'},
        file        => $backup_file,
        backup_file => "$args->{'backup_dir'}/$backup_file",
        message     => "Successfully backed up WordPress from $args->{'path'} to $backup_file",
    };
}

sub restore {
    if (!-e $args->{backup_file}) {
        die "Backup file $args->{backup_file} not found";
    }

    my $backup_fileq = shell_quote($args->{'backup_file'});
    my $manifest = eval { `tar -xOzf $backup_fileq --occurrence=1 wp_backup.MANIFEST 2>/dev/null` } || '';
    my %manifest;
    if (!$? && $manifest) {
        %manifest = map { /^(\w+?):(.*)$/ ? ($1 => $2) : () } split(/\n/, $manifest);
    }

    my $hash        = _lock_path($nice_path);
    my ($time)      = $manifest{time} ? ($manifest{time}) : $args->{backup_file} =~ /wp_backup(\d+)\.tar\.gz$/;
    # time is needed to know the name of the database file in the tarball.
    $time or die 'Cannot determine time of backup';
    our $db_file        = "wp_backup${hash}.sql";
    our $defaults_file  = "wp_backup${hash}.temporary.my.cnf";
    my $path        = $args->{path} || $manifest{path} or die 'Cannot determine path to restore to';
    $nice_path      = $manifest{path} || $path;
    $nice_path      =~ s/^\///;
    @parts          = split '/', $nice_path;
    $wpdir          = $parts[-1];

    my $wpdirq      = shell_quote($wpdir);
    my $pathq       = shell_quote($nice_path);

    our $saved_path = $path;

script/wp-tools  view on Meta::CPAN

    }

    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");

script/wp-tools  view on Meta::CPAN

                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`;

script/wp-tools  view on Meta::CPAN

    ### cleanup
    END {
        unlink($db_file)        if $db_file && -e $db_file;
        unlink($defaults_file)  if $defaults_file && -e $defaults_file;
        rmtree($saved_path)     if $saved_path && -d $saved_path;
    }

    return {
        success => 1,
        path    => $path,
        file    => $args->{'backup_file'},
        message => "Successfully restored WordPress from $args->{'backup_file'} to $path",
        ($args->{'failed'} ? (update_failure => $args->{'failed'}) : ()),
    };
}

sub _canon_ver {(my $n=shift)=~s/(\d+)/sprintf "%06d", $1/eg; $n =~ tr/-:_/.../; return $n}

sub upgrade {
    my $hash            = _lock_path($nice_path);
    our $defaults_file  = "wp_backup${hash}.temporary.my.cnf";

    my %components = map { /^(core|plugin|theme)s?$/i ? (lc($1) => 1) : () } split(/,/, $args->{'components'});

    if ($plus3713) {
        ### check for possible updates
        ### this check only works 3.7.13+, however 3.7.12- are guaranteed to have updates
        my $core_check = _run_wpcli($nice_path, 'core check-update --skip-plugins --skip-themes 2>&1');
        if ($components{'core'} && (!$core_check || $core_check =~ /Success: WordPress is at the latest version./) && !$?) {
            delete $components{'core'};
        }

script/wp-tools  view on Meta::CPAN

        for my $type (qw(theme plugin)) {
            delete $components{$type} if $components{$type};
        }
    }
    ### exit if no updates available
    if (!keys %components) {
        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";'});

script/wp-tools  view on Meta::CPAN

    }

    ### cleanup
    END {
        unlink($defaults_file) if $defaults_file && -e $defaults_file;
        rename($maintenance,'.maintenance') if $maintenance;
    }

    #fail case vvv
    if ($args->{'failed'}) {
        print "Upgrade failure detected:$args->{'failed'}. Restoring from backup $args->{'backup_file'}...\n";
        my $result = restore();
        $result->{'success'} = 0;
        return $result;
    }
    else {
        my $upgraded = join(', ', map { $_ eq 'core' ? 'WordPress' : "${_}s" } keys %components);
        return {
            success => 1,
            path    => $args->{'path'},
            message => "Successfully upgraded $upgraded from $args->{'path'}",

script/wp-tools  view on Meta::CPAN

    $? = $status;
    return $ret;
}

### fix wp-config.php to workaround wpcli bug
#   See https://github.com/wp-cli/wp-cli/issues/1631
sub _fix_wp_config {
    my $path = shift or die 'Path argument required';

    our $config_file    = "$path/wp-config.php";
    our $config_backup  = "$config_file.backup";

    my $config_fileq    = shell_quote($config_file);
    my $config_backupq  = shell_quote($config_backup);

    return if !-e $config_file;

    # make sure we are not generating from an auto-generated config
    my $config_content = `cat $config_fileq`;
    if ($config_content =~ m!WARNING: This config is auto-generated from .* for wpcli compatibility!) {
        return;
        #die "Refusing to generate wpcli-compatible wp-config.php because we already did";
    }

    # copy config to a backup location
    my $err_out = `cp $config_fileq $config_backupq 2>&1`;
    if ($?) {
        die "Could not copy $config_file to $config_backup: $err_out";
    }

    # generate the new config
    open(my $in,  '<', $config_backup) or die "Failed to open (cbf) $config_backup: $!";
    #temporarily set permissions to rw?
    $mode = (stat($config_file))[2] & 0777;
    my $modestr = sprintf qq{%04o}, $mode;
    if ($modestr !~ /^.[67]/) {
        chmod($mode | 0600, $config_file);
    }
    else {
        undef $mode;
    }
    open(my $out, '>', $config_file) or die "Failed to open (cfw) $config_file $!";
    print $out "<?php\n";
    print $out "/* WARNING: This config is auto-generated from $config_backup for wpcli compatibility! */\n";
    while (my $line = <$in>) {
        if ($line =~ /(?:define\((.+?),.+\)\s*;|\$\w+\s*=\s*['"].*['"]\s*;)/) {
            $line = "if (!defined($1)) {$definition_check_string\n$line\n}$definition_check_string\n" if $1;
            print $out $line;
        }
    }
    print $out "require_once(ABSPATH . 'wp-settings.php');\n";
    close($in);
    close($out);

    END {
        _restore_wp_config();
    }
}

sub _restore_wp_config {
    our $config_file;
    our $config_backup;

    # restore original wp-config.php
    if ($config_backup && -e $config_backup) {
        unlink($config_file);
        rename($config_backup, $config_file);
    }
    chmod($mode, $config_file) if $mode;
}



( run in 0.755 second using v1.01-cache-2.11-cpan-49f99fa48dc )