XRL  latest
Simple XML-RPC Library (both client and server)
CLI.php
1 <?php
2 /*
3  * This file is part of XRL, a simple XML-RPC Library for PHP.
4  *
5  * Copyright (c) 2012, XRL Team. All rights reserved.
6  * XRL is licensed under the 3-clause BSD License.
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11 
12 namespace fpoirotte\XRL;
13 
22 class CLI
23 {
31  public static function getVersion()
32  {
33  static $version = null;
34 
35  // Return cached version if possible.
36  if (null !== $version) {
37  return $version;
38  }
39 
40  // From a phar release.
41  if (!strncmp('phar://', __FILE__, 7)) {
42  $phar = new \Phar(dirname(__DIR__));
43  $md = $phar->getMetadata();
44  $version = $md['version'];
45  } else {
46  // From a composer install.
47  $getver = dirname(__DIR__) .
48  DIRECTORY_SEPARATOR . 'vendor' .
49  DIRECTORY_SEPARATOR . 'erebot' .
50  DIRECTORY_SEPARATOR . 'buildenv' .
51  DIRECTORY_SEPARATOR . 'get_version.php';
52  if (file_exists($getver)) {
53  $version = trim(shell_exec($getver));
54  } else {
55  // Default guess
56  $version = 'dev';
57  }
58  }
59 
60  return $version;
61  }
62 
69  public static function getCopyrightAndLicense()
70  {
71  return str_replace(
72  "\n",
73  PHP_EOL,
74  file_get_contents(
75  dirname(__DIR__) .
76  DIRECTORY_SEPARATOR .
77  'LICENSE'
78  )
79  );
80  }
81 
93  public function printUsage($output, $prog)
94  {
95  $usageFile = dirname(__DIR__) .
96  DIRECTORY_SEPARATOR . 'data' .
97  DIRECTORY_SEPARATOR . 'usage.txt';
98  $usage = @file_get_contents($usageFile);
99  $usage = str_replace(array("\r\n", "\r"), "\n", $usage);
100  $usage = trim($usage);
101  $output->write($usage, $prog, 'http://xmlrpc.example.com/');
102  }
103 
121  protected function parseBool($value)
122  {
123  $value = strtolower($value);
124  if (in_array($value, array('0', 'off', 'false'))) {
125  return false;
126  }
127  if (in_array($value, array('1', 'on', 'true'))) {
128  return true;
129  }
130  throw new \Exception('Invalid value "'.$value.'" for type "bool"');
131  }
132 
146  protected function parseFile($value)
147  {
148  $content = @file_get_contents($value);
149  if ($content === false) {
150  throw new \Exception('Could not read content of "'.$value.'"');
151  }
152  return $content;
153  }
154 
174  protected function parseTimestamp($value)
175  {
176  $result = new \DateTime($value);
177  // Older versions of PHP returned false for invalid
178  // values instead of throwing an exception.
179  if (!$result) {
180  throw new \Exception('Invalid datetime value "'.$value.'"');
181  }
182  return $result;
183  }
184 
208  protected function parseParam(array &$args, \DateTimeZone $timezone)
209  {
210  if (!count($args)) {
211  throw new \Exception('Not enough arguments.');
212  }
213 
214  $type = strtolower(array_shift($args));
215  $parseFunc = null;
216  switch ($type) {
217  case 'null':
218  case 'nil':
219  case 'n':
220  return null;
221  break;
222 
223  case 'boolean':
224  case 'bool':
225  case 'b':
226  $parseFunc = array($this, 'parseBool');
227  break;
228 
229  case 'integer':
230  case 'int':
231  case 'i4':
232  case 'i':
233  $parseFunc = 'intval';
234  break;
235 
236  case 'double':
237  case 'float':
238  case 'f':
239  $parseFunc = 'floatval';
240  break;
241 
242  case 'string':
243  case 'str':
244  case 's':
245  $parseFunc = 'strval';
246  break;
247 
248  case 'file':
249  case '@':
250  $parseFunc = array($this, 'parseFile');
251  break;
252 
253  case 'timestamp':
254  case 'datetime':
255  case 'date':
256  case 'time':
257  case 'ts':
258  case 'dt':
259  case 't':
260  case 'd':
261  $parseFunc = array($this, 'parseTimestamp');
262  break;
263 
264  case 'hash':
265  case 'h':
266  $result = array();
267  while (true) {
268  if (!count($args)) {
269  throw new \Exception('Not enough arguments for "hash".');
270  }
271 
272  if (in_array(strtolower($args[0]), array('endhash', 'eh'))) {
273  break;
274  }
275 
276  $key = $this->parseParam($args, $timezone);
277  if (!is_int($key) && !is_string($key)) {
278  throw new \Exception(
279  'Invalid type "'.gettype($key).'" for hash key. '.
280  'Only integer and string keys may be used.'
281  );
282  }
283  $value = $this->parseParam($args, $timezone);
284  $result[$key] = $value;
285  }
286  // Pop the ending "endhash".
287  array_shift($args);
288  return $result;
289 
290  case 'list':
291  case 'l':
292  $result = array();
293  while (true) {
294  if (!count($args)) {
295  throw new \Exception('Not enough arguments for "list".');
296  }
297 
298  if (in_array(strtolower($args[0]), array('endlist', 'el'))) {
299  break;
300  }
301 
302  $result[] = $this->parseParam($args, $timezone);
303  }
304  // Pop the ending "endlist".
305  array_shift($args);
306  return $result;
307 
308  default:
309  throw new \Exception('Unknown type "'.$type.'".');
310  }
311 
312  if (!count($args)) {
313  throw new \Exception('Not enough arguments.');
314  }
315 
316  $value = array_shift($args);
317  $value = call_user_func($parseFunc, $value);
318  if ($value instanceof \DateTime) {
319  $value->setTimezone($timezone);
320  }
321  return $value;
322  }
323 
340  protected function parse(array $args)
341  {
342  $params = array(
343  'serverURL' => null,
344  'procedure' => null,
345  'additional' => array(),
346  );
347  $options = array(
348  'd' => false,
349  'h' => false,
350  'n' => false,
351  't' => new \DateTimeZone(@date_default_timezone_get()),
352  'v' => 0,
353  'V' => false,
354  'x' => false,
355  );
356 
357  while (count($args)) {
358  $v = array_shift($args);
359 
360  if ($params['serverURL'] === null) {
361  if (substr($v, 0, 1) == '-') {
362  $p = array();
363  $v = (string) substr($v, 1);
364  foreach (str_split($v) as $o) {
365  if (!array_key_exists($o, $options)) {
366  throw new \Exception(
367  'Unknown option "'.$o.'". '.
368  'Use -h to get help.'
369  );
370  }
371 
372  if (is_bool($options[$o])) {
373  $options[$o] = true;
374  } elseif (is_int($options[$o])) {
375  $options[$o]++;
376  } else {
377  $p[] = $o;
378  }
379  }
380 
381  foreach ($p as $o) {
382  if (!count($args)) {
383  throw new \Exception(
384  'Not enough arguments for option "'.$o.'".'
385  );
386  }
387  $v = array_shift($args);
388  if (!($options[$o] instanceof \DateTimeZone)) {
389  $options[$o] = new \DateTimeZone($v);
390  }
391  }
392  } else {
393  $params['serverURL'] = $v;
394  }
395  continue;
396  }
397 
398  if ($params['procedure'] === null) {
399  $params['procedure'] = $v;
400  break;
401  }
402  }
403 
404  while (count($args)) {
405  $params['additional'][] = $this->parseParam($args, $options['t']);
406  }
407 
408  return array($options, $params);
409  }
410 
426  public function run(array $args)
427  {
428  $prog = array_shift($args);
429  try {
430  list($options, $params) = $this->parse($args);
431  } catch (\Exception $e) {
432  fprintf(STDERR, '%s: %s' . PHP_EOL, $prog, $e->getMessage());
433  return 2;
434  }
435 
436  // Show help.
437  if ($options['h']) {
438  $this->printUsage(new \fpoirotte\XRL\Output(STDOUT), $prog);
439  return 0;
440  }
441 
442  // Show version.
443  if ($options['V']) {
444  $version = self::getVersion();
445  $license = self::getCopyrightAndLicense();
446  echo 'XRL version ' . $version . PHP_EOL;
447  echo PHP_EOL . $license . PHP_EOL;
448  echo 'Visit https://github.com/fpoirotte/XRL for more!' . PHP_EOL;
449  return 0;
450  }
451 
452  // Do we have enough arguments to do something?
453  if ($params['serverURL'] === null || $params['procedure'] === null) {
454  $this->printUsage(new \fpoirotte\XRL\Output(STDERR), $prog);
455  return 2;
456  }
457 
458  // Then let's do it!
459  $encoder = new \fpoirotte\XRL\NativeEncoder(new \fpoirotte\XRL\Encoder($options['t'], true));
460  $decoder = new \fpoirotte\XRL\NativeDecoder(new \fpoirotte\XRL\Decoder($options['t'], $options['x']));
461  $request = new \fpoirotte\XRL\Request($params['procedure'], $params['additional']);
462 
463  // Change verbosity as necessary.
464  if (class_exists('\\Plop\\Plop')) {
465  $logging = \Plop\Plop::getInstance();
466  $logging->getLogger()->setLevel(40 - min(4, $options['v']) * 10);
467  } else {
468  $logging = null;
469  }
470 
471  // Prepare the request.
472  $xml = $encoder->encodeRequest($request);
473  $logging and $logging->debug(
474  "Request:\n%(request)s",
475  array('request' => $xml)
476  );
477  if ($options['n']) {
478  echo 'Not sending the actual query due to dry run mode.' . PHP_EOL;
479  return 0;
480  }
481 
482  $headers = array(
483  'Content-Type: text/xml',
484  'User-Agent: XRL/' . static::getVersion(),
485  );
486 
487  // Prepare the context.
488  $ctxOptions = array(
489  'http' => array(
490  'method' => 'POST',
491  'content' => $xml,
492  'header' => $headers,
493  ),
494  );
495  $context = stream_context_create($ctxOptions);
496  libxml_set_streams_context($context);
497 
498  // Send the request and process the response.
499  try {
500  $result = $decoder->decodeResponse($params['serverURL']);
501  } catch (\Exception $result) {
502  // Nothing to do.
503  }
504 
505  echo 'Result:' . PHP_EOL . print_r($result, true) . PHP_EOL;
506  return 0;
507  }
508 }
parseParam(array &$args,\DateTimeZone $timezone)
Definition: CLI.php:208
parseFile($value)
Definition: CLI.php:146
An XML-RPC encoder that can produce either compact documents or pretty documents. ...
Definition: Encoder.php:21
An exception that is used to represent XML-RPC errors.
Definition: Exception.php:21
parseTimestamp($value)
Definition: CLI.php:174
parseBool($value)
Definition: CLI.php:121
static getCopyrightAndLicense()
Definition: CLI.php:69
parse(array $args)
Definition: CLI.php:340
run(array $args)
Definition: CLI.php:426
printUsage($output, $prog)
Definition: CLI.php:93
A decoder that can process XML-RPC requests and responses, with optional XML validation.
Definition: Decoder.php:21
static getVersion()
Definition: CLI.php:31
A class that implements a simple CLI script to send XML-RPC queries and to display their result...
Definition: CLI.php:22
A class that formats messages before sending them to a stream.
Definition: Output.php:21