Skip to content

Commit c4b7989

Browse files
committed
Added ResponseCollection::orderBy() with tests;
Added an ".rsc" file with settings for a fresh RouterOS install to run the tests normally; Adjusted composer autoloader lookup to honor COMPOSER_VENDOR_DIR if set, and the project root's autoloader is also matched properly; Adjusted the console to have most default values as part of the XML; The stub no longer includes possible reasons for connection failure, but instead suggests running the console, where if applicable, the same messages are shown.
1 parent e7550ed commit c4b7989

File tree

8 files changed

+373
-90
lines changed

8 files changed

+373
-90
lines changed

RELEASE-1.0.0b5

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ Console and some overall improvements.
1010
* Util::add() and Util::set()/Util::edit() now support flags as values with a numeric key.
1111
* Util::changeMenu() now returns the Util object itself, except when you supply an empty string, when the current menu is returned (as before).
1212
* ResponseCollection can now be searched by argument values, if you first designate an argument name with the new ResponseCollection::setIndex() method.
13+
* ResponseCollection can now produce a sorted response collection based on user defined criteria using the new ResponseCollection::orderBy() method.
1314
* Doc fixes (Notably: Clarified the acceptability of seekable streams as argument values, which has been present for a long time, but never documented).
1415
* CS fixes.

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@
3232
"PEAR2\\Cache\\SHM": "vendor/pear2/cache_shm/src/"
3333
}
3434
},
35+
"bin": ["scripts/roscon.php"],
3536
"minimum-stability": "dev"
3637
}

data/roscon.xml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ Defaults to PHP's default_socket_timeout ini option.</description>
4949
<description>Turn on verbose output.</description>
5050
<action>StoreTrue</action>
5151
</option>
52-
<option name="colors">
52+
<option name="isColored">
5353
<long_name>--colors</long_name>
5454
<description>Choose whether to color output. Possible values:
55-
"auto" - color is always enabled on UNIX operating systems, and on Windows if ANSICON is installed (detected via the ANSICON_VER environment variable).
55+
"auto" - color is always enabled, except on Windows, where ANSICON must be installed (detected via the ANSICON_VER environment variable).
5656
"yes" - force colored output.
5757
"no" - force no coloring of output.
5858
(Default: "auto")</description>
@@ -62,43 +62,45 @@ Defaults to PHP's default_socket_timeout ini option.</description>
6262
<choice>yes</choice>
6363
<choice>no</choice>
6464
</choices>
65+
<default>auto</default>
6566
</option>
6667
<option name="size">
6768
<short_name>-w</short_name>
6869
<long_name>--width</long_name>
6970
<description>Width of console screen. Used in verbose mode to wrap output in this length.
7071
(Default: 80)</description>
7172
<action>StoreInt</action>
73+
<default>80</default>
7274
</option>
7375
<option name="commandMode">
7476
<long_name>--command-mode</long_name>
7577
<description>Mode to send commands in. Can be one of:
7678
"w" - send every word as soon as it is entered
7779
"s" - wait for a sentence to be formed, and send all its words then
7880
"e" - wait for an empty sentence, and send all previous sentences then. You can send an empty sentence by sending two consecutive empty words.
79-
(Default: "s")
80-
</description>
81+
(Default: "s")</description>
8182
<action>StoreString</action>
8283
<choices>
8384
<choice>w</choice>
8485
<choice>s</choice>
8586
<choice>e</choice>
8687
</choices>
88+
<default>s</default>
8789
</option>
8890
<option name="replyMode">
8991
<long_name>--reply-mode</long_name>
9092
<description>Mode to get replies in. Can be one of:
9193
"w" - after every send, try to get a word
9294
"s" - after every send, try to get a sentence
9395
"e" - after every send, try to get all sentences until a timeout.
94-
(Default: "s")
95-
</description>
96+
(Default: "s")</description>
9697
<action>StoreString</action>
9798
<choices>
9899
<choice>w</choice>
99100
<choice>s</choice>
100101
<choice>e</choice>
101102
</choices>
103+
<default>s</default>
102104
</option>
103105
<option name="multiline">
104106
<short_name>-m</short_name>

scripts/roscon.php

Lines changed: 95 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -47,32 +47,72 @@
4747
*/
4848
use PEAR2\Net\Transmitter\SocketException as SE;
4949

50+
if (PHP_SAPI !== 'cli' && count(get_included_files()) === 1) {
51+
header('Content-Type: text/plain;charset=UTF-8');
52+
echo <<<HEREDOC
53+
For security reasons, this file can not be ran DIRECTLY, except from the
54+
command line. It can be included however, even when not using the command line.
55+
HEREDOC;
56+
return;
57+
}
58+
5059
//If there's no appropriate autoloader, add one
5160
if (!class_exists('PEAR2\Net\RouterOS\Communicator', true)) {
61+
$cwd = chdir(__DIR__);
62+
63+
//The composer autoloader from this package.
64+
//Also matched if the bin-dir is changed to a folder that is directly
65+
//descended from the composer project root.
5266
$autoloader = stream_resolve_include_path('../vendor/autoload.php');
5367
if (false !== $autoloader) {
5468
include_once $autoloader;
5569
} else {
56-
$autoloader = stream_resolve_include_path('PEAR2/Autoload.php');
70+
//The composer autoloader, when this package is a dependency.
71+
$autoloader = stream_resolve_include_path(
72+
(false === ($vendorDir = getenv('COMPOSER_VENDOR_DIR'))
73+
? '../../..'
74+
: $vendorDir) . '/autoload.php'
75+
);
76+
unset($vendorDir);
5777
if (false !== $autoloader) {
5878
include_once $autoloader;
59-
Autoload::initialize(realpath('../src'));
60-
Autoload::initialize(realpath('../../Net_Transmitter.git/src'));
61-
Autoload::initialize(realpath('../../Cache_SHM.git/src'));
62-
Autoload::initialize(realpath('../../Console_Color.git/src'));
6379
} else {
64-
fwrite(
65-
STDERR,
66-
<<<HEREDOC
80+
//PEAR2_Autoload, most probably installed globally.
81+
$autoloader = stream_resolve_include_path('PEAR2/Autoload.php');
82+
if (false !== $autoloader) {
83+
include_once $autoloader;
84+
Autoload::initialize(
85+
realpath('../src')
86+
);
87+
Autoload::initialize(
88+
realpath('../../Net_Transmitter.git/src')
89+
);
90+
Autoload::initialize(
91+
realpath('../../Cache_SHM.git/src')
92+
);
93+
Autoload::initialize(
94+
realpath('../../Console_Color.git/src')
95+
);
96+
Autoload::initialize(
97+
realpath('../../Console_CommandLine.git/src')
98+
);
99+
} else {
100+
fwrite(
101+
STDERR,
102+
<<<HEREDOC
67103
No recognized autoloader is available.
68104
Please install this package with Pyrus, PEAR or Composer.
69105
Alternatively, install PEAR2_Autoload, and/or add it to your include_path.
70106
HEREDOC
71-
);
72-
exit(10);
107+
);
108+
chdir($cwd);
109+
exit(10);
110+
}
73111
}
74112
}
75-
unset($autoloader);
113+
114+
chdir($cwd);
115+
unset($autoloader, $cwd);
76116
}
77117

78118
// Locate the data dir, in preference as:
@@ -117,10 +157,6 @@
117157
$cmdParser->displayUsage(13);
118158
}
119159

120-
$cmd->options['colors'] = $cmd->options['colors'] ?: 'auto';
121-
$cmd->options['size'] = $cmd->options['size'] ?: 80;
122-
$cmd->options['commandMode'] = $cmd->options['commandMode'] ?: 's';
123-
$cmd->options['replyMode'] = $cmd->options['replyMode'] ?: 's';
124160
$comTimeout = null === $cmd->options['conTime']
125161
? (null === $cmd->options['time']
126162
? (int)ini_get('default_socket_timeout')
@@ -151,11 +187,11 @@
151187
'NOTE' => '',
152188
'' => ''
153189
);
154-
if ('auto' === $cmd->options['colors']) {
155-
$cmd->options['colors'] = (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN'
190+
if ('auto' === $cmd->options['isColored']) {
191+
$cmd->options['isColored'] = (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN'
156192
|| getenv('ANSICON_VER') != false) ? 'yes' : 'no';
157193
}
158-
if ('yes' === $cmd->options['colors']) {
194+
if ('yes' === $cmd->options['isColored']) {
159195
$c_colors['SENT'] = new Color(
160196
Color\Fonts::BLACK,
161197
Color\Backgrounds::PURPLE
@@ -198,9 +234,46 @@
198234
fwrite(
199235
STDERR,
200236
"Details: ({$previous->getSocketErrorNumber()}) "
201-
. $previous->getSocketErrorMessage()
237+
. $previous->getSocketErrorMessage() . "\n\n"
202238
);
203239
}
240+
if ($e instanceof RouterOS\SocketException
241+
&& $e->getCode() === RouterOS\SocketException::CODE_CONNECTION_FAIL
242+
) {
243+
echo <<<HEREDOC
244+
Possible reasons:
245+
246+
1. You haven't enabled the API service at RouterOS or you've enabled it on a
247+
different TCP port. Make sure that the "api" service at "/ip service" is
248+
enabled, and with that same TCP port (8728 by default or 8729 for "api-ssl").
249+
250+
2. You've mistyped the IP and/or port. Check the IP and port you've specified
251+
are the ones you intended.
252+
253+
3. The router is not reachable from your web server for some reason. Try to
254+
reach the router (!!!)from the web server(!!!) by other means (e.g. Winbox,
255+
ping) using the same IP, and if you're unable to reach it, check the network
256+
settings on your server, router and any intermediate nodes under your control
257+
that may affect the connection.
258+
259+
4. Your web server is configured to forbid that outgoing connection. If you're
260+
the web server administrator, check your web server's firewall's settings. If
261+
you're on a hosting plan... Typically, shared hosts block all outgoing
262+
connections, but it's also possible that only connections to that port are
263+
blocked. Try to connect to a host on a popular port (21, 80, 443, etc.), and if
264+
successful, change the API service port to that port. If the connection fails
265+
even then, ask your host to configure their firewall so as to allow you to make
266+
outgoing connections to the ip:port you've set the API service on.
267+
268+
5. The router has a filter/mangle/nat rule that overrides the settings at
269+
"/ip service". This is a very rare scenario, but if you want to be sure, try to
270+
disable all rules that may cause such a thing, or (if you can afford it) set up
271+
a fresh RouterOS in place of the existing one, and see if you can connect to it
272+
instead. If you still can't connect, such a rule is certainly not the (only)
273+
reason.
274+
275+
HEREDOC;
276+
}
204277
return;
205278
}
206279
if (null !== $cmd->args['username']) {
@@ -224,7 +297,9 @@
224297
allowed to log in from. You can check them at the "address" property
225298
of the user in the "/user" menu.
226299
4. Mistyped password.
227-
If the password contains non-ASCII characters, be careful of your locale.
300+
Make sure you have spelled it correctly.
301+
If it contains spaces, don't forget to quote the whole password.
302+
If it contains non-ASCII characters, be careful of your locale.
228303
It must match that of the terminal you set your password on, or you must
229304
type the equivalent code points in your current locale, which may display as
230305
different characters.

src/PEAR2/Net/RouterOS/ResponseCollection.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ class ResponseCollection implements ArrayAccess, SeekableIterator, Countable
8989
* @var string|null Name of argument to use as index. NULL when disabled.
9090
*/
9191
protected $index = null;
92+
93+
/**
94+
* @var array Criterias used by {@link compare()} to determine the order
95+
* between two respones. See {@link orderBy()} for a detailed
96+
* description of this array's format.
97+
*/
98+
protected $compareBy = array();
9299

93100
/**
94101
* Creates a new collection.
@@ -432,6 +439,36 @@ public function getLast()
432439
return $offset >= 0 ? $this->responses[$offset] : false;
433440
}
434441

442+
/**
443+
* Order resones by criteria.
444+
*
445+
* @param mixed[] $criteria The criteria to order respones by. It takes the
446+
* form of an array where each key is the name of the property to use
447+
* as (N+1)th sorting key. The value of each member can be either NULL
448+
* (for that property, sort normally in ascending order), a single sort
449+
* order constant (SORT_ASC or SORT_DESC) to sort normally in the
450+
* specified order, an array where the first member is an order
451+
* constant, and the second one is sorting flags (same as built in PHP
452+
* array functions) or a callback.
453+
* If a callback is provided, it must accept two arguments
454+
* (the two values to be compared), and return -1, 0 or 1 if the first
455+
* value is respectively less than, equal to or greather than the second
456+
* one.
457+
* Each key of $criteria can also be numeric, in which case the
458+
* value is the name of the property, and sorting is done normally in
459+
* ascending order.
460+
*
461+
* @return static A new collection with the responses sorted in the
462+
* specified order.
463+
*/
464+
public function orderBy(array $criteria)
465+
{
466+
$this->compareBy = $criteria;
467+
$sortedResponses = $this->responses;
468+
usort($sortedResponses, array($this, 'compare'));
469+
return new static($sortedResponses);
470+
}
471+
435472
/**
436473
* Calls a method of the response pointed by the pointer.
437474
*
@@ -451,4 +488,55 @@ public function __call($method, array $args)
451488
$args
452489
);
453490
}
491+
492+
/**
493+
* Compares two respones.
494+
*
495+
* Compares two respones, based on criteria defined in
496+
* {@link static::$compareBy}.
497+
*
498+
* @param Response $a The response to compare.
499+
* @param Response $b The response to compare $a against.
500+
*
501+
* @return int Returns 0 if the two respones are equal according to every
502+
* criteria specified, -1 if $a should be placed before $b, and 1 if $b
503+
* should be placed before $a.
504+
*/
505+
protected function compare(Response $a, Response $b)
506+
{
507+
foreach ($this->compareBy as $name => $spec) {
508+
if (!is_string($name)) {
509+
$name = $spec;
510+
$spec = null;
511+
}
512+
513+
$members = array(
514+
0 => $a->getArgument($name),
515+
1 => $b->getArgument($name)
516+
);
517+
518+
if (is_callable($spec)) {
519+
uasort($members, $spec);
520+
} elseif ($members[0] === $members[1]) {
521+
continue;
522+
} else {
523+
$flags = SORT_REGULAR;
524+
$order = SORT_ASC;
525+
if (is_array($spec)) {
526+
list($order, $flags) = $spec;
527+
} elseif (null !== $spec) {
528+
$order = $spec;
529+
}
530+
531+
if (SORT_ASC === $order) {
532+
asort($members, $flags);
533+
} else {
534+
arsort($members, $flags);
535+
}
536+
}
537+
return (key($members) === 0) ? -1 : 1;
538+
}
539+
540+
return 0;
541+
}
454542
}

0 commit comments

Comments
 (0)