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