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