1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* ~~summary~~ |
5
|
|
|
* |
6
|
|
|
* ~~description~~ |
7
|
|
|
* |
8
|
|
|
* PHP version 5 |
9
|
|
|
* |
10
|
|
|
* @category Net |
11
|
|
|
* @package PEAR2_Net_RouterOS |
12
|
|
|
* @author Vasil Rangelov <[email protected]> |
13
|
|
|
* @copyright 2011 Vasil Rangelov |
14
|
|
|
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 |
15
|
|
|
* @version GIT: $Id$ |
16
|
|
|
* @link http://pear2.php.net/PEAR2_Net_RouterOS |
17
|
|
|
*/ |
18
|
|
|
/** |
19
|
|
|
* The namespace declaration. |
20
|
|
|
*/ |
21
|
|
|
namespace PEAR2\Net\RouterOS; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Returned from {@link Util::getCurrentTime()}. |
25
|
|
|
*/ |
26
|
|
|
use DateTime; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Used at {@link Util::getCurrentTime()} to get the proper time. |
30
|
|
|
*/ |
31
|
|
|
use DateTimeZone; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Implemented by this class. |
35
|
|
|
*/ |
36
|
|
|
use Countable; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Used to detect streams in various methods of this class. |
40
|
|
|
*/ |
41
|
|
|
use PEAR2\Net\Transmitter\Stream; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Used to catch a DateTime exception at {@link Util::getCurrentTime()}. |
45
|
|
|
*/ |
46
|
|
|
use Exception as E; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Utility class. |
50
|
|
|
* |
51
|
|
|
* Abstracts away frequently used functionality (particularly CRUD operations) |
52
|
|
|
* in convenient to use methods by wrapping around a connection. |
53
|
|
|
* |
54
|
|
|
* @category Net |
55
|
|
|
* @package PEAR2_Net_RouterOS |
56
|
|
|
* @author Vasil Rangelov <[email protected]> |
57
|
|
|
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 |
58
|
|
|
* @link http://pear2.php.net/PEAR2_Net_RouterOS |
59
|
|
|
*/ |
60
|
|
|
class Util implements Countable |
61
|
|
|
{ |
62
|
|
|
/** |
63
|
|
|
* @var Client The connection to wrap around. |
64
|
|
|
*/ |
65
|
|
|
protected $client; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @var string The current menu. Note that the root menu (only) uses an |
69
|
|
|
* empty string. This is done to enable commands executed at it without |
70
|
|
|
* special casing it at all commands. Instead, only |
71
|
|
|
* {@link static::setMenu()} is special cased. |
72
|
|
|
*/ |
73
|
|
|
protected $menu = ''; |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* @var array<int,string>|null An array with the numbers of items in |
77
|
|
|
* the current menu as keys, and the corresponding IDs as values. |
78
|
|
|
* NULL when the cache needs regenerating. |
79
|
|
|
*/ |
80
|
|
|
protected $idCache = null; |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Creates a new Util instance. |
84
|
|
|
* |
85
|
|
|
* Wraps around a connection to provide convenience methods. |
86
|
|
|
* |
87
|
|
|
* @param Client $client The connection to wrap around. |
88
|
|
|
*/ |
89
|
|
|
public function __construct(Client $client) |
90
|
|
|
{ |
91
|
|
|
$this->client = $client; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* Gets the current menu. |
96
|
|
|
* |
97
|
|
|
* @return string The absolute path to current menu, using API syntax. |
98
|
|
|
*/ |
99
|
|
|
public function getMenu() |
100
|
|
|
{ |
101
|
|
|
return '' === $this->menu ? '/' : $this->menu; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Sets the current menu. |
106
|
|
|
* |
107
|
|
|
* Sets the current menu. |
108
|
|
|
* |
109
|
|
|
* @param string $newMenu The menu to change to. Can be specified with API |
110
|
|
|
* or CLI syntax and can be either absolute or relative. If relative, |
111
|
|
|
* it's relative to the current menu, which by default is the root. |
112
|
|
|
* |
113
|
|
|
* @return $this The object itself. If an empty string is given for |
114
|
|
|
* a new menu, no change is performed, |
115
|
|
|
* but the ID cache is cleared anyway. |
116
|
|
|
* |
117
|
|
|
* @see static::clearIdCache() |
118
|
|
|
*/ |
119
|
|
|
public function setMenu($newMenu) |
120
|
|
|
{ |
121
|
|
|
$newMenu = (string)$newMenu; |
122
|
|
|
if ('' !== $newMenu) { |
123
|
|
|
$menuRequest = new Request('/menu'); |
124
|
|
|
if ('/' === $newMenu) { |
125
|
|
|
$this->menu = ''; |
126
|
|
|
} elseif ('/' === $newMenu[0]) { |
127
|
|
|
$this->menu = $menuRequest->setCommand($newMenu)->getCommand(); |
128
|
|
|
} else { |
129
|
|
|
$newMenu = substr( |
130
|
|
|
$menuRequest->setCommand( |
131
|
|
|
'/' . str_replace('/', ' ', substr($this->menu, 1)) . |
132
|
|
|
' ' . str_replace('/', ' ', $newMenu) . ' ?' |
133
|
|
|
)->getCommand(), |
134
|
|
|
1, |
135
|
|
|
-2/*strlen('/?')*/ |
136
|
|
|
); |
137
|
|
|
if ('' !== $newMenu) { |
138
|
|
|
$this->menu = '/' . $newMenu; |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
} |
142
|
|
|
$this->clearIdCache(); |
143
|
|
|
return $this; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Creates a Request object. |
148
|
|
|
* |
149
|
|
|
* Creates a {@link Request} object, with a command that's at the |
150
|
|
|
* current menu. The request can then be sent using {@link Client}. |
151
|
|
|
* |
152
|
|
|
* @param string $command The command of the request, not including |
153
|
|
|
* the menu. The request will have that command at the current menu. |
154
|
|
|
* @param array $args Arguments of the request. |
155
|
|
|
* Each array key is the name of the argument, and each array value is |
156
|
|
|
* the value of the argument to be passed. |
157
|
|
|
* Arguments without a value (i.e. empty arguments) can also be |
158
|
|
|
* specified using a numeric key, and the name of the argument as the |
159
|
|
|
* array value. |
160
|
|
|
* @param Query|null $query The {@link Query} of the request. |
161
|
|
|
* @param string|null $tag The tag of the request. |
162
|
|
|
* |
163
|
|
|
* @return Request The {@link Request} object. |
164
|
|
|
* |
165
|
|
|
* @throws NotSupportedException On an attempt to call a command in a |
166
|
|
|
* different menu using API syntax. |
167
|
|
|
* @throws InvalidArgumentException On an attempt to call a command in a |
168
|
|
|
* different menu using CLI syntax. |
169
|
|
|
*/ |
170
|
|
|
public function newRequest( |
171
|
|
|
$command, |
172
|
|
|
array $args = array(), |
173
|
|
|
Query $query = null, |
174
|
|
|
$tag = null |
175
|
|
|
) { |
176
|
|
|
if (false !== strpos($command, '/')) { |
177
|
|
|
throw new NotSupportedException( |
178
|
|
|
'Command tried to go to a different menu', |
179
|
|
|
NotSupportedException::CODE_MENU_MISMATCH, |
180
|
|
|
null, |
181
|
|
|
$command |
182
|
|
|
); |
183
|
|
|
} |
184
|
|
|
$request = new Request('/menu', $query, $tag); |
185
|
|
|
$request->setCommand("{$this->menu}/{$command}"); |
186
|
|
|
foreach ($args as $name => $value) { |
187
|
|
|
if (is_int($name)) { |
188
|
|
|
$request->setArgument($value); |
189
|
|
|
} else { |
190
|
|
|
$request->setArgument($name, $value); |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
return $request; |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Executes a RouterOS script. |
198
|
|
|
* |
199
|
|
|
* Executes a RouterOS script, written as a string or a stream. |
200
|
|
|
* Note that in cases of errors, the line numbers will be off, because the |
201
|
|
|
* script is executed at the current menu as context, with the specified |
202
|
|
|
* variables pre declared. This is achieved by prepending 1+count($params) |
203
|
|
|
* lines before your actual script. |
204
|
|
|
* |
205
|
|
|
* @param string|resource $source The source of the script, as a string |
206
|
|
|
* or stream. If a stream is provided, reading starts from the current |
207
|
|
|
* position to the end of the stream, and the pointer stays at the end |
208
|
|
|
* after reading is done. |
209
|
|
|
* @param array<string,mixed> $params An array of parameters to make |
210
|
|
|
* available in the script as local variables. |
211
|
|
|
* Variable names are array keys, and variable values are array values. |
212
|
|
|
* Array values are automatically processed with |
213
|
|
|
* {@link static::escapeValue()}. Streams are also supported, and are |
214
|
|
|
* processed in chunks, each processed with |
215
|
|
|
* {@link static::escapeString()}. Processing starts from the current |
216
|
|
|
* position to the end of the stream, and the stream's pointer is left |
217
|
|
|
* untouched after the reading is done. |
218
|
|
|
* Note that the script's (generated) name is always added as the |
219
|
|
|
* variable "_", which will be inadvertently lost if you overwrite it |
220
|
|
|
* from here. |
221
|
|
|
* @param string|null $policy Allows you to specify a policy the |
222
|
|
|
* script must follow. Has the same format as in terminal. |
223
|
|
|
* If left NULL, the script has no restrictions beyond those imposed by |
224
|
|
|
* the username. |
225
|
|
|
* @param string|null $name The script is executed after being |
226
|
|
|
* saved in "/system script" and is removed after execution. |
227
|
|
|
* If this argument is left NULL, a random string, |
228
|
|
|
* prefixed with the computer's name, is generated and used |
229
|
|
|
* as the script's name. |
230
|
|
|
* To eliminate any possibility of name clashes, |
231
|
|
|
* you can specify your own name instead. |
232
|
|
|
* |
233
|
|
|
* @return string|resource The source of the script, as it appears after |
234
|
|
|
* the run, right before it is removed. |
235
|
|
|
* This can be used for easily retrieving basic output, |
236
|
|
|
* by modifying the script from inside the script |
237
|
|
|
* (use the $"_" variable to refer to the script's name within the |
238
|
|
|
* "/system script" menu). |
239
|
|
|
* |
240
|
|
|
* @throws RouterErrorException When there is an error in any step of the |
241
|
|
|
* way. The reponses include all successful commands prior to the error |
242
|
|
|
* as well. |
243
|
|
|
*/ |
244
|
|
|
public function exec( |
245
|
|
|
$source, |
246
|
|
|
array $params = array(), |
247
|
|
|
$policy = null, |
248
|
|
|
$name = null |
249
|
|
|
) { |
250
|
|
|
if (null === $name) { |
251
|
|
|
$name = uniqid(gethostname(), true); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
$request = new Request('/system/script/add'); |
255
|
|
|
$request->setArgument('name', $name); |
256
|
|
|
$request->setArgument('policy', $policy); |
257
|
|
|
|
258
|
|
|
$params += array('_' => $name); |
259
|
|
|
|
260
|
|
|
$finalSource = fopen('php://temp', 'r+b'); |
261
|
|
|
fwrite( |
262
|
|
|
$finalSource, |
263
|
|
|
'/' . str_replace('/', ' ', substr($this->menu, 1)). "\n" |
264
|
|
|
); |
265
|
|
|
Script::append($finalSource, $source, $params); |
266
|
|
|
fwrite($finalSource, "\n"); |
267
|
|
|
rewind($finalSource); |
268
|
|
|
|
269
|
|
|
$request->setArgument('source', $finalSource); |
270
|
|
|
$addResult = $this->client->sendSync($request); |
271
|
|
|
|
272
|
|
|
if (count($addResult->getAllOfType(Response::TYPE_ERROR)) > 0) { |
273
|
|
|
throw new RouterErrorException( |
274
|
|
|
'Error when trying to add script', |
275
|
|
|
RouterErrorException::CODE_SCRIPT_ADD_ERROR, |
276
|
|
|
null, |
277
|
|
|
$addResult |
278
|
|
|
); |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
$request = new Request('/system/script/run'); |
282
|
|
|
$request->setArgument('number', $name); |
283
|
|
|
$runResult = $this->client->sendSync($request); |
284
|
|
|
if (count($runResult->getAllOfType(Response::TYPE_ERROR)) > 0) { |
285
|
|
|
throw new RouterErrorException( |
286
|
|
|
'Error when running script', |
287
|
|
|
RouterErrorException::CODE_SCRIPT_RUN_ERROR, |
288
|
|
|
null, |
289
|
|
|
new ResponseCollection( |
290
|
|
|
array_merge($addResult->toArray(), $runResult->toArray()) |
291
|
|
|
) |
292
|
|
|
); |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
$request = new Request('/system/script/get'); |
296
|
|
|
$request->setArgument('number', $name); |
297
|
|
|
$request->setArgument('value-name', 'source'); |
298
|
|
|
$getResult = $this->client->sendSync($request); |
299
|
|
|
if (count($getResult->getAllOfType(Response::TYPE_ERROR)) > 0) { |
300
|
|
|
throw new RouterErrorException( |
301
|
|
|
'Error when getting script source', |
302
|
|
|
RouterErrorException::CODE_SCRIPT_GET_ERROR, |
303
|
|
|
null, |
304
|
|
|
new ResponseCollection( |
305
|
|
|
array_merge( |
306
|
|
|
$addResult->toArray(), |
307
|
|
|
$runResult->toArray(), |
308
|
|
|
$getResult->toArray() |
309
|
|
|
) |
310
|
|
|
) |
311
|
|
|
); |
312
|
|
|
} |
313
|
|
|
$postSource = $getResult->end()->getProperty('ret'); |
314
|
|
|
|
315
|
|
|
$request = new Request('/system/script/remove'); |
316
|
|
|
$request->setArgument('numbers', $name); |
317
|
|
|
$removeResult = $this->client->sendSync($request); |
318
|
|
|
if (count($removeResult->getAllOfType(Response::TYPE_ERROR)) > 0) { |
319
|
|
|
throw new RouterErrorException( |
320
|
|
|
'Error when getting script source', |
321
|
|
|
RouterErrorException::CODE_SCRIPT_GET_ERROR, |
322
|
|
|
null, |
323
|
|
|
new ResponseCollection( |
324
|
|
|
array_merge( |
325
|
|
|
$addResult->toArray(), |
326
|
|
|
$runResult->toArray(), |
327
|
|
|
$getResult->toArray(), |
328
|
|
|
$removeResult->toArray() |
329
|
|
|
) |
330
|
|
|
) |
331
|
|
|
); |
332
|
|
|
} |
333
|
|
|
return $postSource; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* Clears the ID cache. |
338
|
|
|
* |
339
|
|
|
* Normally, the ID cache improves performance when targeting items by a |
340
|
|
|
* number. If you're using both Util's methods and other means (e.g. |
341
|
|
|
* {@link Client} or {@link Util::exec()}) to add/move/remove items, the |
342
|
|
|
* cache may end up being out of date. By calling this method right before |
343
|
|
|
* targeting an item with a number, you can ensure number accuracy. |
344
|
|
|
* |
345
|
|
|
* Note that Util's {@link static::move()} and {@link static::remove()} |
346
|
|
|
* methods automatically clear the cache before returning, while |
347
|
|
|
* {@link static::add()} adds the new item's ID to the cache as the next |
348
|
|
|
* number. A change in the menu also clears the cache. |
349
|
|
|
* |
350
|
|
|
* Note also that the cache is being rebuilt unconditionally every time you |
351
|
|
|
* use {@link static::find()} with a callback. |
352
|
|
|
* |
353
|
|
|
* @return $this The Util object itself. |
354
|
|
|
*/ |
355
|
|
|
public function clearIdCache() |
356
|
|
|
{ |
357
|
|
|
$this->idCache = null; |
358
|
|
|
return $this; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Gets the current time on the router. |
363
|
|
|
* |
364
|
|
|
* Gets the current time on the router, regardless of the current menu. |
365
|
|
|
* |
366
|
|
|
* If the timezone is one known to both RouterOS and PHP, it will be used |
367
|
|
|
* as the timezone identifier. Otherwise (e.g. "manual"), the current GMT |
368
|
|
|
* offset will be used as a timezone, without any DST awareness. |
369
|
|
|
* |
370
|
|
|
* @return DateTime The current time of the router, as a DateTime object. |
371
|
|
|
*/ |
372
|
|
|
public function getCurrentTime() |
373
|
|
|
{ |
374
|
|
|
$clock = $this->client->sendSync( |
375
|
|
|
new Request( |
376
|
|
|
'/system/clock/print |
377
|
|
|
.proplist=date,time,time-zone-name,gmt-offset' |
378
|
|
|
) |
379
|
|
|
)->current(); |
380
|
|
|
$clockParts = array(); |
381
|
|
|
foreach (array( |
382
|
|
|
'date', |
383
|
|
|
'time', |
384
|
|
|
'time-zone-name', |
385
|
|
|
'gmt-offset' |
386
|
|
|
) as $clockPart) { |
387
|
|
|
$clockParts[$clockPart] = $clock->getProperty($clockPart); |
388
|
|
|
if (Stream::isStream($clockParts[$clockPart])) { |
389
|
|
|
$clockParts[$clockPart] = stream_get_contents( |
390
|
|
|
$clockParts[$clockPart] |
391
|
|
|
); |
392
|
|
|
} |
393
|
|
|
} |
394
|
|
|
$datetime = ucfirst(strtolower($clockParts['date'])) . ' ' . |
395
|
|
|
$clockParts['time']; |
396
|
|
|
try { |
397
|
|
|
$result = DateTime::createFromFormat( |
398
|
|
|
'M/j/Y H:i:s', |
399
|
|
|
$datetime, |
400
|
|
|
new DateTimeZone($clockParts['time-zone-name']) |
401
|
|
|
); |
402
|
|
|
} catch (E $e) { |
403
|
|
|
$result = DateTime::createFromFormat( |
404
|
|
|
'M/j/Y H:i:s P', |
405
|
|
|
$datetime . ' ' . $clockParts['gmt-offset'], |
406
|
|
|
new DateTimeZone('UTC') |
407
|
|
|
); |
408
|
|
|
} |
409
|
|
|
return $result; |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
/** |
413
|
|
|
* Finds the IDs of items at the current menu. |
414
|
|
|
* |
415
|
|
|
* Finds the IDs of items based on specified criteria, and returns them as |
416
|
|
|
* a comma separated string, ready for insertion at a "numbers" argument. |
417
|
|
|
* |
418
|
|
|
* Accepts zero or more criteria as arguments. If zero arguments are |
419
|
|
|
* specified, returns all items' IDs. The value of each criteria can be a |
420
|
|
|
* number (just as in Winbox), a literal ID to be included, a {@link Query} |
421
|
|
|
* object, or a callback. If a callback is specified, it is called for each |
422
|
|
|
* item, with the item as an argument. If it returns a true value, the |
423
|
|
|
* item's ID is included in the result. Every other value is casted to a |
424
|
|
|
* string. A string is treated as a comma separated values of IDs, numbers |
425
|
|
|
* or callback names. Non-existent callback names are instead placed in the |
426
|
|
|
* result, which may be useful in menus that accept identifiers other than |
427
|
|
|
* IDs, but note that it can cause errors on other menus. |
428
|
|
|
* |
429
|
|
|
* @return string A comma separated list of all items matching the |
430
|
|
|
* specified criteria. |
431
|
|
|
*/ |
432
|
|
|
public function find() |
433
|
|
|
{ |
434
|
|
|
if (func_num_args() === 0) { |
435
|
|
|
if (null === $this->idCache) { |
436
|
|
|
$ret = $this->client->sendSync( |
437
|
|
|
new Request($this->menu . '/find') |
438
|
|
|
)->getProperty('ret'); |
439
|
|
|
if (null === $ret) { |
440
|
|
|
$this->idCache = array(); |
441
|
|
|
return ''; |
442
|
|
|
} elseif (!is_string($ret)) { |
443
|
|
|
$ret = stream_get_contents($ret); |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
$idCache = str_replace( |
447
|
|
|
';', |
448
|
|
|
',', |
449
|
|
|
strtolower($ret) |
450
|
|
|
); |
451
|
|
|
$this->idCache = explode(',', $idCache); |
452
|
|
|
return $idCache; |
453
|
|
|
} |
454
|
|
|
return implode(',', $this->idCache); |
455
|
|
|
} |
456
|
|
|
$idList = ''; |
457
|
|
|
foreach (func_get_args() as $criteria) { |
458
|
|
|
if ($criteria instanceof Query) { |
459
|
|
|
foreach ($this->client->sendSync( |
460
|
|
|
new Request($this->menu . '/print .proplist=.id', $criteria) |
461
|
|
|
)->getAllOfType(Response::TYPE_DATA) as $response) { |
462
|
|
|
$newId = $response->getProperty('.id'); |
463
|
|
|
$idList .= strtolower( |
464
|
|
|
is_string($newId) |
465
|
|
|
? $newId |
466
|
|
|
: stream_get_contents($newId) . ',' |
467
|
|
|
); |
468
|
|
|
} |
469
|
|
|
} elseif (is_callable($criteria)) { |
470
|
|
|
$idCache = array(); |
471
|
|
|
foreach ($this->client->sendSync( |
472
|
|
|
new Request($this->menu . '/print') |
473
|
|
|
)->getAllOfType(Response::TYPE_DATA) as $response) { |
474
|
|
|
$newId = $response->getProperty('.id'); |
475
|
|
|
$newId = strtolower( |
476
|
|
|
is_string($newId) |
477
|
|
|
? $newId |
478
|
|
|
: stream_get_contents($newId) |
479
|
|
|
); |
480
|
|
|
if ($criteria($response)) { |
481
|
|
|
$idList .= $newId . ','; |
482
|
|
|
} |
483
|
|
|
$idCache[] = $newId; |
484
|
|
|
} |
485
|
|
|
$this->idCache = $idCache; |
486
|
|
|
} else { |
487
|
|
|
$this->find(); |
488
|
|
|
if (is_int($criteria)) { |
489
|
|
|
if (isset($this->idCache[$criteria])) { |
490
|
|
|
$idList = $this->idCache[$criteria] . ','; |
491
|
|
|
} |
492
|
|
|
} else { |
493
|
|
|
$criteria = (string)$criteria; |
494
|
|
|
if ($criteria === (string)(int)$criteria) { |
495
|
|
|
if (isset($this->idCache[(int)$criteria])) { |
496
|
|
|
$idList .= $this->idCache[(int)$criteria] . ','; |
497
|
|
|
} |
498
|
|
|
} elseif (false === strpos($criteria, ',')) { |
499
|
|
|
$idList .= $criteria . ','; |
500
|
|
|
} else { |
501
|
|
|
$criteriaArr = explode(',', $criteria); |
502
|
|
|
for ($i = count($criteriaArr) - 1; $i >= 0; --$i) { |
503
|
|
|
if ('' === $criteriaArr[$i]) { |
504
|
|
|
unset($criteriaArr[$i]); |
505
|
|
|
} elseif ('*' === $criteriaArr[$i][0]) { |
506
|
|
|
$idList .= $criteriaArr[$i] . ','; |
507
|
|
|
unset($criteriaArr[$i]); |
508
|
|
|
} |
509
|
|
|
} |
510
|
|
|
if (!empty($criteriaArr)) { |
511
|
|
|
$idList .= call_user_func_array( |
512
|
|
|
array($this, 'find'), |
513
|
|
|
$criteriaArr |
514
|
|
|
) . ','; |
515
|
|
|
} |
516
|
|
|
} |
517
|
|
|
} |
518
|
|
|
} |
519
|
|
|
} |
520
|
|
|
return rtrim($idList, ','); |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
/** |
524
|
|
|
* Gets a value of a specified item at the current menu. |
525
|
|
|
* |
526
|
|
|
* @param int|string|null|Query $number A number identifying the target |
527
|
|
|
* item. Can also be an ID or (in some menus) name. For menus where |
528
|
|
|
* there are no items (e.g. "/system identity"), you can specify NULL. |
529
|
|
|
* You can also specify a query, in which case the first match will be |
530
|
|
|
* considered the target item. |
531
|
|
|
* @param string|null $valueName The name of the value to get. |
532
|
|
|
* If omitted, or set to NULL, gets all properties of the target item. |
533
|
|
|
* Note that for versions that don't support omitting $valueName |
534
|
|
|
* natively, a "print" with "detail" argument is used as a fallback, |
535
|
|
|
* which may not contain certain properties. |
536
|
|
|
* |
537
|
|
|
* @return string|resource|null|array The value of the specified |
538
|
|
|
* property as a string or as new PHP temp stream if the underlying |
539
|
|
|
* {@link Client::isStreamingResponses()} is set to TRUE. |
540
|
|
|
* If the property is not set, NULL will be returned. |
541
|
|
|
* If $valueName is NULL, returns all properties as an array, where |
542
|
|
|
* the result is parsed with {@link Script::parseValueToArray()}. |
543
|
|
|
* |
544
|
|
|
* @throws RouterErrorException When the router returns an error response |
545
|
|
|
* (e.g. no such item, invalid property, etc.). |
546
|
|
|
*/ |
547
|
|
|
public function get($number, $valueName = null) |
548
|
|
|
{ |
549
|
|
|
if (is_int($number) || ((string)$number === (string)(int)$number)) { |
550
|
|
|
$this->find(); |
551
|
|
|
if (isset($this->idCache[(int)$number])) { |
552
|
|
|
$number = $this->idCache[(int)$number]; |
553
|
|
|
} else { |
554
|
|
|
throw new RouterErrorException( |
555
|
|
|
'Unable to resolve number from ID cache (no such item maybe)', |
556
|
|
|
RouterErrorException::CODE_CACHE_ERROR |
557
|
|
|
); |
558
|
|
|
} |
559
|
|
|
} elseif ($number instanceof Query) { |
560
|
|
|
$number = explode(',', $this->find($number)); |
561
|
|
|
$number = $number[0]; |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
$request = new Request($this->menu . '/get'); |
565
|
|
|
$request->setArgument('number', $number); |
566
|
|
|
$request->setArgument('value-name', $valueName); |
567
|
|
|
$responses = $this->client->sendSync($request); |
568
|
|
|
if (Response::TYPE_ERROR === $responses->getType()) { |
569
|
|
|
throw new RouterErrorException( |
570
|
|
|
'Error getting property', |
571
|
|
|
RouterErrorException::CODE_GET_ERROR, |
572
|
|
|
null, |
573
|
|
|
$responses |
574
|
|
|
); |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
$result = $responses->getProperty('ret'); |
578
|
|
|
if (Stream::isStream($result)) { |
579
|
|
|
$result = stream_get_contents($result); |
580
|
|
|
} |
581
|
|
|
if (null === $valueName) { |
582
|
|
|
//Some earlier RouterOS versions use "," instead of ";" as separator |
583
|
|
|
if (false === strpos($result, ';') |
584
|
|
|
&& 1 === preg_match('/^([^=,]+\=[^=,]*)(?:\,(?1))+$/', $result) |
585
|
|
|
) { |
586
|
|
|
$result = str_replace(',', ';', $result); |
587
|
|
|
} |
588
|
|
|
return Script::parseValueToArray('{' . $result . '}'); |
589
|
|
|
} |
590
|
|
|
return $result; |
591
|
|
|
} |
592
|
|
|
|
593
|
|
|
/** |
594
|
|
|
* Enables all items at the current menu matching certain criteria. |
595
|
|
|
* |
596
|
|
|
* Zero or more arguments can be specified, each being a criteria. |
597
|
|
|
* If zero arguments are specified, enables all items. |
598
|
|
|
* See {@link static::find()} for a description of what criteria are |
599
|
|
|
* accepted. |
600
|
|
|
* |
601
|
|
|
* @return ResponseCollection returns the response collection, allowing you |
602
|
|
|
* to inspect errors, if any. |
603
|
|
|
*/ |
604
|
|
|
public function enable() |
605
|
|
|
{ |
606
|
|
|
return $this->doBulk('enable', func_get_args()); |
607
|
|
|
} |
608
|
|
|
|
609
|
|
|
/** |
610
|
|
|
* Disables all items at the current menu matching certain criteria. |
611
|
|
|
* |
612
|
|
|
* Zero or more arguments can be specified, each being a criteria. |
613
|
|
|
* If zero arguments are specified, disables all items. |
614
|
|
|
* See {@link static::find()} for a description of what criteria are |
615
|
|
|
* accepted. |
616
|
|
|
* |
617
|
|
|
* @return ResponseCollection Returns the response collection, allowing you |
618
|
|
|
* to inspect errors, if any. |
619
|
|
|
*/ |
620
|
|
|
public function disable() |
621
|
|
|
{ |
622
|
|
|
return $this->doBulk('disable', func_get_args()); |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
/** |
626
|
|
|
* Removes all items at the current menu matching certain criteria. |
627
|
|
|
* |
628
|
|
|
* Zero or more arguments can be specified, each being a criteria. |
629
|
|
|
* If zero arguments are specified, removes all items. |
630
|
|
|
* See {@link static::find()} for a description of what criteria are |
631
|
|
|
* accepted. |
632
|
|
|
* |
633
|
|
|
* @return ResponseCollection Returns the response collection, allowing you |
634
|
|
|
* to inspect errors, if any. |
635
|
|
|
*/ |
636
|
|
|
public function remove() |
637
|
|
|
{ |
638
|
|
|
$result = $this->doBulk('remove', func_get_args()); |
639
|
|
|
$this->clearIdCache(); |
640
|
|
|
return $result; |
641
|
|
|
} |
642
|
|
|
|
643
|
|
|
/** |
644
|
|
|
* Comments items. |
645
|
|
|
* |
646
|
|
|
* Sets new comments on all items at the current menu |
647
|
|
|
* which match certain criteria, using the "comment" command. |
648
|
|
|
* |
649
|
|
|
* Note that not all menus have a "comment" command. Most notably, those are |
650
|
|
|
* menus without items in them (e.g. "/system identity"), and menus with |
651
|
|
|
* fixed items (e.g. "/ip service"). |
652
|
|
|
* |
653
|
|
|
* @param mixed $numbers Targeted items. Can be any criteria |
654
|
|
|
* accepted by {@link static::find()}. |
655
|
|
|
* @param string|resource $comment The new comment to set on the item as a |
656
|
|
|
* string or a seekable stream. |
657
|
|
|
* If a seekable stream is provided, it is sent from its current |
658
|
|
|
* position to its end, and the pointer is seeked back to its current |
659
|
|
|
* position after sending. |
660
|
|
|
* Non seekable streams, as well as all other types, are casted to a |
661
|
|
|
* string. |
662
|
|
|
* |
663
|
|
|
* @return ResponseCollection Returns the response collection, allowing you |
664
|
|
|
* to inspect errors, if any. |
665
|
|
|
*/ |
666
|
|
|
public function comment($numbers, $comment) |
667
|
|
|
{ |
668
|
|
|
$commentRequest = new Request($this->menu . '/comment'); |
669
|
|
|
$commentRequest->setArgument('comment', $comment); |
670
|
|
|
$commentRequest->setArgument('numbers', $this->find($numbers)); |
671
|
|
|
return $this->client->sendSync($commentRequest); |
672
|
|
|
} |
673
|
|
|
|
674
|
|
|
/** |
675
|
|
|
* Sets new values. |
676
|
|
|
* |
677
|
|
|
* Sets new values on certain properties on all items at the current menu |
678
|
|
|
* which match certain criteria. |
679
|
|
|
* |
680
|
|
|
* @param mixed $numbers Items |
681
|
|
|
* to be modified. |
682
|
|
|
* Can be any criteria accepted by {@link static::find()} or NULL |
683
|
|
|
* in case the menu is one without items (e.g. "/system identity"). |
684
|
|
|
* @param array<string,string|resource>|array<int,string> $newValues An |
685
|
|
|
* array with the names of each property to set as an array key, and the |
686
|
|
|
* new value as an array value. |
687
|
|
|
* Flags (properties with a value "true" that is interpreted as |
688
|
|
|
* equivalent of "yes" from CLI) can also be specified with a numeric |
689
|
|
|
* index as the array key, and the name of the flag as the array value. |
690
|
|
|
* |
691
|
|
|
* @return ResponseCollection Returns the response collection, allowing you |
692
|
|
|
* to inspect errors, if any. |
693
|
|
|
*/ |
694
|
|
|
public function set($numbers, array $newValues) |
695
|
|
|
{ |
696
|
|
|
$setRequest = new Request($this->menu . '/set'); |
697
|
|
|
foreach ($newValues as $name => $value) { |
698
|
|
|
if (is_int($name)) { |
699
|
|
|
$setRequest->setArgument($value, 'true'); |
700
|
|
|
} else { |
701
|
|
|
$setRequest->setArgument($name, $value); |
702
|
|
|
} |
703
|
|
|
} |
704
|
|
|
if (null !== $numbers) { |
705
|
|
|
$setRequest->setArgument('numbers', $this->find($numbers)); |
706
|
|
|
} |
707
|
|
|
return $this->client->sendSync($setRequest); |
708
|
|
|
} |
709
|
|
|
|
710
|
|
|
/** |
711
|
|
|
* Alias of {@link static::set()} |
712
|
|
|
* |
713
|
|
|
* @param mixed $numbers Items to be modified. |
714
|
|
|
* Can be any criteria accepted by {@link static::find()} or NULL |
715
|
|
|
* in case the menu is one without items (e.g. "/system identity"). |
716
|
|
|
* @param string $valueName Name of property to be modified. |
717
|
|
|
* @param string|resource|null $newValue The new value to set. |
718
|
|
|
* If set to NULL, the property is unset. |
719
|
|
|
* |
720
|
|
|
* @return ResponseCollection Returns the response collection, allowing you |
721
|
|
|
* to inspect errors, if any. |
722
|
|
|
*/ |
723
|
|
|
public function edit($numbers, $valueName, $newValue) |
724
|
|
|
{ |
725
|
|
|
return null === $newValue |
726
|
|
|
? $this->unsetValue($numbers, $valueName) |
727
|
|
|
: $this->set($numbers, array($valueName => $newValue)); |
728
|
|
|
} |
729
|
|
|
|
730
|
|
|
/** |
731
|
|
|
* Unsets a value of a specified item at the current menu. |
732
|
|
|
* |
733
|
|
|
* Equivalent of scripting's "unset" command. The "Value" part in the method |
734
|
|
|
* name is added because "unset" is a language construct, and thus a |
735
|
|
|
* reserved word. |
736
|
|
|
* |
737
|
|
|
* @param mixed $numbers Targeted items. Can be any criteria accepted |
738
|
|
|
* by {@link static::find()}. |
739
|
|
|
* @param string $valueName The name of the value you want to unset. |
740
|
|
|
* |
741
|
|
|
* @return ResponseCollection Returns the response collection, allowing you |
742
|
|
|
* to inspect errors, if any. |
743
|
|
|
*/ |
744
|
|
|
public function unsetValue($numbers, $valueName) |
745
|
|
|
{ |
746
|
|
|
$unsetRequest = new Request($this->menu . '/unset'); |
747
|
|
|
return $this->client->sendSync( |
748
|
|
|
$unsetRequest->setArgument('numbers', $this->find($numbers)) |
749
|
|
|
->setArgument('value-name', $valueName) |
750
|
|
|
); |
751
|
|
|
} |
752
|
|
|
|
753
|
|
|
/** |
754
|
|
|
* Adds a new item at the current menu. |
755
|
|
|
* |
756
|
|
|
* @param array<string,string|resource>|array<int,string> $values Accepts |
757
|
|
|
* one or more items to add to the current menu. |
758
|
|
|
* The data about each item is specified as an array with the names of |
759
|
|
|
* each property as an array key, and the value as an array value. |
760
|
|
|
* Flags (properties with a value "true" that is interpreted as |
761
|
|
|
* equivalent of "yes" from CLI) can also be specified with a numeric |
762
|
|
|
* index as the array key, and the name of the flag as the array value. |
763
|
|
|
* @param array<string,string|resource>|array<int,string> $... Additional |
764
|
|
|
* items. |
765
|
|
|
* |
766
|
|
|
* @return string A comma separated list of the new items' IDs. |
767
|
|
|
* |
768
|
|
|
* @throws RouterErrorException When one or more items were not succesfully |
769
|
|
|
* added. Note that the response collection will include all replies of |
770
|
|
|
* all add commands, including the successful ones, in order. |
771
|
|
|
*/ |
772
|
|
|
public function add(array $values) |
773
|
|
|
{ |
774
|
|
|
$addRequest = new Request($this->menu . '/add'); |
775
|
|
|
$hasErrors = false; |
776
|
|
|
$results = array(); |
777
|
|
|
foreach (func_get_args() as $values) { |
778
|
|
|
if (!is_array($values)) { |
779
|
|
|
continue; |
780
|
|
|
} |
781
|
|
|
foreach ($values as $name => $value) { |
782
|
|
|
if (is_int($name)) { |
783
|
|
|
$addRequest->setArgument($value, 'true'); |
784
|
|
|
} else { |
785
|
|
|
$addRequest->setArgument($name, $value); |
786
|
|
|
} |
787
|
|
|
} |
788
|
|
|
$result = $this->client->sendSync($addRequest); |
789
|
|
|
if (count($result->getAllOfType(Response::TYPE_ERROR)) > 0) { |
790
|
|
|
$hasErrors = true; |
791
|
|
|
} |
792
|
|
|
$results = array_merge($results, $result->toArray()); |
793
|
|
|
$addRequest->removeAllArguments(); |
794
|
|
|
} |
795
|
|
|
|
796
|
|
|
$this->clearIdCache(); |
797
|
|
|
if ($hasErrors) { |
798
|
|
|
throw new RouterErrorException( |
799
|
|
|
'Router returned error when adding items', |
800
|
|
|
RouterErrorException::CODE_ADD_ERROR, |
801
|
|
|
null, |
802
|
|
|
new ResponseCollection($results) |
803
|
|
|
); |
804
|
|
|
} |
805
|
|
|
$results = new ResponseCollection($results); |
806
|
|
|
$idList = ''; |
807
|
|
|
foreach ($results->getAllOfType(Response::TYPE_FINAL) as $final) { |
808
|
|
|
$idList .= ',' . strtolower($final->getProperty('ret')); |
809
|
|
|
} |
810
|
|
|
return substr($idList, 1); |
811
|
|
|
} |
812
|
|
|
|
813
|
|
|
/** |
814
|
|
|
* Moves items at the current menu before a certain other item. |
815
|
|
|
* |
816
|
|
|
* Moves items before a certain other item. Note that the "move" |
817
|
|
|
* command is not available on all menus. As a rule of thumb, if the order |
818
|
|
|
* of items in a menu is irrelevant to their interpretation, there won't |
819
|
|
|
* be a move command on that menu. If in doubt, check from a terminal. |
820
|
|
|
* |
821
|
|
|
* @param mixed $numbers Targeted items. Can be any criteria accepted |
822
|
|
|
* by {@link static::find()}. |
823
|
|
|
* @param mixed $destination Item before which the targeted items will be |
824
|
|
|
* moved to. Can be any criteria accepted by {@link static::find()}. |
825
|
|
|
* If multiple items match the criteria, the targeted items will move |
826
|
|
|
* above the first match. |
827
|
|
|
* If NULL is given (or this argument is omitted), the targeted items |
828
|
|
|
* will be moved to the bottom of the menu. |
829
|
|
|
* |
830
|
|
|
* @return ResponseCollection Returns the response collection, allowing you |
831
|
|
|
* to inspect errors, if any. |
832
|
|
|
*/ |
833
|
|
|
public function move($numbers, $destination = null) |
834
|
|
|
{ |
835
|
|
|
$moveRequest = new Request($this->menu . '/move'); |
836
|
|
|
$moveRequest->setArgument('numbers', $this->find($numbers)); |
837
|
|
|
if (null !== $destination) { |
838
|
|
|
$destination = $this->find($destination); |
839
|
|
|
if (false !== strpos($destination, ',')) { |
840
|
|
|
$destination = strstr($destination, ',', true); |
841
|
|
|
} |
842
|
|
|
$moveRequest->setArgument('destination', $destination); |
843
|
|
|
} |
844
|
|
|
$this->clearIdCache(); |
845
|
|
|
return $this->client->sendSync($moveRequest); |
846
|
|
|
} |
847
|
|
|
|
848
|
|
|
/** |
849
|
|
|
* Counts items at the current menu. |
850
|
|
|
* |
851
|
|
|
* Counts items at the current menu. This executes a dedicated command |
852
|
|
|
* ("print" with a "count-only" argument) on RouterOS, which is why only |
853
|
|
|
* queries are allowed as a criteria, in contrast with |
854
|
|
|
* {@link static::find()}, where numbers and callbacks are allowed also. |
855
|
|
|
* |
856
|
|
|
* @param Query|null $query A query to filter items by. Without it, all items |
857
|
|
|
* are included in the count. |
858
|
|
|
* |
859
|
|
|
* @return int The number of items, or -1 on failure (e.g. if the |
860
|
|
|
* current menu does not have a "print" command or items to be counted). |
861
|
|
|
*/ |
862
|
|
|
public function count(Query $query = null) |
863
|
|
|
{ |
864
|
|
|
$result = $this->client->sendSync( |
865
|
|
|
new Request($this->menu . '/print count-only=""', $query) |
866
|
|
|
)->end()->getProperty('ret'); |
867
|
|
|
|
868
|
|
|
if (null === $result) { |
869
|
|
|
return -1; |
870
|
|
|
} |
871
|
|
|
if (Stream::isStream($result)) { |
872
|
|
|
$result = stream_get_contents($result); |
873
|
|
|
} |
874
|
|
|
return (int)$result; |
875
|
|
|
} |
876
|
|
|
|
877
|
|
|
/** |
878
|
|
|
* Gets all items in the current menu. |
879
|
|
|
* |
880
|
|
|
* Gets all items in the current menu, using a print request. |
881
|
|
|
* |
882
|
|
|
* @param array<string,string|resource>|array<int,string> $args Additional |
883
|
|
|
* arguments to pass to the request. |
884
|
|
|
* Each array key is the name of the argument, and each array value is |
885
|
|
|
* the value of the argument to be passed. |
886
|
|
|
* Arguments without a value (i.e. empty arguments) can also be |
887
|
|
|
* specified using a numeric key, and the name of the argument as the |
888
|
|
|
* array value. |
889
|
|
|
* The "follow" and "follow-only" arguments are prohibited, |
890
|
|
|
* as they would cause a synchronous request to run forever, without |
891
|
|
|
* allowing the results to be observed. |
892
|
|
|
* If you need to use those arguments, use {@link static::newRequest()}, |
893
|
|
|
* and pass the resulting {@link Request} to {@link Client::sendAsync()}. |
894
|
|
|
* The "count-only" argument is also prohibited, as results from it |
895
|
|
|
* would not be consumable. Use {@link static::count()} for that. |
896
|
|
|
* @param Query|null $query A query to |
897
|
|
|
* filter items by. |
898
|
|
|
* NULL to get all items. |
899
|
|
|
* |
900
|
|
|
* @return ResponseCollection A response collection with all |
901
|
|
|
* {@link Response::TYPE_DATA} responses. The collection will be empty |
902
|
|
|
* when there are no matching items. |
903
|
|
|
* |
904
|
|
|
* @throws NotSupportedException If $args contains prohibited arguments |
905
|
|
|
* ("follow", "follow-only" or "count-only"). |
906
|
|
|
* |
907
|
|
|
* @throws RouterErrorException When there's an error upon attempting to |
908
|
|
|
* call the "print" command on the specified menu (e.g. if there's no |
909
|
|
|
* "print" command at the menu to begin with). |
910
|
|
|
*/ |
911
|
|
|
public function getAll(array $args = array(), Query $query = null) |
912
|
|
|
{ |
913
|
|
|
$printRequest = new Request($this->menu . '/print', $query); |
914
|
|
|
foreach ($args as $name => $value) { |
915
|
|
|
if (is_int($name)) { |
916
|
|
|
$printRequest->setArgument($value); |
917
|
|
|
} else { |
918
|
|
|
$printRequest->setArgument($name, $value); |
919
|
|
|
} |
920
|
|
|
} |
921
|
|
|
|
922
|
|
|
foreach (array('follow', 'follow-only', 'count-only') as $arg) { |
923
|
|
|
if ($printRequest->getArgument($arg) !== null) { |
924
|
|
|
throw new NotSupportedException( |
925
|
|
|
"The argument '{$arg}' was specified, but is prohibited", |
926
|
|
|
NotSupportedException::CODE_ARG_PROHIBITED, |
927
|
|
|
null, |
928
|
|
|
$arg |
929
|
|
|
); |
930
|
|
|
} |
931
|
|
|
} |
932
|
|
|
$responses = $this->client->sendSync($printRequest); |
933
|
|
|
|
934
|
|
|
if (count($responses->getAllOfType(Response::TYPE_ERROR)) > 0) { |
935
|
|
|
throw new RouterErrorException( |
936
|
|
|
'Error when reading items', |
937
|
|
|
RouterErrorException::CODE_GETALL_ERROR, |
938
|
|
|
null, |
939
|
|
|
$responses |
940
|
|
|
); |
941
|
|
|
} |
942
|
|
|
return $responses->getAllOfType(Response::TYPE_DATA); |
943
|
|
|
} |
944
|
|
|
|
945
|
|
|
/** |
946
|
|
|
* Puts a file on RouterOS's file system. |
947
|
|
|
* |
948
|
|
|
* Puts a file on RouterOS's file system, regardless of the current menu. |
949
|
|
|
* Note that this is a **VERY VERY VERY** time consuming method - it takes a |
950
|
|
|
* minimum of a little over 4 seconds, most of which are in sleep. It waits |
951
|
|
|
* 2 seconds after a file is first created (required to actually start |
952
|
|
|
* writing to the file), and another 2 seconds after its contents is written |
953
|
|
|
* (performed in order to verify success afterwards). |
954
|
|
|
* Similarly for removal (when $data is NULL) - there are two seconds in |
955
|
|
|
* sleep, used to verify the file was really deleted. |
956
|
|
|
* |
957
|
|
|
* If you want an efficient way of transferring files, use (T)FTP. |
958
|
|
|
* If you want an efficient way of removing files, use |
959
|
|
|
* {@link static::setMenu()} to move to the "/file" menu, and call |
960
|
|
|
* {@link static::remove()} without performing verification afterwards. |
961
|
|
|
* |
962
|
|
|
* @param string $filename The filename to write data in. |
963
|
|
|
* @param string|resource|null $data The data the file is going to have |
964
|
|
|
* as a string or a seekable stream. |
965
|
|
|
* Setting the value to NULL removes a file of this name. |
966
|
|
|
* If a seekable stream is provided, it is sent from its current |
967
|
|
|
* position to its end, and the pointer is seeked back to its current |
968
|
|
|
* position after sending. |
969
|
|
|
* Non seekable streams, as well as all other types, are casted to a |
970
|
|
|
* string. |
971
|
|
|
* @param bool $overwrite Whether to overwrite the file if |
972
|
|
|
* it exists. |
973
|
|
|
* |
974
|
|
|
* @return bool TRUE on success, FALSE on failure. |
975
|
|
|
*/ |
976
|
|
|
public function filePutContents($filename, $data, $overwrite = false) |
977
|
|
|
{ |
978
|
|
|
$printRequest = new Request( |
979
|
|
|
'/file/print .proplist=""', |
980
|
|
|
Query::where('name', $filename) |
981
|
|
|
); |
982
|
|
|
$fileExists = count($this->client->sendSync($printRequest)) > 1; |
983
|
|
|
|
984
|
|
|
if (null === $data) { |
985
|
|
|
if (!$fileExists) { |
986
|
|
|
return false; |
987
|
|
|
} |
988
|
|
|
$removeRequest = new Request('/file/remove'); |
989
|
|
|
$this->client->sendSync( |
990
|
|
|
$removeRequest->setArgument('numbers', $filename) |
991
|
|
|
); |
992
|
|
|
//Required for RouterOS to REALLY remove the file. |
993
|
|
|
sleep(2); |
994
|
|
|
return !(count($this->client->sendSync($printRequest)) > 1); |
995
|
|
|
} |
996
|
|
|
|
997
|
|
|
if (!$overwrite && $fileExists) { |
998
|
|
|
return false; |
999
|
|
|
} |
1000
|
|
|
$result = $this->client->sendSync( |
1001
|
|
|
$printRequest->setArgument('file', $filename) |
1002
|
|
|
); |
1003
|
|
|
if (count($result->getAllOfType(Response::TYPE_ERROR)) > 0) { |
1004
|
|
|
return false; |
1005
|
|
|
} |
1006
|
|
|
//Required for RouterOS to write the initial file. |
1007
|
|
|
sleep(2); |
1008
|
|
|
$setRequest = new Request('/file/set contents=""'); |
1009
|
|
|
$setRequest->setArgument('numbers', $filename); |
1010
|
|
|
$this->client->sendSync($setRequest); |
1011
|
|
|
$this->client->sendSync($setRequest->setArgument('contents', $data)); |
1012
|
|
|
//Required for RouterOS to write the file's new contents. |
1013
|
|
|
sleep(2); |
1014
|
|
|
|
1015
|
|
|
$fileSize = $this->client->sendSync( |
1016
|
|
|
$printRequest->setArgument('file', null) |
1017
|
|
|
->setArgument('.proplist', 'size') |
1018
|
|
|
)->getProperty('size'); |
1019
|
|
|
if (Stream::isStream($fileSize)) { |
1020
|
|
|
$fileSize = stream_get_contents($fileSize); |
1021
|
|
|
} |
1022
|
|
|
if (Communicator::isSeekableStream($data)) { |
1023
|
|
|
return Communicator::seekableStreamLength($data) == $fileSize; |
|
|
|
|
1024
|
|
|
} else { |
1025
|
|
|
return sprintf('%u', strlen((string)$data)) === $fileSize; |
1026
|
|
|
} |
1027
|
|
|
} |
1028
|
|
|
|
1029
|
|
|
/** |
1030
|
|
|
* Gets the contents of a specified file. |
1031
|
|
|
* |
1032
|
|
|
* @param string $filename The name of the file to get |
1033
|
|
|
* the contents of. |
1034
|
|
|
* @param string|null $tmpScriptName In order to get the file's contents, a |
1035
|
|
|
* script is created at "/system script", the source of which is then |
1036
|
|
|
* overwritten with the file's contents, then retrieved from there, |
1037
|
|
|
* after which the script is removed. |
1038
|
|
|
* If this argument is left NULL, a random string, |
1039
|
|
|
* prefixed with the computer's name, is generated and used |
1040
|
|
|
* as the script's name. |
1041
|
|
|
* To eliminate any possibility of name clashes, |
1042
|
|
|
* you can specify your own name instead. |
1043
|
|
|
* |
1044
|
|
|
* @return string|resource|false The contents of the file as a string or as |
1045
|
|
|
* new PHP temp stream if the underlying |
1046
|
|
|
* {@link Client::isStreamingResponses()} is set to TRUE. |
1047
|
|
|
* FALSE is returned if there is no such file. |
1048
|
|
|
* |
1049
|
|
|
* @throws RouterErrorException When there's an error with the temporary |
1050
|
|
|
* script used to get the file. |
1051
|
|
|
*/ |
1052
|
|
|
public function fileGetContents($filename, $tmpScriptName = null) |
1053
|
|
|
{ |
1054
|
|
|
$checkRequest = new Request( |
1055
|
|
|
'/file/print .proplist=""', |
1056
|
|
|
Query::where('name', $filename) |
1057
|
|
|
); |
1058
|
|
|
if (1 === count($this->client->sendSync($checkRequest))) { |
1059
|
|
|
return false; |
1060
|
|
|
} |
1061
|
|
|
return $this->exec( |
1062
|
|
|
'/system script set $"_" source=[/file get $filename contents]', |
1063
|
|
|
array('filename' => $filename), |
1064
|
|
|
null, |
1065
|
|
|
$tmpScriptName |
1066
|
|
|
); |
1067
|
|
|
} |
1068
|
|
|
|
1069
|
|
|
/** |
1070
|
|
|
* Performs an action on a bulk of items at the current menu. |
1071
|
|
|
* |
1072
|
|
|
* @param string $what What action to perform. |
1073
|
|
|
* @param array $args Zero or more arguments can be specified, each being |
1074
|
|
|
* a criteria. If zero arguments are specified, removes all items. |
1075
|
|
|
* See {@link static::find()} for a description of what criteria are |
1076
|
|
|
* accepted. |
1077
|
|
|
* |
1078
|
|
|
* @return ResponseCollection Returns the response collection, allowing you |
1079
|
|
|
* to inspect errors, if any. |
1080
|
|
|
*/ |
1081
|
|
|
protected function doBulk($what, array $args = array()) |
1082
|
|
|
{ |
1083
|
|
|
$bulkRequest = new Request($this->menu . '/' . $what); |
1084
|
|
|
$bulkRequest->setArgument( |
1085
|
|
|
'numbers', |
1086
|
|
|
call_user_func_array(array($this, 'find'), $args) |
1087
|
|
|
); |
1088
|
|
|
return $this->client->sendSync($bulkRequest); |
1089
|
|
|
} |
1090
|
|
|
} |
1091
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.