Newer
Older
<?php
/*
+--------------------------------------------------------------------+
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
/**
*
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
*
*/
if (!defined('CIVICRM_WPCLI_LOADED')) {
define('CIVICRM_WPCLI_LOADED', 1);
* WP-CLI port of drush-civicrm integration.
*
* @see https://github.com/andy-walker/wp-cli-civicrm
*/
class CiviCRM_Command extends WP_CLI_Command {
/**
* WP-CLI integration with CiviCRM.
*
* wp civicrm api
* ==============
* Command for accessing the CiviCRM API. Syntax is identical to `drush cvap`.
* ======================
* Command for accessing clearing cache. Equivilant of running `civicrm/admin/setting/updateConfigBackend&reset=1`.
* Command for to turn debug on.
*
* wp civicrm disable-debug
* Command for to turn debug off.
*
* wp civicrm member-records
* Run the CiviMember UpdateMembershipRecord cron (civicrm member-records).
* wp civicrm pipe <connection-flags>
* ==================
* Start a Civi::pipe session (JSON-RPC 2.0)
* See https://docs.civicrm.org/dev/en/latest/framework/pipe#flags
*
* Process pending CiviMail mailing jobs.
* Example:
* wp civicrm process-mail-queue -u admin
*
* wp civicrm rest
* ===============
* Rest interface for accessing CiviCRM APIs. It can return xml or json formatted data.
*
* wp civicrm restore
* ==================
* Restore CiviCRM codebase and database back from the specified backup directory.
*
* wp civicrm sql-conf
* ===================
*
* wp civicrm sql-connect
* ======================
* A string which connects to the CiviCRM database.
*
* wp civicrm sql-cli
* ==================
* Quickly enter the mysql command line.
*
* wp civicrm sql-dump
* ===================
* Prints the whole CiviCRM database to STDOUT or save to a file.
*
* wp civicrm sql-query
* ====================
* Usage: wp civicrm sql-query <query> <options>...
* <query> is a SQL statement, which can alternatively be passed via STDIN. Any additional arguments are passed to the mysql command directly.
*
* wp civicrm update-cfg
* =====================
* Update config_backend to correct config settings, especially when the CiviCRM site has been cloned or migrated.
*
* wp civicrm upgrade
* ==================
* Take backups, replace CiviCRM codebase with new specified tarfile and upgrade database by executing the CiviCRM upgrade process - civicrm/upgrade?reset=1. Use civicrm-restore to revert to previous state in case anything goes wrong.
*
* wp civicrm upgrade-db
* =====================
* Run civicrm/upgrade?reset=1 just as a web browser would.
*
* wp civicrm install
* Command for to install CiviCRM. The install command requires that you have downloaded a tarball or zip file first.
* Options:
* --dbhost MySQL host for your WordPress/CiviCRM database. Defaults to localhost.
* --dbname MySQL database name of your WordPress/CiviCRM database.
* --dbpass MySQL password for your WordPress/CiviCRM database.
* --dbuser MySQL username for your WordPress/CiviCRM database.
* --lang Default language to use for installation.
* --langtarfile Path to your l10n tar.gz file.
* --site_url Base Url for your WordPress/CiviCRM website without http (e.g. mysite.com)
* --ssl Using ssl for your WordPress/CiviCRM website if set to on (e.g. --ssl=on)
$this->args = $args;
$this->assoc_args = $assoc_args;
'api' => 'api',
'cache-clear' => 'cacheClear',
'enable-debug' => 'enableDebug',
'disable-debug' => 'disableDebug',
'install' => 'install',
'member-records' => 'memberRecords',
'process-mail-queue' => 'processMailQueue',
'rest' => 'rest',
'restore' => 'restore',
'sql-cli' => 'sqlCLI',
'sql-conf' => 'sqlConf',
'sql-connect' => 'sqlConnect',
'sql-dump' => 'sqlDump',
'sql-query' => 'sqlQuery',
'update-cfg' => 'updateConfig',
'upgrade' => 'upgrade',
'upgrade-db' => 'upgradeDB',
// Allow help to pass.
if ('help' === $command) {
return;
}
// Check for existence of CiviCRM (except for command 'install').
if (!function_exists('civicrm_initialize') && 'install' != $command) {
return WP_CLI::error('Unable to find CiviCRM install.');
// Check existence of router entry / handler method.
if (!isset($command_router[$command]) || !method_exists($this, $command_router[$command])) {
return WP_CLI::error(sprintf('Unrecognized command: %s', $command));
* Implementation of command 'api'.
*
* @since 4.5
array_shift($this->args);
list($entity, $action) = explode('.', $this->args[0]);
array_shift($this->args);
$format = $this->getOption('in', 'args');
switch ($format) {
foreach ($this->args as $arg) {
preg_match('/^([^=]+)=(.*)$/', $arg, $matches);
$params[$matches[1]] = $matches[2];
$json = stream_get_contents(STDIN);
$params = (empty($json) ? $defaults : array_merge($defaults, json_decode($json, TRUE)));
WP_CLI::error(sprintf('Unknown format: %s', $format));
$wp_user_timezone = $this->getOption('timezone', get_option('timezone_string'));
if ($wp_user_timezone) {
date_default_timezone_set($wp_user_timezone);
CRM_Core_Config::singleton()->userSystem->setMySQLTimeZone();
}
if ($wp_base_timezone) {
date_default_timezone_set($wp_base_timezone);
return WP_CLI::error(sprintf('Unknown format: %s', $format));
* Implementation of command 'cache-clear'.
*
* @since 4.5
*/
private function cacheClear() {
civicrm_initialize();
require_once 'CRM/Core/Config.php';
$config = CRM_Core_Config::singleton();
* Implementation of command 'enable-debug'.
*
* @since 4.5
*/
private function enableDebug() {
civicrm_initialize();
* Implementation of command 'disable-debug'.
*
* @since 4.7
*/
private function disableDebug() {
civicrm_initialize();
* Implementation of command 'install'.
*
* @since 4.5
if ('on' === $this->getOption('ssl', FALSE)) {
$_SERVER['HTTPS'] = 'on';
}
if ($plugin_path = $this->getOption('destination', FALSE)) {
$plugin_path = ABSPATH . $plugin_path;
}
else {
$plugin_path = WP_PLUGIN_DIR . '/civicrm';
}
global $crmPath;
$crmPath = "$plugin_path/civicrm";
$crm_files_present = is_dir($crmPath);
if (!$dbuser = $this->getOption('dbuser', FALSE)) {
return WP_CLI::error('CiviCRM database username not specified.');
if (!$dbpass = $this->getOption('dbpass', FALSE)) {
return WP_CLI::error('CiviCRM database password not specified.');
if (!$dbhost = $this->getOption('dbhost', FALSE)) {
return WP_CLI::error('CiviCRM database host not specified.');
if (!$dbname = $this->getOption('dbname', FALSE)) {
return WP_CLI::error('CiviCRM database name not specified.');
if ($lang = $this->getOption('lang', FALSE)) {
$moPath = "$crmPath/l10n/$lang/LC_MESSAGES/civicrm.mo";
if (!($langtarfile = $this->getOption('langtarfile', FALSE)) && !file_exists($moPath)) {
return WP_CLI::error("Failed to find data for language ($lang). Please download valid language data with --langtarfile=<path/to/tarfile>.");
}
// Should probably never get to here as Wordpress Civi comes in a zip file.
// Check anyway just in case that ever changes.
if ($crm_files_present) {
return WP_CLI::error('Existing CiviCRM found. No action taken.');
return WP_CLI::error('Error extracting tarfile.');
}
elseif ($this->getOption('zipfile', FALSE)) {
if ($crm_files_present) {
return WP_CLI::error('Existing CiviCRM found. No action taken.');
return WP_CLI::error('Error extracting zipfile.');
// Site is already extracted - which is how we're running this script.
// We just need to run the installer.
return WP_CLI::error('No zipfile specified. Use "--zipfile=path/to/zipfile" or extract file ahead of time.');
// Include CiviCRM classloader - so that we can run `Civi\Setup`.
$classLoaderPath = "$crmPath/CRM/Core/ClassLoader.php";
return WP_CLI::error('Archive could not be unpacked or CiviCRM installer helper file is missing.');
// We were using a directory that was already there.
WP_CLI::success('Using installer files found on the site.');
}
else {
// We must've just unpacked the archive because it wasn't there
// before.
return WP_CLI::error('Error downloading langtarfile');
return WP_CLI::error("Failed to find data for language ($lang). Please download valid language data with \"--langtarfile=<path/to/tarfile>\".");
// Initialize civicrm-setup
@WP_CLI::run_command(['plugin', 'activate', 'civicrm'], []);
require_once $classLoaderPath;
CRM_Core_ClassLoader::singleton()->register();
\Civi\Setup::assertProtocolCompatibility(1.0);
\Civi\Setup::init(['cms' => 'WordPress', 'srcPath' => $crmPath]);
$setup = \Civi\Setup::instance();
$setup->getModel()->db = ['server' => $dbhost, 'username' => $dbuser, 'password' => $dbpass, 'database' => $dbname];
$setup->getModel()->lang = (empty($lang) ? 'en_US' : $lang);
if ($base_url = $this->getOption('site_url', FALSE)) {
$base_url = $protocol . '://' . $base_url;
if (substr($base_url, -1) != '/') {
$base_url .= '/';
}
$setup->getModel()->cmsBaseUrl = $base_url;
// Check system requirements
$reqs = $setup->checkRequirements();
array_map('WP_CLI::print_value', $this->formatRequirements(array_merge($reqs->getErrors(), $reqs->getWarnings())));
if ($reqs->getErrors()) {
WP_CLI::error(sprintf("Cannot install. Please check requirements and resolve errors.", count($reqs->getErrors()), count($reqs->getWarnings())));
$installed = $setup->checkInstalled();
if ($installed->isSettingInstalled() || $installed->isDatabaseInstalled()) {
WP_CLI::error("Cannot install. CiviCRM has already been installed.");
// Go time
$setup->installFiles();
WP_CLI::success('CiviCRM data files initialized successfully.');
$setup->installDatabase();
WP_CLI::success('CiviCRM database loaded successfully.');
private function formatRequirements(array $messages): array {
$formatted = [];
foreach ($messages as $message) {
$formatted[] = sprintf("[%s] %s: %s", $message['severity'], $message['section'], $message['message']);
}
return array_unique($formatted);
* Implementation of command 'member-records'.
*
* @since 4.5
*/
private function memberRecords() {
civicrm_initialize();
if (substr(CRM_Utils_System::version(), 0, 3) >= '4.3') {
$job->executeJobByAction('job', 'process_membership');
WP_CLI::success('Executed "process_membership" job.');
$_REQUEST['name'] = $this->getOption('civicrm_cron_username', NULL);
$_REQUEST['pass'] = $this->getOption('civicrm_cron_password', NULL);
$_REQUEST['key'] = $this->getOption('civicrm_sitekey', NULL);
0 => 'drush',
1 => '-u' . $_REQUEST['name'],
2 => '-p' . $_REQUEST['pass'],
# if (!defined('CIVICRM_CONFDIR')) {
# $plugins_dir = plugin_dir_path(__FILE__);
# define('CIVICRM_CONFDIR', $plugins_dir);
# }
include 'bin/UpdateMembershipRecord.php';
}
}
private function pipe() {
civicrm_initialize();
if (!is_callable(['Civi', 'pipe'])) {
return WP_CLI::error('This version of CiviCRM does not include Civi::pipe() support.');
}
if (!empty($this->args[1])) {
Civi::pipe($this->args[1]);
}
else {
Civi::pipe();
}
}
* Implementation of command 'process-mail-queue'.
*
* @since 4.5
if (substr(CRM_Utils_System::version(), 0, 3) >= '4.3') {
$job->executeJobByAction('job', 'process_mailing');
WP_CLI::success("Executed 'process_mailing' job.");
$result = civicrm_api('Mailing', 'Process', ['version' => 3]);
if ($result['is_error']) {
WP_CLI::error($result['error_message']);
* Implementation of command 'rest'.
*
* @since 4.5
if (!$query = $this->getOption('query', FALSE)) {
return WP_CLI::error('query not specified.');
$query = explode('&', $query);
$_GET['q'] = array_shift($query);
foreach ($query as $key_val) {
list($key, $val) = explode('=', $key_val);
$_REQUEST[$key] = $val;
$_GET[$key] = $val;
require_once 'CRM/Utils/REST.php';
$rest = new CRM_Utils_REST();
require_once 'CRM/Core/Config.php';
$config = CRM_Core_Config::singleton();
// Adding dummy script, since based on this api file path is computed.
$_SERVER['SCRIPT_FILENAME'] = "$civicrm_root/extern/rest.php";
if (isset($_GET['json']) && $_GET['json']) {
header('Content-Type: text/javascript');
}
else {
header('Content-Type: text/xml');
* Implementation of command 'restore'.
*
* @since 4.5
$restore_dir = $this->getOption('restore-dir', FALSE);
$restore_dir = rtrim($restore_dir, '/');
if (!$restore_dir) {
return WP_CLI::error('"restore-dir" not specified.');
return WP_CLI::error('Could not locate "civicrm.sql" file in the restore directory.');
return WP_CLI::error('Could not locate the CiviCRM directory inside "restore-dir".');
elseif (!file_exists("$code_dir/civicrm/civicrm-version.txt") && !file_exists("$code_dir/civicrm/civicrm-version.php")) {
return WP_CLI::error('The CiviCRM directory inside "restore-dir" does not seem to be a valid CiviCRM codebase.');
$civicrm_root_base = explode('/', $civicrm_root);
array_pop($civicrm_root_base);
$civicrm_root_base = implode('/', $civicrm_root_base) . '/';
array_pop($basepath);
$project_path = implode('/', $basepath) . '/';
$restore_backup_dir = $this->getOption('backup-dir', $wp_root . '/../backup');
$restore_backup_dir = rtrim($restore_backup_dir, '/');
if (!defined('CIVICRM_DSN')) {
WP_CLI::error('CIVICRM_DSN is not defined.');
$db_spec = DB::parseDSN(CIVICRM_DSN);
WP_CLI::line('');
WP_CLI::line('Process involves:');
WP_CLI::line(sprintf("1. Restoring '\$restore-dir/civicrm' directory to '%s'.", $civicrm_root_base));
WP_CLI::line(sprintf("2. Dropping and creating '%s' database.", $db_spec['database']));
WP_CLI::line("3. Loading '\$restore-dir/civicrm.sql' file into the database.");
WP_CLI::line('');
WP_CLI::line(sprintf("Note: Before restoring, a backup will be taken in '%s' directory.", "$restore_backup_dir/plugins/restore"));
$restore_backup_dir .= '/plugins/restore/' . $date;
return WP_CLI::error(sprintf('Failed to create directory: %s', $restore_backup_dir));
// 1. Backup and restore codebase.
WP_CLI::line('Restoring CiviCRM codebase...');
if (is_dir($project_path) && !rename($project_path, $restore_backup_dir . '/civicrm')) {
return WP_CLI::error(sprintf("Failed to take backup for '%s' directory", $project_path));
return WP_CLI::error(sprintf("Failed to restore CiviCRM directory '%s' to '%s'", $code_dir, $project_path));
['civicrm', 'sql-dump'],
['result-file' => $restore_backup_dir . '/civicrm.sql']
// Prepare a mysql command-line string for issuing db drop/create commands.
$command = sprintf(
'mysql --user=%s --password=%s',
$db_spec['username'],
$db_spec['password']
);
$command .= ' --host=' . $db_spec['hostspec'];
}
if (isset($dsn['port']) && !mpty($dsn['port'])) {
if (system($command . sprintf(' --execute="DROP DATABASE IF EXISTS %s"', $db_spec['database']))) {
return WP_CLI::error(sprintf('Could not drop database: %s', $db_spec['database']));
if (system($command . sprintf(' --execute="CREATE DATABASE %s"', $db_spec['database']))) {
WP_CLI::error(sprintf('Could not create new database: %s', $db_spec['database']));
// 3. Restore database.
WP_CLI::line('Loading "civicrm.sql" file from "restore-dir"...');
system($command . ' ' . $db_spec['database'] . ' < ' . $sql_file);
* Implementation of command 'sql-conf'.
*
* @since 4.5
if (!defined('CIVICRM_DSN')) {
WP_CLI::error('CIVICRM_DSN is not defined.');
WP_CLI::line(print_r(DB::parseDSN(CIVICRM_DSN), TRUE));
* Implementation of command 'sql-connect'.
*
* @since 4.5
if (!defined('CIVICRM_DSN')) {
return WP_CLI::error('CIVICRM_DSN is not defined.');
$command = sprintf(
'mysql --database=%s --host=%s --user=%s --password=%s',
$dsn['database'],
$dsn['hostspec'],
$dsn['username'],
$dsn['password']
);
if (isset($dsn['port']) && !empty($dsn['port'])) {
* Implementation of command 'sql-dump'.
*
* @since 4.5
// Bootstrap CiviCRM when we're not being called as part of an upgrade.
if (!defined('CIVICRM_DSN') && !defined('CIVICRM_OLD_DSN')) {
$dsn = self::parseDSN(defined('CIVICRM_DSN') ? CIVICRM_DSN : CIVICRM_OLD_DSN);
$command = "mysqldump --no-defaults --host={$dsn['hostspec']} --user={$dsn['username']} --password='{$dsn['password']}' %s";
if (isset($assoc_args['tables'])) {
$tables = explode(',', $assoc_args['tables']);
unset($assoc_args['tables']);
$escaped_command = call_user_func_array(
'\WP_CLI\Utils\esc_cmd',
array_merge(
\WP_CLI\Utils\run_mysql_command($escaped_command, $assoc_args);
if (!$stdout) {
WP_CLI::success(sprintf('Exported to %s', $assoc_args['result-file']));
* Implementation of command 'sql-query'.
*
* @since 4.5
if (!isset($this->args[0])) {
WP_CLI::error('No query specified.');
$query = $this->args[0];
civicrm_initialize();
if (!defined('CIVICRM_DSN')) {
WP_CLI::error('CIVICRM_DSN is not defined.');
'host' => $dsn['hostspec'],
'database' => $dsn['database'],
'user' => $dsn['username'],
'password' => $dsn['password'],
'execute' => $query,
\WP_CLI\Utils\run_mysql_command('mysql --no-defaults', $mysql_args);
* Implementation of command 'sql-cli'.
*
* @since 4.5
*/
private function sqlCLI() {
civicrm_initialize();
if (!defined('CIVICRM_DSN')) {
WP_CLI::error('CIVICRM_DSN is not defined.');
'host' => $dsn['hostspec'],
'database' => $dsn['database'],
'user' => $dsn['username'],
'password' => $dsn['password'],
\WP_CLI\Utils\run_mysql_command('mysql --no-defaults', $mysql_args);
* Implementation of command 'update-cfg'.
*
* @since 4.5
*/
private function updateConfig() {
civicrm_initialize();
for ($i = 1; $i <= 3; $i++) {
foreach ($states as $state) {
$value = $this->getOption($name, NULL);
if ($value) {
$default_values[$name] = $value;
}
}
}
$webserver_user = $this->getWebServerUser();
$webserver_group = $this->getWebServerGroup();
require_once 'CRM/Core/I18n.php';
require_once 'CRM/Core/BAO/ConfigSetting.php';
$result = CRM_Core_BAO_ConfigSetting::doSiteMove($default_values);
// Attempt to preserve webserver ownership of templates_c, civicrm/upload.
if ($webserver_user && $webserver_group) {
$upload_dir = wp_upload_dir();
$civicrm_files_dir = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR;
system(sprintf('chown -R %s:%s %s/templates_c', $webserver_user, $webserver_group, $civicrm_files_dir));
system(sprintf('chown -R %s:%s %s/upload', $webserver_user, $webserver_group, $civicrm_files_dir));
}
else {
WP_CLI::error('Config update failed.');
* Implementation of command 'upgrade'.
*
* @since 4.5
// TODO: Use wp-cli to download tarfile.
// TODO: If tarfile is not specified, see if the code already exists and use that instead.
if (!$this->getOption('tarfile', FALSE) && !$this->getOption('zipfile', FALSE)) {
return WP_CLI::error('Must specify either --tarfile or --zipfile');
// FIXME: Throw error if tarfile is not in a valid format.
if (!defined('CIVICRM_UPGRADE_ACTIVE')) {
define('CIVICRM_UPGRADE_ACTIVE', 1);
$legacy_settings_file = $plugins_dir . '/civicrm.settings.php';
$upload_dir = wp_upload_dir();
$settings_file = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'civicrm.settings.php';
if (!file_exists($legacy_settings_file) && !file_exists($settings_file)) {
return WP_CLI::error(sprintf('Unable to locate settings file at "%s" or at "%s"', $legacy_settings_file, $settings_file));
/*
* We don't want to require "civicrm.settings.php" here, because:
*
* a) This is the old environment we're going to replace.
* b) upgrade-db needs to bootstrap the new environment, so requiring the file
* now will create multiple inclusion problems later on.
*
* However, all we're really after is $civicrm_root and CIVICRM_DSN, so we're going to
* pull out the lines we need using a regex and run them - yes, it's pretty silly.
* Don't try this at home, kids.
*/
$legacy_settings = file_get_contents($legacy_settings_file);
$legacy_settings = str_replace("\r", '', $legacy_settings);
$legacy_settings = explode("\n", $legacy_settings);
$settings = file_get_contents($settings_file);
$settings = str_replace("\r", '', $settings);
$settings = explode("\n", $settings);
if ($civicrm_root_code = reset(preg_grep('/^\s*\$civicrm_root\s*=.*$/', $legacy_settings))) {
// phpcs:disable
eval($civicrm_root_code);
// phpcs:enable
}
elseif ($civicrm_root_code = reset(preg_grep('/^\s*\$civicrm_root\s*=.*$/', $settings))) {
// phpcs:disable
eval($civicrm_root_code);
// phpcs:enable
}
else {
return WP_CLI::error('Unable to read $civicrm_root from civicrm.settings.php');
if ($civicrm_dsn_code = reset(preg_grep('/^\s*define.*CIVICRM_DSN.*$/', $settings))) {
$civicrm_dsn_code = str_replace('CIVICRM_DSN', 'CIVICRM_OLD_DSN', $civicrm_dsn_code);
// phpcs:disable
eval($civicrm_dsn_code);
// phpcs:enable
}
else {
return WP_CLI::error('Unable to read CIVICRM_DSN from civicrm.settings.php');
return WP_CLI::error('Unable to set CIVICRM_OLD_DSN.');
array_pop($basepath);
$project_path = implode('/', $basepath) . '/';
array_pop($basepath);
$plugin_path = implode('/', $basepath) . '/';
$backup_dir = $this->getOption('backup-dir', $wp_root . '../backup');
$backup_dir = rtrim($backup_dir, '/');