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));
/*
* CRM-12523
* WordPress has it's own timezone calculations. CiviCRM relies on the PHP
* default timezone which WordPress overrides with UTC in wp-settings.php
*/
$original_timezone = date_default_timezone_get();
$wp_site_timezone = $this->getOption('timezone', $this->getTimeZoneString());
if ($wp_site_timezone) {
date_default_timezone_set($wp_site_timezone);
CRM_Core_Config::singleton()->userSystem->setMySQLTimeZone();
}
// Restore original timezone.
if ($original_timezone) {
date_default_timezone_set($original_timezone);
return WP_CLI::error(sprintf('Unknown format: %s', $format));
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
/**
* Returns the timezone string for the current WordPress site.
*
* If a timezone identifier is used, return that.
* If an offset is used, try to build a suitable timezone.
* If all else fails, uses UTC.
*
* @since 5.64
*
* @return string $tzstring The site timezone string.
*/
private function getTimeZoneString() {
// Return the timezone string when set.
$tzstring = get_option('timezone_string');
if (!empty($tzstring)) {
return $tzstring;
}
/*
* Try and build a deprecated (but currently valid) timezone string
* from the GMT offset value.
*
* Note: manual offsets should be discouraged. WordPress works more
* reliably when setting an actual timezone (e.g. "Europe/London")
* because of support for Daylight Saving changes.
*
* Note: the IANA timezone database that provides PHP's timezone
* support uses (reversed) POSIX style signs.
*
* @see https://www.php.net/manual/en/timezones.others.php
*/
$offset = get_option('gmt_offset');
if (0 != $offset && floor($offset) == $offset) {
$offset_string = $offset > 0 ? "-$offset" : '+' . abs((int) $offset);
$tzstring = 'Etc/GMT' . $offset_string;
}
// Default to "UTC" if the timezone string is still empty.
if (empty($tzstring)) {
$tzstring = 'UTC';
}
return $tzstring;
}
* 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);