@@ -59,12 +59,114 @@ class Util
5959 * keys, and the corresponding IDs as values.
6060 */
6161 protected $ idCache = null ;
62+
63+ /**
64+ * Parses a value from a RouterOS scripting context.
65+ *
66+ * Turns a value from RouterOS into an equivalent PHP value, based on
67+ * determining the type in the same way RouterOS would determine it for a
68+ * literal.
69+ *
70+ * This method is intended to be the very opposite of {@link escapeValue()}.
71+ * That is, results from that method, if given to this method, should
72+ * produce equivalent results.
73+ *
74+ * @param string $value The value to be parsed. Must be a literal of a
75+ * value, e.g. what {@link escapeValue()} will give you.
76+ *
77+ * @return mixed Depending on RouterOS type detected:
78+ * - "nil" or "nothing" - NULL.
79+ * - "number" - int or double for large values.
80+ * - "bool" - a boolean.
81+ * - "time" - a {@link DateInterval} object.
82+ * - "array" - an array, with the values processed recursively.
83+ * - "str" - a string.
84+ * - Unrecognized type - treated as an unquoted string.
85+ */
86+ public static function parseValue ($ value )
87+ {
88+ $ value = (string )$ value ;
89+
90+ if ('' === $ value ) {
91+ return null ;
92+ } elseif (in_array ($ value , array ('true ' , 'false ' , 'yes ' , 'no ' ), true )) {
93+ return $ value === 'true ' || $ value === 'yes ' ;
94+ } elseif ($ value === (string )($ num = (int )$ value )
95+ || $ value === (string )($ num = (double )$ value )
96+ ) {
97+ return $ num ;
98+ } elseif (preg_match (
99+ '/^
100+ (?:(\d+)w)?
101+ (?:(\d+)d)?
102+ (?:(\d\d)\:)?
103+ (\d\d)\:
104+ (\d\d(:\.\d{1,6})?)
105+ $/x ' ,
106+ $ value ,
107+ $ time
108+ )) {
109+ $ days = isset ($ time [2 ]) ? (int )$ time [2 ] : 0 ;
110+ if (isset ($ time [1 ])) {
111+ $ days += 7 * (int )$ time [1 ];
112+ }
113+ if ('' === $ time [3 ]) {
114+ $ time [3 ] = 0 ;
115+ }
116+ return new DateInterval (
117+ "P {$ days }DT {$ time [3 ]}H {$ time [4 ]}M {$ time [5 ]}S "
118+ );
119+ } elseif (('" ' === $ value [0 ]) && substr (strrev ($ value ), 0 , 1 ) === '" ' ) {
120+ return str_replace (
121+ array ('\" ' , '\\\\' , "\\\n" , "\\\r\n" , "\\\r" ),
122+ array ('" ' , '\\' ),
123+ substr ($ value , 1 , -1 )
124+ );
125+ } elseif ('{ ' === $ value [0 ]) {
126+ $ len = strlen ($ value );
127+ if ($ value [$ len - 1 ] === '} ' ) {
128+ $ value = substr ($ value , 1 , -1 );
129+ if ('' === $ value ) {
130+ return array ();
131+ }
132+ $ parsedValue = preg_split (
133+ '/
134+ (\"[^"]*\")
135+ |
136+ (\{[^{}]*(?2)?\})
137+ |
138+ ([^;]+)
139+ /sx ' ,
140+ $ value ,
141+ null ,
142+ PREG_SPLIT_DELIM_CAPTURE
143+ );
144+ $ result = array ();
145+ foreach ($ parsedValue as $ token ) {
146+ if ('' === $ token || '; ' === $ token ) {
147+ continue ;
148+ }
149+ $ result [] = static ::parseValue ($ token );
150+ }
151+ return $ result ;
152+ }
153+ }
154+ return $ value ;
155+ }
62156
63157 /**
64158 * Escapes a value for a RouterOS scripting context.
65159 *
66- * Turns any PHP value into an equivalent whole value that can be inserted
67- * as part of a RouterOS script.
160+ * Turns any native PHP value into an equivalent whole value that can be
161+ * inserted as part of a RouterOS script.
162+ *
163+ * DateTime and DateInterval objects will be casted to RouterOS' "time"
164+ * type. A DateTime object will be converted to a time relative to the UNIX
165+ * epoch time. Note that if a DateInterval does not have the "days" property
166+ * ("a" in formatting), then its months and years will be ignored, because
167+ * they can't be unambigiously converted to a "time" value.
168+ *
169+ * Unrecognized types are casted to strings.
68170 *
69171 * @param mixed $value The value to be escaped.
70172 *
@@ -85,7 +187,7 @@ public static function escapeValue($value)
85187 break ;
86188 case 'array ' :
87189 if (0 === count ($ value )) {
88- $ value = '{} ' ;
190+ $ value = '({}) ' ;
89191 break ;
90192 }
91193 $ result = '' ;
@@ -105,13 +207,11 @@ public static function escapeValue($value)
105207 }
106208 if ($ value instanceof DateInterval) {
107209 if (false === $ value ->days || $ value ->days < 0 ) {
108- $ value = $ value ->format ('%r ' )
109- . ($ value ->y * 365 + $ value ->m * 12 + $ value ->d )
110- . $ value ->format ('d%H:%I:%S ' );
210+ $ value = $ value ->format ('%r%dd%H:%I:%S ' );
111211 } else {
112212 $ value = $ value ->format ('%r%ad%H:%I:%S ' );
113213 }
114- if (isset ($ usec )) {
214+ if (strpos ( ' . ' , $ value ) === false && isset ($ usec )) {
115215 $ value .= '. ' . $ usec ;
116216 }
117217 break ;
@@ -226,11 +326,10 @@ public function changeMenu($newMenu = '')
226326 * @param string $source A script to execute.
227327 * @param array $params An array of local variables to make available in
228328 * the script. Variable names are array keys, and variable values are
229- * array values. Note that the script's (generated) name is always added
230- * as the variable "_", which you can overwrite from here.
231- * Native PHP types will be converted to their RouterOS equivalents.
232- * DateTime and DateInterval objects will be casted to RouterOS' "time"
233- * type. Other types are casted to strings.
329+ * array values. Array values are automatically processed with
330+ * {@link escapeValue()}.
331+ * Note that the script's (generated) name is always added as the
332+ * variable "_", which you can overwrite from here.
234333 * @param string $policy Allows you to specify a policy the script must
235334 * follow. Has the same format as in terminal. If left NULL, the script
236335 * has no restrictions.
@@ -369,9 +468,10 @@ public function find()
369468 /**
370469 * Gets a value of a specified entry at the current menu.
371470 *
372- * @param int $number A number identifying the entry you're
373- * targeting. Can also be an ID or (in some menus) name.
374- * @param string $value_name The name of the value you want to get.
471+ * @param int|string|null $number A number identifying the entry you're
472+ * targeting. Can also be an ID or (in some menus) name. For menus where
473+ * there are no entries (e.g. "/system identity"), you can specify NULL.
474+ * @param string $value_name The name of the value you want to get.
375475 *
376476 * @return string|null|bool The value of the specified property. If the
377477 * property is not set, NULL will be returned. If no such entry exists,
@@ -388,11 +488,13 @@ public function get($number, $value_name)
388488 }
389489 }
390490
391- $ number = (string )$ number ;
392- $ request = new Request (
393- $ this ->menu . '/print ' ,
394- Query::where ('.id ' , $ number )->orWhere ('name ' , $ number )
395- );
491+ $ request = new Request ($ this ->menu . '/print ' );
492+ if (null !== $ number ) {
493+ $ number = (string )$ number ;
494+ $ request ->setQuery (
495+ Query::where ('.id ' , $ number )->orWhere ('name ' , $ number )
496+ );
497+ }
396498 $ request ->setArgument ('.proplist ' , $ value_name );
397499 $ responses = $ this ->client ->sendSync ($ request )
398500 ->getAllOfType (Response::TYPE_DATA );
@@ -457,7 +559,8 @@ public function remove()
457559 * which match certain criteria.
458560 *
459561 * @param mixed $numbers Targeted entries. Can be any criteria accepted by
460- * {@link find()}.
562+ * {@link find()} or NULL in case the menu is one without entries
563+ * (e.g. "/system identity").
461564 * @param array $newValues An array with the names of each property to set
462565 * as an array key, and the new value as an array value.
463566 *
@@ -470,9 +573,10 @@ public function set($numbers, array $newValues)
470573 foreach ($ newValues as $ name => $ value ) {
471574 $ setRequest ->setArgument ($ name , $ value );
472575 }
473- return $ this ->client ->sendSync (
474- $ setRequest ->setArgument ('numbers ' , $ this ->find ($ numbers ))
475- );
576+ if (null !== $ numbers ) {
577+ $ setRequest ->setArgument ('numbers ' , $ this ->find ($ numbers ));
578+ }
579+ return $ this ->client ->sendSync ($ setRequest );
476580 }
477581
478582 /**
@@ -516,11 +620,11 @@ public function unsetValue($numbers, $value_name)
516620 /**
517621 * Adds a new entry at the current menu.
518622 *
519- * @param array $values Accepts one or more entries to add to the
623+ * @param array $values Accepts one or more entries to add to the
520624 * current menu. The data about each entry is specified as an array with
521625 * the names of each property as an array key, and the value as an array
522626 * value.
523- * @param array $values, ... Additional entries.
627+ * @param array $... Additional entries.
524628 *
525629 * @return string A comma separated list of the new entries' IDs.
526630 */
0 commit comments