Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
7161 lines (6666 sloc) 344 KB
#!/bin/bash
# Note - if you change that path to bash, then you should also change it in the configuration section below
VERSION="1.3.140 (2017-12-01)"
# (c) David Anderson 2011-
#
# https://github.com/DavidAnderson684/wordshell
#
# Licence: https://github.com/DavidAnderson684/wordshell/blob/master/LICENSE
# * * * BEGIN CONFIGURATION * * * #
# Looking for a command that outputs a file modification time (output should be an integer)
# This next one hopefully "just works" : if it does not on your system, then please tell us a) a working command and b) the output of /bin/uname on your system
# BSD/Mac OS X style
STAT_MODTIME="stat -f %m"
# GNU style
UNAME=`uname`
[[ $UNAME = "Linux" || $UNAME =~ ^Cygwin || $UNAME =~ ^CYGWIN ]] && STAT_MODTIME="stat -c %Y"
# If diff or patch is not in your path, then change these locations to find them.
DIFF="diff"
PATCH="patch"
# If lftp is not in your path, then change this to the location to find it. Only required if you are modifying sites via FTP.
LFTP="lftp"
# Note that by default lftp uses the command 'ssh -a -x' by default for accessing SFTP or fish sites. So if you wish to use SFTP or fish, you need to have a working ssh binary installed as well.
# Curl is presently used only (if there is no lftp) for basic GET/POST HTTP operations (e.g. downloading API information, getting zips); in a future version it may also handle (S)FTP operations; but for now it is simply an alternative to wget.
CURL="curl"
# Here, you can set any curl options you wish/need to set (note that some SSL options are set each time by the code). Will be over-ridden by configuration file option curl-ftp-options. (You can also specify options in a default curl configuration file - see the curl man page).
# --insecure means that SSL certificate checks need not pass; it is common to access FTP servers under a variety of host names, and many do not have certificates signed by signers known to curl.
CURL_FTPOPTIONS="--insecure"
# wget is only used for fetching API information and downloading zips if lftp and curl not found. This is only useful if you are not maintaining any sites via ftp (wget cannot upload via FTP)
WGET="wget"
# We use rsync for local copying
RSYNC="rsync"
# Unzip is used for unpacking new plugins/themes
UNZIP="unzip"
# Zip is used for packaging up old plugins/themes
ZIP="zip"
# Takes you to the website for manual downloading if requested
W3M="w3m"
# Alternatives to w3m if you did not have that available
ELINKS="elinks"
LYNX="lynx"
# used to edit the configuration files. No special vi features are needed; any text editor will do (e.g. emacs, joe, pico, nano)
VI="vi"
# used to unserialize, and to make MD5 checksums if nothing else was found
PHP="php"
# used as a scrolling pager
LESS="less"
# used for more reliable re-invoking self internally. This should match line 1.
BASH="/bin/bash"
# If you have multiple versions of date available, then point this to GNU date (coreutils) for slightly increased functionality
DATE="date"
# Finally, we find a binary that can do an MD5 checksum and return the results as the first output parameter
# WordShell will run through a list of possibilities (including your choice for PHP); the one specified here may not be used, but if nothing is found automatically then you can specify one here
MD5CHECKSUM_TRY="md5sum"
# Default configuration file. Will be created if it does not exist.
# Format is that each line is like so (or comments begin with #):
# site:site-name:access-method:directory:username:[password, optional]
# or:
# config:key:value
WORKDIR="$HOME/.wordshell"
CONFIGFILE="$WORKDIR/rc"
# Initial default number of threads for lftp to use when mirroring (some FTP servers will limit the maximum connections)
# Can be over-ridden in the configuration file (e.g. --setconfig=ftpparallel:24) and on the command-line (--ftpparallel=<num>)
FTPPARALLEL=9
# Globs to exclude from local synchronisation of plugins/themes
# Separate each addition glob with spaces. The default value is a plugin you may not use - but if you do, it can save you hundreds of megabytes.
# Can be over-ridden in the configuration file (e.g. --setconfig=exclude-glob-ftp:<value> and --setconfig=exclude-glob-rsync:value)
EXCLUDE_GLOB_FTP="wponlinebackup/tmp/*"
# Note that if you do not have rsync installed then this next line is totally ignored
EXCLUDE_GLOB_RSYNC="wponlinebackup/tmp/*"
# Where to fetch a wp-wordshell-controller.php script of sufficiently high protocol from
REMOTE_MANAGER_MINIMUM_PROTOCOL=22
REMOTE_MANAGER_FROM="https://wordshellnet.s3.amazonaws.com/wp-wordshell-controller.php.$REMOTE_MANAGER_MINIMUM_PROTOCOL"
SELINUX=0
[[ $UNAME = "Linux" ]] && selinuxenabled 2>/dev/null && SELINUX=1
# sed -i is not part of POSIX; an -i option is present on BSDs but works differently to GNU.
# Must provide the filename as $2
function ws_sed_i() {
if [[ $UNAME = "Linux" || $UNAME =~ ^Cygwin || $UNAME =~ ^CYGWIN ]]; then
sed -i "$@"
elif [[ $UNAME = "FreeBSD" || $UNAME = "Darwin" ]]; then
sed -i "" "$@"
else
local SED_TMP=`mktemp`
sed "$@" >$SED_TMP
cat $SED_TMP >"$2"
rm -f $SED_TMP
fi
}
# Now a function to recursively set directory ownership, recursively, based upon the owner of a specified file. Hopefully again this will "just work", but if not please let us know and send us the output of /bin/uname
function setownership() {
# Input: $1 = the reference file
# Input: $2 = the directory to set ownership upon
# Note - where you specify a directory finishing with /, this routine will not touch that directory itself (or dotfiles inside it)
if [[ $UNAME = "Linux" || $UNAME =~ ^Cygwin || $UNAME =~ ^CYGWIN ]]; then
# GNU style
if [[ $2 =~ \/$ ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_debug "setownership: GNU: contents: $2"
chown -R --reference="$1" "$2"/*
if [[ $SELINUX -eq 1 ]]; then
if [[ $DEBUG -ge 1 ]]; then
ws_event_debug "setownership: SELinux chcon: contents: $2"
chcon -R --reference="$1" "$2"/*
else
chcon -R --reference="$1" "$2"/* 2>/dev/null
fi
fi
else
[[ $DEBUG -ge 1 ]] && ws_event_debug "setownership: GNU: $2"
chown -R --reference="$1" "$2"
if [[ $SELINUX -eq 1 ]]; then
if [[ $DEBUG -ge 1 ]]; then
ws_event_debug "setownership: SELinux chcon: $2"
chcon -R --reference="$1" "$2"
else
chcon -R --reference="$1" "$2" 2>/dev/null
fi
fi
fi
else
# Should work on some BSDs (including Mac OS X)
local REFOWNER=`stat -f %u "$1"`
local REFGROUP=`stat -f %g "$1"`
if [[ $2 =~ \/$ ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_debug "setownership: BSD: contents: $2"
chown -R $REFOWNER:$REFGROUP "$2"/*
else
[[ $DEBUG -ge 1 ]] && ws_event_debug "setownership: BSD: $2"
chown -R $REFOWNER:$REFGROUP "$2"
fi
fi
}
# We want to know if date will accept the --date switch (GNU coreutils), or if we can use php to do the same; this unlocks functionality for parsing user-supplied dates
CAN_STRTOTIME=0; GNU_DATE=0
$DATE --version 2>/dev/null | grep -q GNU && GNU_DATE=1
[[ $GNU_DATE -eq 1 || -n $PHP ]] && CAN_STRTOTIME=1
function ws_strtotime() {
if [[ $GNU_DATE -eq 1 ]]; then
date --date="$1" +%s
elif [[ -n $PHP ]]; then
$PHP -r "print strtotime(\"$1\");"
else
echo "error";
fi
}
# The follow function converts UNIX (epoch) times into the given format. The reason for its existence is because --date=@ is not part of POSIX.
# Input: $1 = epoch time, $2 = output format (accepted by the system's date binary)
function ws_date_from_epoch() {
if [[ ${BASH_VERSINFO[0]} -ge 4 && ${BASH_VERSINFO[1]} -ge 2 ]]; then
printf "%($2)T" "$1"
elif [[ $GNU_DATE -eq 1 ]]; then
date --date=@"$1" +"$2"
else
date -r "$1" +"$2"
fi
}
# The following is optional; it aims to obtain terminal codes to turn on and off bold output
BOLD=""
OFFBOLD=""
TPUTTEST=`tput cols 2>/dev/null || echo 0`
TPUT_BOLD="bold"
TPUT_OFFBOLD="sgr0"
if [[ $UNAME = "FreeBSD" ]]; then
TPUT_BOLD="md"
TPUT_OFFBOLD="me"
fi
if [[ $TPUTTEST -gt 0 && -n $TERM && $TERM != "dumb" ]]; then
BOLD=`tput $TPUT_BOLD`
OFFBOLD=`tput $TPUT_OFFBOLD`
fi
# Default location of the WordPress content directory. Can be over-ridden with --contentdir= or a site option
DEFAULT_CONTENTDIR=wp-content
# * * * END CONFIGURATION * * * #
WORDSHELL_PID=$$
CONTENTDIR=$DEFAULT_CONTENTDIR
# The next function (only) is under the MIT licence
# http://blog.yjl.im/2012/01/printing-out-call-stack-in-bash.html
function ws_callstack() {
local i=0
local FRAMES=${#BASH_LINENO[@]}
# FRAMES-2 skips main, the last one in arrays
for ((i=FRAMES-2; i>=0; i--)); do
echo ' File' \"${BASH_SOURCE[i+1]}\", line ${BASH_LINENO[i]}, in ${FUNCNAME[i+1]}
# Grab the source code of the line
sed -n "${BASH_LINENO[i]}{s/^/ /;p}" "${BASH_SOURCE[i+1]}"
done
}
function ws_logger() {
# Code everywhere calls into here (eventually) to log an event.
# The task here is to decide what to do with it. This can be outputting on the screen, logging, syslog, etc.
# If/when adding syslog, note that whilst POSIX specifies no parameters for logger, Linux/Net/Open/FreeBSD/Solaris/Mac all accept -i -p and -t
# Input:
# $1 = log level
# $2 = log message
# Screen printing: all ERROR and WARNING level messages; and everything when in debug mode
if [[ ( $1 = "ERROR" || $1 = "WARNING" ) || $DEBUG -ge 1 ]]; then
# Use bold only with ERROR/WARNING
[[ ( $1 = "ERROR" || $1 = "WARNING" ) ]] && echo -n "${BOLD}" >/dev/stderr
echo -n "$1: " >/dev/stderr
[[ ( $1 = "ERROR" || $1 = "WARNING" ) ]] && echo -n "${OFFBOLD}" >/dev/stderr
echo "$2" >/dev/stderr
fi
# File logging: all ERROR, WARNING and NOTICE messages
if [[ $1 = "ERROR" || $1 = "WARNING" || $1 = "NOTICE" ]]; then
# Log if the working directory path is now known, and if the log file location is not a symlink
[[ -n $WORKINGDIR_FULL && ! -L $WORKINGDIR_FULL/log ]] && echo "`$DATE +'%Y-%b-%d %T'` [$WORDSHELL_PID] $1: $2" >> "$WORKINGDIR_FULL/log"
fi
}
function ws_event_error() {
# "Non-urgent failures, these should be relayed to developers or admins; each item must be resolved within a given time."
ws_logger ERROR "$1"
}
function ws_event_warning() {
# "Warning messages, not an error, but indication that an error will occur if action is not taken, e.g. file system 85% full - each item must be resolved within a given time."
ws_logger WARNING "$1"
}
function ws_event_notice() {
# "Events that are unusual but not error conditions - might be summarized in an email to developers or admins to spot potential problems - no immediate action required."
ws_logger NOTICE "$1"
}
function ws_event_info() {
# "Normal operational messages - may be harvested for reporting, measuring throughput, etc. - no action required."
# If $2 is 1, then also echo to the screen
[[ $2 = "1" ]] && echo "$1"
ws_logger INFO "$1"
}
function ws_event_debug() {
# "Info useful to developers for debugging the application, not useful during operations."
ws_logger DEBUG "$1"
}
if [ "$1" = "--help" -o "$1" = "-?" -o "$1" = "-h" ]; then
echo "WordShell: www.wordshell.net (full manual and ticket system available online)
Tool for managing WordPress via the command line. Can operate on a WP install on the local filesystem or via FTP or SFTP. Various binaries are needed for full functionality; lftp in particular for FTP/SFTP; run with --checkrequirements for more information.
Usage: wordshell [<site-name>|all] [<plugin|theme|file|all>] (options)
Site names are aliases for sites whose fuller details are stored in the configuration file. If you enter an unknown site name, you will be requested for the site details and these will be added to the configuration file. You can specify multiple sites, separating them with commas.
If you enter the site name 'all', then all sites that are in the configuration file will be processed in alphabetical (e.g. wordshell all --listupdates). Add --reverse to do them in reverse order (or --random for random order, where supported).
If you enter the plugin/theme as 'all', then all the plugins/themes on the indicated site are processed (e.g. wordshell mysite all --update --latest)
Backups are taken and versions are compared. If there any potential problems spotted then user interaction is required.
${BOLD}Meta-mode selection:${OFFBOLD}
--plugin : Work on a plugin (this is the default mode, and does not need to be manually specified)
--theme : Work on a theme (e.g. wordshell mysite twentyten --delete --theme)
--content : Work with WordPress file content (including uploaded media). Can be used with --list, --refreshcache, --login and --restore. Most useful for site backup (--refreshcache) and restoration (--restore). If you use --list, then the second parameter to WordShell is a regex to match, e.g. 'wordshell mysite index.php --list'.
--core : Work on WordPress core. Not all operations make sense in core mode.
--everything | -e : Precisely equivalent to running each of the above in sequence. Again, this does not make sense with every combination of options. It is most useful with --list or --listupdates.
--pluginsandthemes : Precisely equivalent to running with --plugin and --theme in sequence. Again, this does not make sense with every combination of options. It is most useful with --list or --listupdates.
--user : User management (incomplete)
${BOLD}Modes for performing plugin/theme/core operations (mostly mutually exclusive - you can only choose one mode at once):${OFFBOLD}
--activate : Activate the specified plugin/theme (can be combined with install/update/rollback/restore)
--deactivate : Deactivate the specified plugin/theme (can be combined with update/rollback/restore; is automatically implied with --delete). These two switches are not applicable in core mode.
--changelog : Show the changelog for the indicated entity (requires w3m). If you also specify a site then after showing the changelog, move forward to the delete/install/rollback/update (otherwise stop).
--description : Show the description for the indicated entity (requires w3m). If you also specify a site then after showing the description, move forward to the delete/install/rollback/update (otherwise stop).
--lastupdated : Show the last upstream updated date for the indicated entity (plugins/themes from wordpress.org only)
--downloadurl : Show the download URL for the indicated entity. If you also specify a site then after showing the description, move forward to the delete/install/rollback/update (otherwise stop).
--checkmodifications: Check entity for modifications compared with pristine version. This check happens automatically when updating; this switch is only for when you wish to do no other actions. If you wish to view the modifications, then specify --checkmodifications=view. Add showall to show status of all entities, whether modified or not (e.g. --checkmodifications=showall or --checkmodifications=showall,view). Note that when working with --core, only core WordPress files are checked; any additional files in your WP directory will be ignored (which includes wp-config.php).
--delete : Delete mode: does not ask for or upload new plugins/themes, simply deletes the files of the old one (WordPress will display an error next time you log in). Implies --deactivate unless you specify --skipdeactivate
--downloadonly : Download the specified entity from wordpress.org (into our internal cache, inside the working directory); do not update anything
--editconfig : Opens up the WordShell configuration in the configured text editor
--install : Install mode (uploads plugin; add --activate to also activate it)
--list : List installed plugins on the site. If you specify a plugin name, then this is used as an extended regex for matching; use --listexact to require an exact match. When the list is shown, a (-) next to the plugin name implies that the plugin could not be found at wordpress.org (hence either removed, or was third-party).
--login : Drops you into a shell or (S)FTP prompt in the site's WordPress installation root
--rollback : Roll back to the most recently replaced version. If you optionally specify a time, then this will instead then automatically select the version of the plugin installed at that time, as far as we know. If you have GNU date (coreutils) installed, then any string supported by the 'date' command can be used here; e.g. --time='last Tuesday', --time='10 days ago', --time='24 Feb'. Otherwise, you must supply an epoch time. Use --listrollbacks to see what is available.
--restore : Restores the current copy of the indicated entities. This differs from --rollback in that it restores the believed-to-be-current version from our local cache/backup (rather than any previously replaced version). This implies --cache --cache (as otherwise, if the site has been damaged, we would first replicate that damage to the local cache) and then you'd need to head to your backups). In fact, we assume that your site is damaged, and drop all sanity checks related to the currently installed entity and the backup you are replacing it from. With an undamaged site and up-to-date cache, --restore should result in no real changes. When operating upon core, this will restore whatever you backed up (see --justwp). If you wish to restore the backup to a different location (i.e. not the site's regular home), then supply a parameter, e.g. --restore=/var/www/newlocation (accepts both file paths and FTP/SFTP URLs - specify the path for the WordPress base; do not include wp-content/plugins etc.).
--update : Upgrade mode (this is the default if no other mode is specified; you only need to specify it manually if combining with --activate (--activate without --update does not perform a plugin update)). --upgrade is a synonym.
${BOLD}Database operations (--database)${OFFBOLD}:
--searchandreplace=<search>^<replacement> : Search and replace all WordPress tables in the database (which is defined as all those matching your \$table_prefix in wp-config.php). Uses the code under licence from the popular searchreplacedb2.php (http://interconnectit.com/products/search-and-replace-for-wordpress-databases/). Example: --searchandreplace=http://localhost/test^http://example.com
--tables=<list> : A list of tables for --searchandreplace to apply to. Tables should be supplied *without* prefixies and be comma-separated, e.g. --tables=options,usermeta,postmeta
${BOLD}User management operations (--user)${OFFBOLD}
--add : Adds a user. You must then supply the parameter --email=<address> and optionally --role=<role> (e.g. --role=admin)
--delete : Deletes the specified user (may be specified by username or by email address). Add the optional paramater --reassign=<username|email|@admin> to reassign all the user's posts/comments/etc. to a specified user, or to the first admin found (or leave out this parameter to just delete them).
--list : Lists existing users. Add --detailed for more details
--passwordreset : The specified user will have their password reset to a new, random password. In this case you can also specify the user by email address (e.g. wordshell mysite bob@example.com --passwordreset)
${BOLD}Modes for performing internal management/site management/checks (mutually exclusive - you can only choose one mode at once):${OFFBOLD}
--checkrequirements: Checks that all possible helpers are available (lftp, rsync, diff, patch, unzip, vi, w3m), then exits
--entermaintenance : Enter maintenance mode (add a parameter to specify a number of minutes, e.g. --entermaintenance=10; otherwise is indefinite)
--exitmaintenance : Leave maintenance mode
--waitmaintenance : Wait until the site is no longer in maintenance mode (but do not clear it ourselves)
--listsites : Show information on configured sites
--showpass : When used with --listsites, shows the passwords for (S)FTP sites
--maintenancestate : Show maintenance mode status
--addsite : Configure a new site (equivalent to specifying 'new' as the site and listing (--list))
--delsite : Permanently remove a site from the configuration file (but do not touch the site itself)
--disablesite : Disable a site in the configuration file (but do not permanently remove it)
--definegroup : Define a group of sites: syntax is --definegroup=<groupname>:<list> See the section on specifying multiple sites below. The list can be any list of sites or groups as described there. Therefore, the way to add a site to a pre-existing group is, for example, --definegroup=mygroup:@mygroup,newsite; and the way to remove it is --definegroup=mygroup:@mygroup,-newsite. Group lists are evaluated immediately (i.e. the definition you gave is not stored, only the result).
--delgroup : Delete a defined group (not the sites themselves): syntax is --delgroup=<groupname>. This differs from --definegroup=<groupname>:-@groupname, which empties but does not delete the group.
--listgroups : List all defined groups in the configuration file, together with their members.
--enablesite : Enable a previously-disabled site in the configuration file.
--showlog : Show the WordPress log (the file 'log' from the configuration directory)
--visit : Launch the site in a web browser
The following are also valid with --database:
--refreshcache : Ensure freshness of our local cached copy of the site, then exit. You could use this to prime all the caches for quicker subsequent operation (e.g. in a cron job before your working day starts). If the cache was recently updated, then this is a no-op; to enforce an update, add --sync.
--listrollbacks : Show possible rollbacks for a given site (and plugin/theme, if indicated)
--deleterollbacks : Deletes all stored rollbacks older than 90 days, or the specified number of days (e.g. --deleterollbacks=45)
${BOLD}Mode-specific options:${OFFBOLD}
${BOLD}Order of operations when working with multiple sites:${OFFBOLD}
--groupbytype : If you choose to operate on all sites together with --everything or --pluginsandthemes, then this will order the operations/output by type (i.e. do the plugins on all sites, then the themes, then the core), instead of the default (which is to do all the plugins, then themes, then core on a single site, then move on to the next site, etc.)
${BOLD}Core operations:${OFFBOLD}
--justwp : By default, when working with WordPress core, WordShell will ensure it has a backup of any files in your WordPress directory. This guarantees safety for disaster recovery (though note that current versions of WordShell only back up plugins and themes from your content directory (usually wp-content), and nothing else from in there). However, it can be slow. If you know that you either have nothing else stored there, or that it does not need backing up, then you can supply this swtich. Note that this will NOT delete existing non-WP files from the local mirror (i.e. backups made when --justwp was not used). For that, add --justwpwipeothers.
--justwpwipeothers : This flag means the same as --justwp, except that back-ups of existing files that are not part of WordPress core will be deleted from the local mirror.
--filemethod : When upgrading to the latest WordPress version, by default WordShell will try to use WordPress's built-in updating procedures, running on the remote website. Use this switch to instead upgrade via the filesystem. The built-in method is faster; files are more reliable if your site is broken and sometimes the only option if your web host has a very restrictive set-up. If you are not upgrading to the latest version, then this switch is redundant, as it is the only available method.
${BOLD}Install and update modes:${OFFBOLD}
--latest : Synonym for --new=latest
--new=<file|version>: Either specify a particular version that can be downloaded from wordpress.org (e.g. --new=1.3.2), or a version of an already-known plugin/theme, or specify a path to a zip file to use (e.g. --new=myfolder/myplugin.1.9.zip). Only valid when a particular plugin/theme is specified. If you enter 'latest' then the latest version available from wordpress.org will be downloaded.
--trunk : When looking for the latest version, look in the development trunk, not at stable released versions (only relevant for core and for themes/plugins from wordpress.org). This will always require downloading from the trunk, so brings some slow-down.
--nolint : Skip PHP linting (checking of the syntax of PHP files with the -l option to PHP)
${BOLD}Update mode:${OFFBOLD}
--donotrequirenew : Do not abort the update if the chosen new plugin/theme's version number is not newer than that currently installed (manual confirmation will still be required)
--autopatch : If the installed and plain versions differ, then automatically attempt to patch the new version appropriately (without asking first).
--ignorenamechange : Usually WordShell asks for user confirmation if the plugin's name has changed. This switch will skip the request.
--ignoremodifications: Skip the step of checking for plugin modifications
${BOLD}Delete, install, update, rollback and deleterollbacks modes:${OFFBOLD}
--confirm : Ask an extra \"are you sure?\" question before going forward; this is useful if you want to use --changelog or --showchanges and perhaps back out if you do not like what you see.
--dryrun | -n : Skip the step of installing/deleting/updating/rolling back/(de)activating; only notify what would have been done
--thisoneonly : If the specified plugin/theme does not exist, then do not present a menu of existing plugins/themes; just finish
--skipdeactivate : In delete mode, skip the deactivation step
${BOLD}Update, rollback and restore modes:${OFFBOLD}
--showchanges : Show changes from the installed plugin/theme to the indicated version. If you want to be asked to confirm the update after seeing the changes, use --confirm.
--noconfirmpristine: Skip requesting confirmation when no pristine zip was found to compare an installed version with
${BOLD}List mode:${OFFBOLD}
--listonlythirdparty: List only plugins/themes which were not found at wordpress.org (hence either removed, or obtained from elsewhere) (implies --list)
--listonlyupdates : List only installed plugins/themes for which an update is known to be available (implies --list)
--warnmissing : Prints a warning if no plugin/theme was found
--listexact : Turns off regex matching when listing - only list plugins that match the name exactly
${BOLD}List, update, restore and delete modes:${OFFBOLD}
--active : Operate on plugins/themes only if they are active
--inactive : Operate on plugins/themes only if they are inactive
--currentversion=<ver>: Operate on entities only if the installed version matches this one. You can use --cver= as a shorter alias. This switch is also available in rollback mode. Prefix the version with x to negate the test, e.g. --cver=x3.3 means \"not version 3.3\".
--fast : Decide which sites to work on based upon WordShell's internal cache of sites (the effect of this option is to cut out the sync-ing of all sites, if you have chosen to work upon all sites)
--nochecksum : If files are being sycned over HTTP, then don't (internally) ask for and check the checksum. This is intended only for if you hit a PHP execution timeout and can't increase it.
${BOLD}Activate and deactivate modes:${OFFBOLD}
--network : When used with --activate on a WP multisite installation, performs \"network activation\" of the plugin (see http://codex.wordpress.org/Multisite_Network_Administration#Plugins)
--blog=<blog> : On a WP multisite installation, operates upon the specified blog instead of upon the default blog.
${BOLD}Verbosity and logging options:${OFFBOLD}
--debug | -d : Print lots of internal information. Specify twice or thrice for even more verbosity.
--showdiskspace : Show disk usage message at the end (only valid for update/install/rollback/restore)
--novaliditywarnings: Do not show warnings about plugin/theme sub-directories in which a plugin/theme could not be detected
${BOLD}General options:${OFFBOLD}
--workdir=<dir> : Working directory to use (internal data is stored here)
--contentdir=<path>: Relative path to the installation's content directory. Defaults to: wp-content. If you have a site on which the content directory is elsewhere, then you will want to specify this as a site option.
${BOLD}Debugging options:${OFFBOLD}
--debug : Print lots of internal information. Specify twice for even more verbosity.
--disablecache : Disables relying on state of local caches (e.g. of WordPress API calls). Implies --sync.
--disableapi : Do not use WordPress's API (http://api.wordpress.org) for any operations. This is mainly useful for debugging.
--disableremotecli : Miss out some (not guaranteed to be all) remote CLI calls
--disableftptls : For FTP sites, explicitly disables use of encryption (lftp option: ftp:ssl-allow no)
--requireftptls : For FTP sites, explicitly requires use of encryption (lftp option: ftp:ssl-force yes)
--ftpparallel=<num>: Maximum number of parallel FTP connections to use (lftp switch: --mirror=)
--sync : For (S)FTP sites, force initial sync of plugins/themes directory (i.e. do not skip sync if it had been synced recently). Use this if you know your local cache is out of date (e.g. a manual change was recently made at the remote side). See also --refreshcache.
--cache : For (S)FTP sites, force skipping initial sync of plugins/themes directory; use with caution. If specified twice, then does so also for filesystem mode sites.
--lftpdryrun : Specify --dry-run when doing an LFTP mirror operation.
--no(lftp|wget|curl|w3m|elinks|rsync): Behave as if the indicated binary does not exist on the system
--nosslverify : Do not verify SSL certificates
--nositetest : Do not check on whether the site is reachable without an HTTP error before/after installing/upgrading etc.
--phpinfo : Get the phpinfo() output from the remote side
--phpversion : Get the phpversion() output from the remote side
--mysqlversion : Get the MySQL server version (mysql_get_server_info()) output from the remote side
--skipmakerollback : When deleting or updating, skip the creation of a rollback file (actually just skips some time-consuming step)
--testurl : Tests to see if a WordPress site exists
--remotecli : Drops into a CLI that allows you to run internal commands against the remote site. This mode is not officially supported; it is an internal interface which is not guaranteed between versions. Use --remotecli=<command> to run command non-interactively (can specify multiple times).
--workdir : Working directory to use (internal data is stored here)
${BOLD}Configuration management:${OFFBOLD}
--setconfig : Set global configuration parameters, e.g. --setconfig=key:value. To set multiple parameters, separate with a comma, e.g. --setconfig=key1:value1,key2:value2
--getconfig : Read global configuration parameters, e.g. --getconfig=key1,key2,key3. Leave blank to show all.
There are also per-site configuration options; these are configured in the configuration file using a key of 'siteopt', followed by the site name, followed by the option (only one per line).
${BOLD}Global variables that can be configured:${OFFBOLD}
ftpparallel : Default number of threads for lftp to use (lower this if you consistently find sites rejecting your connections for having too many at once). Can be over-ridden on a per-site basis if you add a per-site option in the configuration file.
postfilecommand : A command that is run after newly installing plugins/themes in the filesystem, after selecting the new plugin/theme directory. e.g. \"chmod 700 .\" Do not use to perform a chown operation - that is already done automatically.
exclude-glob-ftp : A glob to match for files that should never be synced locally (e.g. if some plugin/theme stores an enormous amount of useless temporary data). Specify in a form compatible with lftp's --exclude-glob option and separate multiple globs with spaces.
exclude-glob-rsync : A glob to match for files that should never be synced locally (e.g. if some plugin/theme stores an enormous amount of useless temporary data). Specify in a form compatible with rsync's --exclude option and separate multiple globs with spaces.. If rsync is not installed, then this option has no effect (and thus such files will be synced).
curl-ftp-options : Command line options to add when curl is called for an FTP operation. Read the curl ftp man page for more information. Useful if you have some exotic authentication scheme. (To set lftp over-ride options, edit the file lftp/rc in your WordShell working directory).
${BOLD}Specifying multiple sites${OFFBOLD}
WordShell allows you to specify one site, a list of sites, or to pre-define groups of sites (see the --definegroup, --delgroup and --listgroups options). You can specify arbitrarily long lists, and include or exclude individual sites or groups at will. Lists are separated with commas. Groups are indicated by prefixing a @, and exclusions (whether of an individual site or group) are indicated with a prefixed minus sign. Lists are parsed from left to right, and a site is finally included if and only if was included at some point and not subsequently excluded. Disabled sites are also excluded, except if the chosen mode was --listsites or --enablesite. Group names must be alphanumerics only. You can always test out the effect of any list by using --listsites.
Example:
wordshell mysite,@mygroup,-site2,-@group2,site3 --listsites
The above command would include the site mysite, then the sites in group mygroup, then remove site2 and all sites in group2, and finally add in site3.
The default working directory is $WORKDIR and the default configuration file is $CONFIGFILE.
"
exit 0;
fi
MODE="normal"
ORIGINAL_PARAMS="$@"
ORIGINAL_PARAMS_COUNT=${#@}
if [[ $ORIGINAL_PARAMS_COUNT -eq 0 ]]; then
MODE="addsite"
PLUGIN=""
elif [[ ${1:0:1} != "-" ]]; then
SITE=$1
if [ -n "$SITE" ]; then
shift
if [ "${1:0:1}" != "-" ]; then
PLUGIN=$1
[[ -n $PLUGIN ]] && shift
fi
fi
fi
function abort_die() {
local ERR_CODE=$1
echo -n "${BOLD}ABORT:${OFFBOLD} " >/dev/stderr
ws_event_error "$2"
ws_event_debug "WordShell internal abort code: $ERR_CODE"
exit $ERR_CODE
}
function options_abort() {
abort_die 40 "$1"
}
function mode_switch() {
if [ "$MODE" = "normal" ]; then
MODE=$1
elif [ "$MODE" != "$1" ]; then
# Don't whinge if they just specified the same thing twice
options_abort "Incompatible options; cannot specify both --$MODE and --$1"
fi
}
function metamode_switch() {
if [[ $METAMODE = "plugin-default" ]]; then
METAMODE=$1
METAMODE_DIR=${1}s
if [[ $1 = "core" ]]; then
METAMODE_DIR="core"
# We use this flag to work out, when changing core, if the zip we've been given is a full one or simply a collection of changes
CHOSEN_CORE_IS_OVERLAY=0
elif [[ $1 = "content" || $1 = "database" ]]; then
METAMODE_DIR=$1
fi
elif [[ $METAMODE != $1 ]]; then
options_abort "Incompatible options; cannot specify both --$METAMODE and --$1"
fi
# Bash 3.2 does not have ^ substitutions
METAMODE_CAP=`echo -n ${METAMODE:0:1} | tr '[:lower:]' '[:upper:]'; echo ${METAMODE:1}`
}
METAMODE="plugin-default"
METAMODE_CAP="Plugin"
METAMODE_DIR="plugins"
DEBUG=0
CACHE=0
DISABLECACHE=0
SYNC=0
DISABLEAPI=0
SHOWDISKSPACE=0
USEZIP=""
THISONEONLY=0
REQUIRENEWVERSION=1
CHECKREQUIREMENTS=0
LISTEXACT=0
VALIDITYWARNINGS=1
WARNMISSING=0
LISTONLYUPDATES=0
ONLYACTIVE=0
ONLYINACTIVE=0
IREALLYMEANIT=0
LISTONLYTHIRDPARTY=0
DRYRUN=0
DETAILED=0
SHOWCHANGELOG=0
SHOWDESCRIPTION=0
SHOWLASTUPDATED=0
SHOWDOWNLOADURL=0
SHOWCHANGES=0
AUTOPATCH=0
DEACTIVATE=0
SKIPDEACTIVATE=0
ACTIVATE=0
GROUPBYTYPE=0
GETCONFIG=""
JUSTWP=0
JUSTWPWIPEOTHERS=0
SHOWPASS=0
SETCONFIG=""
TESTURL=0
DISABLEFTPTLS=0
REQUIREFTPTLS=0
ROLLBACKTIME="x"
# By default, maintenance mode is indefinite
unset MAINTENANCE_MINUTES
DELETEROLLBACKDAYS=90
CHECKMODIFICATIONS_VIEW=0
CHECKMODIFICATIONS_SHOWALL=1
GETCONFIRM=0
USETRUNK=0
REMOTECLI_NONINTERACTIVE=()
# An internal switch that is merely used to prevent unnecessary duplicate operations (e.g. clearing up tmp)
SELFINVOCATION=0
FILEMETHOD=0
SKIPMAKEROLLBACK=0
LFTPDRYRUN=0
CURRENTVERSION=""
DISABLEREMOTECLI=0
DISABLESITEMODE="disable"
DOSITETEST=1
IGNORENAMECHANGE=0
CONFIRM_WHEN_NO_PRISTINE=1
RESTOREPATH=""
POSTS_REASSIGN=""
PLUGIN_MODE_EXPLICIT=0
GROUP_DEFINES=""
GROUP_DELETES=""
REMOTE_PHPVERSION=""
REMOTE_WPVERSION=""
USER_EMAIL=""
USER_ROLE=""
FAST=0
PHPLINT=1
SORTORDER=0
CALCULATE_CHECKSUM=1
ALREADY_LINTED=0
NETWORK_PLUGIN=0
NETWORK_BLOGID=-1
TABLELIST=""
SSLVERIFY=1
IGNOREMODIFICATIONS=0
function parse_option() {
# Input: $1: option to parse
local i=$1
# Put this in a variable for bash 3.2 compatibility
local SEARCHREPLACEREGEX="^--searchandreplace=(.*)\^(.*)$"
if [[ $i = "--debug" || $i = "-d" ]]; then DEBUG=$((DEBUG+1))
elif [[ $i = "--everything" || $i = "-e" ]]; then metamode_switch everything
elif [[ $i = "--dryrun" || $i = "--dry-run" || $i = "-n" ]]; then DRYRUN=1
elif [[ $i =~ ^-([a-zA-Z].*$) ]]; then options_abort "Unknown option: $i: Note that WordShell options generally use double-dashes (--). Perhaps you meant --${BASH_REMATCH[1]} instead."
elif [ "$i" = "--plugin" ]; then metamode_switch plugin; PLUGIN_MODE_EXPLICIT=1
elif [ "$i" = "--theme" ]; then metamode_switch theme
elif [ "$i" = "--core" ]; then metamode_switch core
elif [[ $i = "--database" || $i = "--db" ]]; then metamode_switch database
elif [[ $i = "--pluginsandthemes" ]]; then metamode_switch pluginsandthemes
elif [[ $i = "--user" ]]; then metamode_switch user
elif [[ $i = "--content" ]]; then metamode_switch content
elif [ "$i" = "--cache" ]; then CACHE=$((CACHE+1))
elif [ "$i" = "--sync" ]; then SYNC=1
elif [ "$i" = "--deactivate" ]; then DEACTIVATE=1
elif [ "$i" = "--activate" ]; then ACTIVATE=1
elif [ "$i" = "--active" ]; then ONLYACTIVE=1
elif [ "$i" = "--fast" ]; then FAST=1
elif [ "$i" = "--inactive" ]; then ONLYINACTIVE=1
elif [ "$i" = "--version" ]; then echo $VERSION; exit
elif [ "$i" = "--changelog" ]; then SHOWCHANGELOG=1
elif [ "$i" = "--description" ]; then SHOWDESCRIPTION=1
elif [ "$i" = "--lastupdated" ]; then SHOWLASTUPDATED=1
elif [ "$i" = "--downloadurl" ]; then SHOWDOWNLOADURL=1
elif [ "$i" = "--checkmodifications" -o "$i" = "--checkmod" ]; then mode_switch "checkmodifications"; CHECKMODIFICATIONS_SHOWALL=0
elif [ "$i" = "--checkmodifications=view" -o "$i" = "--checkmod=view" ]; then mode_switch "checkmodifications"; CHECKMODIFICATIONS_VIEW=1; CHECKMODIFICATIONS_SHOWALL=0
elif [ "$i" = "--checkmodifications=showall" -o "$i" = "--checkmod=showall" ]; then mode_switch "checkmodifications";
elif [ "$i" = "--checkmodifications=view,showall" -o "$i" = "--checkmod=view,showall" -o "$i" = "--checkmodifications=showall,view" -o "$i" = "--checkmod=showall,view" ]; then mode_switch "checkmodifications"; CHECKMODIFICATIONS_VIEW=1;
elif [ "$i" = "--showchanges" ]; then SHOWCHANGES=1
elif [ "$i" = "--getconfig" ]; then GETCONFIG="all"
elif [ "$i" = "--addsite" ]; then mode_switch "addsite";
elif [ "$i" = "--enablesite" ]; then mode_switch "enablesite";
elif [[ $i = "--add" ]]; then mode_switch "add"
elif [[ $i =~ ^--entermaintenance=([0-9]+)$ ]]; then mode_switch "entermaintenance"; MAINTENANCE_MINUTES=${BASH_REMATCH[1]}
elif [[ $i =~ ^--ftpparallel=([0-9]+)$ ]]; then FTPPARALLEL=${BASH_REMATCH[1]}
elif [ "$i" = "--checkrequirements" ]; then CHECKREQUIREMENTS=1
elif [ "$i" = "--install" ]; then mode_switch "install"
elif [ "$i" = "--ireallymeanit" ]; then IREALLYMEANIT=1
elif [ "$i" = "--thisoneonly" ]; then THISONEONLY=1
elif [ "$i" = "--autopatch" ]; then AUTOPATCH=1
elif [ "$i" = "--detailed" ]; then DETAILED=1
elif [ "$i" = "--selfinvoked" ]; then SELFINVOCATION=1
elif [ "$i" = "--selfinvoked=e" ]; then SELFINVOCATION=2
elif [ "$i" = "--confirm" ]; then GETCONFIRM=1
elif [ "$i" = "--nosslverify" ]; then SSLVERIFY=0
elif [ "$i" = "--list" ]; then mode_switch "list"
elif [ "$i" = "--listrollbacks" ]; then mode_switch "listrollbacks"
elif [ "$i" = "--listsites" ]; then mode_switch "listsites"
elif [ "$i" = "--showpass" ]; then SHOWPASS=1
elif [ "$i" = "--rollback" ]; then mode_switch "rollback"; ROLLBACKTIME="most recent"; REQUIRENEWVERSION=0
elif [ "${i:0:11}" = "--rollback=" ]; then mode_switch "rollback"; ROLLBACKTIME=${i:11}; REQUIRENEWVERSION=0
elif [ "$i" = "--restore" ]; then mode_switch "restore"; CACHE=2;
elif [ "${i:0:10}" = "--restore=" ]; then mode_switch "restore"; RESTOREPATH=${i:10}; CACHE=2; [[ $RESTOREPATH =~ ^/ || $RESTOREPATH =~ ^s?ftps?: ]] || options_abort "Restoration paths need to be absolute, not relative (i.e. must begin with /); you may want --restore=`pwd`/$RESTOREPATH"
elif [ "$i" = "--listexact" ]; then LISTEXACT=1
elif [ "$i" = "--listonlyupdates" -o "$i" = "--listupdates" -o "$i" = "--listupdatesonly" ]; then mode_switch "list"; LISTONLYUPDATES=1
elif [ "$i" = "--listonlythirdparty" -o "$i" = "--listthirdpartyonly" ]; then mode_switch "list"; LISTONLYTHIRDPARTY=1
# We switch back into normal mode later; this is just so that we know it was explicitly specified on the command line
elif [[ $i = "--update" || $i = "--upgrade" ]]; then mode_switch "update"
elif [ "$i" = "--login" ]; then mode_switch "login"
# We do these instantly to allow multiple options
elif [[ $i =~ ^--def(ine)?group=([A-Za-z0-9]+):(.*)$ ]]; then GROUP_DEFINES="$GROUP_DEFINES ${BASH_REMATCH[2]}:${BASH_REMATCH[3]}"
elif [[ $i =~ ^--del(ete)?group=([A-Za-z0-9]+)$ ]]; then GROUP_DELETES="$GROUP_DELETES ${BASH_REMATCH[2]}"
elif [ "$i" = "--entermaintenance" ]; then mode_switch "entermaintenance";
elif [ "$i" = "--waitmaintenance" ]; then mode_switch "waitmaintenance";
elif [ "$i" = "--exitmaintenance" ]; then mode_switch "exitmaintenance";
elif [ "$i" = "--maintenancestate" ]; then mode_switch "maintenancestate";
elif [[ $i =~ $SEARCHREPLACEREGEX ]]; then mode_switch "searchandreplace"; SEARCHANDREPLACE_SEARCH=${BASH_REMATCH[1]}; SEARCHANDREPLACE_REPLACE=${BASH_REMATCH[2]}
elif [ "$i" = "--downloadonly" ]; then mode_switch "downloadonly";
elif [ "$i" = "--deleterollbacks" ]; then mode_switch "deleterollbacks";
elif [[ $i =~ ^--deleterollbacks=([0-9]+)$ ]]; then mode_switch "deleterollbacks"; DELETEROLLBACKDAYS=${BASH_REMATCH[1]};
elif [[ $i =~ ^--email=(.*)$ ]]; then USER_EMAIL=${BASH_REMATCH[1]};
elif [[ $i =~ ^--reassign=(.*)$ ]]; then POSTS_REASSIGN=${BASH_REMATCH[1]};
elif [[ $i =~ ^--role=(.*)$ ]]; then USER_ROLE=${BASH_REMATCH[1]};
elif [ "$i" = "--listgroups" ]; then mode_switch "listgroups"
elif [ "$i" = "--novaliditywarnings" ]; then VALIDITYWARNINGS=0
elif [[ $i = "--delete" || $i = "--del" ]]; then mode_switch "delete"
elif [ "$i" = "--showdiskspace" ]; then SHOWDISKSPACE=1
elif [ "$i" = "--reverse" ]; then SORTORDER=1
elif [ "$i" = "--random" ]; then SORTORDER=2
elif [ "$i" = "--passwordreset" ]; then mode_switch "passwordreset"
elif [ "$i" = "--nocountdiskspace" ]; then SHOWDISKSPACE=0
elif [ "$i" = "--donotrequirenew" ]; then REQUIRENEWVERSION=0
elif [ "$i" = "--phpinfo" ]; then mode_switch "phpinfo"
elif [ "$i" = "--phpversion" ]; then mode_switch "phpversion"
elif [ "$i" = "--mysqlversion" ]; then mode_switch "mysqlversion"
elif [ "$i" = "--remotecli" ]; then mode_switch "remotecli"
elif [ "${i:0:12}" = "--remotecli=" ]; then mode_switch "remotecli"; REMOTECLI_NONINTERACTIVE+=("${i:12}")
elif [ "$i" = "--trunk" ]; then USETRUNK=1
elif [ "$i" = "--justwp" ]; then JUSTWP=1
elif [ "$i" = "--justwpwipeothers" ]; then JUSTWP=1; JUSTWPWIPEOTHERS=1
elif [ "$i" = "--testurl" ]; then TESTURL=1
elif [ "$i" = "--refreshcache" ]; then mode_switch "refreshcache"
elif [ "$i" = "--warnmissing" ]; then WARNMISSING=1
elif [ "$i" = "--groupbytype" ]; then GROUPBYTYPE=1
elif [ "$i" = "--filemethod" ]; then FILEMETHOD=1
elif [ "$i" = "--visit" ]; then mode_switch "visit"
elif [ "$i" = "--network" ]; then NETWORK_PLUGIN=1
elif [ "$i" = "--noconfirmpristine" ]; then CONFIRM_WHEN_NO_PRISTINE=0
elif [ "$i" = "--nolint" ]; then PHPLINT=0
elif [ "$i" = "--nochecksum" ]; then CALCULATE_CHECKSUM=0
elif [ "$i" = "--skipmakerollback" ]; then SKIPMAKEROLLBACK=1
elif [ "$i" = "--disableftptls" ]; then DISABLEFTPTLS=1
elif [ "$i" = "--requireftptls" ]; then REQUIREFTPTLS=1
elif [ "${i:0:7}" = "--blog=" ]; then NETWORK_BLOGID=${i:7}
elif [ "${i:0:17}" = "--currentversion=" ]; then CURRENTVERSION=${i:17}
elif [ "${i:0:7}" = "--cver=" ]; then CURRENTVERSION=${i:7}
elif [ "${i:0:13}" = "--contentdir=" ]; then CONTENTDIR=${i:13}
elif [ "${i:0:12}" = "--getconfig=" ]; then GETCONFIG=${i:12}
elif [ "${i:0:12}" = "--setconfig=" ]; then SETCONFIG=${i:12}
elif [ "${i:0:9}" = "--tables=" ]; then TABLELIST=${i:9}
elif [ "$i" = "--disableremotecli" ]; then DISABLEREMOTECLI=1
elif [ "$i" = "--disableapi" ]; then DISABLEAPI=1
elif [ "$i" = "--disablecache" ]; then DISABLECACHE=1; SYNC=1
elif [ "$i" = "--disablesite" ]; then mode_switch "disablesite"
elif [ "$i" = "--delsite" -o "$i" = "--deletesite" ]; then mode_switch "disablesite"; DISABLESITEMODE="delete"
elif [ "${i:0:9}" = "--config=" ]; then CONFIGFILE=${i:9}
elif [ "${i:0:6}" = "--new=" ]; then if [ -n "$USEZIP" ]; then options_abort "--new has been specified twice (remember that --latest is a synonym for --new=latest)"; else USEZIP=${i:6}; fi
elif [ "$i" = "--latest" ]; then if [ -n "$USEZIP" ]; then options_abort "--new has been specified twice (remember that --latest is a synonym for --new=latest)"; else USEZIP="latest"; fi
elif [ "$i" = "--verbose" ]; then options_abort "There is no option --verbose; did you want --debug?"
elif [ "${i:0:10}" = "--workdir=" ]; then WORKDIR=${i:10}
elif [[ $i = "--editconfig" || $i = "--editrc" ]]; then mode_switch editconfig
elif [ "$i" = "--showlog" ]; then mode_switch showlog
elif [ "$i" = "--noelinks" ]; then ELINKS=""
elif [ "$i" = "--nolynx" ]; then LYNX=""
elif [ "$i" = "--now3m" ]; then W3M=""
elif [ "$i" = "--nocurl" ]; then CURL=""
elif [ "$i" = "--nolftp" ]; then LFTP=""
elif [ "$i" = "--norsync" ]; then RSYNC=""
elif [ "$i" = "--nowget" ]; then WGET=""
elif [ "$i" = "--nositetest" ]; then DOSITETEST=0
elif [[ $i = "--ignorenamechange" ]]; then IGNORENAMECHANGE=1
elif [[ $i = "--ignoremodifications" ]]; then IGNOREMODIFICATIONS=1
elif [ "$i" = "--lftpdryrun" ]; then LFTPDRYRUN=1
elif [ -n "$i" ]; then options_abort "Unknown option: $i"
#else echo "???: $i"
fi
}
for i in "$@"; do
parse_option "$i"
done
[[ $METAMODE = "plugin-default" ]] && METAMODE="plugin"
[[ $DEBUG -ge 1 ]] && ws_event_info "Configuration file in use: $CONFIGFILE ; working directory: $WORKDIR"
# 0 means nothing missing; 1 means something missing; 2 means something missing that will affect functionality
CHECKREQ_RESULTS=0
[[ $DISABLEAPI -eq 1 && $DEBUG -ge 1 ]] && ws_event_debug "Was run with --disableapi; will do best-effort"
function set_debug_level() {
# Input: $1 = debugging level
DEBUG=$1
LFTPVERB=""
WGETVERB=""
LFTPDEBUG=""
CURLVERB="--silent"
if [ $DEBUG -eq 1 ]; then
CURLVERB="-#"
elif [ $DEBUG -eq 2 ]; then
WGETVERB="--server-response"
LFTPVERB="-v"
LFTPDEBUG="-d"
CURLVERB="--verbose"
elif [ $DEBUG -ge 3 ]; then
LFTPVERB="-v"
LFTPDEBUG="-d"
WGETVERB="--server-response --debug"
CURLVERB="--trace-ascii /dev/stderr"
elif [ $DEBUG -eq 0 ]; then
WGETVERB="-q"
fi
if [[ $SSLVERIFY -eq 0 ]]; then
$CURLVERB="$CURLVERB --insecure"
$WGETVERB="$WGETVERB --no-check-certificate"
fi
}
# Initialise values
set_debug_level $DEBUG
# Avoid unpleasant surprises
[[ -z $WORKDIR || $WORKDIR = "/" || $WORKDIR = "//" ]] && abort_die 6 "Working directory must not be empty or /"
# Check that the working directory is not an already-existing non-directory
# We need to do this surprisingly early, as we will soon need lftp to be invoked using our configuration file
[[ -e $WORKDIR && ! -d $WORKDIR ]] && abort_die 24 "Given working directory ($WORKDIR) exists but is not a directory"
# Offer to create working directory if it does not exist
if [ ! -d "$WORKDIR" ]; then
read -p "Given working directory ($WORKDIR) does not exist. This is normal if this is your first run and you should accept the option to create it now. Create? (y to create, other to abort): " -n 1 CWORKDIR
echo
if [[ $CWORKDIR = "y" || $CWORKDIR = "Y" ]]; then
mkdir -p "$WORKDIR" || abort_die 45 "Could not successfully create directory"
else
abort_die 25 "User chose to abort"
fi
fi
# These functions allow us to follow the flow more easily when debugging
function ws_cd() {
[[ $DEBUG -ge 1 ]] && ws_event_debug "ws_cd: (`caller`): $1"
cd "$1" || abort_die 71 "Failed to cd ($1), pwd=`pwd`, caller=`caller`"
}
function ws_pushd() {
if [[ $DEBUG -ge 1 ]]; then
ws_event_debug "ws_pushd: (`caller`): (pwd: `pwd`)"
pushd "$1" >/dev/stderr || abort_die 71 "Failed to pushd ($1), pwd=`pwd`"
else
pushd "$1" >/dev/null || abort_die 71 "Failed to pushd ($1), pwd=`pwd`"
fi
}
function ws_popd() {
if [[ $DEBUG -ge 1 ]]; then
ws_event_debug "ws_popd: (`caller`): (pwd: `pwd`)"
popd >/dev/stderr || abort_die 71 "Failed to popd"
else
popd >/dev/null || abort_die 71 "Failed to popd"
fi
}
# Normalise the location of the configuration file, and make absolute (so it can be accessed from any part of the code)
ws_pushd `dirname "$CONFIGFILE"`
CONFIGFILE=`pwd -P`/`basename "$CONFIGFILE"`
ws_popd
if [ ! -f "$CONFIGFILE" ]; then
read -p "Given configuration file ($CONFIGFILE) does not exist. This is normal if this is your first run and you should accept the option to create it now. Create? (y to create, other to abort): " -n 1 CCONFIG
echo
if [[ $CCONFIG = "y" || $CCONFIG = "Y" ]]; then
touch $CONFIGFILE || abort_die 45 "$CONFIGFILE: Could not successfully create file"
else
abort_die 25 "User chose to abort"
fi
fi
# Enter working directory. All cds after this are relative, thus allowing the user to supply a relative path.
ORIGDIR=`pwd -P` || exit 71
ws_cd "$WORKDIR" || abort_die 26 "Could not enter working directory ($WORKDIR)"
WORKINGDIR_FULL=`pwd`
function get_file_size() {
local FILE=$1
if [[ $UNAME = "Linux" || $UNAME =~ ^Cygwin || $UNAME =~ ^CYGWIN ]]; then
stat -c %s "$FILE"
else
stat -f %z "$FILE"
fi
}
function show_more_less() {
local DIFFTMP=$1
local DIFFLINES=`(wc -l $DIFFTMP || echo 0) | awk '{print $1}'`
if [[ -n $LESS && $DIFFLINES -ge 20 ]]; then
$LESS "$DIFFTMP"
else
more "$DIFFTMP"
fi
}
if [[ $MODE = "showlog" ]]; then
if [[ -f log ]]; then
show_more_less log
else
echo "No log file found - apparently nothing has yet been logged"
fi
exit
fi
# Check for presence of diff/patch; don't complain yet as they may not be needed
$DIFF -v >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working diff not found (looked for: $DIFF; path is: $PATH)"
unset DIFF
CHECKREQ_RESULTS=2
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working diff was found"
fi
$PATCH -v >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working patch not found (looked for: $PATCH; path is: $PATH) - will not be able to copy modifications (if any) you have made"
unset PATCH
CHECKREQ_RESULTS=2
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working patch was found"
fi
PATCHOPT="--quiet"
[[ $DEBUG -eq 1 ]] && PATCHOPT=""
[[ $DEBUG -ge 3 ]] && PATCHOPT="--verbose"
$PHP -v >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working php not found (looked for: $PHP; path is: $PATH) - will not be able to speak with api.wordpress.org and will have to make some guesses"
unset PHP
CHECKREQ_RESULTS=2
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working php was found"
fi
$LESS --version 2>/dev/null >/dev/null
if [ $? -ne 0 ]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working less not found (looked for: $LESS; path is: $PATH) - will instead use 'more' as a pager"
unset LESS
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working less was found"
fi
$VI --version >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working vi not found (looked for: $VI; path is: $PATH) - needed for manual editing of the configuration file (rare)"
unset VI
[[ $CHECKREQ_RESULTS -eq 0 ]] && CHECKREQ_RESULTS=1
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working vi was found"
fi
if [[ $MODE = "editconfig" ]]; then
if [[ -n $VI ]]; then
$VI "$CONFIGFILE"
exit
else
abort_die 82 "No working vi found; needed to edit the configuration file. Run with --checkrequirements for more information."
fi
fi
export LFTP_HOME=$WORKINGDIR_FULL/lftp
# Do we have lftp?
$LFTP -v >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working lftp not found (looked for: $LFTP; path is: $PATH) - will require curl to operate on sites via ftp/sftp (though lftp is faster + recommended); will require wget or curl in order to access api.wordpress.org and avoid making guesses"
unset LFTP
CHECKREQ_RESULTS=1
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working lftp was found"
# First try with quotes, then try without
LFTP_WILL_USE_SSH=`$LFTP -c 'set -a' | grep "^set sftp:connect-program"`
if [[ $LFTP_WILL_USE_SSH =~ \" ]]; then
LFTP_WILL_USE_SSH=`echo $LFTP_WILL_USE_SSH | cut -d \" -f2 | cut -d" " -f1`
else
LFTP_WILL_USE_SSH=`echo $LFTP_WILL_USE_SSH | cut -d" " -f3`
fi
# Only run the test if our attempt to read the configuration worked
if [[ -n $LFTP_WILL_USE_SSH ]]; then
$LFTP_WILL_USE_SSH -V >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_warning "lftp is configured to use the following binary for SFTP sites: $LFTP_WILL_USE_SSH. However, when we tried to run that binary the operation failed; hence you may have trouble with SFTP sites. If you wish to use SFTP then either install ssh, or edit the lftp configuration ($LFTP_HOME/rc or /etc/lftp.conf) to point to the location of an ssh binary (use a line like 'set sftp:connect-program \"/path/to/ssh -a -x\")."
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working $LFTP_WILL_USE_SSH was found (configured in lftp for use with SFTP sites)"
fi
elif [[ $DEBUG -ge 1 ]]; then
ws_event_info "Attempt to read lftp configuration failed"
fi
fi
# Do we have curl?
CURL_CAN_FTP=0
CURL_CAN_SFTP=0
CURL_CAN_FTPS=0
# This one is set later
CURL_CAN_ACCESSMETHOD=0
# Earlier versions of curl 7 returned 2 (same code as for non-understood parameters) with --version. curl 7 was in existence by the year 2000 so we don't need to bother about earlier versions.
CURLTEST=`$CURL --version 2>/dev/null`
if [[ $? -ne 0 && ! $CURLTEST =~ "curl 7" ]]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working curl not found (looked for: $CURL; path is: $PATH) - will not be able to perform certain operations unless wget or lftp is available"
unset CURL
[[ $CHECKREQ_RESULTS -eq 0 ]] && CHECKREQ_RESULTS=1
[[ -z $LFTP ]] && CHECKREQ_RESULTS=2
else
echo "$CURLTEST" | grep -q ' ftp ' && CURL_CAN_FTP=1
echo "$CURLTEST" | grep -q ' sftp ' && CURL_CAN_SFTP=1
echo "$CURLTEST" | grep -q ' ftps ' && CURL_CAN_FTPS=1
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working curl was found; capabilities: FTP:$CURL_CAN_FTP FTPS:$CURL_CAN_FTPS SFTP:$CURL_CAN_SFTP"
fi
# Do we have wget?
$WGET --version >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working wget not found (looked for: $WGET; path is: $PATH) - only needed if you have neither a working lftp nor curl"
unset WGET
[[ $CHECKREQ_RESULTS -eq 0 ]] && CHECKREQ_RESULTS=1
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working wget was found"
fi
# Set this variable to prevent having to check 3 variables every time
CAN_URLGET=0
[[ -n "${LFTP}${WGET}${CURL}" ]] && CAN_URLGET=1
$RSYNC --version >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working rsync not found (looked for: $RSYNC; path is: $PATH) - recommended (but not required) for working on local filesystem WordPress installations"
unset RSYNC
[[ $CHECKREQ_RESULTS -eq 0 ]] && CHECKREQ_RESULTS=1
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working rsync was found"
fi
RSYNCOPT=""
[[ $DEBUG -ge 1 ]] && RSYNCOPT="-v --stats"
[[ $DEBUG -ge 3 ]] && RSYNCOPT="-vv --stats"
# Also used for mv as well as cp
COPYOPT=""
[[ $DEBUG -ge 1 ]] && COPYOPT="-v"
$UNZIP -v >/dev/null 2>/dev/null
UNZIPRET=$?
# Some BSD zips have no -v switch, so we can't use that; but instead they return code 1 and a usage summary
[[ $UNZIPRET -eq 1 && $UNAME != "Linux" && ${UNAME:0:6} != "Cygwin" && ${UNAME:0:6} != "CYGWIN" ]] && UNZIPRET=0
if [[ $UNZIPRET -ne 0 ]]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working unzip not found (looked for: $UNZIP; path is: $PATH) - required to make new installations and check modifications to existing ones"
unset UNZIP
[[ $CHECKREQ_RESULTS -eq 0 ]] && CHECKREQ_RESULTS=2
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working unzip was found"
fi
UNZIPOPT="-q"
[[ $DEBUG -ge 3 ]] && UNZIPOPT=""
$ZIP -v >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working zip not found (looked for: $ZIP; path is: $PATH) - needed to compress backups of old entities"
unset ZIP
[[ $CHECKREQ_RESULTS -eq 0 ]] && CHECKREQ_RESULTS=2
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working zip was found"
fi
ZIPOPT="-q"
[[ $DEBUG -ge 3 ]] && ZIPOPT=""
$W3M -version >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working w3m not found (looked for: $W3M; path is: $PATH) - needed for viewing changelogs, unless you have elinks or lynx available"
unset W3M
[[ $CHECKREQ_RESULTS -eq 0 ]] && CHECKREQ_RESULTS=1
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working w3m was found"
fi
$ELINKS -version >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
# Don't bother troubling the user with this if it won't be needed
if [[ -z $W3M ]]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working elinks not found (looked for: $ELINKS; path is: $PATH) - needed for viewing changelogs, unless you have w3m or lynx available"
unset ELINKS
[[ $CHECKREQ_RESULTS -eq 0 ]] && CHECKREQ_RESULTS=1
fi
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working elinks was found"
fi
$LYNX -version >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
# Don't bother troubling the user with this if it won't be needed
if [[ -z $W3M && -z $ELINKS ]]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working lynx not found (looked for: $LYNX; path is: $PATH) - needed for viewing changelogs, unless you have w3m or elinks available"
unset LYNX
[[ $CHECKREQ_RESULTS -eq 0 ]] && CHECKREQ_RESULTS=1
[[ -z $W3M && -z $ELINKS ]] && CHECKREQ_RESULTS=2
fi
else
[[ $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working lynx was found"
fi
# Spare ourselves from having to check 3 variables each time
CAN_BROWSE=0
[[ -n "${LYNX}${ELINKS}${W3M}" ]] && CAN_BROWSE=1
# Find an MD5 checksumming binary
CHECKSUM=""
if [[ "`echo X | md5sum 2>/dev/null| cut -f1 -d' '`" = "253bcac7dd806bb7cf57dc19f71f2fa0" ]]; then
CHECKSUM="md5sum"
elif [[ "`echo X | openssl dgst -md5 -r 2>/dev/null | cut -f1 -d' '`" = "253bcac7dd806bb7cf57dc19f71f2fa0" ]]; then
CHECKSUM="openssl dgst -md5 -r"
elif [[ "`echo X | md5 -r 2>/dev/null| cut -f1 -d' '`" = "253bcac7dd806bb7cf57dc19f71f2fa0" ]]; then
CHECKSUM="md5 -r"
else
[[ ! -s "$WORKINGDIR_FULL/md5.php" ]] && echo '<?php echo md5_file("php://stdin"); ?>' >"$WORKINGDIR_FULL/md5.php"
if [[ -n $PHP && "`echo X | $PHP "$WORKINGDIR_FULL/md5.php" 2>/dev/null| cut -f1 -d' '`" = "253bcac7dd806bb7cf57dc19f71f2fa0" ]]; then
CHECKSUM="$PHP $WORKINGDIR_FULL/md5.php"
elif [[ -n $MD5CHECKSUM_TRY ]]; then
CK_OUTPUT=`echo | $MD5CHECKSUM_TRY 2>/dev/null`
if [ $? -eq 0 && -n $CK_OUTPUT ]; then
CHECKSUM="$MD5CHECKSUM_TRY"
fi
fi
if [[ -z $CHECKSUM ]]; then
[[ $DEBUG -ge 1 || $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working MD5 checksum binary not found (looked for: md5sum, openssl, md5, php, $MDCHECKSUM_TRY; path is: $PATH) - useful to produce hashes (some cacheing operations will be disabled without it)"
unset CHECKSUM
[[ $CHECKREQ_RESULTS -eq 0 ]] && CHECKREQ_RESULTS=2
fi
fi
[[ -n $CHECKSUM && $CHECKREQUIREMENTS -eq 1 ]] && ws_event_info "Working MD5 checksum binary ($CHECKSUM) was found"
if [[ $CHECKREQUIREMENTS -eq 1 ]]; then
if [ $CHECKREQ_RESULTS -eq 0 ]; then
echo "${BOLD}All is well${OFFBOLD} - you can run WordShell with 100% of available features"
elif [ $CHECKREQ_RESULTS -eq 1 ]; then
echo -n "${BOLD}All is well${OFFBOLD} - you did not have every tool we looked for, but you had enough to run WordShell with 100% of available features. You do not need to install any missing tools"
[[ -z $LFTP ]] && echo " (though if you install lftp then your access to ftp/sftp sites will be faster)"
echo "."
else
echo "${BOLD}Some requirements were not found${OFFBOLD} - you may find that some operations fail or some functionality is reduced"
fi
exit $CHECKREQ_RESULTS
fi
# Get this once rather than call potentially many times (especially comparing stat times)
NOWDATE=`date +%s`
# Reset this
REMOTEMANAGER_UPLOADTIME=0
# Unset because it is consulted by mktemp
unset TMPDIR
# $1 = prompt
# $2 = optional file to delete if user aborts
function check_continue() {
read -p "$1" -n 1 DOCONT
echo >/dev/stderr
if [[ $DOCONT != "c" && $DOCONT != "C" ]]; then
[[ -n $2 ]] && rm -f "$2"
abort_die 27 "User chose to abort"
fi
}
# Check reasonable free disk space. df -k -P is POSIX-compatible and has standardised output
FREESPACE=`(df -k -P . 2>/dev/null || echo 0 0 0 0) | tail -1 | awk '{print $4}'`
if [[ $FREESPACE =~ ^[0-9]+$ ]]; then
if [[ $FREESPACE -lt 102400 && $FREESPACE -ne 0 ]]; then
ws_event_notice "Disk space is low on WordShell's partition: df -k -P . returned: $FREESPACE Kb free"
check_continue "There is less than 100Mb free on the local filesystem (in $WORKDIR; space: $FREESPACE Kb) - do you want to continue (c to continue, other to abort) : "
fi
else
[[ $DEBUG -ge 1 ]] && ws_event_info "The output of 'df -k -P' could not be parsed. Please send a bug report."
fi
ws_event_notice "Invoked: $ORIGINAL_PARAMS"
[[ $DEBUG -ge 1 ]] && ws_event_debug "WordShell: $VERSION Uname: $UNAME"
function reinvokeself() {
# Purpose - re-invoke self, replacing any instances of --everything or --pluginsandthemes with another
# $1 : Replacement parameter
local LINE
for i in $ORIGINAL_PARAMS; do
if [[ $i = "--everything" || $i = "-e" || $i = "--pluginsandthemes" ]]; then
LINE="$LINE $1"
elif [[ ( $i != "--selfinvoked" && $i != "--selfinvoked=e" ) && $i != "--nocountdiskspace" && $i != "--groupbytype" && ( ( $1 != "--plugin" && $1 != "--theme" && $1 != "--content" && $1 != "--database" ) || ( $i != "--justwp" && $i != "--justwpwipeothers" ) ) ]]; then
LINE="$LINE $i"
fi
done
ws_pushd "$ORIGDIR"
[[ $METAMODE = "everything" ]] && $BASH $0 $LINE --selfinvoked=e --nocountdiskspace
[[ $METAMODE != "everything" ]] && $BASH $0 $LINE --selfinvoked --nocountdiskspace
ws_popd
}
DO_MULTIPLE_METAMODE=0
[[ $METAMODE = "everything" || $METAMODE = "pluginsandthemes" ]] && DO_MULTIPLE_METAMODE=1
PLUGIN_OR_THEME_MODE=0
[[ $METAMODE = "plugin" || $METAMODE = "theme" ]] && PLUGIN_OR_THEME_MODE=1
# Sanity checks of options
# First, sanity checks that do not involve the non-multiple metamodes (--core, --theme, --plugin)
[[ $GROUPBYTYPE -eq 1 && $DO_MULTIPLE_METAMODE -eq 0 ]] && options_abort "--groupbytype only makes sense when given together with --everything or --pluginsandthemes"
[[ $DISABLEFTPTLS -eq 1 && $REQUIREFTPTLS -eq 1 ]] && options_abort "--disableftptls and --requireftptls are mutually incompatible options"
[[ $SYNC -eq 1 && $CACHE -ge 1 ]] && options_abort "--cache and --sync are mutually contradictory options"
[[ $ACTIVATE -eq 1 && $DEACTIVATE -eq 1 ]] && options_abort "You cannot both activate and de-activate at the same time"
[[ $ONLYACTIVE -eq 1 && $ONLYINACTIVE -eq 1 ]] && options_abort "Cannot specify both --active and --inactive"
[[ -n $CURRENTVERSION && $MODE != "normal" && $MODE != "update" && $MODE != "rollback" && $MODE != "delete" && $MODE != "list" ]] && options_abort "You can only use --currentversion/--cver in update, install, rollback, list and delete modes (not with --$MODE)"
[[ $SHOWCHANGES -ge 1 && $MODE != "normal" && $MODE != "update" && $MODE != "rollback" ]] && options_abort "You cannot use --showchanges in $MODE mode (--$MODE)"
[[ $SHOWCHANGES -ge 1 && -z $DIFF ]] && options_abort "--showchanges requires a working diff command (run again with --checkrequirements or --debug for more information)"
[[ $MODE = "checkmodifications" && -z $DIFF ]] && options_abort "--checkmodifications requires a working diff command (run again with --checkrequirements or --debug for more information)"
[[ $LISTONLYUPDATES -eq 1 && $LISTONLYTHIRDPARTY -eq 1 ]] && options_abort "--listonlyupdates and --listonlythirdparty are mututally exclusive options (because --listonlyupdates only works with wordpress.org code, not third-party)."
[[ $WARNMISSING -eq 1 && $MODE != "list" ]] && options_abort "--warnmissing can only be used in list mode"
[[ $LISTEXACT -eq 1 && $MODE != "list" ]] && options_abort "--listexact can only be specified in list mode (--list)"
[[ ( $LISTONLYTHIRDPARTY -eq 1 || $LISTONLYUPDATES -eq 1 ) && $MODE != "list" ]] && options_abort "--list* parameters can only be used in list mode (--list)"
[[ $REQUIRENEWVERSION -eq 0 && ( $MODE != "normal" && $MODE != "update" && $MODE != "rollback" && $MODE != "restore" ) ]] && options_abort "--donotrequirenew can only be used when upgrading (cannot use --$MODE)"
[[ $MODE = "refreshcache" && $CACHE -ge 1 ]] && options_abort "Cannot specify both --refreshcache and --cache"
[[ $FILEMETHOD -eq 1 && ( $METAMODE != "core" || ( $MODE != "normal" && $MODE != "update" ) ) ]] && options_abort "--filemethod can only be used when updating core"
[[ $METAMODE = "core" && $MODE = "delete" ]] && options_abort "WordShell does not yet support deleting core; but if you want to delete an installation then you can use --login and then delete all your files with one command from there."
[[ $MODE = "visit" && -z $SITE ]] && options_abort "You must specify a site with --visit"
# For .php plugins, only --list, --delete, --activate and --deactivate are allowed
[[ $METAMODE = "plugin" && ${PLUGIN%.php} != $PLUGIN && ( $MODE != "list" && $MODE != "delete" && $ACTIVATE -ne 1 && $DEACTIVATE -ne 1 ) ]] && options_abort "With single-file plugins, only --list, --delete, --activate and --deactivate modes are supported (feel free to contact us to tell us if you need more)"
[[ $NETWORK_PLUGIN -eq 1 && ( $METAMODE != "plugin" || ( $ACTIVATE -ne 1 && $DEACTIVATE -ne 1 && $MODE != "list" ) ) ]] && options_abort "--network only makes sense in plugin mode together with --list, --activate or --deactivate"
[[ $NETWORK_BLOGID != "-1" && ( $METAMODE != "plugin" || ( $ACTIVATE -ne 1 && $DEACTIVATE -ne 1 && $MODE != "list" ) ) ]] && options_abort "--blog only makes sense in plugin mode together with --list, --activate or --deactivate"
[[ $NETWORK_BLOGID != "-1" && $NETWORK_PLUGIN -eq 1 ]] && options_abort "You cannot specify both --network (i.e. network-wide) and --blog= (i.e. a particular site) at the same time"
# Now, sanity checks that depend upon the metamode, or which change supplied parameters
if [[ $METAMODE != "everything" && $METAMODE != "pluginsandthemes" ]]; then
# This is good to put early
[[ $METAMODE = "content" && $MODE != "restore" && $MODE != "refreshcache" && $MODE != "list" && $MODE != "login" ]] && options_abort "--content can only be used in one of the following modes: --restore, --list, --refreshcache, --login (not with --$MODE)"
[[ $METAMODE = "database" && $MODE != "refreshcache" && $MODE != "listrollbacks" && $MODE != "deleterollbacks" && $MODE != "searchandreplace" ]] && options_abort "--database can only be used in --refreshcache/--listrollbacks/--deleterollbacks/--searchandreplace modes (not with --$MODE)"
[[ $METAMODE != "database" && $MODE = "searchandreplace" ]] && options_abort "--searchandreplace can only be used in database mode (--database)"
[[ -n $TABLELIST && $MODE != "searchandreplace" ]] && options_abort "--tables can only be specified with --searchandreplace (not with --$MODE)"
[[ $METAMODE = "core" && $MODE = "install" && -n $SITE && $SITE != "new" ]] && options_abort "--core --install can only be used with a new site (specify no site, or 'new'), not an existing one"
[[ $METAMODE != "core" && $JUSTWP -eq 1 ]] && options_abort "--justwp and --justwpwipeothers only make sense in --core mode (it instructs WordShell, when working with core, to only attempt to backup your WordPress installation (and not any extra files you have placed in the same directory))"
[[ ( $METAMODE != "plugin" && $METAMODE != "theme" ) && ( $ONLYINACTIVE -eq 1 || $ONLYACTIVE -eq 1 ) ]] && options_abort "--onlyactive and --onlyinactive do not make sense in --$METAMODE mode (only themes and plugins have this concept)"
[[ ( $METAMODE != "plugin" && $METAMODE != "theme" ) && ( $ACTIVATE -eq 1 || $DEACTIVATE -eq 1 ) ]] && options_abort "--activate and --deactivate do not make sense in --$METAMODE mode (only themes and plugins have this concept)"
# --user
[[ $METAMODE = "user" && $MODE != "add" && $MODE != "delete" && $MODE != "list" && "$MODE" != "passwordreset" ]] && options_abort "--user must be used with --add, --delete or --list (not with --$MODE)"
[[ $MODE = "add" && $METAMODE != "user" ]] && options_abort "--add can only be used with --user (not with --$METAMODE)"
[[ $MODE = "passwordreset" && $METAMODE != "user" ]] && options_abort "--passwordreset can only be used with --user (not with --$METAMODE)"
[[ $METAMODE != "user" && -n $USER_ROLE ]] && options_abort "--role can only be used with --user (not with --$METAMODE)"
[[ $METAMODE != "user" && -n $USER_EMAIL ]] && options_abort "--email can only be used with --user (not with --$METAMODE)"
[[ ( $METAMODE != "user" || $MODE != "delete" ) && -n $POSTS_REASSIGN ]] && options_abort "--reassign can only be specified with --user --delete"
[[ -n $USEZIP && $USEZIP != "latest" && $PLUGIN = "all" ]] && options_abort "Cannot specify a particular new ${METAMODE} zip (--new) together with ${METAMODE} 'all'"
[[ $MODE = "login" && $PLUGIN = "all" ]] && options_abort "You cannot specify 'all' in login mode (--login)"
[[ ( $MODE != "normal" && $MODE != "install" && $MODE != "update" ) && -n $USEZIP ]] && options_abort "Can only specify a zip or version to install when updating or installing (cannot use --usezip or --latest with --$MODE)"
# In delete mode, also deactivate unless requested not to
# In theme mode, deactivate has no meaning. WP will automatically switch to the default theme.
[[ $MODE = "delete" && $SKIPDEACTIVATE -ne 1 && $METAMODE != "theme" ]] && DEACTIVATE=1
# Specifying plugin "all" is redundant when listing, and in fact will invoke a different code path.
[[ $MODE = "list" && $PLUGIN = "all" ]] && unset PLUGIN
[[ ( $MODE = "definegroup" || $MODE = "delgroup" || $MODE = "listgroups" ) && -n $SITE ]] && options_abort "Do not specify a site with --$MODE"
if [[ ( $MODE = "remotecli" || $MODE = "phpinfo" || $MODE = "phpversion" || $MODE = "mysqlversion" ) && -n $PLUGIN ]]; then
ws_event_info "Do not specify a $METAMODE in --$MODE mode"
unset PLUGIN
fi
# If self-invoked from an original --restore --everything without specifying a particular theme/plugin, then do them all
[[ $PLUGIN_OR_THEME_MODE -eq 1 && $SELFINVOCATION -eq 2 && $MODE = "restore" && -z $PLUGIN ]] && PLUGIN="all"
[[ $PLUGIN_OR_THEME_MODE -eq 1 && $MODE = "restore" && -z $PLUGIN ]] && options_abort "You must specify both a site and a $METAMODE with --restore (or specify \"all\")"
[[ $DEACTIVATE -eq 1 && $METAMODE = "theme" ]] && options_abort "You cannot use --deactivate with a theme; rather, you must use --activate on the new theme that you wish to use"
[[ $PLUGIN_OR_THEME_MODE -eq 1 && $MODE = "checkmodifications" && -z $PLUGIN ]] && PLUGIN="all"
# --delete followed by --active or --inactive implies all plugins
[[ $MODE = "delete" && -z $PLUGIN && ($ONLYACTIVE -eq 1 || $ONLYINACTIVE -eq 1 ) ]] && PLUGIN="all"
[[ $METAMODE = "core" && -n $PLUGIN && $PLUGIN != "wordpress" && $PLUGIN != "WordPress" ]] && options_abort "$PLUGIN: You cannot specify this parameter in --core mode"
# This value is used in a few places
[[ $METAMODE = "core" ]] && PLUGIN="wordpress"
[[ $SHOWCHANGES -ge 1 && -z $PLUGIN ]] && options_abort "You must specify both a site and a $METAMODE with --showchanges"
# If we asked to list updates without specifying a site, then do them all
[[ $LISTONLYUPDATES -eq 1 && -z $SITE ]] && SITE="all"
[[ ( $MODE = "listrollbacks" || $MODE = "deleterollbacks" ) && -z $SITE ]] && SITE="all"
[[ $MODE = "rollback" && -n $USEZIP ]] && options_abort "Cannot specify --new or --latest in rollback mode (--rollback)"
[[ $METAMODE = "core" && $LISTONLYTHIRDPARTY -eq 1 ]] && options_abort "--listonlythirdparty makes no sense with --core"
[[ $MODE = "install" && $PLUGIN = "all" ]] && options_abort "Incompatible options specified: cannot specify $METAMODE as 'all' when in install mode (--install)"
[[ ( $MODE = "entermaintenance" || $MODE = "waitmaintenance" || $MODE = "exitmaintenance" || $MODE = "maintenancestate" ) && -n $PLUGIN ]] && options_abort "Cannot specify a particular $METAMODE in this mode (--$MODE)"
[[ $MODE = "refreshcache" && -n $PLUGIN && $PLUGIN != "all" && $PLUGIN_OR_THEME_MODE -eq 1 ]] && options_abort "Cannot specify a $METAMODE in refresh cache mode (--refreshcache)"
# Was --update explicitly passed on the command line? (needed sometimes when using --activate / --deactivate, to make intention clear)
if [[ ( $ACTIVATE -eq 1 || $DEACTIVATE -eq 1 ) && $MODE = "update" ]]; then
MODE="normal"
elif [[ $ACTIVATE -eq 1 && $MODE = "normal" ]]; then
MODE="activate"
elif [[ $DEACTIVATE -eq 1 && $MODE = "normal" ]]; then
MODE="deactivate"
elif [[ $MODE = "update" ]]; then
MODE="normal"
fi
[[ ( $ONLYACTIVE -eq 1 || $ONLYINACTIVE -eq 1 || $FAST -eq 1 ) && ( $MODE != "list" && $MODE != "normal" && $MODE != "delete" && $MODE != "checkmodifications" ) ]] && options_abort "Can only specify --active or --inactive in list, delete, restore and update modes, and --fast only in those and checkmodifications mode (not with --$MODE)"
if [[ $MODE = "addsite" ]]; then
if [[ -n $SITE && $SITE != "new" ]]; then
options_abort "Cannot specify a site when adding a new site"
elif [[ -n $PLUGIN ]]; then
options_abort "Cannot specify a $METAMODE when adding a new site"
fi
# --addsite, after adding the site, comes out to --list
SITE="new"
MODE="list"
fi
[[ $ACTIVATE -ge 1 && $MODE != "activate" && $MODE != "normal" && $MODE != "install" && $MODE != "rollback" && $MODE != "restore" ]] && options_abort "--activate can only be specified on its own or in install/update/rollback/restore modes (not with --$MODE)"
[[ $DEACTIVATE -ge 1 && $MODE != "deactivate" && $MODE != "normal" && $MODE != "delete" && $MODE != "rollback" && $MODE != "restore" ]] && options_abort "--deactivate can only be specified on its own or in delete/update/rollback/restore modes (not with --$MODE)"
[[ $MODE != "normal" && $AUTOPATCH -eq 1 ]] && options_abort "--autopatch can only be specified in update mode (cannot use --$MODE)"
[[ $DETAILED -eq 1 && ( $METAMODE != "user" || $MODE != "list" ) ]] && options_abort "--detailed can only be specified with --user --list"
[[ $MODE = "delete" && $SITE = "all" && ( -z $PLUGIN || $PLUGIN = "all" ) && $IREALLYMEANIT -ne 1 ]] && options_abort "Deleting everything requires that you also specify --ireallymeanit"
[[ $THISONEONLY -eq 1 && $MODE != "normal" && $MODE != "delete" && $MODE != "restore" && $MODE != "rollback" ]] && options_abort "--thisoneonly can only be used in update/delete/restore modes (cannot use --$MODE)"
[[ $THISONEONLY -eq 1 && -z $PLUGIN ]] && options_abort "You must specify both a site and a $METAMODE when using --thisoneonly"
[[ ( $MODE = "listrollbacks" || $MODE = "deleterollbacks" ) && $PLUGIN = "all" ]] && PLUGIN=""
# End of the sanity checks that depend somehow upon the meta-mode
fi
function url_detectredirection {
# Input: $1=URI
# Output: returns 0 if no redirection detected, 1 if found; and if so, sets URL_REDIRECTEDURL
if [[ -n $CURL ]]; then
local CURLOPTS=""
local CURL_URL=$1
if [[ $1 =~ ^(https?)://([^/]+):([^/]+)\@(.*)$ ]]; then
CURLUSER="user = \"${BASH_REMATCH[2]}:${BASH_REMATCH[3]}\"
"
CURL_URL="${BASH_REMATCH[1]}://${BASH_REMATCH[4]}"
CURLOPTS="$CURLOPTS --config - --fail --anyauth --netrc-optional"
fi
local RTMP=`mktemp "$WORKINGDIR_FULL/tmp/URLREDIRECT.XXXXX"`
echo $CURLUSER | $CURL $CURLVERB --head $CURLOPTS -o $RTMP "$CURL_URL"
CURLRET=$?
if [[ $CURLRET -eq 47 ]]; then
rm -f $RTMP
# Maximum redirections exceeded - try to find the URL
return 1
fi
rm -f $RTMP
if [[ $CURLRET -eq 0 ]]; then
return 0
else
[[ $DEBUG -ge 1 ]] && ws_event_info "Curl returned code $CURLRET when trying to detect redirections at $CURL_URL"
return 0
fi
fi
# TODO - wget / lftp; then use this function in the intended places
true
cat <<-ENDHERE
$ wget --max-redirect=0 --spider -S http://www.homeedsuccess.co.uk -q
HTTP/1.0 301 Moved Permanently
Location: http://www.homeedsuccess.co.uk/he
Connection: keep-alive
Date: Tue, 15 May 2012 10:20:31 GMT
Server: lighttpd
$ lftp -c "set xfer:max-redirections 0; open http://www.homeedsuccess.co.uk"
cd: File moved: 301 Moved Permanently (/ -> http://www.homeedsuccess.co.uk/he)
Or with -d:
<--- HTTP/1.1 301 Moved Permanently
<--- Location: http://www.homeedsuccess.co.uk/he
<--- Date: Tue, 15 May 2012 10:26:17 GMT
<--- Server: lighttpd
<---
cd: File moved: 301 Moved Permanently (/ -> http://www.homeedsuccess.co.uk/he)
curl --head http://www.homeedsuccess.co.uk
HTTP/1.1 301 Moved Permanently
Location: http://www.homeedsuccess.co.uk/he
Date: Tue, 15 May 2012 10:14:14 GMT
Server: lighttpd
ENDHERE
}
function url_get() {
# Input: $1=URI If $2=stdout then output on terminal, else (if set) indicates output filename
# We assume one of LFTP and WGET and CURL is there (caller should check first)
# FTP requires you to set tryonlycurl
# $3: Other parameters:
# e.g. tryonlylftp, tryonlywget, tryonlycurl
# passwarning (if password will be put on command line)
# testonly - informs url_get that we wish to test if the server returns an error code. You would likely then also specify $2=stdout and redirect to /dev/null. Note that without this switch, the return code from url_get may be zero in the case of failure; curl does this, returning the error document and suppressing the HTTP error.
# Returns URLGET_RETURNCODE as the return code, in case you were piping the output somewhere
# Set the command to get the desire output concerning the output selection ($2)
if [[ $2 = "stdout" ]]; then
local LFTP_COM="cat"
local URL_GOT="-"
local CURL_OUTPUT=""
elif [[ -n $2 ]]; then
local LFTP_COM="get1 -o \"$2\""
local URL_GOT=$2
local CURL_OUTPUT="-o $2"
else
local LFTP_COM="get1"
local URL_GOT=`basename "$1" 2>/dev/null`
local CURL_OUTPUT="-O"
fi
if [[ -n $LFTP && ( -z $3 || $3 = "passwarning" || $3 = "testonly" || $3 =~ "tryonlylftp" ) ]]; then
if [[ -n $LFTP ]]; then
if [[ $DEBUG -ge 1 ]]; then
ws_event_info "url_get: lftp: $1"
# We don't put the URL on the command-line in case it contains passwords
$LFTP $LFTPDEBUG <<-ENDHERE
set xfer:max-redirections 16
set ssl:verify-certificate $SSLVERIFY
$LFTP_COM "$1"
ENDHERE
else
$LFTP $LFTPDEBUG 2>/dev/null <<-ENDHERE
set xfer:max-redirections 16
set ssl:verify-certificate $SSLVERIFY
$LFTP_COM "$1"
ENDHERE
fi
URLGET_RETURNCODE=$?
else
URLGET_RETURNCODE=3
fi
elif [[ -n $CURL && ( -z $3 || $3 = "passwarning" || $3 = "testonly" || $3 =~ "tryonlycurl" ) ]]; then
if [[ -n $CURL ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "url_get: curl: $1"
# Prior to 1.3.139, --fail was only used with HTTP auth
local CURLOPTS="--location"
local CURL_URL=$1
local CURLUSER=""
local CURLRET
[[ $1 =~ ^s?ftp ]] && CURLOPTS="$CURL_FTPOPTIONS "
# --location forces curl to follow any Location: headers; otherwise the test may fail (a redirect code comes successfully back, though the site you'd get if you followed it may be down)
if [[ $1 =~ ^(https?|s?ftps?)://([^/]+):([^/]+)\@(.*)$ ]]; then
CURLUSER="user = \"${BASH_REMATCH[2]}:${BASH_REMATCH[3]}\"
"
CURL_URL="${BASH_REMATCH[1]}://${BASH_REMATCH[4]}"
CURLOPTS="$CURLOPTS --config -"
[[ $1 =~ ^http ]] && CURLOPTS="$CURLOPTS --anyauth --netrc-optional"
fi
if [[ $DEBUG -ge 2 ]]; then
echo
echo $CURLUSER | $CURL --fail $CURLVERB --show-error $CURLOPTS $CURL_OUTPUT "$CURL_URL"
CURLRET=$?
else
if [[ "$MODE" = "downloadonly" || $DEBUG -eq 1 ]]; then
echo $CURLUSER | $CURL --fail $CURL_OUTPUT $CURLOPTS -# "$CURL_URL"
CURLRET=$?
else
echo $CURLUSER | $CURL --fail $CURL_OUTPUT $CURLOPTS --silent "$CURL_URL"
CURLRET=$?
fi
fi
URLGET_RETURNCODE=$CURLRET
else
URLGET_RETURNCODE=3
fi
# We try wget last because it is harder to hide username/passwords in the URL with
elif [[ -n $WGET && ( -z $3 || $3 = "passwarning" || $3 = "testonly" || $3 =~ "tryonlywget" ) ]]; then
if [[ -n $WGET ]]; then
local WGET_URL=$1
if [[ $3 =~ passwarning && $WGET_URL =~ ^(https?|s?ftps?)://([^/]+):([^/]+)\@(.*)$ ]]; then
check_continue "${BOLD}Warning:${OFFBOLD} Your system does not have lftp or curl installed, so we are using wget to fetch URLs. However, wget has no easy way to hide passwords contained in URLs. This is not a problem unless your system is multi-user; someone then may be able to spot the password in the system's process table (by running the 'ps' command). To avoid this, you should (if you cannot install lftp or curl) add a line to your .netrc file in your home directory (see 'man netrc' for more information on how to do so; a typical line is 'machine www.example.com<tab>login myusername<tab>password mypassword'). After doing that, you can tell WordShell the URL without needing to include a username or password; wget will pick up the username/password automatically without WordShell needing to pass it. Do you want to continue? (c to continue if this does not matter, e.g. you are on a single user system, or if the entry is already in your .netrc; or any other key to abort): "
fi
if [[ $DEBUG -ge 1 ]]; then
ws_event_info "url_get: wget: $1"
$WGET $WGETVERB -O $URL_GOT "$WGET_URL"
else
if [[ $MODE = "downloadonly" ]]; then
$WGET -O $URL_GOT -nv "$WGET_URL"
else
$WGET -O $URL_GOT -q "$WGET_URL"
fi
fi
else
URLGET_RETURNCODE=3
fi
else
ws_event_error "No suitable program to fetch the URL was found"
URLGET_RETURNCODE=254
fi
return $URLGET_RETURNCODE
}
function url_post() {
# Input: $1 = URI, $2 = path, $3 = params
# $4 is options (comma-separated):
# ignoreexisting: do not use an existing cache file
# nocache: do not cache the results (implies ignoreexisting)
# preferwget to prefer wget (or preferlftp to not do so - presently default, but you never know...). Or prefercurl
# returnfile to return the full file path (not the contents); setvar to put the contents in URLPOST_RESULTS
# We assume either lftp or wget or curl is available
# Output: sets RETCODE_URLPOST
local POST_OPTS=$4
# This is needed to use STAT_MODTIME correctly (the spaces in it need recognising as parameter separators)
local OLDIFS=$IFS
IFS="
"
if [[ -z $CHECKSUM ]]; then
# If no checksum binary, then cannot store predictable cache file name, so just use temp file
REQHASH_FILE=`mktemp "$WORKINGDIR_FULL/tmp/url-post.XXXXX"`
# Since the temp file name was not predictable, do not cache the file (unless the caller requested it)
[[ ! $POST_OPTS =~ returnfile ]] && POST_OPTS="$4,nocache"
else
REQHASH_FILE="$WORKINGDIR_FULL/tmp/post-`echo $1 $2 $3 | $CHECKSUM | cut -d' ' -f1`"
fi
# Do the download if not cacheing, or if told to ignore existing cache, or cache disabled, or no cache file exists, or if cache file is old
if [[ $POST_OPTS =~ nocache || $POST_OPTS =~ ignoreexisting || $DISABLECACHE -eq 1 || ! -f $REQHASH_FILE || $((NOWDATE - `$STAT_MODTIME $REQHASH_FILE 2>/dev/null|| echo 0`)) -ge 3600 ]]; then
if [[ ( $POST_OPTS =~ preferwget && -n $WGET ) || ( -z $CURL && -z $LFTP) ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "url_post: wget_post: options=$4: $1 $2 $3"
$WGET $WGETVERB --post-data="$3" -O $REQHASH_FILE $1$2
RETCODE_URLPOST=$?
elif [[ ( $POST_OPTS =~ prefercurl && -n $CURL ) || -z $LFTP ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "url_post: curl_post: options=$4: $1 $2 $3"
CURL_URL=$1
CURLUSER=""
CURLOPTS=""
if [[ $1 =~ ^(https?)://([^/]+):([^/]+)\@(.*)$ ]]; then
CURLUSER="user = \"${BASH_REMATCH[2]}:${BASH_REMATCH[3]}\"
"
CURL_URL="${BASH_REMATCH[1]}://${BASH_REMATCH[4]}"
CURLOPTS="$CURLOPTS --config -"
[[ $1 =~ ^http ]] && CURLOPTS="$CURLOPTS --fail --anyauth --netrc-optional"
fi
echo $CURLUSER | $CURL $CURLVERB $CURLOPTS --output $REQHASH_FILE --data "$3" --url "${CURL_URL}$2"
RETCODE_URLPOST=$?
else
[[ $DEBUG -ge 1 ]] && ws_event_info "url_post: lftp_post: options=$4: $1 $2 $3"
# if [[ $1 =~ ^(https?)://([^/]+):([^/]+)\@(.*)$ ]]; then
# Don't put the URL on the command-line, in case it contains passwords
$LFTP $LFTPDEBUG >"$REQHASH_FILE" <<-ENDHERE
set ssl:verify-certificate $SSLVERIFY
open $1$2
quote post '$2' '$3'"
ENDHERE
RETCODE_URLPOST=$?
# else
# # http://api.wordpress.org/themes/info/1.0/ returns a 500 error if HEAD is used, as happens with lftp's open command above. So, avoid that if possible by using this method.
# $LFTP $LFTPDEBUG "$1" >"$REQHASH_FILE" <<-ENDHERE
# quote post '$2' '$3'"
# ENDHERE
# RETCODE_URLPOST=$?
# fi
fi
[[ $DEBUG -ge 1 ]] && ws_event_info "url_post: wrote to file: $REQHASH_FILE"
else
[[ $DEBUG -ge 1 ]] && ws_event_info "HTTP POST: options=$4: $1 $2 $3: Found valid cache file: $REQHASH_FILE"
fi
if [[ $POST_OPTS =~ returnfile ]]; then
echo $REQHASH_FILE
elif [[ $POST_OPTS =~ setvar ]]; then
URLPOST_RESULTS=`cat $REQHASH_FILE`
rm -f $REQHASH_FILE
else
cat $REQHASH_FILE
[[ $POST_OPTS =~ nocache ]] && rm -f $REQHASH_FILE
fi
IFS=$OLDIFS
}
# Deal with --setconfig
if [ -n "$SETCONFIG" ]; then
OLDIFS="$IFS"
IFS=","
for CVAR in $SETCONFIG; do
if [[ $CVAR =~ ^([^:]+):(.*)$ ]]; then
CKEY=${BASH_REMATCH[1]}
CVAL=${BASH_REMATCH[2]}
grep -q "^config#$CKEY#" $CONFIGFILE
if [ $? -eq 0 ]; then
ws_sed_i "s/^config#$CKEY#.*$/config#$CKEY#$CVAL/" $CONFIGFILE
else
echo "config#${CKEY}#${CVAL}" >>"$CONFIGFILE"
fi
else
abort_die 56 "illegitimate setconfig: $CVAR"
fi
done
IFS="$OLDIFS"
fi
# Deal with --getconfig
if [ -n "$GETCONFIG" ]; then
[[ -n $SITE ]] && options_abort "--getconfig takes no other parameters"
OLDIFS="$IFS"
IFS=","
if [ "$GETCONFIG" = "all" ]; then
grep "^config#" $CONFIGFILE | cut -d\# -f2,3
else
for CVAR in $GETCONFIG; do
grep "^config#$CVAR#" $CONFIGFILE | cut -d\# -f2,3
done
IFS="$OLDIFS"
fi
fi
function group_mod() {
local GMODE=$1
local ADDTHIS=$2
if [[ $GMODE = "add" ]]; then
grep -q "^d*site#$ADDTHIS#" $CONFIGFILE || abort_die 97 "Adding $ADDTHIS: this site was not found in the configuration file"
fi
if [[ $GROUPRESULT_COUNT -eq 0 && $GMODE = "add" ]]; then
GROUPRESULT_COUNT=1
GROUPRESULT[1]=$ADDTHIS
elif [[ $GROUPRESULT_COUNT -gt 0 || $GMODE = "add" ]]; then
local FOUNDIT=0
local OLDIFS=$IFS
IFS="
"
for k in `seq 1 $GROUPRESULT_COUNT`; do
if [[ "${GROUPRESULT[$k]}" = "$ADDTHIS" ]]; then
# Is already there
FOUNDIT=1
[[ $GMODE = "del" ]] && GROUPRESULT[$k]=""
fi
done
IFS=$OLDIFS
if [[ $FOUNDIT -eq 0 && $GMODE = "add" ]]; then
GROUPRESULT_COUNT=$((GROUPRESULT_COUNT+1))
GROUPRESULT[$GROUPRESULT_COUNT]=$ADDTHIS
fi
fi
}
declare -a GROUPRESULT
function process_group_define() {
# This function processes the input ($1 - a group definition) and sets $GROUPRESULT and $GROUPRESULT_COUNT accordingly (note that GROUPRESULT_COUNT is an upper limit; the highest used index in the array, not an exact result, as some array entries may be empty; others beyond it may be used but should be ignored)
# $1 - a group definition
local GROUPDEF=$1
OLDIFS=$IFS
IFS=","
# When we write out the group, we write this many members (rest of the array should be discarded)
GROUPRESULT_COUNT=0
for GPART in $GROUPDEF; do
if [[ $GPART = "all" ]]; then
SITELIST=`grep "^site#" "$CONFIGFILE" | cut -d\# -f2`
IFS="
"
for ADD_THIS in $SITELIST; do
group_mod add "$ADD_THIS"
done
IFS=","
elif [[ $GPART = "-all" ]]; then
GROUPRESULT_COUNT=0
elif [[ ${GPART:0:2} = "-@" && ${#GPART} -gt 2 && $GROUPRESULT_COUNT -gt 0 ]]; then
# Delete group
GROUP_TO_DEL=${GPART:2}
DEL_THESE=`grep "^group#$GROUP_TO_DEL#" "$CONFIGFILE" | cut -d\# -f3`
if [[ -n $DEL_THESE ]]; then
# IFS is already ,
for DEL_THIS in $DEL_THESE; do
group_mod del "$DEL_THIS"
done
else
abort_die 97 "$GROUP_TO_DEL: Unknown group"
fi
elif [[ ${GPART:0:1} = "-" && ${#GPART} -gt 1 && $GROUPRESULT_COUNT -gt 0 ]]; then
# Delete site
group_mod del "${GPART:1}"
elif [[ ${GPART:0:1} = "@" && ${#GPART} -gt 1 ]]; then
# Add a group: loop over, adding its members
GROUP_TO_ADD=${GPART:1}
ADD_THESE=`grep "^group#$GROUP_TO_ADD#" "$CONFIGFILE" | cut -d\# -f3`
if [[ -n $ADD_THESE ]]; then
# IFS is already ,
for ADD_THIS in $ADD_THESE; do
group_mod add "$ADD_THIS"
done
else
abort_die 97 "$GROUP_TO_ADD: Unknown or empty group"
fi
elif [[ $GPART =~ ^[\-\.a-zA-Z0-9]+$ ]]; then
# Add an item
group_mod add "$GPART"
else
[[ $GROUPRESULT_COUNT -gt 0 || ${GPART:0:1} != "-" ]] && ws_event_warning "$GPART: this item could not be parsed"
fi
done
IFS=$OLDIFS
}
# Deal with --definegroup, --delgroup, --listgroups
if [[ $MODE = "listgroups" ]]; then
grep "^group#" $CONFIGFILE | while read LINE; do
if [[ $LINE =~ ^group#([A-Za-z0-9]+)#(.*)$ ]]; then
echo "${BOLD}${BASH_REMATCH[1]}:${OFFBOLD} ${BASH_REMATCH[2]}"
else
[[ $DEBUG -ge 1 ]] && ws_event_warning "Corrupt line in configuration file: $LINE"
fi
done
exit
else
if [[ -n $GROUP_DEFINES ]]; then
for GROUPLINE in $GROUP_DEFINES; do
# Each line is of format ([A-Za-z0-9]+):(.*)
# Second part is a comma separarated list
# When we're done, we need to either replace or add the relevant config line
if [[ $GROUPLINE =~ ^([^:]+):(.*)$ ]]; then
GROUPNAME=${BASH_REMATCH[1]}
GROUPDEF=${BASH_REMATCH[2]}
process_group_define "$GROUPDEF"
# Now write it to the config file
GROUP_RAW=""
if [[ $GROUPRESULT_COUNT -gt 0 ]]; then
for i in `seq 1 $GROUPRESULT_COUNT`; do
if [[ -n ${GROUPRESULT[$i]} ]]; then
[[ -n $GROUP_RAW ]] && GROUP_RAW="${GROUP_RAW},"
GROUP_RAW="${GROUP_RAW}${GROUPRESULT[$i]}"
fi
done
fi
# Delete the line if it is present
grep -q "^group#$GROUPNAME#" $CONFIGFILE && ws_sed_i "/^group#$GROUPNAME#/d" "$CONFIGFILE"
# Now add it
echo "group#$GROUPNAME#$GROUP_RAW" >>"$CONFIGFILE"
fi
done
fi
if [[ -n $GROUP_DELETES ]]; then
for GROUPNAME in $GROUP_DELETES; do
# Each line is of format ([A-Za-z0-9]+)
# Just delete the relevant line from the configuration file
# Delete the line if it is present
[[ -n $GROUPNAME ]] && grep -q "^group#$GROUPNAME#" $CONFIGFILE && ws_sed_i "/^group#$GROUPNAME#/d" $CONFIGFILE
done
fi
# Exit unless they explicitly requested to do something else
[[ ( -n $GROUP_DEFINES || -n $GROUP_DELETES ) && $MODE = "normal" ]] && exit
fi
# Read in configuration variables
grep -q "^config#ftpparallel#" $CONFIGFILE && FTPPARALLEL=`grep "^config#ftpparallel#" $CONFIGFILE | cut -d\# -f3`
grep -q "^config#exclude-glob-ftp#" $CONFIGFILE && EXCLUDE_GLOB_FTP=`grep "^config#exclude-glob-ftp#" $CONFIGFILE | cut -d\# -f3`
grep -q "^config#exclude-glob-rsync#" $CONFIGFILE && EXCLUDE_GLOB_RSYNC=`grep "^config#exclude-glob-rsync#" $CONFIGFILE | cut -d\# -f3`
grep -q "^config#curl-ftp-options#" $CONFIGFILE && CURL_FTPOPTIONS=`grep "^config#curl-ftp-options#" $CONFIGFILE | cut -d\# -f3`
# If all that was specified was get/setconfig, then finish
[[ ( -n $GETCONFIG || -n $SETCONFIG ) && -z $SITE ]] && exit
function download_version() {
# Core-friendly: yes
# Input: $1 = plugin/theme name (if in appropriate mode), $2 = version
# Assumes we are in working directory
# Returns code to indicate success or failure
local PLUGIN=$1
local USEZIP=$2
[[ -s "fromwporg.$METAMODE/$PLUGIN.$USEZIP.zip" ]] && return 0
if [ $CAN_URLGET -eq 1 ]; then
ws_pushd fromwporg.$METAMODE
local FETCH_THIS
local FETCH_AS=""
if [[ $METAMODE = "plugin" ]]; then
FETCH_THIS="https://downloads.wordpress.org/plugin/$PLUGIN.$USEZIP.zip"
FETCH_AS="$PLUGIN.$USEZIP.zip"
elif [[ $METAMODE = "theme" ]]; then
FETCH_THIS="https://wordpress.org/themes/download/$PLUGIN.$USEZIP.zip"
FETCH_AS="$PLUGIN.$USEZIP.zip"
elif [[ $METAMODE = "core" ]]; then
FETCH_THIS="https://wordpress.org/wordpress-$USEZIP.zip"
FETCH_AS="wordpress.$USEZIP.zip"
fi
[[ -n $FETCH_THIS ]] && url_get "$FETCH_THIS" "$FETCH_AS"
ws_popd
if [[ ! -s "fromwporg.$METAMODE/$FETCH_AS" ]]; then
ws_event_error "$USEZIP: Failed to successfully download this version (of $PLUGIN) from wordpress.org"
return 1
fi
# In core mode, checksums are available
# MD5 is broken for cryptographic verification purposes; however, since both files come from the same server, we are only checking the download success and not having cryptographic security (if you trojan the zip, you could trojan the checksum file)
if [[ $METAMODE = "core" ]]; then
[[ -s "fromwporg.core/$FETCH_AS.md5" ]] || url_get "$FETCH_THIS.md5" stdout >fromwporg.core/$FETCH_AS.md5
DOWNLOADEDCORE_CHECKSUM_SHOULDBE=`cat fromwporg.core/$FETCH_AS.md5`
if [[ ! $DOWNLOADEDCORE_CHECKSUM_SHOULDBE =~ ^[a-f0-9]+$ ]]; then
ws_event_error "${BOLD}FAILED:${OFFBOLD} Could not successfully download the WordPress core checksum (output: $DOWNLOADEDCORE_CHECKSUM_SHOULDBE)"
return 3
fi
DOWNLOADEDCORE_CHECKSUM=`$CHECKSUM "fromwporg.$METAMODE/$FETCH_AS" | cut -d' ' -f1`
if [[ $DOWNLOADEDCORE_CHECKSUM = $DOWNLOADEDCORE_CHECKSUM_SHOULDBE ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "Download good: Checksum of downloaded WordPress zip matched what was expected ($DOWNLOADEDCORE_CHECKSUM)"
else
ws_event_error "${BOLD}FAILED:${OFFBOLD} Checksum of downloaded WordPress zip ($DOWNLOADEDCORE_CHECKSUM) did not match the expected value ($DOWNLOADEDCORE_CHECKSUM_SHOULDBE)"
return 4
fi
fi
return 0
else
ws_event_error "Could not find a working lftp or wget or curl. Run with --checkrequirements or --debug for more information"
return 2
fi
}
# This is checked for later
APPARENT_NEW_VERSION=""
function make_and_enter() {
[[ $DEBUG -ge 2 ]] && ws_event_debug "make_and_enter: (`caller`): $1"
[[ -d "$1" ]] || mkdir -p "$1"
ws_cd "$1"
}
function make_and_pushd() {
[[ $DEBUG -ge 2 ]] && ws_event_debug "make_and_pushd: (`caller`): $1"
[[ -d "$1" ]] || mkdir -p "$1"
ws_pushd "$1"
}
function managerollbacks() {
# Core-friendly: yes. Database-friendly: yes
# Input: $SITE will be set
# Input: $PLUGIN may be blank
# Input: $1 = list | time (then $2 = time) - returns $LISTPLUG_USEFILE, $LISTPLUG_USETIME } | delete (then $2 = time)
# We need to be in working directory
make_and_enter old.$METAMODE
if [[ $METAMODE = "database" ]]; then
FINDPLUGREGEX="database"
elif [[ -n $PLUGIN ]]; then
FINDPLUGREGEX="$PLUGIN"
else
FINDPLUGREGEX=".*"
fi
LISTPLUG_USEFILE=""
LISTPLUG_USETIME=0
LISTPLUG_USEDATE=0
LISTPLUG_USEVERSION=""
local LISTROLLDAYS=""
[[ $1 = "delete" ]] && LISTROLLDAYS="-mtime +$2"
# Files have names like this:
# <site>-<plugslug>-<unixtime>-<version>.zip
for LISTPLUGLINE in `find . -regex "./$SITE-.*" $LISTROLLDAYS | cut -c3- |sort`; do
if [[ $LISTPLUGLINE =~ ^$SITE-($FINDPLUGREGEX)-([0-9]{6,})-(.*)\.zip$ ]]; then
LISTPLUG_NAME=${BASH_REMATCH[1]}
LISTPLUG_TIME=${BASH_REMATCH[2]}
LISTPLUG_VERSION=${BASH_REMATCH[3]}
[[ $METAMODE = "database" ]] && LISTPLUG_VERSION=${LISTPLUG_VERSION%%\.sql}
# The following line is Bash >= 4.2 only
#printf -v LISTPLUG_DATE '%(%c)T' $LISTPLUG_TIME
LISTPLUG_DATE=`ws_date_from_epoch $LISTPLUG_TIME "%c"`
[[ $CAN_STRTOTIME -eq 0 ]] && LISTPLUG_DATE="$LISTPLUG_DATE ($LISTPLUG_TIME)"
if [[ $1 = "list" ]]; then
printf "${BOLD}%-14s${OFFBOLD} %-26s ${BOLD}%-14s${OFFBOLD} %s\n" "$SITE" "$LISTPLUG_NAME" "${LISTPLUG_VERSION}" "$LISTPLUG_DATE"
elif [[ $1 = "delete" ]]; then
if [ $DRYRUN -eq 1 ]; then
echo "$SITE: Dry run mode: skipped deletion of rollback file: $LISTPLUGLINE"
ws_event_info "$SITE: Dry run mode: skipped deletion of rollback file: $LISTPLUGLINE"
else
echo "$SITE: Deleting rollback file: $LISTPLUGLINE"
ws_event_info "$SITE: Deleting rollback file: $LISTPLUGLINE"
rm -f "$LISTPLUGLINE"
fi
elif [ "$1" = "time" ]; then
# The one we are looking for satisfies these criteria:
# 1) Its date is on or after the specified date
# 2) It is earlier than any others matching 1)
# Unless the specified date is "most recent" in which case we do just that.
if [[ $2 = "most recent" ]]; then
if [[ $LISTPLUG_TIME -gt $LISTPLUG_USETIME ]]; then
LISTPLUG_USEFILE=$LISTPLUGLINE
LISTPLUG_USETIME=$LISTPLUG_TIME
LISTPLUG_USEDATE=$LISTPLUG_DATE
LISTPLUG_USEVERSION=$LISTPLUG_VERSION
fi
elif [[ $LISTPLUG_TIME -ge $2 && ( -z "$LISTPLUG_USEFILE" || $LISTPLUG_TIME -lt $LISTPLUG_USETIME ) ]]; then
LISTPLUG_USEFILE=$LISTPLUGLINE
LISTPLUG_USETIME=$LISTPLUG_TIME
LISTPLUG_USEDATE=$LISTPLUG_DATE
LISTPLUG_USEVERSION=$LISTPLUG_VERSION
fi
fi
fi
done
# Return to working directory
ws_cd ..
}
function choose_rollback() {
# Core-friendly: yes
# Input:
# Assume we are in working directory
# Output:
# If one was chosen, then set NEWZIPSOURCE and USEZIP accordingly
# If one was not chosen, then unset USEZIP
# Before, USEZIP="rollback"
USEZIP=""
while [ -z "$USEZIP" ]; do
echo "Available rollbacks:"
managerollbacks list
local DATE_EXPLAIN="many formats accepted"
[[ $CAN_STRTOTIME -eq 0 ]] && DATE_EXPLAIN="which in this environment means, enter the number of seconds since the epoch (or install either PHP or the GNU/coreutils version of 'date' and then enter almost anything)"
read -p "Enter a valid date ($DATE_EXPLAIN), or back to return to previous menu: " WHICHROLLBACK
if [ "$WHICHROLLBACK" = "back" ]; then
USEZIP="breakout"
else
[[ $DEBUG -ge 1 ]] && ws_event_info "Looking for installed version from time: $WHICHROLLBACK"
SAVE_WHICHROLLBACK=$WHICHROLLBACK
if [ "$WHICHROLLBACK" != "most recent" ]; then
# Convert what is typed in into a UNIX time
if [[ $CAN_STRTOTIME -eq 1 ]]; then
WHICHROLLBACK=`ws_strtotime "$WHICHROLLBACK"`
[[ $? -ne 0 ]] && WHICHROLLBACK="error"
fi
if [[ ! $WHICHROLLBACK =~ ^[0-9]+$ ]]; then
ws_event_error "Could not process rollback time: $SAVE_WHICHROLLBACK"
else
[[ $DEBUG -ge 1 ]] && ws_event_info "Rollback time in epoch time: $WHICHROLLBACK"
managerollbacks time "$WHICHROLLBACK"
if [ -z "$LISTPLUG_USEFILE" ]; then
ws_event_error "Could not find any old version for time: $SAVE_WHICHROLLBACK (possibly the same version was installed then as now)"
else
ROLLBACKTIME=$WHICHROLLBACK
USEZIP=$LISTPLUG_USEFILE
NEWZIPSOURCE="old.$METAMODE"
REQUIRENEWVERSION=0
# When we save a rollback, we only save the changes
CHOSEN_CORE_IS_OVERLAY=1
APPARENT_NEW_VERSION=$LISTPLUG_USEVERSION
fi
fi
fi
fi
done
[[ $USEZIP = "breakout" ]] && unset USEZIP
}
# This function recursively lints (php -l) files in the current directory
# The parameter ($1) is a key to use for cacheing, and nothing else
function lint_dir() {
[[ $DEBUG -ge 1 ]] && ws_event_debug "lint_dir: pwd=`pwd`, key=$1"
if [[ $PHPLINT -eq 0 ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_debug "PHP linting has been disabled: skipping"
return
fi
if [[ $ALREADY_LINTED -eq 1 ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_debug "We have already linted during this program run: skipping"
return
fi
if [[ -z $PHP ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "Cannot lint the contents of this directory, since no working PHP binary was found (run with --checkrequirements or --debug for more information)"
return
fi
local CACHE_KEY=$1
local END_ALL_LINTING=0
# Look for existing cache
local KEYCK=`echo "$CACHE_KEY" | $CHECKSUM | cut -d' ' -f1`
local CACHEFILE=$WORKINGDIR_FULL/tmp/cache-lint-$KEYCK
if [[ $DISABLECACHE -eq 0 && -f $CACHEFILE && $((NOWDATE - `$STAT_MODTIME $CACHEFILE 2>/dev/null|| echo 0`)) -lt 3600 ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_debug "Successful lint result was cached ($CACHEFILE) - will skip linting process"
else
local ERRORS=0
TMPLINT=`mktemp "$WORKINGDIR_FULL/tmp/LINT.XXXXX"`
find . -name '*.php' -type f >$TMPLINT
OLDIFS=$IFS
IFS="
"
for LINTIT in `cat $TMPLINT`; do
LINTOUT=`$PHP -l "$LINTIT"`
if [[ $? -eq 0 ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_debug "Lint: $LINTIT: PHP Syntax OK ($LINTOUT)"
else
ERRORS=1
echo "$LINTIT: This file in the entity you have chosen to upload is apparently invalid PHP. The PHP syntax check returned: $LINTOUT."
check_continue "Are you sure you wish to continue (press c to continue, any other key to abort) ? " $TMPLINT
fi
done
IFS=$OLDIFS
rm -f $TMPLINT
if [[ $ERRORS -eq 0 ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_debug "No errors were found during linting, so will save the successful result to the cache ($CACHEFILE)"
touch "$CACHEFILE"
fi
fi
ALREADY_LINTED=1
}
function extract_prospective_zip() {
# Works with: plugins / themes / core
# Input: Be inside tmp directory
# Input: $1 = path to zip
# $2 = extra unzip parameter, if any
# $3 = further options: currently, "nolint" to prevent linting
# Output: either exits with error, or cds you into a temporary copy of the unzipped plugin/theme/core
# Sets USETMP as the name of the directory to be deleted (by caller) when he has finished
# Also (if in plugin/theme meta-mode) lints the included .php files
PROSPECTIVE_PLUG=$1
ws_event_debug "extract_prospective_zip: zip=$PROSPECTIVE_PLUG, pwd=`pwd`, caller=`caller`"
local EXTRA_UNZIP_OPT=$2
local OUR_OPTS=$3
if [[ ! -f "$PROSPECTIVE_PLUG" ]]; then
ws_event_error "No such zip file ($PROSPECTIVE_PLUG) (`pwd`)"
return 24
elif [[ ! -s "$PROSPECTIVE_PLUG" ]]; then
ws_event_error "Zero-sized zip file ($PROSPECTIVE_PLUG) (`pwd`)"
return 24
fi
USETMP=`mktemp -d tmp-PRISTINE.XXXXX`
ws_cd $USETMP
[[ -z $UNZIP ]] && abort_die 20 "required unzip command not found (re-run with --checkrequirements or --debug for more information)"
if [[ $PROSPECTIVE_PLUG =~ ^/ ]]; then
$UNZIP $UNZIPOPT "$PROSPECTIVE_PLUG" $EXTRA_UNZIP_OPT
else
$UNZIP $UNZIPOPT "../$PROSPECTIVE_PLUG" $EXTRA_UNZIP_OPT
fi
local RETCODE=$?
[[ $RETCODE -ne 0 ]] && abort_die 21 "Unzip command returned error (code: $RETCODE)"
local HOWMANY=`find . -maxdepth 1 -type d | wc -l | awk '{print $1}'`
# Should be two - . and the plugin/theme/core
[[ $HOWMANY -ne 2 ]] && abort_die 22 "Did not find exactly one expected directory in $WORKDIR/tmp/$USETMP"
# cd into the one that has no dot in it
if [[ $METAMODE = "core" ]]; then
ws_cd wordpress
[[ $? -ne 0 ]] && abort_die 23 "Could not enter unzipped directory under $WORKDIR/tmp/$USETMP"
else
ws_cd "`find . -maxdepth 1 -type d | grep -v '^\.$'`"
[[ $? -ne 0 ]] && abort_die 23 "Could not enter unzipped directory under $WORKDIR/tmp/$USETMP"
[[ $PLUGIN_OR_THEME_MODE -eq 1 && $OUR_OPTS != "nolint" ]] && lint_dir "$PROSPECTIVE_PLUG"
fi
}
# This function is just used by get_entity_version. It is abstracted out because it is used in two different places
function get_entity_version_frompluginfile() {
# Input: $1 = the file to get the information from
local PLUG=$1
XPNAME=`head -47 $PLUG | grep -Ei "Plugin Name:" | head -1 | cut -d: -f2- | sed 's/[^-\/0-9a-zA-Z\. ]//g' | sed 's/^ //'`
XPVER=`head -47 $PLUG | grep -Ei "Version:" | head -1 | cut -d: -f2- | awk '{print $1}' | sed 's/[^-0-9a-zA-Z\.]//g'`
if [[ -n $XPNAME && -n $XPVER ]]; then
FOUNDPLUG=1
PPARENT=""
PNAME=$XPNAME
PVER=$XPVER
# This is a special flag which should be used with caution to avoid confusion - we brought it in after seeing a version string "beta 1 (0.6)" which led to a fail of the "expected version = actual version" check. It should be used when you really need to know the full original version. PVER should be used instead for our rough-and-ready (but parsed and sane) version. Normally they are identical.
PVER_ORIG_FULL=`head -47 $PLUG | grep -Ei "Version:" | head -1 | cut -d: -f2- | sed 's/^[[:space:]]*//g' | sed 's/\r//g'`
if [[ $PLUG =~ ^\./ ]]; then
# Plugin file: the last directory we are in, plus the file name, which we assume to being with ./ (and cut that bit off)
PFILE="`basename $(pwd)`/`echo $PLUG | cut -c3-`"
else
PFILE=$PLUG
fi
fi
}
function get_entity_version() {
# Core-friendly: yes
# Input: Usually none. Just cd into the directory of the plugin/theme/core to be analysed.
# Special case: in plugin mode for a single-file plugin, specify the file name of the plugin (in the current directory)
# Output: sets FOUNDPLUG=0|1, PNAME, PVER, PVER_ORIG_FULL, PFILE (including/or directory name as relevant), PPARENT (themes)
local EFILE=$1
FOUNDPLUG=0
if [[ $METAMODE = "core" ]]; then
if [[ -f wp-includes/version.php ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "Found wp-includes/version.php whilst checking version"
XPVER=$(grep -E 'wp_version[[:space:]]*=' wp-includes/version.php | head -1 | cut -d\' -f2)
if [[ $XPVER =~ ^[0-9]+\. ]]; then
FOUNDPLUG=1
PVER=$XPVER
PVER_ORIG_FULL=$XPVER
PNAME="WordPress Core"
PPARENT=""
PFILE=wp-includes/version.php
fi
else
ws_event_error "Did not find wp-includes/version.php whilst checking version"
fi
elif [[ $METAMODE = "plugin" ]]; then
if [[ -n $EFILE ]]; then
get_entity_version_frompluginfile "$EFILE"
else
# Look through all the files
for PLUG in `find . -maxdepth 1 -type f -iregex '.*\.php$'`; do
get_entity_version_frompluginfile "$PLUG"
done
fi
elif [[ $METAMODE = "theme" && -f style.css ]]; then
XPNAME=`head -45 style.css | grep -Ei "Theme Name:" | head -1 | cut -d: -f2- | sed 's/[^-\/0-9a-zA-Z\. ]//g' | sed 's/^ //'`
XPVER=`head -45 style.css | grep -Ei "Version:" | head -1 | cut -d: -f2- | sed 's/[^-0-9a-zA-Z\.]//g'`
PFILE="`basename $(pwd)`"
PPARENT=`head -45 style.css | grep -Ei "Template:" | head -1 | cut -d: -f2- | sed 's/[^-\/0-9a-zA-Z\. ]//g' | sed 's/^ //'`
if [[ -n $XPNAME && -n $XPVER ]]; then
FOUNDPLUG=1
PNAME=$XPNAME
PVER=$XPVER
PVER_ORIG_FULL=`head -45 style.css | grep -Ei "Version:" | head -1 | cut -d: -f2- | sed 's/^[[:space:]]*//g' | sed 's/\r$//g'`
elif [[ -n $XPNAME && -z $XPVER ]]; then
ws_event_warning "$XPNAME: Could not detect theme version: could not find tag in stylesheet: will set as 0"
FOUNDPLUG=1
PNAME=$XPNAME
PVER=0
PVER_ORIG_FULL=0
fi
elif [[ $METAMODE = "theme" ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_warning "Could not detect theme information: stylesheet (style.css) not found (`pwd`)"
elif [[ $METAMODE != "theme" ]]; then
abort_die 74 "$METAMODE: Unknown metamode in get_entity_version"
fi
}
if [[ $METAMODE = "core" || $METAMODE = "plugin" || $METAMODE = "theme" ]]; then
# In zip selection code below we look in and download to these locations
make_and_enter fromwporg.$METAMODE
ws_cd ..
make_and_enter customimports.$METAMODE
ws_cd ..
fi
function unpack_wordpress() {
# Unpacks a WordPress zip in current directory, and returns the version
# Input: $1 = zip file
# Output: Sets UNPACKED_VER to the version found in the unpacked zip
$UNZIP $UNZIPOPT "$1" || abort_die 21 "Unzip command returned an error"
[[ ! -f wordpress/wp-includes/version.php ]] && abort_die 21 "The zip file (`basename "$1"`) was unzipped; but did not appear to be a copy of WordPress"
UNPACKED_VER=`grep -E 'wp_version[[:space:]]+=' wordpress/wp-includes/version.php | head -1 | cut -d\' -f2`
}
function ensure_unpacked_core_exists() {
# $1 = version
# You can be anywhere in the directory structure when you call this
local THEVERSION=$1
if [[ -f "$WORKINGDIR_FULL/fromwporg.core/$THEVERSION/wordpress/wp-includes/version.php" ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "WordPress $THEVERSION: we already have this unpacked (fromwporg.core/$THEVERSION)"
else
[[ $DEBUG -ge 1 ]] && ws_event_info "WordPress $THEVERSION: requested to get one; we do not yet have an unpacked copy"
[[ -z $UNZIP ]] && abort_die 20 "No unzip command found (needed to unpack WordPress zip) - run again with --checkrequirements or --debug for more information"
if [[ -f "$WORKINGDIR_FULL/customimports.core/wordpress.$THEVERSION.zip" ]]; then
COREFILE="wordpress.$THEVERSION.zip"
make_and_pushd "$WORKINGDIR_FULL/customimports.core/$THEVERSION"
elif [[ -f "$WORKINGDIR_FULL/fromwporg.core/wordpress.$THEVERSION.zip" ]]; then
COREFILE="wordpress.$THEVERSION.zip"
make_and_pushd "$WORKINGDIR_FULL/fromwporg.core/$THEVERSION"
else
[[ $DEBUG -ge 1 ]] && ws_event_info "Could not find a local WordPress $THEVERSION; going to download"
# download_version assumes we are in working directory
ws_pushd "$WORKINGDIR_FULL"
download_version wordpress "$THEVERSION"
[[ $? -ne 0 ]] && abort_die 89 "Failed to find version $THEVERSION of WordPress core"
ws_popd
make_and_pushd "$WORKINGDIR_FULL/fromwporg.core/$THEVERSION"
# If we are still here, then it is in fromwporg.core/wordpress.$THEVERSION.zip
COREFILE="wordpress.$THEVERSION.zip"
fi
[[ $DEBUG -ge 1 ]] && ws_event_info "WordPress core $THEVERSION downloaded ($COREFILE)"
# Unpacks (or aborts upon failure) and sets UNPACKED_VER
unpack_wordpress "../$COREFILE"
# Sanity checks
if [[ $UNPACKED_VER = $THEVERSION ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_debug "Unpackaged WordPress zip was the correct version ($THEVERSION)"
else
abort_die 83 "The zip file ($COREFILE) had a different WordPress version ($UNPACKED_VER) than expected ($THEVERSION)"
fi
# If the zip had a wp-content, then get rid of it
rm -rf wordpress/wp-content 2>/dev/null
# Return to where we were
ws_popd
fi
}
# This is put in a function, as there is another code path than the one immediately below which can call it
# Expects to be in working directory
function process_rollbacktime() {
# The info that we wanted a rollback is found in $ROLLBACKTIME = x(=no rollback)/n(=show menu)/time string -
MODE="normal"
if [[ $ROLLBACKTIME != "x" ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "Looking for installed version from time: $ROLLBACKTIME"
local SAVEROLLBACKTIME=$ROLLBACKTIME
if [[ $ROLLBACKTIME != "most recent" ]]; then
if [[ $CAN_STRTOTIME -eq 1 ]]; then
ROLLBACKTIME=`ws_strtotime "$ROLLBACKTIME"`
[[ $? -ne 0 ]] && ROLLBACKTIME="error"
fi
[[ ! $ROLLBACKTIME =~ ^[0-9]+$ ]] && abort_die 59 "Could not process rollback time: $SAVEROLLBACKTIME"
fi
managerollbacks time "$ROLLBACKTIME"
[[ -z $LISTPLUG_USEFILE ]] && abort_die 60 "Could not find any rollback version for time: $SAVEROLLBACKTIME (possibly the same version was installed then as now)"
local EXTRABLURB=""
[[ -L old.$METAMODE/$LISTPLUG_USEFILE ]] && EXTRABLURB=" (unmodified)"
echo "${BOLD}Available rollback is:${OFFBOLD} ${LISTPLUG_USEVERSION}${EXTRABLURB}, replaced at: $LISTPLUG_USEDATE"
[[ $DEBUG -ge 1 ]] && ws_event_info "Rollback file: ${LISTPLUG_USEFILE}${EXTRABLURB}"
NEWZIPSOURCE="old.$METAMODE"
USEZIP=$LISTPLUG_USEFILE
APPARENT_NEW_VERSION=$LISTPLUG_USEVERSION
CHOSEN_CORE_IS_OVERLAY=1
[[ $METAMODE = "core" ]] && ensure_unpacked_core_exists "$LISTPLUG_USEVERSION"
fi
}
# Identify where is the zip that we will use
# Rollback mode is the same as update mode internally; except we fetch the file from somewhere else
if [[ $MODE = "rollback" && $DO_MULTIPLE_METAMODE -eq 0 ]]; then
if [[ -n $SITE && ( $SITE =~ , || $SITE =~ \@ || $SITE =~ \- ) ]]; then
# Processing is deferred until later (either on re-invocation, or when we discover there really was only one site)
[[ $DEBUG -ge 1 ]] && ws_event_debug "Processing of the --rollback parameter is deferred until we know we are working on a single site"
else
process_rollbacktime
fi
elif [[ $DO_MULTIPLE_METAMODE -eq 0 ]]; then
NEWZIPSOURCE="fromwporg.$METAMODE"
# If they specify a file/version, then act appropriately
# customimports should override fromwporg
if [[ -n $USEZIP && $USEZIP != "latest" ]]; then
if [ -s "customimports.$METAMODE/$USEZIP" ]; then
if [[ -s "$ORIGDIR/$USEZIP" && -n $CHECKSUM ]]; then
FOUND_CUST_CKSUM=`$CHECKSUM "customimports.$METAMODE/$USEZIP" | cut -d' ' -f1`
FOUND_NEW_CKSUM=`$CHECKSUM "$ORIGDIR/$USEZIP" | cut -d' ' -f1`
[[ $FOUND_CUST_CKSUM != $FOUND_NEW_CKSUM ]] && abort_die 65 "Ambiguous file specification - named file exists, and so does an identically-named (but different contents) file that is already imported. To resolve this conflict, either rename the the file your are trying to import, or delete the existing file ($WORKDIR/customimports.$METAMODE/$USEZIP)"
elif [[ -s "$ORIGDIR/$USEZIP" ]]; then
abort_die 65 "Needed to perform a checksum operation, but no checksumming binary could be found (run again with --checkrequirements for more information"
fi
NEWZIPSOURCE="customimports.$METAMODE"
elif [[ -s "customimports.$METAMODE/$PLUGIN.$USEZIP.zip" ]]; then
NEWZIPSOURCE="customimports.$METAMODE"
APPARENT_NEW_VERSION=$USEZIP
USEZIP="$PLUGIN.$USEZIP.zip"
elif [[ -s "$ORIGDIR/$USEZIP" || -s "$USEZIP" || $USEZIP =~ ^(https?|s?ftps?):// ]]; then
if [[ $USEZIP =~ ^(https?|s?ftps?):// ]]; then
# Download it
[[ $DEBUG -ge 1 ]] && ws_event_info "Requested to pull new zip from remote URL: $USEZIP"
if [[ $CAN_URLGET -eq 1 ]]; then
make_and_enter tmp
DOWNLOAD_TMP=`mktemp downloaded.XXXXX`
url_get "$USEZIP" stdout >$DOWNLOAD_TMP
if [[ -s $DOWNLOAD_TMP ]]; then
# Parameter to extract_prospective_zip is relative to inside tmp
NEWONE_FINDDIR="./"
USEZIP=$DOWNLOAD_TMP
else
abort_die 65 "$USEZIP: Download failed"
fi
else
abort_die 65 "Cannot download URL: $USEZIP: You need to install one of lftp, curl or wget (run again with --checkrequirements for more information"
fi
# Reverse earlier cd tmp
ws_cd ..
elif [[ -s "$ORIGDIR/$USEZIP" ]]; then
NEWONE_FINDDIR="$ORIGDIR/"
else
NEWONE_FINDDIR=""
fi
# Check that this is a valid entity. Sets USETMP
make_and_enter tmp
EXTRA_ZIP_OPT=""
[[ $METAMODE = "core" ]] && EXTRA_ZIP_OPT="wordpress/wp-includes/version.php"
# This sets USETMP and enters us into an unzipped directory
extract_prospective_zip "${NEWONE_FINDDIR}${USEZIP}" $EXTRA_ZIP_OPT
# Check it is a valid plugin/theme/core
get_entity_version
EXTRACTED_SLUG=`basename $(pwd)`
# Return to tmp
ws_cd ../..
# Remove the extracted plugin
rm -rf $USETMP
if [ $FOUNDPLUG -eq 0 ]; then
[[ $NEWONE_FINDDIR = "./" ]] && rm -f $USEZIP
abort_die 63 "Could not find a valid $METAMODE inside this zip file ($USEZIP)"
fi
[[ $DEBUG -ge 1 ]] && ws_event_info "Found valid $METAMODE in $USEZIP: name=$PNAME, version=$PVER, slug=$EXTRACTED_SLUG)"
if [[ -n $PLUGIN && $PLUGIN != $EXTRACTED_SLUG ]]; then
[[ $NEWONE_FINDDIR = "./" ]] && rm -f $USEZIP
abort_die 64 "$METAMODE slug of given zip ($EXTRACTED_SLUG) does not match that given on the command line ($PLUGIN)"
fi
# Return to working directory
ws_cd ..
# Rename it to the proper filename scheme; also reduce $USEZIP to that new name
if [[ $NEWONE_FINDDIR = "./" ]]; then
mv -f $COPYOPT "tmp/$USEZIP" "customimports.$METAMODE/$EXTRACTED_SLUG.$PVER.zip" || exit $?
else
cp -f $COPYOPT "${NEWONE_FINDDIR}${USEZIP}" "customimports.$METAMODE/$EXTRACTED_SLUG.$PVER.zip" || exit $?
fi
USEZIP="$EXTRACTED_SLUG.$PVER.zip"
NEWZIPSOURCE="customimports.$METAMODE"
elif [[ -s "fromwporg.$METAMODE/$USEZIP" ]]; then
# Note - this may actually be a directory when specifying a version with core; we deal with that later
true
elif [[ -s "fromwporg.$METAMODE/$PLUGIN.$USEZIP.zip" ]]; then
APPARENT_NEW_VERSION=$USEZIP
USEZIP="$PLUGIN.$USEZIP.zip"
elif [[ ! $USEZIP =~ \.zip$ && ( ( $METAMODE = "core" && $USEZIP =~ ^([0-9][0-9a-z\.]+)(-(alpha|beta)[0-9])?$ ) || ( -n $PLUGIN && $USEZIP =~ ^([0-9][0-9a-z\.]+)$ ) ) ]]; then
# Attempt to download this version from WordPress.Org...
download_version "$PLUGIN" $USEZIP || exit 44
APPARENT_NEW_VERSION=$USEZIP
USEZIP=$PLUGIN.$USEZIP.zip
else
ws_event_error "site:${SITE}, ${METAMODE}:${PLUGIN}, --new=$USEZIP: Could not find a suitable zip file (either specify a version of an already-known $METAMODE, or to retrieve afresh from wordpress.org (e.g. --new=1.3.2), or a path or URL to a zip file (e.g. --new=myfolder/my${METAMODE}.1.5.zip))"
exit 44
fi
fi
fi
ACCESSMETHOD=""
FILEDIR=""
FTPHOST=""
FTPPASS=""
# Setup now finished - into the actions
# This next section ensures existence of tmp
if [[ $SELFINVOCATION -eq 0 ]] ; then
# This is required for caching url_post output
mkdir -p lftp
# Default lftp configuration - allow (but not force) TLS, plus a few other useful ones
[[ ! -f lftp/rc && -n $LFTP ]] && echo "set ftp:list-options -a
set net:reconnect-interval-base 3
set net:reconnect-interval-multiplier 2
set net:reconnect-interval-max 300
set net:max-retries 9
set ftp:ssl-protect-data yes
set ftp:ssl-protect-list yes
# Note that the ftp:ssl-allow and ftp:ssl-force options are over-written afresh at run-time; changing them here will have no effect (the combination depends on site options and upon --disableftptls and --requireftptls)
set ftp:ssl-allow yes
set ftp:ssl-force no" >lftp/rc
make_and_enter tmp
# Clean up
[[ $DEBUG -ge 1 ]] && ws_event_info "Cleaning up tmp directory"
find . -maxdepth 1 -mtime +1 -type d -regex './[A-Za-z0-9].*' -exec rm -rf {} \;
find . -maxdepth 1 -mtime +1 -type f -exec rm -f {} \;
ws_cd ..
fi
function version_sort() {
# Return values:
# 0 = first was bigger
# 1 = equal
# 2 = second was bigger
# Handles numbers, periods, and <num>(-)<rc|alpha|beta><num>
local FIRST=$1
local SECOND=$2
[[ $FIRST = $SECOND ]] && return 1
local NEXTFIRST="0"
local NEXTSECOND="0"
local REMFIRST=""
local REMSECOND=""
# Discard anything after a space (3rd Dec 2012)
local MATCHSREGEX='^([^ ]+) '
[[ $FIRST =~ $MATCHSREGEX ]] && FIRST=${BASH_REMATCH[1]}
[[ $SECOND =~ $MATCHSREGEX ]] && SECOND=${BASH_REMATCH[1]}
# Deal with starting with a period (3rd Dec 2012): treat it as zero
[[ ${FIRST:0:1} = "." ]] && FIRST="0$FIRST"
[[ ${SECOND:0:1} = "." ]] && SECOND="0$SECOND"
# Remove any leading v - seen 14th January 2012
[[ ${FIRST:0:1} = "v" ]] && FIRST=${FIRST:1}
[[ ${SECOND:0:1} = "v" ]] && SECOND=${SECOND:1}
# Split off before the period - get this segment
# Strip leading zeroes (added 24th Sep 2012)
local MATCHREGEX='^([0-9]+)\.(.*)$'
if [[ $FIRST =~ $MATCHREGEX ]]; then
FIRST=${BASH_REMATCH[1]}
NEXTFIRST=${BASH_REMATCH[2]}
fi
if [[ $SECOND =~ $MATCHREGEX ]]; then
SECOND=${BASH_REMATCH[1]}
NEXTSECOND=${BASH_REMATCH[2]}
fi
# Ignore anything after the numbers in pattern (number)(letter)(anything)
local MATCHREGEX2='^([0-9]+)([-A-Za-z][-A-Za-z0-9]*)$'
if [[ $FIRST =~ $MATCHREGEX2 ]]; then FIRST=${BASH_REMATCH[1]}; REMFIRST=${BASH_REMATCH[2]}; fi
if [[ $SECOND =~ $MATCHREGEX2 ]]; then SECOND=${BASH_REMATCH[1]}; REMSECOND=${BASH_REMATCH[2]}; fi
[[ $FIRST -gt $SECOND ]] && return 0
[[ $FIRST -lt $SECOND ]] && return 2
# Now have to deal with the fact that something like 0.2rc1 is less than 0.2
local FIRST_RC=0
if [[ $FIRST = $SECOND ]]; then
if [[ $REMFIRST =~ ^-?[rR][cC](.*)$ ]]; then
NEXTFIRST="2.${BASH_REMATCH[1]}"
FIRST_RC=3
elif [[ $REMFIRST =~ ^-?[aA][lL][pP][hH][aA]-?(.*)$ ]]; then
NEXTFIRST="0.${BASH_REMATCH[1]}"
FIRST_RC=2
elif [[ $REMFIRST =~ ^-?[bB][eE][tT][aA]-?(.*)$ ]]; then
NEXTFIRST="1.${BASH_REMATCH[1]}"
FIRST_RC=1
fi
# Is the second an rc?
if [[ $REMSECOND =~ ^-?[rR][cC](.*)$ ]]; then
# Is an RC. If first part was not, then first is greater
[[ $FIRST_RC -eq 0 ]] && return 0
NEXTSECOND="2.${BASH_REMATCH[1]}"
elif [[ $REMSECOND =~ ^-?[aA][lL][pP][hH][aA](.*)$ ]]; then
# Is an alpha
[[ $FIRST_RC -eq 0 ]] && return 0
NEXTSECOND="0.${BASH_REMATCH[1]}"
elif [[ $REMSECOND =~ ^-?[bB][eE][tT][aA](.*)$ ]]; then
# Is a beta
[[ $FIRST_RC -eq 0 ]] && return 0
NEXTSECOND="1.${BASH_REMATCH[1]}"
elif [ $FIRST_RC -ge 1 ]; then
# First was an RC/alpha/beta, this is not. So this is later.
return 2
fi
fi
[[ $NEXTFIRST =~ ^0[0-9] ]] && NEXTFIRST=${NEXTFIRST:1}
[[ $NEXTSECOND =~ ^0[0-9] ]] && NEXTSECOND=${NEXTSECOND:1}
version_sort $NEXTFIRST $NEXTSECOND
}
function url_browser() {
# Input: $1=URL
# We assume a browser is available
if [[ -n $W3M ]]; then
$W3M "$1"
elif [[ -n $ELINKS ]]; then
$ELINKS "$1"
else
$LYNX "$1"
fi
}
function get_api_changelog() {
# Core-friendly: only if calling with "changelog" (not description/download_link)
# Input: $1 = plugin slug
# $2 = changelog | description | download_link | last_updated
if [[ $CAN_BROWSE -ne 1 ]]; then
ws_event_error "Cannot find a working w3m, elinks or lynx, needed to display output (run with --checkrequirements or --debug for more information)"
return 5
elif [[ $CAN_URLGET -eq 0 ]]; then
ws_event_error "Cannot find a working lftp, wget or curl, needed to get API info (run with --checkrequirements or --debug for more information)"
return 5
fi
local CHANGELOG_PLUGIN=$1
local SHOW_WHAT=$2
if [[ $DISABLEAPI -eq 1 || -z $PHP || $METAMODE = "core" ]]; then
if [[ $DISABLEAPI -eq 1 ]]; then ws_event_info "API was disabled via --disableapi; so will use text browser instead"
elif [[ -z $PHP ]]; then ws_event_info "No php binary found (run with --debug for more information); so will use text browser instead"
fi
if [[ $SHOW_WHAT = "changelog" ]]; then
if [[ $METAMODE = "core" ]]; then
url_browser http://codex.wordpress.org/Category:Changelogs
else
url_browser https://wordpress.org/${METAMODE}s/$CHANGELOG_PLUGIN/changelog
fi
else
url_browser https://wordpress.org/${METAMODE}s/$CHANGELOG_PLUGIN
fi
return
fi
SERIALTMP=`mktemp TMP-WPAPI.XXXXX` || exit 67
SLUG=$1
SLUGLEN=${#SLUG}
URL_STRING="action=${METAMODE}_information&request=O:8:\"stdClass\":2:{s:4:\"slug\";s:$SLUGLEN:\"$SLUG\";s:8:\"per_page\";i:24;}"
# Don't prefer lftp, because of its extraneous HEAD requests which cause 500s on /themes/info/1.0/
url_post http://api.wordpress.org /${METAMODE}s/info/1.0/ "$URL_STRING" preferwget,prefercurl >$SERIALTMP
if [[ -s "$SERIALTMP" ]]; then
grep -q $SHOW_WHAT $SERIALTMP
if [ $? -ne 0 ]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "API call did not result in file in expected format"
echo "$SLUG: Could not find a $SHOW_WHAT for this ${METAMODE}" >/dev/stderr
ws_event_notice "$SLUG: Could not find a $SHOW_WHAT for this ${METAMODE}"
rm -f $SERIALTMP
return 3
fi
if [[ $SHOW_WHAT = "changelog" ]]; then
if [[ -n $W3M ]]; then
( echo "<h1>Changelog for ${METAMODE}: $CHANGELOG_PLUGIN (Q to quit)</h1>" ; $PHP -r "\$o=unserialize(file_get_contents(\"$SERIALTMP\"));\$a=\$o->sections;print \$a['changelog'].\"\n\";" ) | $W3M -T text/html
elif [[ -n $ELINKS ]]; then
( echo "<h1>Changelog for ${METAMODE}: $CHANGELOG_PLUGIN (Q to quit)</h1>" ; $PHP -r "\$o=unserialize(file_get_contents(\"$SERIALTMP\"));\$a=\$o->sections;print \$a['changelog'].\"\n\";" ) | $ELINKS -force-html
else
( echo "<h1>Changelog for ${METAMODE}: $CHANGELOG_PLUGIN (Q to quit)</h1>" ; $PHP -r "\$o=unserialize(file_get_contents(\"$SERIALTMP\"));\$a=\$o->sections;print \$a['changelog'].\"\n\";" ) | $LYNX -force_html -stdin
fi
elif [[ $SHOW_WHAT = "description" ]]; then
if [[ -n $W3M ]]; then
( echo "<h1>Description for ${METAMODE}: $CHANGELOG_PLUGIN (Q to quit)</h1>" ; $PHP -r "\$o=unserialize(file_get_contents(\"$SERIALTMP\"));\$a=\$o->sections;print \$a['description'].\"\n\";" ) | $W3M -T text/html
elif [[ -n $ELINKS ]]; then
( echo "<h1>Description for ${METAMODE}: $CHANGELOG_PLUGIN (Q to quit)</h1>" ; $PHP -r "\$o=unserialize(file_get_contents(\"$SERIALTMP\"));\$a=\$o->sections;print \$a['description'].\"\n\";" ) | $ELINKS -force-html
else
( echo "<h1>Description for ${METAMODE}: $CHANGELOG_PLUGIN (Q to quit)</h1>" ; $PHP -r "\$o=unserialize(file_get_contents(\"$SERIALTMP\"));\$a=\$o->sections;print \$a['description'].\"\n\";" ) | $LYNX -force_html -stdin
fi
elif [[ $SHOW_WHAT = "download_link" || $SHOW_WHAT = "last_updated" ]]; then
$PHP -r "\$o=unserialize(file_get_contents(\"$SERIALTMP\"));\$a=\$o->${SHOW_WHAT};print \$a.\"\n\";"
fi
else
rm -f $SERIALTMP
echo "$SLUG: Could not find a $SHOW_WHAT for this ${METAMODE}" >/dev/stderr
ws_event_notice "$SLUG: Could not find a $SHOW_WHAT for this ${METAMODE}"
return 2
fi
rm -f $SERIALTMP
return 0
}
function get_api_entity_info() {
# Core-friendly: yes
# Input: $1 = entity slug
# Optional: $2 = plugin file, if known : will then use faster plugins/update-check API call instead of plugins/info
# If $2 specified, then must also specify $3 = plugin version and $4 = plugin name
# Output: Exit code = 0 success / else failure - 6 indicates that we got a reply back, which was null (indicates no such plugin)
# If success, sets API_PLUGIN_URL and API_PLUGIN_VER and API_PLUGIN_NAME
local SLUG=$1
unset API_PLUGIN_URL API_PLUGIN_VER API_PLUGIN_NAME API_PLUGIN_NOTICE
# First, look in the cache
API_INFO_CACHEFILE="$WORKINGDIR_FULL/tmp/cache-info-$METAMODE-$SLUG"
if [[ $DISABLECACHE -eq 0 && -s $API_INFO_CACHEFILE && $((NOWDATE - `$STAT_MODTIME $API_INFO_CACHEFILE 2>/dev/null || echo 0`)) -lt 3600 ]]; then
if [[ $METAMODE = "core" ]]; then
head -1 $API_INFO_CACHEFILE | read API_PLUGIN_VER MINIMUM_PHP_VERSION API_PLUGIN_URL API_PLUGIN_NAME MINIMUM_MYSQL_VERSION
else
head -1 $API_INFO_CACHEFILE | read API_PLUGIN_VER API_PLUGIN_URL API_PLUGIN_NAME MINIMUM_WP_VERSION
fi
API_PLUGIN_NOTICE=`tail -n +2 $API_INFO_CACHEFILE`
# If cache had valid data
if [[ -n $API_PLUGIN_NAME && -n $API_PLUGIN_URL && -n $API_PLUGIN_VER ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "Found valid API cache file: $API_INFO_CACHEFILE"
return 0
fi
if [ "$API_PLUGIN_VER" = "N;" ]; then
unset API_PLUGIN_VER
return 6
fi
fi
if [ $DISABLEAPI -eq 1 ]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "API was disabled via --disableapi"
return 5
fi
if [[ $METAMODE = "core" ]]; then
# From version 1.6 onwards, it sends serialized (and richer) data
# If we ever switch to using that, we need to change callers of this function to check for $PHP
local CACHE_CVCFILE="$WORKINGDIR_FULL/tmp/cache-info-core-version-check"
if [[ $DISABLECACHE -eq 1 || ! -s $CACHE_CVCFILE || $((NOWDATE - `$STAT_MODTIME "$CACHE_CVCFILE" 2>/dev/null || echo 0`)) -ge 3600 ]]; then
url_get http://api.wordpress.org/core/version-check/1.5/ stdout >"$CACHE_CVCFILE.tmp"
if [[ -s "$CACHE_CVCFILE.tmp" ]]; then
mv "$CACHE_CVCFILE.tmp" "$CACHE_CVCFILE"
else
rm -f "$CACHE_CVCFILE.tmp"
fi
else
[[ $DEBUG -ge 1 ]] && ws_event_info "Version check was cached within last hour; using that ($CACHE_CVCFILE)"
fi
if [[ -s $CACHE_CVCFILE ]]; then
local TESTIT=`head -1 $CACHE_CVCFILE`
local TESTVER=`head -4 $CACHE_CVCFILE | tail -1`
local API_RET=10
if [[ $TESTIT = "upgrade" && $TESTVER =~ ^[0-9]+\. ]]; then
MINIMUM_MYSQL_VERSION=`head -7 $CACHE_CVCFILE | tail -1`
MINIMUM_PHP_VERSION=`head -6 $CACHE_CVCFILE | tail -1`
API_PLUGIN_VER=$TESTVER
API_PLUGIN_URL=`head -3 $CACHE_CVCFILE | tail -1`
API_PLUGIN_NAME="WordPress Core"
API_PLUGIN_NOTICE=""
echo "$API_PLUGIN_VER $MINIMUM_PHP_VERSION $API_PLUGIN_URL $API_PLUGIN_NAME $MINIMUM_MYSQL_VERSION" >"$API_INFO_CACHEFILE"
API_RET=0
else
[[ $DEBUG -ge 1 ]] && ws_event_notice "Core version check: Did not get the expected response from api.wordpress.org (response was: `cat $CACHE_CVCFILE`)"
fi
else
ws_event_error "Unable to discern the latest WordPress core version by querying api.wordpress.org"
API_RET=10
fi
return $API_RET
fi
# Core meta-mode has returned; now we know we are in plugin/theme meta-mode
[[ -z $PHP ]] && return 1
SERIALTMP=`mktemp TMP-WPAPI2.XXXXX` || exit 67
SLUGLEN=${#SLUG}
# Were we advised a file name?
if [[ -n $2 && -n $3 && -n $4 ]]; then
local PLUGVER=$3
local PLUGVERLEN=${#PLUGVER}
local PLUGNAME=$4
local PLUGNAMELEN=${#PLUGNAME}
local PLUGFILE=$2
local PLUGFILELEN=${#PLUGFILE}
# plugins=O:8:"stdClass":2:{s:7:"plugins";a:1:{s:51:"simple-social-bookmarks/simple-social-bookmarks;a:2:{s:4:"Name";s:23:"Simple Social Bookmarks";s:7:"Version";s:5:"3.2.2";}}s:6:"active";a:1:{i:0;s:51:"simple-social-bookmarks/simple-social-bookmarks.php";}}
# We don't send our current version, but rather send 0 because we want a definite reply
local METAMODE_LEN=$((${#METAMODE}+1))
if [[ $METAMODE = "plugin" ]]; then
local URL_STRING="${METAMODE}s=O:8:\"stdClass\":2:{s:${METAMODE_LEN}:\"${METAMODE}s\";a:1:{s:$PLUGFILELEN:\"$PLUGFILE\";a:2:{s:4:\"Name\";s:$PLUGNAMELEN:\"$PLUGNAME\";s:7:\"Version\";s:1:\"0\";}}s:6:\"active\";a:1:{i:0;s:$PLUGFILELEN:\"$PLUGFILE\";}}"
else
# themes=a:1:{s:10:"responsive";a:6:{s:4:"Name";s:10:"Responsive";s:7:"Version";s:5:"1.4.7";s:5:"Title";s:10:"Responsive";s:8:"Template";s:10:"responsive";s:10:"Stylesheet";s:10:"responsive";s:12:"Parent Theme";s:0:"";}}
local URL_STRING="${METAMODE}s=a:1:{s:$PLUGFILELEN:\"$PLUGFILE\";a:6:{s:4:\"Name\";s:$PLUGNAMELEN:\"$PLUGNAME\";s:7:\"Version\";s:$PLUGVERLEN:\"$PLUGVER\";s:5:\"Title\";s:$PLUGNAMELEN:\"$PLUGNAME\";s:8:\"Template\";s:$PLUGFILELEN:\"$PLUGNAME\";s:10:\"Stylesheet\";s:$PLUGFILELEN:\"$PLUGFILE\";s:12:\"Parent Theme\";s:0:\"\";}}"
fi
if [[ $CAN_URLGET -eq 1 ]]; then
url_post http://api.wordpress.org /${METAMODE}s/update-check/1.0/ "$URL_STRING" >$SERIALTMP
else
ws_event_error "No working lftp, wget or curl was found; could not get ${METAMODE} API info"
fi
if [[ -s $SERIALTMP ]]; then
FIRSTL_OF_RESULT="`head -1 $SERIALTMP`"
if [[ $FIRSTL_OF_RESULT = "N;" ]]; then
rm -f $SERIALTMP
[[ $DEBUG -ge 1 ]] && ws_event_info "$SLUG: This ${METAMODE} is unknown at api.wordpress.org"
echo "N;" >"$API_INFO_CACHEFILE"
return 6
fi
if [[ $FIRSTL_OF_RESULT =~ stdClass ]]; then
local OLDIFS="$IFS"
# Newline
IFS="
"
for LINE in $($PHP -r "\$obj=unserialize(file_get_contents(\"$SERIALTMP\")); if (isset(\$obj[\"$PLUGFILE\"]->upgrade_notice)) {print \"Notice:\".\$obj[\"$PLUGFILE\"]->upgrade_notice.\"\n\";}; print \"Version:\".\$obj[\"$PLUGFILE\"]->new_version; print \"\nDownload:\".\$obj[\"$PLUGFILE\"]->package.\"\n\"; if (isset(\$obj[\"$PLUGFILE\"]->requires)) { print \"MinWP:\".\$obj[\"$PLUGFILE\"]->requires.\"\n\"; }"); do
if [[ $LINE =~ Version:(.*)$ ]]; then API_PLUGIN_VER=${BASH_REMATCH[1]}
elif [[ $LINE =~ Notice:(.*)$ ]]; then API_PLUGIN_NOTICE=${BASH_REMATCH[1]};
elif [[ $LINE =~ Download:(.*)$ ]]; then API_PLUGIN_URL=${BASH_REMATCH[1]};
elif [[ $LINE =~ MinWP:(.*)$ ]]; then MINIMUM_WP_VERSION=${BASH_REMATCH[1]};
fi
# As this is not returned, we just assume it is unchanged
API_PLUGIN_NAME="$PLUGNAME"
done
IFS="$OLDIFS"
fi
fi
# Truncate, as later the file is used + tested again
echo -n >$SERIALTMP
fi
# Try the other way
if [[ -z $API_PLUGIN_NAME || -z $API_PLUGIN_VER || -z $API_PLUGIN_URL ]]; then
URL_STRING="action=${METAMODE}_information&request=O:8:\"stdClass\":2:{s:4:\"slug\";s:$SLUGLEN:\"$SLUG\";s:8:\"per_page\";i:24;}"
# Prefer wget or curl, because lftp uses HEAD which results in a 500 and (harmless) error output on /themes/info/1.0/
[[ $CAN_URLGET -eq 1 ]] && url_post http://api.wordpress.org /${METAMODE}s/info/1.0/ "$URL_STRING" preferwget,prefercurl >$SERIALTMP
if [[ -s $SERIALTMP ]]; then
FIRSTL_OF_RESULT="`head -1 $SERIALTMP`"
if [[ $FIRSTL_OF_RESULT = "N;" ]]; then
rm -f $SERIALTMP
[[ $DEBUG -ge 1 ]] && ws_event_info "$SLUG: This ${METAMODE} is unknown at api.wordpress.org"
echo "N;" >"$API_INFO_CACHEFILE"
return 6
fi
if [[ ! $FIRSTL_OF_RESULT =~ stdClass ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "API call did not result in file in expected format ($SERIALTMP)"
rm -f $SERIALTMP
return 3
fi
local OLDIFS2="$IFS"
# Newline
IFS="
"
for LINE in $($PHP -r "\$obj=unserialize(file_get_contents(\"$SERIALTMP\")); print \"Version:\".\$obj->version.\"\nDownload:\".\$obj->download_link.\"\nName:\".\$obj->name.\"\n\"; if (isset(\$obj->requires)) print \"MinWP:\".\$obj->requires.\"\n\";"); do
if [[ $LINE =~ Version:(.*)$ ]]; then API_PLUGIN_VER=${BASH_REMATCH[1]}
elif [[ $LINE =~ Download:(.*)$ ]]; then API_PLUGIN_URL=${BASH_REMATCH[1]};
elif [[ $LINE =~ Name:(.*)$ ]]; then API_PLUGIN_NAME=${BASH_REMATCH[1]};
elif [[ $LINE =~ MinWP:(.*)$ ]]; then MINIMUM_WP_VERSION=${BASH_REMATCH[1]};
fi
done
IFS="$OLDIFS2"
else
rm -f $SERIALTMP
return 2
fi
fi
# Tidy up and either write out results to cache or return error
rm -f $SERIALTMP
[[ -z "$API_PLUGIN_VER" || -z "$API_PLUGIN_URL" || -z "$API_PLUGIN_NAME" ]] && return 4
echo "$API_PLUGIN_VER $API_PLUGIN_URL $API_PLUGIN_NAME $MINIMUM_WP_VERSION" >"$API_INFO_CACHEFILE"
[[ -n $API_PLUGIN_NOTICE ]] && echo $API_PLUGIN_NOTICE >>"$API_INFO_CACHEFILE"
return 0
}
function download_latest() {
# Core-friendly: yes
# Compatible with --trunk ($USETRUNK): yes
# Input: set variables:
# 1) $PLUGIN
# 2) $1 = allowed sources (wp = download.wordpress.org; any = anywhere we have access to - for now, customimports)
# 3) be in working directory;
# 4) Also: USETRUNK and METAMODE are consulted
# Output: $PVER $FOUNDPLUG Also if sources = wp then $PNAME is set (otherwise may be corrupted)
# Also if USEZIP="latest" then we update NEWZIPSOURCE if it was found in customimports
# Requires unzip, as we want to unzip and interrogate the version number
# Return code non-zero indicates error; return code of 6 indicates entity not known at wordpress.org
local ALLOWED_SOURCES=$1
DOWNLOAD_LATEST_RETCODE=0
unset PNAME PVER FOUNDPLUG
[[ $DEBUG -ge 1 ]] && ws_event_debug "download_latest: entity=$PLUGIN, sources=$1, caller=`caller`"
# We download into a temporary directory inside tmp
make_and_enter tmp
DOWNTMP=`mktemp -d TMP-DLATEST.XXXXX`
ws_cd $DOWNTMP
if [ $CAN_URLGET -eq 1 ]; then
local TRY_URL=""
# This next variable is only going to differ from `basename $TRY_URL` if the Version: and Stable: tags differ, which is a bad thing, but we have seen it once (Nov 2012) in the wordpress.org directory
local DOWNLOADED_BASE_OVERRIDE=""
API_SUCCESS=0
NOT_KNOWN_AT_WP=0 # Only if we get a definite response to confirm that it is not known
BE_SILENT_ON_DOWNLOAD_FAIL=0
# First, see if we can get the version number from an api.wordpress.org call
# API cannot tell us the latest trunk version
# PHP is not needed in core mode, as there is still an older non-serialized API output available
if [[ ( -n $PHP || $METAMODE = "core" ) && $USETRUNK -eq 0 ]]; then
# Sets: API_PLUGIN_URL API_PLUGIN_VER API_PLUGIN_NAME
get_api_entity_info "$PLUGIN"
API_RET=$?
if [ $API_RET -eq 0 ]; then
API_SUCCESS=1
# Do we have it cached already?
if [[ -s "../../fromwporg.$METAMODE/$PLUGIN.$API_PLUGIN_VER.zip" ]]; then
# When we test this later, we will find we already have it; no download will occur
if [[ $METAMODE = "plugin" ]]; then
TRY_URL="https://downloads.wordpress.org/plugin/$PLUGIN.$API_PLUGIN_VER.zip"
DOWNLOADED_BASE_OVERRIDE="$PLUGIN.$API_PLUGIN_VER.zip"
elif [[ $METAMODE = "theme" ]]; then
TRY_URL="https://wordpress.org/themes/download/$PLUGIN.$API_PLUGIN_VER.zip"
DOWNLOADED_BASE_OVERRIDE="$PLUGIN.$API_PLUGIN_VER.zip"
else
TRY_URL="https://wordpress.org/wordpress-$API_PLUGIN_VER.zip"
fi
else
TRY_URL=$API_PLUGIN_URL
fi
elif [ $API_RET -eq 6 ]; then
NOT_KNOWN_AT_WP=1
BE_SILENT_ON_DOWNLOAD_FAIL=1
else
[[ $DEBUG -ge 1 ]] && ws_event_notice "API call returned failure code: $API_RET"
fi
fi
# Special case; here is as good a place as any to put it
if [[ $METAMODE = "core" && $USETRUNK -eq 1 ]]; then
TRY_URL="http://core.trac.wordpress.org/changeset/latest/trunk?old_path=/&format=zip"
elif [[ $NOT_KNOWN_AT_WP -eq 0 && ( -z $TRY_URL || $API_SUCCESS -eq 0 ) ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_warning "$PLUGIN: Could not get $METAMODE info from wordpress.org API; $METAMODE probably not available from wordpress.org"
# Only worth trying this if the API is down
# Note that this gets the latest trunk version, which can be different to the latest stable version
BE_SILENT_ON_DOWNLOAD_FAIL=1
if [[ $METAMODE = "plugin" ]]; then
TRY_URL=https://downloads.wordpress.org/plugin/$PLUGIN.zip
elif [[ $METAMODE = "theme" ]]; then
TRY_URL=https://wordpress.org/themes/download/$PLUGIN.zip
else
# Note that https://wordpress.org/latest.zip.md5 does exist, so this is OK with the below
TRY_URL=https://wordpress.org/latest.zip
fi
fi
[[ $DEBUG -ge 1 && -n $TRY_URL ]] && ws_event_info "URL chosen to download: $TRY_URL"
if [[ $METAMODE = "core" && $TRY_URL != "https://wordpress.org/latest.zip" ]]; then
if [[ $USETRUNK -eq 0 ]]; then
DOWNLOADED="wordpress.$API_PLUGIN_VER.zip"
else
# Remove the cached file when using --core --trunk if it is 30 minutes old
DOWNLOADED="wordpress.trunk.zip"
local WHEN_DOWNLOADED=../../fromwporg.core/$DOWNLOADED
if [[ $DISABLECACHE -eq 1 || ( -f $WHEN_DOWNLOADED && $((NOWDATE - `$STAT_MODTIME $WHEN_DOWNLOADED 2>/dev/null|| echo 0`)) -ge 1800 ) ]]; then
ws_event_debug "Cached file fromwporg.core/$DOWNLOADED exists, but either --disablecache was specified or the file is older than 30 minutes - will delete"
rm -f $WHEN_DOWNLOADED
fi
fi
else
# This used to take the file name from the basename of TRY_URL (basename $TRY_URL) - then (Nov 2012) we encountered a plugin with differing Version: and Stable: fields.
# The Version: was 'beta 1 (0.6)', whereas Stable:, which filtered into TRY_URL, was just 0.6
if [[ -n $DOWNLOADED_BASE_OVERRIDE ]]; then
DOWNLOADED=$DOWNLOADED_BASE_OVERRIDE
else
DOWNLOADED=`basename $TRY_URL 2>/dev/null`
fi
fi
if [[ $NOT_KNOWN_AT_WP -eq 0 && -s "../../fromwporg.$METAMODE/$DOWNLOADED" ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "Found existing version already in fromwporg cache (fromwporg.$METAMODE/$DOWNLOADED); will copy"
cp $COPYOPT "../../fromwporg.$METAMODE/$DOWNLOADED" .
elif [[ $NOT_KNOWN_AT_WP -eq 0 ]]; then
[[ -n $TRY_URL ]] && url_get "$TRY_URL" "$DOWNLOADED"
# In core mode, we can checksum the download (unless it was a --trunk fetch)
if [[ $METAMODE = "core" && $USETRUNK -eq 0 ]]; then
[[ ! -s "../../fromwporg.$METAMODE/$DOWNLOADED.md5" ]] && url_get "$TRY_URL.md5" stdout >../../fromwporg.$METAMODE/$DOWNLOADED.md5
local DOWNLOADED_MD5_DESIRED=`cat "../../fromwporg.$METAMODE/$DOWNLOADED.md5"`
local DOWNLOADED_MD5_ACTUAL=`$CHECKSUM "$DOWNLOADED" | cut -d' ' -f1`
if [[ -z $DOWNLOADED_MD5_DESIRED ]]; then
echo "${BOLD}FAILED:${OFFBOLD} Could not successfully download the WordPress core checksum" >/dev/stderr
ws_event_warning "Could not successfully download the WordPress core checksum"
DOWNLOAD_LATEST_RETCODE=7
else
if [[ $DOWNLOADED_MD5_ACTUAL = $DOWNLOADED_MD5_DESIRED ]]; then
[[ $DEBUG -ge 1 ]] && ws_event_info "Download good: Checksum of downloaded WordPress zip matched what was expected ($DOWNLOADED_MD5_DESIRED)"
else
echo "${BOLD}FAILED:${OFFBOLD} Checksum of downloaded WordPress zip ($DOWNLOADED_MD5_ACTUAL) did not match the expected value ($DOWNLOADED_MD5_DESIRED)" >/dev/stderr
ws_event_warning "Checksum of downloaded WordPress zip ($DOWNLOADED_MD5_ACTUAL) did not match the expected value ($DOWNLOADED_MD5_DESIRED)"
rm -f "$DOWNLOADED"
DOWNLOADED=""
DOWNLOAD_LATEST_RETCODE=8
fi
fi
fi
fi
if [[ $NOT_KNOWN_AT_WP -eq 0 && -n $DOWNLOADED && -s $DOWNLOADED ]]; then
if [ $API_SUCCESS -eq 1 ]; then
# Save time - no need to unzip
[[ $DEBUG -ge 1 ]] && ws_event_info "API found latest version as: $API_PLUGIN_VER"
APPARENT_NEW_VERSION=$API_PLUGIN_VER
FOUNDPLUG=1
PVER=$API_PLUGIN_VER
PNAME=$API_PLUGIN_NAME
if [[ ! -e ../../fromwporg.$METAMODE/$PLUGIN.$PVER.zip ]]; then
mv "$DOWNLOADED" "../../fromwporg.$METAMODE/$PLUGIN.$PVER.zip"
fi
else
# API was not success
if [[ -z $UNZIP ]]; then
ws_event_info "could not unpack $METAMODE - unzip command not found (re-run with --debug for more information)"
DOWNLOAD_LATEST_RETCODE=5
else
local EXTRAZIP_OPT=""
if [[ $METAMODE = "core" ]]; then
if [[ $USETRUNK -eq 1 ]]; then
EXTRAZIP_OPT="trunk/wp-includes/version.php"
else
EXTRAZIP_OPT="wordpress/wp-includes/version.php"
fi
fi
ws_event_info "Unpacking: $DOWNLOADED"