| 1 | <?php |
| 2 | /* |
| 3 | +--------------------------------------------------------------------+ |
| 4 | | CiviCRM version 4.4 | |
| 5 | +--------------------------------------------------------------------+ |
| 6 | | Copyright Tech To The People http:tttp.eu (c) 2008 | |
| 7 | +--------------------------------------------------------------------+ |
| 8 | | | |
| 9 | | CiviCRM is free software; you can copy, modify, and distribute it | |
| 10 | | under the terms of the GNU Affero General Public License | |
| 11 | | Version 3, 19 November 2007. | |
| 12 | | | |
| 13 | | CiviCRM is distributed in the hope that it will be useful, but | |
| 14 | | WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 15 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
| 16 | | See the GNU Affero General Public License for more details. | |
| 17 | | | |
| 18 | | You should have received a copy of the GNU Affero General Public | |
| 19 | | License along with this program; if not, contact CiviCRM LLC | |
| 20 | | at info[AT]civicrm[DOT]org. If you have questions about the | |
| 21 | | GNU Affero General Public License or the licensing of CiviCRM, | |
| 22 | | see the CiviCRM license FAQ at http://civicrm.org/licensing | |
| 23 | +--------------------------------------------------------------------+ |
| 24 | */ |
| 25 | |
| 26 | /** |
| 27 | * This files provides several classes for doing command line work with |
| 28 | * CiviCRM. civicrm_cli is the base class. It's used by cli.php. |
| 29 | * |
| 30 | * In addition, there are several additional classes that inherit |
| 31 | * civicrm_cli to do more precise functions. |
| 32 | * |
| 33 | **/ |
| 34 | |
| 35 | /** |
| 36 | * base class for doing all command line operations via civicrm |
| 37 | * used by cli.php |
| 38 | **/ |
| 39 | |
| 40 | class civicrm_cli { |
| 41 | // required values that must be passed |
| 42 | // via the command line |
| 43 | var $_required_arguments = array('action', 'entity'); |
| 44 | var $_additional_arguments = array(); |
| 45 | var $_entity = NULL; |
| 46 | var $_action = NULL; |
| 47 | var $_output = FALSE; |
| 48 | var $_joblog = FALSE; |
| 49 | var $_config; |
| 50 | |
| 51 | // optional arguments |
| 52 | var $_site = 'localhost'; |
| 53 | var $_user = NULL; |
| 54 | var $_password = NULL; |
| 55 | |
| 56 | // all other arguments populate the parameters |
| 57 | // array that is passed to civicrm_api |
| 58 | var $_params = array('version' => 3); |
| 59 | |
| 60 | var $_errors = array(); |
| 61 | |
| 62 | public function initialize() { |
| 63 | if (!$this->_accessing_from_cli()) { |
| 64 | return FALSE; |
| 65 | } |
| 66 | if (!$this->_parseOptions()) { |
| 67 | return FALSE; |
| 68 | } |
| 69 | if (!$this->_bootstrap()) { |
| 70 | return FALSE; |
| 71 | } |
| 72 | if (!$this->_validateOptions()) { |
| 73 | return FALSE; |
| 74 | } |
| 75 | return TRUE; |
| 76 | } |
| 77 | |
| 78 | public function _accessing_from_cli() { |
| 79 | if (PHP_SAPI === 'cli') { |
| 80 | return TRUE; |
| 81 | } |
| 82 | else { |
| 83 | trigger_error("cli.php can only be run from command line.", E_USER_ERROR); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | public function callApi() { |
| 88 | require_once 'api/api.php'; |
| 89 | |
| 90 | // CRM-9822 -'execute' action always goes thru Job api and always writes to log |
| 91 | if ($this->_action != 'execute' && $this->_joblog) { |
| 92 | require_once 'CRM/Core/JobManager.php'; |
| 93 | $facility = new CRM_Core_JobManager(); |
| 94 | $facility->setSingleRunParams($this->_entity, $this->_action, $this->_params, 'From Cli.php'); |
| 95 | $facility->executeJobByAction($this->_entity, $this->_action); |
| 96 | } |
| 97 | else { |
| 98 | // CRM-9822 cli.php calls don't require site-key, so bypass site-key authentication |
| 99 | $this->_params['auth'] = FALSE; |
| 100 | $result = civicrm_api($this->_entity, $this->_action, $this->_params); |
| 101 | } |
| 102 | |
| 103 | if ($result['is_error'] != 0) { |
| 104 | $this->_log($result['error_message']); |
| 105 | return FALSE; |
| 106 | } |
| 107 | elseif ($this->_output) { |
| 108 | print_r($result['values']); |
| 109 | } |
| 110 | return TRUE; |
| 111 | } |
| 112 | |
| 113 | private function _parseOptions() { |
| 114 | $args = $_SERVER['argv']; |
| 115 | // remove the first argument, which is the name |
| 116 | // of this script |
| 117 | array_shift($args); |
| 118 | |
| 119 | while (list($k, $arg) = each($args)) { |
| 120 | // sanitize all user input |
| 121 | $arg = $this->_sanitize($arg); |
| 122 | |
| 123 | // if we're not parsing an option signifier |
| 124 | // continue to the next one |
| 125 | if (!preg_match('/^-/', $arg)) { |
| 126 | continue; |
| 127 | } |
| 128 | |
| 129 | // find the value of this arg |
| 130 | if (preg_match('/=/', $arg)) { |
| 131 | $parts = explode('=', $arg); |
| 132 | $arg = $parts[0]; |
| 133 | $value = $parts[1]; |
| 134 | } |
| 135 | else { |
| 136 | if (isset($args[$k + 1])) { |
| 137 | $next_arg = $this->_sanitize($args[$k + 1]); |
| 138 | // if the next argument is not another option |
| 139 | // it's the value for this argument |
| 140 | if (!preg_match('/^-/', $next_arg)) { |
| 141 | $value = $next_arg; |
| 142 | } |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | // parse the special args first |
| 147 | if ($arg == '-e' || $arg == '--entity') { |
| 148 | $this->_entity = $value; |
| 149 | } |
| 150 | elseif ($arg == '-a' || $arg == '--action') { |
| 151 | $this->_action = $value; |
| 152 | } |
| 153 | elseif ($arg == '-s' || $arg == '--site') { |
| 154 | $this->_site = $value; |
| 155 | } |
| 156 | elseif ($arg == '-u' || $arg == '--user') { |
| 157 | $this->_user = $value; |
| 158 | } |
| 159 | elseif ($arg == '-p' || $arg == '--password') { |
| 160 | $this->_password = $value; |
| 161 | } |
| 162 | elseif ($arg == '-o' || $arg == '--output') { |
| 163 | $this->_output = TRUE; |
| 164 | } |
| 165 | elseif ($arg == '-j' || $arg == '--joblog') { |
| 166 | $this->_joblog = TRUE; |
| 167 | } |
| 168 | else { |
| 169 | while(list($short, $long) = each ($this->_additional_arguments)) { |
| 170 | if ($arg == '-' . $short || $arg == '--' . $long) { |
| 171 | $property = '_' . $long; |
| 172 | $this->$property = $value; |
| 173 | continue; |
| 174 | } |
| 175 | } |
| 176 | // all other arguments are parameters |
| 177 | $key = ltrim($arg, '--'); |
| 178 | $this->_params[$key] = isset($value) ? $value : NULL; |
| 179 | } |
| 180 | } |
| 181 | return TRUE; |
| 182 | } |
| 183 | |
| 184 | private function _bootstrap() { |
| 185 | // so the configuration works with php-cli |
| 186 | $_SERVER['PHP_SELF'] = "/index.php"; |
| 187 | $_SERVER['HTTP_HOST'] = $this->_site; |
| 188 | $_SERVER['REMOTE_ADDR'] = "127.0.0.1"; |
| 189 | $_SERVER['SERVER_SOFTWARE'] = NULL; |
| 190 | $_SERVER['REQUEST_METHOD'] = 'GET'; |
| 191 | |
| 192 | // SCRIPT_FILENAME needed by CRM_Utils_System::cmsRootPath |
| 193 | $_SERVER['SCRIPT_FILENAME'] = __FILE__; |
| 194 | |
| 195 | // CRM-8917 - check if script name starts with /, if not - prepend it. |
| 196 | if (ord($_SERVER['SCRIPT_NAME']) != 47) { |
| 197 | $_SERVER['SCRIPT_NAME'] = '/' . $_SERVER['SCRIPT_NAME']; |
| 198 | } |
| 199 | |
| 200 | $civicrm_root = dirname(__DIR__); |
| 201 | chdir($civicrm_root); |
| 202 | require_once ('civicrm.config.php'); |
| 203 | // autoload |
| 204 | require_once $civicrm_root . '/CRM/Core/ClassLoader.php'; |
| 205 | CRM_Core_ClassLoader::singleton()->register(); |
| 206 | |
| 207 | $this->_config = CRM_Core_Config::singleton(); |
| 208 | |
| 209 | // HTTP_HOST will be 'localhost' unless overwritten with the -s argument. |
| 210 | // Now we have a Config object, we can set it from the Base URL. |
| 211 | if ($_SERVER['HTTP_HOST'] == 'localhost') { |
| 212 | $_SERVER['HTTP_HOST'] = preg_replace( |
| 213 | '!^https?://([^/]+)/$!i', |
| 214 | '$1', |
| 215 | $this->_config->userFrameworkBaseURL); |
| 216 | } |
| 217 | |
| 218 | $class = 'CRM_Utils_System_' . $this->_config->userFramework; |
| 219 | |
| 220 | $cms = new $class(); |
| 221 | if (!CRM_Utils_System::loadBootstrap(array(), FALSE, FALSE, $civicrm_root)) { |
| 222 | $this->_log(ts("Failed to bootstrap CMS")); |
| 223 | return FALSE; |
| 224 | } |
| 225 | |
| 226 | if (strtolower($this->_entity) == 'job') { |
| 227 | if (!$this->_user) { |
| 228 | $this->_log(ts("Jobs called from cli.php require valid user as parameter")); |
| 229 | return FALSE; |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | if (!empty($this->_user)) { |
| 234 | if(!CRM_Utils_System::authenticateScript(TRUE, $this->_user, $this->_password, TRUE, FALSE, FALSE)) { |
| 235 | $this->_log(ts("Failed to login as %1. Wrong username or password.", array('1' => $this->_user))); |
| 236 | return FALSE; |
| 237 | } |
| 238 | if (!$cms->loadUser($this->_user)) { |
| 239 | $this->_log(ts("Failed to login as %1", array('1' => $this->_user))); |
| 240 | return FALSE; |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | return TRUE; |
| 245 | } |
| 246 | |
| 247 | private function _validateOptions() { |
| 248 | $required = $this->_required_arguments; |
| 249 | while (list(, $var) = each($required)) { |
| 250 | $index = '_' . $var; |
| 251 | if (empty($this->$index)) { |
| 252 | $missing_arg = '--' . $var; |
| 253 | $this->_log(ts("The %1 argument is required", array(1 => $missing_arg))); |
| 254 | $this->_log($this->_getUsage()); |
| 255 | return FALSE; |
| 256 | } |
| 257 | } |
| 258 | return TRUE; |
| 259 | } |
| 260 | |
| 261 | private function _sanitize($value) { |
| 262 | // restrict user input - we should not be needing anything |
| 263 | // other than normal alpha numeric plus - and _. |
| 264 | return trim(preg_replace('#^[^a-zA-Z0-9\-_=/]$#', '', $value)); |
| 265 | } |
| 266 | |
| 267 | private function _getUsage() { |
| 268 | $out = "Usage: cli.php -e entity -a action [-u user] [-s site] [--output] [PARAMS]\n"; |
| 269 | $out .= " entity is the name of the entity, e.g. Contact, Event, etc.\n"; |
| 270 | $out .= " action is the name of the action e.g. Get, Create, etc.\n"; |
| 271 | $out .= " user is an optional username to run the script as\n"; |
| 272 | $out .= " site is the domain name of the web site (for Drupal multi site installs)\n"; |
| 273 | $out .= " --output will print the result from the api call\n"; |
| 274 | $out .= " PARAMS is one or more --param=value combinations to pass to the api\n"; |
| 275 | return ts($out); |
| 276 | } |
| 277 | |
| 278 | private function _log($error) { |
| 279 | // fixme, this should call some CRM_Core_Error:: function |
| 280 | // that properly logs |
| 281 | print "$error\n"; |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | /** |
| 286 | * class used by csv/export.php to export records from |
| 287 | * the database in a csv file format. |
| 288 | **/ |
| 289 | |
| 290 | class civicrm_cli_csv_exporter extends civicrm_cli { |
| 291 | var $separator = ','; |
| 292 | |
| 293 | function __construct() { |
| 294 | $this->_required_arguments = array('entity'); |
| 295 | parent::initialize(); |
| 296 | } |
| 297 | |
| 298 | function run() { |
| 299 | $out = fopen("php://output", 'w'); |
| 300 | fputcsv($out, $this->columns, $this->separator, '"'); |
| 301 | |
| 302 | $this->row = 1; |
| 303 | $result = civicrm_api($this->_entity, 'Get', $this->_params); |
| 304 | $first = true; |
| 305 | foreach ($result['values'] as $row) { |
| 306 | if($first) { |
| 307 | $columns = array_keys($row); |
| 308 | fputcsv($out, $columns, $this->separator, '"'); |
| 309 | $first = false; |
| 310 | } |
| 311 | //handle values returned as arrays (i.e. custom fields that allow multiple selections) by inserting a control character |
| 312 | foreach ($row as &$field) { |
| 313 | if(is_array($field)) { |
| 314 | //convert to string |
| 315 | $field = implode($field,CRM_Core_DAO::VALUE_SEPARATOR) . CRM_Core_DAO::VALUE_SEPARATOR; |
| 316 | } |
| 317 | } |
| 318 | fputcsv($out, $row, $this->separator, '"'); |
| 319 | } |
| 320 | fclose($out); |
| 321 | echo "\n"; |
| 322 | } |
| 323 | } |
| 324 | |
| 325 | /** |
| 326 | * base class used by both civicrm_cli_csv_import |
| 327 | * and civicrm_cli_csv_deleter to add or delete |
| 328 | * records based on those found in a csv file |
| 329 | * passed to the script. |
| 330 | **/ |
| 331 | |
| 332 | class civicrm_cli_csv_file extends civicrm_cli { |
| 333 | var $header; |
| 334 | var $separator = ','; |
| 335 | |
| 336 | function __construct() { |
| 337 | $this->_required_arguments = array('entity','file'); |
| 338 | $this->_additional_arguments = array('f' => 'file'); |
| 339 | parent::initialize(); |
| 340 | } |
| 341 | |
| 342 | function run() { |
| 343 | $this->row = 1; |
| 344 | $handle = fopen($this->_file, "r"); |
| 345 | |
| 346 | if (!$handle) { |
| 347 | die("Could not open file: " . $this->_file . ". Please provide an absolute path.\n"); |
| 348 | } |
| 349 | |
| 350 | //header |
| 351 | $header = fgetcsv($handle, 0, $this->separator); |
| 352 | // In case fgetcsv couldn't parse the header and dumped the whole line in 1 array element |
| 353 | // Try a different separator char |
| 354 | if (count($header) == 1) { |
| 355 | $this->separator = ";"; |
| 356 | rewind($handle); |
| 357 | $header = fgetcsv($handle, 0, $this->separator); |
| 358 | if (count($header) == 1) { |
| 359 | die("Invalid file format for " . $this->_file . ". It must be a valid csv with separator ',' or ';'\n"); |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | $this->header = $header; |
| 364 | while (($data = fgetcsv($handle, 0, $this->separator)) !== FALSE) { |
| 365 | // skip blank lines |
| 366 | if(count($data) == 1 && is_null($data[0])) continue; |
| 367 | $this->row++; |
| 368 | $params = $this->convertLine($data); |
| 369 | $this->processLine($params); |
| 370 | } |
| 371 | fclose($handle); |
| 372 | return; |
| 373 | } |
| 374 | |
| 375 | /* return a params as expected */ |
| 376 | function convertLine($data) { |
| 377 | $params = array(); |
| 378 | foreach ($this->header as $i => $field) { |
| 379 | //split any multiselect data, denoted with CRM_Core_DAO::VALUE_SEPARATOR |
| 380 | if (strpos($data[$i], CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE) { |
| 381 | $data[$i] = explode(CRM_Core_DAO::VALUE_SEPARATOR,$data[$i]); |
| 382 | $data[$i] = array_combine($data[$i], $data[$i]); |
| 383 | } |
| 384 | $params[$field] = $data[$i]; |
| 385 | } |
| 386 | $params['version'] = 3; |
| 387 | return $params; |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | /** |
| 392 | * class for processing records to add |
| 393 | * used by csv/import.php |
| 394 | * |
| 395 | **/ |
| 396 | |
| 397 | class civicrm_cli_csv_importer extends civicrm_cli_csv_file { |
| 398 | function processline($params) { |
| 399 | $result = civicrm_api($this->_entity, 'Create', $params); |
| 400 | if ($result['is_error']) { |
| 401 | echo "\nERROR line " . $this->row . ": " . $result['error_message'] . "\n"; |
| 402 | } |
| 403 | else { |
| 404 | echo "\nline " . $this->row . ": created " . $this->_entity . " id: " . $result['id'] . "\n"; |
| 405 | } |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | /** |
| 410 | * class for processing records to delete |
| 411 | * used by csv/delete.php |
| 412 | * |
| 413 | **/ |
| 414 | |
| 415 | class civicrm_cli_csv_deleter extends civicrm_cli_csv_file { |
| 416 | function processline($params) { |
| 417 | $result = civicrm_api($this->_entity, 'Delete', $params); |
| 418 | if ($result['is_error']) { |
| 419 | echo "\nERROR line " . $this->row . ": " . $result['error_message'] . "\n"; |
| 420 | } else { |
| 421 | echo "\nline " . $this->row . ": deleted\n"; |
| 422 | } |
| 423 | } |
| 424 | } |