506 lines
15 KiB
PHP
506 lines
15 KiB
PHP
<?php
|
|
/*
|
|
+--------------------------------------------------------------------+
|
|
| CiviCRM version 4.7 |
|
|
+--------------------------------------------------------------------+
|
|
| Copyright Tech To The People http:tttp.eu (c) 2008 |
|
|
+--------------------------------------------------------------------+
|
|
| |
|
|
| CiviCRM is free software; you can copy, modify, and distribute it |
|
|
| under the terms of the GNU Affero General Public License |
|
|
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
|
|
| |
|
|
| CiviCRM is distributed in the hope that it will be useful, but |
|
|
| WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|
|
| See the GNU Affero General Public License for more details. |
|
|
| |
|
|
| You should have received a copy of the GNU Affero General Public |
|
|
| License and the CiviCRM Licensing Exception along |
|
|
| with this program; if not, contact CiviCRM LLC |
|
|
| at info[AT]civicrm[DOT]org. If you have questions about the |
|
|
| GNU Affero General Public License or the licensing of CiviCRM, |
|
|
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
|
|
+--------------------------------------------------------------------+
|
|
*/
|
|
|
|
/**
|
|
* This files provides several classes for doing command line work with
|
|
* CiviCRM. civicrm_cli is the base class. It's used by cli.php.
|
|
*
|
|
* In addition, there are several additional classes that inherit
|
|
* civicrm_cli to do more precise functions.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* base class for doing all command line operations via civicrm
|
|
* used by cli.php
|
|
*/
|
|
class civicrm_cli {
|
|
// required values that must be passed
|
|
// via the command line
|
|
var $_required_arguments = array('action', 'entity');
|
|
var $_additional_arguments = array();
|
|
var $_entity = NULL;
|
|
var $_action = NULL;
|
|
var $_output = FALSE;
|
|
var $_joblog = FALSE;
|
|
var $_semicolon = FALSE;
|
|
var $_config;
|
|
|
|
// optional arguments
|
|
var $_site = 'localhost';
|
|
var $_user = NULL;
|
|
var $_password = NULL;
|
|
|
|
// all other arguments populate the parameters
|
|
// array that is passed to civicrm_api
|
|
var $_params = array('version' => 3);
|
|
|
|
var $_errors = array();
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function initialize() {
|
|
if (!$this->_accessing_from_cli()) {
|
|
return FALSE;
|
|
}
|
|
if (!$this->_parseOptions()) {
|
|
return FALSE;
|
|
}
|
|
if (!$this->_bootstrap()) {
|
|
return FALSE;
|
|
}
|
|
if (!$this->_validateOptions()) {
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Ensure function is being run from the cli.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function _accessing_from_cli() {
|
|
if (PHP_SAPI === 'cli') {
|
|
return TRUE;
|
|
}
|
|
else {
|
|
die("cli.php can only be run from command line.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function callApi() {
|
|
require_once 'api/api.php';
|
|
|
|
CRM_Core_Config::setPermitCacheFlushMode(FALSE);
|
|
// CRM-9822 -'execute' action always goes thru Job api and always writes to log
|
|
if ($this->_action != 'execute' && $this->_joblog) {
|
|
require_once 'CRM/Core/JobManager.php';
|
|
$facility = new CRM_Core_JobManager();
|
|
$facility->setSingleRunParams($this->_entity, $this->_action, $this->_params, 'From Cli.php');
|
|
$facility->executeJobByAction($this->_entity, $this->_action);
|
|
}
|
|
else {
|
|
// CRM-9822 cli.php calls don't require site-key, so bypass site-key authentication
|
|
$this->_params['auth'] = FALSE;
|
|
$result = civicrm_api($this->_entity, $this->_action, $this->_params);
|
|
}
|
|
CRM_Core_Config::setPermitCacheFlushMode(TRUE);
|
|
CRM_Contact_BAO_Contact_Utils::clearContactCaches();
|
|
|
|
if (!empty($result['is_error'])) {
|
|
$this->_log($result['error_message']);
|
|
return FALSE;
|
|
}
|
|
elseif ($this->_output === 'json') {
|
|
echo json_encode($result, defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0);
|
|
}
|
|
elseif ($this->_output) {
|
|
print_r($result['values']);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
private function _parseOptions() {
|
|
$args = $_SERVER['argv'];
|
|
// remove the first argument, which is the name
|
|
// of this script
|
|
array_shift($args);
|
|
|
|
while (list($k, $arg) = each($args)) {
|
|
// sanitize all user input
|
|
$arg = $this->_sanitize($arg);
|
|
|
|
// if we're not parsing an option signifier
|
|
// continue to the next one
|
|
if (!preg_match('/^-/', $arg)) {
|
|
continue;
|
|
}
|
|
|
|
// find the value of this arg
|
|
if (preg_match('/=/', $arg)) {
|
|
$parts = explode('=', $arg);
|
|
$arg = $parts[0];
|
|
$value = $parts[1];
|
|
}
|
|
else {
|
|
if (isset($args[$k + 1])) {
|
|
$next_arg = $this->_sanitize($args[$k + 1]);
|
|
// if the next argument is not another option
|
|
// it's the value for this argument
|
|
if (!preg_match('/^-/', $next_arg)) {
|
|
$value = $next_arg;
|
|
}
|
|
}
|
|
}
|
|
|
|
// parse the special args first
|
|
if ($arg == '-e' || $arg == '--entity') {
|
|
$this->_entity = $value;
|
|
}
|
|
elseif ($arg == '-a' || $arg == '--action') {
|
|
$this->_action = $value;
|
|
}
|
|
elseif ($arg == '-s' || $arg == '--site') {
|
|
$this->_site = $value;
|
|
}
|
|
elseif ($arg == '-u' || $arg == '--user') {
|
|
$this->_user = $value;
|
|
}
|
|
elseif ($arg == '-p' || $arg == '--password') {
|
|
$this->_password = $value;
|
|
}
|
|
elseif ($arg == '-o' || $arg == '--output') {
|
|
$this->_output = TRUE;
|
|
}
|
|
elseif ($arg == '-J' || $arg == '--json') {
|
|
$this->_output = 'json';
|
|
}
|
|
elseif ($arg == '-j' || $arg == '--joblog') {
|
|
$this->_joblog = TRUE;
|
|
}
|
|
elseif ($arg == '-sem' || $arg == '--semicolon') {
|
|
$this->_semicolon = TRUE;
|
|
}
|
|
else {
|
|
foreach ($this->_additional_arguments as $short => $long) {
|
|
if ($arg == '-' . $short || $arg == '--' . $long) {
|
|
$property = '_' . $long;
|
|
$this->$property = $value;
|
|
continue;
|
|
}
|
|
}
|
|
// all other arguments are parameters
|
|
$key = ltrim($arg, '--');
|
|
$this->_params[$key] = isset($value) ? $value : NULL;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
private function _bootstrap() {
|
|
// so the configuration works with php-cli
|
|
$_SERVER['PHP_SELF'] = "/index.php";
|
|
$_SERVER['HTTP_HOST'] = $this->_site;
|
|
$_SERVER['REMOTE_ADDR'] = "127.0.0.1";
|
|
$_SERVER['SERVER_SOFTWARE'] = NULL;
|
|
$_SERVER['REQUEST_METHOD'] = 'GET';
|
|
|
|
// SCRIPT_FILENAME needed by CRM_Utils_System::cmsRootPath
|
|
$_SERVER['SCRIPT_FILENAME'] = __FILE__;
|
|
|
|
// CRM-8917 - check if script name starts with /, if not - prepend it.
|
|
if (ord($_SERVER['SCRIPT_NAME']) != 47) {
|
|
$_SERVER['SCRIPT_NAME'] = '/' . $_SERVER['SCRIPT_NAME'];
|
|
}
|
|
|
|
$civicrm_root = dirname(__DIR__);
|
|
chdir($civicrm_root);
|
|
if (getenv('CIVICRM_SETTINGS')) {
|
|
require_once getenv('CIVICRM_SETTINGS');
|
|
}
|
|
else {
|
|
require_once 'civicrm.config.php';
|
|
}
|
|
// autoload
|
|
if (!class_exists('CRM_Core_ClassLoader')) {
|
|
require_once $civicrm_root . '/CRM/Core/ClassLoader.php';
|
|
}
|
|
CRM_Core_ClassLoader::singleton()->register();
|
|
|
|
$this->_config = CRM_Core_Config::singleton();
|
|
|
|
// HTTP_HOST will be 'localhost' unless overwritten with the -s argument.
|
|
// Now we have a Config object, we can set it from the Base URL.
|
|
if ($_SERVER['HTTP_HOST'] == 'localhost') {
|
|
$_SERVER['HTTP_HOST'] = preg_replace(
|
|
'!^https?://([^/]+)/$!i',
|
|
'$1',
|
|
$this->_config->userFrameworkBaseURL);
|
|
}
|
|
|
|
$class = 'CRM_Utils_System_' . $this->_config->userFramework;
|
|
|
|
$cms = new $class();
|
|
if (!CRM_Utils_System::loadBootstrap(array(), FALSE, FALSE, $civicrm_root)) {
|
|
$this->_log(ts("Failed to bootstrap CMS"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (strtolower($this->_entity) == 'job') {
|
|
if (!$this->_user) {
|
|
$this->_log(ts("Jobs called from cli.php require valid user as parameter"));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (!empty($this->_user)) {
|
|
if (!CRM_Utils_System::authenticateScript(TRUE, $this->_user, $this->_password, TRUE, FALSE, FALSE)) {
|
|
$this->_log(ts("Failed to login as %1. Wrong username or password.", array('1' => $this->_user)));
|
|
return FALSE;
|
|
}
|
|
if (($this->_config->userFramework == 'Joomla' && !$cms->loadUser($this->_user, $this->_password)) || !$cms->loadUser($this->_user)) {
|
|
$this->_log(ts("Failed to login as %1", array('1' => $this->_user)));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
private function _validateOptions() {
|
|
$required = $this->_required_arguments;
|
|
while (list(, $var) = each($required)) {
|
|
$index = '_' . $var;
|
|
if (empty($this->$index)) {
|
|
$missing_arg = '--' . $var;
|
|
$this->_log(ts("The %1 argument is required", array(1 => $missing_arg)));
|
|
$this->_log($this->_getUsage());
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @param $value
|
|
*
|
|
* @return string
|
|
*/
|
|
private function _sanitize($value) {
|
|
// restrict user input - we should not be needing anything
|
|
// other than normal alpha numeric plus - and _.
|
|
return trim(preg_replace('#^[^a-zA-Z0-9\-_=/]$#', '', $value));
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
private function _getUsage() {
|
|
$out = "Usage: cli.php -e entity -a action [-u user] [-s site] [--output|--json] [PARAMS]\n";
|
|
$out .= " entity is the name of the entity, e.g. Contact, Event, etc.\n";
|
|
$out .= " action is the name of the action e.g. Get, Create, etc.\n";
|
|
$out .= " user is an optional username to run the script as\n";
|
|
$out .= " site is the domain name of the web site (for Drupal multi site installs)\n";
|
|
$out .= " --output will pretty print the result from the api call\n";
|
|
$out .= " --json will print the result from the api call as JSON\n";
|
|
$out .= " PARAMS is one or more --param=value combinations to pass to the api\n";
|
|
return ts($out);
|
|
}
|
|
|
|
/**
|
|
* @param $error
|
|
*/
|
|
private function _log($error) {
|
|
// fixme, this should call some CRM_Core_Error:: function
|
|
// that properly logs
|
|
print "$error\n";
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* class used by csv/export.php to export records from
|
|
* the database in a csv file format.
|
|
*/
|
|
class civicrm_cli_csv_exporter extends civicrm_cli {
|
|
var $separator = ',';
|
|
|
|
/**
|
|
*/
|
|
public function __construct() {
|
|
$this->_required_arguments = array('entity');
|
|
parent::initialize();
|
|
}
|
|
|
|
/**
|
|
* Run the script.
|
|
*/
|
|
public function run() {
|
|
if ($this->_semicolon) {
|
|
$this->separator = ';';
|
|
}
|
|
|
|
$out = fopen("php://output", 'w');
|
|
fputcsv($out, $this->columns, $this->separator, '"');
|
|
|
|
$this->row = 1;
|
|
$result = civicrm_api($this->_entity, 'Get', $this->_params);
|
|
$first = TRUE;
|
|
foreach ($result['values'] as $row) {
|
|
if ($first) {
|
|
$columns = array_keys($row);
|
|
fputcsv($out, $columns, $this->separator, '"');
|
|
$first = FALSE;
|
|
}
|
|
//handle values returned as arrays (i.e. custom fields that allow multiple selections) by inserting a control character
|
|
foreach ($row as &$field) {
|
|
if (is_array($field)) {
|
|
//convert to string
|
|
$field = implode($field, CRM_Core_DAO::VALUE_SEPARATOR) . CRM_Core_DAO::VALUE_SEPARATOR;
|
|
}
|
|
}
|
|
fputcsv($out, $row, $this->separator, '"');
|
|
}
|
|
fclose($out);
|
|
echo "\n";
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* base class used by both civicrm_cli_csv_import
|
|
* and civicrm_cli_csv_deleter to add or delete
|
|
* records based on those found in a csv file
|
|
* passed to the script.
|
|
*/
|
|
class civicrm_cli_csv_file extends civicrm_cli {
|
|
var $header;
|
|
var $separator = ',';
|
|
|
|
/**
|
|
*/
|
|
public function __construct() {
|
|
$this->_required_arguments = array('entity', 'file');
|
|
$this->_additional_arguments = array('f' => 'file');
|
|
parent::initialize();
|
|
}
|
|
|
|
/**
|
|
* Run CLI function.
|
|
*/
|
|
public function run() {
|
|
$this->row = 1;
|
|
$handle = fopen($this->_file, "r");
|
|
|
|
if (!$handle) {
|
|
die("Could not open file: " . $this->_file . ". Please provide an absolute path.\n");
|
|
}
|
|
|
|
//header
|
|
$header = fgetcsv($handle, 0, $this->separator);
|
|
// In case fgetcsv couldn't parse the header and dumped the whole line in 1 array element
|
|
// Try a different separator char
|
|
if (count($header) == 1) {
|
|
$this->separator = ";";
|
|
rewind($handle);
|
|
$header = fgetcsv($handle, 0, $this->separator);
|
|
}
|
|
|
|
$this->header = $header;
|
|
while (($data = fgetcsv($handle, 0, $this->separator)) !== FALSE) {
|
|
// skip blank lines
|
|
if (count($data) == 1 && is_null($data[0])) {
|
|
continue;
|
|
}
|
|
$this->row++;
|
|
if ($this->row % 1000 == 0) {
|
|
// Reset PEAR_DB_DATAOBJECT cache to prevent memory leak
|
|
CRM_Core_DAO::freeResult();
|
|
}
|
|
$params = $this->convertLine($data);
|
|
$this->processLine($params);
|
|
}
|
|
fclose($handle);
|
|
}
|
|
|
|
/* return a params as expected */
|
|
/**
|
|
* @param $data
|
|
*
|
|
* @return array
|
|
*/
|
|
public function convertLine($data) {
|
|
$params = array();
|
|
foreach ($this->header as $i => $field) {
|
|
//split any multiselect data, denoted with CRM_Core_DAO::VALUE_SEPARATOR
|
|
if (strpos($data[$i], CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE) {
|
|
$data[$i] = explode(CRM_Core_DAO::VALUE_SEPARATOR, $data[$i]);
|
|
$data[$i] = array_combine($data[$i], $data[$i]);
|
|
}
|
|
$params[$field] = $data[$i];
|
|
}
|
|
$params['version'] = 3;
|
|
return $params;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* class for processing records to add
|
|
* used by csv/import.php
|
|
*
|
|
*/
|
|
class civicrm_cli_csv_importer extends civicrm_cli_csv_file {
|
|
/**
|
|
* @param array $params
|
|
*/
|
|
public function processline($params) {
|
|
$result = civicrm_api($this->_entity, 'Create', $params);
|
|
if ($result['is_error']) {
|
|
echo "\nERROR line " . $this->row . ": " . $result['error_message'] . "\n";
|
|
}
|
|
else {
|
|
echo "\nline " . $this->row . ": created " . $this->_entity . " id: " . $result['id'] . "\n";
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* class for processing records to delete
|
|
* used by csv/delete.php
|
|
*
|
|
*/
|
|
class civicrm_cli_csv_deleter extends civicrm_cli_csv_file {
|
|
/**
|
|
* @param array $params
|
|
*/
|
|
public function processline($params) {
|
|
$result = civicrm_api($this->_entity, 'Delete', $params);
|
|
if ($result['is_error']) {
|
|
echo "\nERROR line " . $this->row . ": " . $result['error_message'] . "\n";
|
|
}
|
|
else {
|
|
echo "\nline " . $this->row . ": deleted\n";
|
|
}
|
|
}
|
|
|
|
}
|