XRL  3.0.0
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  // From a phar release.
34  if (!strncmp('phar://', __FILE__, 7)) {
35  $phar = new \Phar(__FILE__);
36  $md = $phar->getMetadata();
37  return $md['version'];
38  }
39 
40  // From a composer install.
41  $getver = dirname(__DIR__) .
42  DIRECTORY_SEPARATOR . 'vendor' .
43  DIRECTORY_SEPARATOR . 'erebot' .
44  DIRECTORY_SEPARATOR . 'buildenv' .
45  DIRECTORY_SEPARATOR . 'get_version.php';
46  if (file_exists($getver)) {
47  return trim(shell_exec($getver));
48  }
49 
50  // Default guess.
51  return 'dev';
52  }
53 
60  public static function getCopyrightAndLicense()
61  {
62  return str_replace(
63  "\n",
64  PHP_EOL,
65  file_get_contents(
66  dirname(__DIR__) .
67  DIRECTORY_SEPARATOR .
68  'LICENSE'
69  )
70  );
71  }
72 
84  public function printUsage($output, $prog)
85  {
86  $usageFile = dirname(__DIR__) .
87  DIRECTORY_SEPARATOR . 'data' .
88  DIRECTORY_SEPARATOR . 'usage.txt';
89  $usage = @file_get_contents($usageFile);
90  $usage = str_replace(array("\r\n", "\r"), "\n", $usage);
91  $usage = trim($usage);
92  $output->write($usage, $prog, 'http://xmlrpc.example.com/');
93  }
94 
112  protected function parseBool($value)
113  {
114  $value = strtolower($value);
115  if (in_array($value, array('0', 'off', 'false'))) {
116  return false;
117  }
118  if (in_array($value, array('1', 'on', 'true'))) {
119  return true;
120  }
121  throw new \Exception('Invalid value "'.$value.'" for type "bool"');
122  }
123 
137  protected function parseFile($value)
138  {
139  $content = @file_get_contents($value);
140  if ($content === false) {
141  throw new \Exception('Could not read content of "'.$value.'"');
142  }
143  return $content;
144  }
145 
165  protected function parseTimestamp($value)
166  {
167  $result = new \DateTime($value);
168  // Older versions of PHP returned false for invalid
169  // values instead of throwing an exception.
170  if (!$result) {
171  throw new \Exception('Invalid datetime value "'.$value.'"');
172  }
173  return $result;
174  }
175 
199  protected function parseParam(array &$args, \DateTimeZone $timezone)
200  {
201  if (!count($args)) {
202  throw new \Exception('Not enough arguments.');
203  }
204 
205  $type = strtolower(array_shift($args));
206  $parseFunc = null;
207  switch ($type) {
208  case 'null':
209  case 'nil':
210  case 'n':
211  return null;
212  break;
213 
214  case 'boolean':
215  case 'bool':
216  case 'b':
217  $parseFunc = array($this, 'parseBool');
218  break;
219 
220  case 'integer':
221  case 'int':
222  case 'i4':
223  case 'i':
224  $parseFunc = 'intval';
225  break;
226 
227  case 'double':
228  case 'float':
229  case 'f':
230  $parseFunc = 'floatval';
231  break;
232 
233  case 'string':
234  case 'str':
235  case 's':
236  $parseFunc = 'strval';
237  break;
238 
239  case 'file':
240  case '@':
241  $parseFunc = array($this, 'parseFile');
242  break;
243 
244  case 'timestamp':
245  case 'datetime':
246  case 'date':
247  case 'time':
248  case 'ts':
249  case 'dt':
250  case 't':
251  case 'd':
252  $parseFunc = array($this, 'parseTimestamp');
253  break;
254 
255  case 'hash':
256  case 'h':
257  $result = array();
258  while (true) {
259  if (!count($args)) {
260  throw new \Exception('Not enough arguments for "hash".');
261  }
262 
263  if (in_array(strtolower($args[0]), array('endhash', 'eh'))) {
264  break;
265  }
266 
267  $key = $this->parseParam($args, $timezone);
268  if (!is_int($key) && !is_string($key)) {
269  throw new \Exception(
270  'Invalid type "'.gettype($key).'" for hash key. '.
271  'Only integer and string keys may be used.'
272  );
273  }
274  $value = $this->parseParam($args, $timezone);
275  $result[$key] = $value;
276  }
277  // Pop the ending "endhash".
278  array_shift($args);
279  return $result;
280 
281  case 'list':
282  case 'l':
283  $result = array();
284  while (true) {
285  if (!count($args)) {
286  throw new \Exception('Not enough arguments for "list".');
287  }
288 
289  if (in_array(strtolower($args[0]), array('endlist', 'el'))) {
290  break;
291  }
292 
293  $result[] = $this->parseParam($args, $timezone);
294  }
295  // Pop the ending "endlist".
296  array_shift($args);
297  return $result;
298 
299  default:
300  throw new \Exception('Unknown type "'.$type.'".');
301  }
302 
303  if (!count($args)) {
304  throw new \Exception('Not enough arguments.');
305  }
306 
307  $value = array_shift($args);
308  $value = call_user_func($parseFunc, $value);
309  if ($value instanceof \DateTime) {
310  $value->setTimezone($timezone);
311  }
312  return $value;
313  }
314 
331  protected function parse(array $args)
332  {
333  $params = array(
334  'serverURL' => null,
335  'procedure' => null,
336  'additional' => array(),
337  );
338  $options = array(
339  'd' => false,
340  'h' => false,
341  'n' => false,
342  't' => new \DateTimeZone(@date_default_timezone_get()),
343  'v' => 0,
344  'V' => false,
345  'x' => false,
346  );
347 
348  while (count($args)) {
349  $v = array_shift($args);
350 
351  if ($params['serverURL'] === null) {
352  if (substr($v, 0, 1) == '-') {
353  $p = array();
354  $v = (string) substr($v, 1);
355  foreach (str_split($v) as $o) {
356  if (!array_key_exists($o, $options)) {
357  throw new \Exception(
358  'Unknown option "'.$o.'". '.
359  'Use -h to get help.'
360  );
361  }
362 
363  if (is_bool($options[$o])) {
364  $options[$o] = true;
365  } elseif (is_int($options[$o])) {
366  $options[$o]++;
367  } else {
368  $p[] = $o;
369  }
370  }
371 
372  foreach ($p as $o) {
373  if (!count($args)) {
374  throw new \Exception(
375  'Not enough arguments for option "'.$o.'".'
376  );
377  }
378  $v = array_shift($args);
379  if (!($options[$o] instanceof \DateTimeZone)) {
380  $options[$o] = new \DateTimeZone($v);
381  }
382  }
383  } else {
384  $params['serverURL'] = $v;
385  }
386  continue;
387  }
388 
389  if ($params['procedure'] === null) {
390  $params['procedure'] = $v;
391  break;
392  }
393  }
394 
395  while (count($args)) {
396  $params['additional'][] = $this->parseParam($args, $options['t']);
397  }
398 
399  return array($options, $params);
400  }
401 
417  public function run(array $args)
418  {
419  $prog = array_shift($args);
420  try {
421  list($options, $params) = $this->parse($args);
422  } catch (\Exception $e) {
423  fprintf(STDERR, '%s: %s' . PHP_EOL, $prog, $e->getMessage());
424  return 2;
425  }
426 
427  // Show help.
428  if ($options['h']) {
429  $this->printUsage(new \fpoirotte\XRL\Output(STDOUT), $prog);
430  return 0;
431  }
432 
433  // Show version.
434  if ($options['V']) {
435  $version = self::getVersion();
436  $license = self::getCopyrightAndLicense();
437  echo 'XRL version ' . $version . PHP_EOL;
438  echo PHP_EOL . $license . PHP_EOL;
439  echo 'Visit https://github.com/fpoirotte/XRL for more!' . PHP_EOL;
440  return 0;
441  }
442 
443  // Do we have enough arguments to do something?
444  if ($params['serverURL'] === null || $params['procedure'] === null) {
445  $this->printUsage(new \fpoirotte\XRL\Output(STDERR), $prog);
446  return 2;
447  }
448 
449  // Then let's do it!
450  $encoder = new \fpoirotte\XRL\NativeEncoder(new \fpoirotte\XRL\Encoder($options['t'], true));
451  $decoder = new \fpoirotte\XRL\NativeDecoder(new \fpoirotte\XRL\Decoder($options['t'], $options['x']));
452  $request = new \fpoirotte\XRL\Request($params['procedure'], $params['additional']);
453 
454  // Change verbosity as necessary.
455  if (class_exists('\\Plop\\Plop')) {
456  $logging = \Plop\Plop::getInstance();
457  $logging->getLogger()->setLevel(40 - max(4, $options['v']) * 10);
458  } else {
459  $logging = null;
460  }
461 
462  // Prepare the request.
463  $xml = $encoder->encodeRequest($request);
464  $logging and $logging->debug(
465  "Request:\n%(request)s",
466  array('request' => $xml)
467  );
468  if ($options['n']) {
469  echo 'Not sending the actual query due to dry run mode.' . PHP_EOL;
470  return 0;
471  }
472 
473  // Prepare the context.
474  $ctxOptions = array(
475  'http' => array(
476  'method' => 'POST',
477  'content' => $xml,
478  'header' => 'Content-Type: text/xml',
479  ),
480  );
481  $context = stream_context_create($ctxOptions);
482  libxml_set_streams_context($context);
483 
484  // Send the request and process the response.
485  try {
486  $result = $decoder->decodeResponse($params['serverURL']);
487  } catch (\Exception $result) {
488  // Nothing to do.
489  }
490 
491  echo 'Result:' . PHP_EOL . print_r($result, true) . PHP_EOL;
492  return 0;
493  }
494 }
parseParam(array &$args,\DateTimeZone $timezone)
Definition: CLI.php:199
parseFile($value)
Definition: CLI.php:137
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:165
parseBool($value)
Definition: CLI.php:112
static getCopyrightAndLicense()
Definition: CLI.php:60
parse(array $args)
Definition: CLI.php:331
run(array $args)
Definition: CLI.php:417
printUsage($output, $prog)
Definition: CLI.php:84
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