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