| Total Complexity | 102 |
| Total Lines | 1153 |
| Duplicated Lines | 0 % |
| Changes | 18 | ||
| Bugs | 9 | Features | 1 |
Complex classes like Util often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Util, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 60 | class Util implements Countable |
||
| 61 | { |
||
| 62 | /** |
||
| 63 | * The connection to wrap around. |
||
| 64 | * |
||
| 65 | * @var Client |
||
| 66 | */ |
||
| 67 | protected $client; |
||
| 68 | |||
| 69 | /** |
||
| 70 | * The current menu. |
||
| 71 | * |
||
| 72 | * Note that the root menu (only) uses an empty string. |
||
| 73 | * This is done to enable commands executed at it without special casing it |
||
| 74 | * at all commands. |
||
| 75 | * Instead, only {@link static::setMenu()} is special cased. |
||
| 76 | * |
||
| 77 | * @var string |
||
| 78 | */ |
||
| 79 | protected $menu = ''; |
||
| 80 | |||
| 81 | /** |
||
| 82 | * An array with the numbers of items in the current menu. |
||
| 83 | * |
||
| 84 | * Numbers as keys, and the corresponding IDs as values. |
||
| 85 | * NULL when the cache needs regenerating. |
||
| 86 | * |
||
| 87 | * @var array<int,string>|null |
||
| 88 | */ |
||
| 89 | protected $idCache = null; |
||
| 90 | |||
| 91 | /** |
||
| 92 | * Creates a new Util instance. |
||
| 93 | * |
||
| 94 | * Wraps around a connection to provide convenience methods. |
||
| 95 | * |
||
| 96 | * @param Client $client The connection to wrap around. |
||
| 97 | */ |
||
| 98 | public function __construct(Client $client) |
||
| 99 | { |
||
| 100 | $this->client = $client; |
||
| 101 | } |
||
| 102 | |||
| 103 | /** |
||
| 104 | * Gets the current menu. |
||
| 105 | * |
||
| 106 | * @return string The absolute path to current menu, using API syntax. |
||
| 107 | */ |
||
| 108 | public function getMenu() |
||
| 111 | } |
||
| 112 | |||
| 113 | /** |
||
| 114 | * Sets the current menu. |
||
| 115 | * |
||
| 116 | * Sets the current menu. |
||
| 117 | * |
||
| 118 | * @param string $newMenu The menu to change to. Can be specified with API |
||
| 119 | * or CLI syntax and can be either absolute or relative. If relative, |
||
| 120 | * it's relative to the current menu, which by default is the root. |
||
| 121 | * |
||
| 122 | * @return $this The object itself. If an empty string is given for |
||
| 123 | * a new menu, no change is performed, |
||
| 124 | * but the ID cache is cleared anyway. |
||
| 125 | * |
||
| 126 | * @see static::clearIdCache() |
||
| 127 | */ |
||
| 128 | public function setMenu($newMenu) |
||
| 129 | { |
||
| 130 | $newMenu = (string)$newMenu; |
||
| 131 | if ('' !== $newMenu) { |
||
| 132 | $menuRequest = new Request('/menu'); |
||
| 133 | if ('/' === $newMenu) { |
||
| 134 | $this->menu = ''; |
||
| 135 | } elseif (strpos($newMenu, '/') === 0) { |
||
| 136 | $this->menu = $menuRequest->setCommand($newMenu)->getCommand(); |
||
| 137 | } else { |
||
| 138 | $newMenu = (string)substr( |
||
| 139 | $menuRequest->setCommand( |
||
| 140 | '/' . |
||
| 141 | str_replace('/', ' ', (string)substr($this->menu, 1)) . |
||
| 142 | ' ' . |
||
| 143 | str_replace('/', ' ', $newMenu) |
||
| 144 | . ' ?' |
||
| 145 | )->getCommand(), |
||
| 146 | 1, |
||
| 147 | -2/*strlen('/?')*/ |
||
| 148 | ); |
||
| 149 | if ('' !== $newMenu) { |
||
| 150 | $this->menu = '/' . $newMenu; |
||
| 151 | } else { |
||
| 152 | $this->menu = ''; |
||
| 153 | } |
||
| 154 | } |
||
| 155 | } |
||
| 156 | $this->clearIdCache(); |
||
| 157 | return $this; |
||
| 158 | } |
||
| 159 | |||
| 160 | /** |
||
| 161 | * Creates a Request object. |
||
| 162 | * |
||
| 163 | * Creates a {@link Request} object, with a command that's at the |
||
| 164 | * current menu. The request can then be sent using {@link Client}. |
||
| 165 | * |
||
| 166 | * @param string $command The command of the request, not including |
||
| 167 | * the menu. The request will have that command at the current menu. |
||
| 168 | * @param array $args Arguments of the request. |
||
| 169 | * Each array key is the name of the argument, and each array value is |
||
| 170 | * the value of the argument to be passed. |
||
| 171 | * Arguments without a value (i.e. empty arguments) can also be |
||
| 172 | * specified using a numeric key, and the name of the argument as the |
||
| 173 | * array value. |
||
| 174 | * @param Query|null $query The {@link Query} of the request. |
||
| 175 | * @param string|null $tag The tag of the request. |
||
| 176 | * |
||
| 177 | * @return Request The {@link Request} object. |
||
| 178 | * |
||
| 179 | * @throws NotSupportedException On an attempt to call a command in a |
||
| 180 | * different menu using API syntax. |
||
| 181 | * @throws InvalidArgumentException On an attempt to call a command in a |
||
| 182 | * different menu using CLI syntax. |
||
| 183 | */ |
||
| 184 | public function newRequest( |
||
| 185 | $command, |
||
| 186 | array $args = array(), |
||
| 187 | Query $query = null, |
||
| 188 | $tag = null |
||
| 189 | ) { |
||
| 190 | if (false !== strpos($command, '/')) { |
||
| 191 | throw new NotSupportedException( |
||
| 192 | 'Command tried to go to a different menu', |
||
| 193 | NotSupportedException::CODE_MENU_MISMATCH, |
||
| 194 | null, |
||
| 195 | $command |
||
| 196 | ); |
||
| 197 | } |
||
| 198 | $request = new Request('/menu', $query, $tag); |
||
| 199 | $request->setCommand("{$this->menu}/{$command}"); |
||
| 200 | foreach ($args as $name => $value) { |
||
| 201 | if (is_int($name)) { |
||
| 202 | $request->setArgument($value); |
||
| 203 | } else { |
||
| 204 | $request->setArgument($name, $value); |
||
| 205 | } |
||
| 206 | } |
||
| 207 | return $request; |
||
| 208 | } |
||
| 209 | |||
| 210 | /** |
||
| 211 | * Executes a RouterOS script. |
||
| 212 | * |
||
| 213 | * Executes a RouterOS script, written as a string or a stream. |
||
| 214 | * Note that in cases of errors, the line numbers will be off, because the |
||
| 215 | * script is executed at the current menu as context, with the specified |
||
| 216 | * variables pre declared. This is achieved by prepending 1+count($params) |
||
| 217 | * lines before your actual script. |
||
| 218 | * |
||
| 219 | * @param string|resource $source The source of the script, |
||
| 220 | * as a string or stream. If a stream is provided, reading starts from |
||
| 221 | * the current position to the end of the stream, and the pointer stays |
||
| 222 | * at the end after reading is done. |
||
| 223 | * @param array<string|int,mixed> $params An array of parameters to make |
||
| 224 | * available in the script as local variables. |
||
| 225 | * Variable names are array keys, and variable values are array values. |
||
| 226 | * Array values are automatically processed with |
||
| 227 | * {@link static::escapeValue()}. Streams are also supported, and are |
||
| 228 | * processed in chunks, each with |
||
| 229 | * {@link static::escapeString()} with all bytes being escaped. |
||
| 230 | * Processing starts from the current position to the end of the stream, |
||
| 231 | * and the stream's pointer is left untouched after the reading is done. |
||
| 232 | * Variables with a value of type "nothing" can be declared with a |
||
| 233 | * numeric array key and the variable name as the array value |
||
| 234 | * (that is casted to a string). |
||
| 235 | * Note that the script's (generated) name is always added as the |
||
| 236 | * variable "_", which will be inadvertently lost if you overwrite it |
||
| 237 | * from here. |
||
| 238 | * @param string|null $policy Allows you to specify a policy the |
||
| 239 | * script must follow. Has the same format as in terminal. |
||
| 240 | * If left NULL, the script has no restrictions beyond those imposed by |
||
| 241 | * the username. |
||
| 242 | * @param string|null $name The script is executed after being |
||
| 243 | * saved in "/system script" and is removed after execution. |
||
| 244 | * If this argument is left NULL, a random string, |
||
| 245 | * prefixed with the computer's name, is generated and used |
||
| 246 | * as the script's name. |
||
| 247 | * To eliminate any possibility of name clashes, |
||
| 248 | * you can specify your own name instead. |
||
| 249 | * |
||
| 250 | * @return ResponseCollection The responses of all requests involved, i.e. |
||
| 251 | * the add, the run and the remove. |
||
| 252 | * |
||
| 253 | * @throws RouterErrorException When there is an error in any step of the |
||
| 254 | * way. The reponses include all successful commands prior to the error |
||
| 255 | * as well. If the error occurs during the run, there will also be a |
||
| 256 | * remove attempt, and the results will include its results as well. |
||
| 257 | */ |
||
| 258 | public function exec( |
||
| 259 | $source, |
||
| 260 | array $params = array(), |
||
| 261 | $policy = null, |
||
| 262 | $name = null |
||
| 263 | ) { |
||
| 264 | if (null === $name) { |
||
| 265 | $name = uniqid(gethostname(), true); |
||
| 266 | } |
||
| 267 | |||
| 268 | $request = new Request('/system/script/add'); |
||
| 269 | $request->setArgument('name', $name); |
||
| 270 | $request->setArgument('policy', $policy); |
||
| 271 | |||
| 272 | $params += array('_' => $name); |
||
| 273 | |||
| 274 | $finalSource = fopen('php://temp', 'r+b'); |
||
| 275 | fwrite( |
||
| 276 | $finalSource, |
||
| 277 | '/' . str_replace('/', ' ', substr($this->menu, 1)). "\n" |
||
| 278 | ); |
||
| 279 | Script::append($finalSource, $source, $params); |
||
| 280 | fwrite($finalSource, "\n"); |
||
| 281 | rewind($finalSource); |
||
| 282 | |||
| 283 | $request->setArgument('source', $finalSource); |
||
| 284 | $addResult = $this->client->sendSync($request); |
||
| 285 | |||
| 286 | if (count($addResult->getAllOfType(Response::TYPE_ERROR)) > 0) { |
||
| 287 | throw new RouterErrorException( |
||
| 288 | 'Error when trying to add script', |
||
| 289 | RouterErrorException::CODE_SCRIPT_ADD_ERROR, |
||
| 290 | null, |
||
| 291 | $addResult |
||
| 292 | ); |
||
| 293 | } |
||
| 294 | |||
| 295 | $request = new Request('/system/script/run'); |
||
| 296 | $request->setArgument('number', $name); |
||
| 297 | $runResult = $this->client->sendSync($request); |
||
| 298 | $request = new Request('/system/script/remove'); |
||
| 299 | $request->setArgument('numbers', $name); |
||
| 300 | $removeResult = $this->client->sendSync($request); |
||
| 301 | |||
| 302 | $results = new ResponseCollection( |
||
| 303 | array_merge( |
||
| 304 | $addResult->toArray(), |
||
| 305 | $runResult->toArray(), |
||
| 306 | $removeResult->toArray() |
||
| 307 | ) |
||
| 308 | ); |
||
| 309 | |||
| 310 | if (count($runResult->getAllOfType(Response::TYPE_ERROR)) > 0) { |
||
| 311 | throw new RouterErrorException( |
||
| 312 | 'Error when running script', |
||
| 313 | RouterErrorException::CODE_SCRIPT_RUN_ERROR, |
||
| 314 | null, |
||
| 315 | $results |
||
| 316 | ); |
||
| 317 | } |
||
| 318 | if (count($removeResult->getAllOfType(Response::TYPE_ERROR)) > 0) { |
||
| 319 | throw new RouterErrorException( |
||
| 320 | 'Error when removing script', |
||
| 321 | RouterErrorException::CODE_SCRIPT_REMOVE_ERROR, |
||
| 322 | null, |
||
| 323 | $results |
||
| 324 | ); |
||
| 325 | } |
||
| 326 | |||
| 327 | return $results; |
||
| 328 | } |
||
| 329 | |||
| 330 | /** |
||
| 331 | * Clears the ID cache. |
||
| 332 | * |
||
| 333 | * Normally, the ID cache improves performance when targeting items by a |
||
| 334 | * number. If you're using both Util's methods and other means (e.g. |
||
| 335 | * {@link Client} or {@link Util::exec()}) to add/move/remove items, the |
||
| 336 | * cache may end up being out of date. By calling this method right before |
||
| 337 | * targeting an item with a number, you can ensure number accuracy. |
||
| 338 | * |
||
| 339 | * Note that Util's {@link static::move()} and {@link static::remove()} |
||
| 340 | * methods automatically clear the cache before returning, while |
||
| 341 | * {@link static::add()} adds the new item's ID to the cache as the next |
||
| 342 | * number. A change in the menu also clears the cache. |
||
| 343 | * |
||
| 344 | * Note also that the cache is being rebuilt unconditionally every time you |
||
| 345 | * use {@link static::find()} with a callback. |
||
| 346 | * |
||
| 347 | * @return $this The Util object itself. |
||
| 348 | */ |
||
| 349 | public function clearIdCache() |
||
| 353 | } |
||
| 354 | |||
| 355 | /** |
||
| 356 | * Gets the current time on the router. |
||
| 357 | * |
||
| 358 | * Gets the current time on the router, regardless of the current menu. |
||
| 359 | * |
||
| 360 | * If the timezone is one known to both RouterOS and PHP, it will be used |
||
| 361 | * as the timezone identifier. Otherwise (e.g. "manual"), the current GMT |
||
| 362 | * offset will be used as a timezone, without any DST awareness. |
||
| 363 | * |
||
| 364 | * @return DateTime The current time of the router, as a DateTime object. |
||
| 365 | */ |
||
| 366 | public function getCurrentTime() |
||
| 404 | } |
||
| 405 | |||
| 406 | /** |
||
| 407 | * Finds the IDs of items at the current menu. |
||
| 408 | * |
||
| 409 | * Finds the IDs of items based on specified criteria, and returns them as |
||
| 410 | * a comma separated list, ready for insertion at a "numbers" argument, |
||
| 411 | * a print's "from" argument, or an equivalent items targeting argument. |
||
| 412 | * |
||
| 413 | * Accepts zero or more criteria as arguments. If zero arguments are |
||
| 414 | * specified, returns all items' IDs. The value of each criteria can be an |
||
| 415 | * integer (a number as in Winbox), a {@link Query} object, or a callback. |
||
| 416 | * Other values (including non existent callback names) are casted to a |
||
| 417 | * string. A string is treated as a comma separated list of IDs or |
||
| 418 | * whatever else the menu can accept as a unique identifier. |
||
| 419 | * Those lists are then normalized (meaning excessive commas are stripped |
||
| 420 | * and trimmed), and appended to the result. |
||
| 421 | * |
||
| 422 | * Callbacks are called for each item, with the item |
||
| 423 | * as an argument. If the callback returns a true value, the item's ID is |
||
| 424 | * included in the result. |
||
| 425 | * |
||
| 426 | * @return string A comma separated list of all items matching the |
||
| 427 | * specified criteria. |
||
| 428 | */ |
||
| 429 | public function find() |
||
| 430 | { |
||
| 431 | if (func_num_args() === 0) { |
||
| 432 | if (null === $this->idCache) { |
||
| 433 | $ret = $this->client->sendSync( |
||
| 434 | new Request($this->menu . '/find') |
||
| 435 | )->getProperty('ret'); |
||
| 436 | if (null === $ret) { |
||
| 437 | $this->idCache = array(); |
||
| 438 | return ''; |
||
| 439 | } |
||
| 440 | |||
| 441 | if (!is_string($ret)) { |
||
| 442 | $ret = stream_get_contents($ret); |
||
| 443 | } |
||
| 444 | |||
| 445 | $idCache = str_replace( |
||
| 446 | ';', |
||
| 447 | ',', |
||
| 448 | strtolower($ret) |
||
| 449 | ); |
||
| 450 | $this->idCache = explode(',', $idCache); |
||
| 451 | return $idCache; |
||
| 452 | } |
||
| 453 | return implode(',', $this->idCache); |
||
| 454 | } |
||
| 455 | $idList = ''; |
||
| 456 | foreach (func_get_args() as $criteria) { |
||
| 457 | if ($criteria instanceof Query) { |
||
| 458 | /** @var Response $response */ |
||
| 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 | $newId = strtolower( |
||
| 464 | is_string($newId) |
||
| 465 | ? $newId |
||
| 466 | : stream_get_contents($newId) |
||
| 467 | ); |
||
| 468 | $idList .= $newId . ','; |
||
| 469 | } |
||
| 470 | } elseif (is_callable($criteria)) { |
||
| 471 | $idCache = array(); |
||
| 472 | /** @var Response $response */ |
||
| 473 | foreach ($this->client->sendSync( |
||
| 474 | new Request($this->menu . '/print') |
||
| 475 | )->getAllOfType(Response::TYPE_DATA) as $response) { |
||
| 476 | $newId = $response->getProperty('.id'); |
||
| 477 | $newId = strtolower( |
||
| 478 | is_string($newId) |
||
| 479 | ? $newId |
||
| 480 | : stream_get_contents($newId) |
||
| 481 | ); |
||
| 482 | if ($criteria($response)) { |
||
| 483 | $idList .= $newId . ','; |
||
| 484 | } |
||
| 485 | $idCache[] = $newId; |
||
| 486 | } |
||
| 487 | $this->idCache = $idCache; |
||
| 488 | } elseif (is_int($criteria)) { |
||
| 489 | $this->find(); |
||
| 490 | if (isset($this->idCache[$criteria])) { |
||
| 491 | $idList .= $this->idCache[$criteria] . ','; |
||
| 492 | } |
||
| 493 | } else { |
||
| 494 | $criteria = (string)$criteria; |
||
| 495 | if (false === strpos($criteria, ',')) { |
||
| 496 | $idList .= $criteria . ','; |
||
| 497 | } else { |
||
| 498 | $idList .= trim( |
||
| 499 | preg_replace('/,+/S', ',', $criteria), |
||
| 500 | ',' |
||
| 501 | ) . ','; |
||
| 502 | } |
||
| 503 | } |
||
| 504 | } |
||
| 505 | return rtrim($idList, ','); |
||
| 506 | } |
||
| 507 | |||
| 508 | /** |
||
| 509 | * Gets a value of a specified item at the current menu. |
||
| 510 | * |
||
| 511 | * @param int|string|null|Query $number A number identifying the target |
||
| 512 | * item. Can also be an ID or (in some menus) name. For menus where |
||
| 513 | * there are no items (e.g. "/system identity"), you can specify NULL. |
||
| 514 | * You can also specify a {@link Query}, in which case the first match |
||
| 515 | * will be considered the target item. |
||
| 516 | * @param string|resource|null $valueName The name of the value to get. |
||
| 517 | * If omitted, or set to NULL, gets all properties of the target item. |
||
| 518 | * |
||
| 519 | * @return string|resource|null|array The value of the specified |
||
| 520 | * property as a string or as new PHP temp stream if the underlying |
||
| 521 | * {@link Client::isStreamingResponses()} is set to TRUE. |
||
| 522 | * If the property is not set, NULL will be returned. |
||
| 523 | * If $valueName is NULL, returns all properties as an array, where |
||
| 524 | * the result is parsed with {@link Script::parseValueToArray()}. |
||
| 525 | * |
||
| 526 | * @throws RouterErrorException When the router returns an error response |
||
| 527 | * (e.g. no such item, invalid property, etc.). |
||
| 528 | * This exception is also thrown for a query that has no match or |
||
| 529 | * if $numbers is an int not found in the ID cache, which most often |
||
| 530 | * happens if there is no such item in the first place. |
||
| 531 | */ |
||
| 532 | public function get($number, $valueName = null) |
||
| 533 | { |
||
| 534 | if ($number instanceof Query) { |
||
| 535 | /** @noinspection CallableParameterUseCaseInTypeContextInspection */ |
||
| 536 | $number = explode(',', $this->find($number)); |
||
| 537 | $number = $number[0]; |
||
| 538 | if ('' === $number) { |
||
| 539 | throw new RouterErrorException( |
||
| 540 | 'Query did not match an item', |
||
| 541 | RouterErrorException::CODE_GET_LOOKUP_ERROR |
||
| 542 | ); |
||
| 543 | } |
||
| 544 | } elseif (is_int($number)) { |
||
| 545 | $this->find(); |
||
| 546 | if (isset($this->idCache[$number])) { |
||
| 547 | $number = $this->idCache[$number]; |
||
| 548 | } else { |
||
| 549 | throw new RouterErrorException( |
||
| 550 | <<< 'EOT' |
||
| 551 | Unable to resolve number from ID cache (no such item maybe) |
||
| 552 | EOT |
||
| 553 | , |
||
| 554 | RouterErrorException::CODE_CACHE_ERROR |
||
| 555 | ); |
||
| 556 | } |
||
| 557 | } |
||
| 558 | |||
| 559 | $request = new Request($this->menu . '/get'); |
||
| 560 | $request->setArgument('number', $number); |
||
| 561 | $request->setArgument('value-name', $valueName); |
||
| 562 | $responses = $this->client->sendSync($request); |
||
| 563 | if (Response::TYPE_ERROR === $responses->getType()) { |
||
| 564 | throw new RouterErrorException( |
||
| 565 | 'Error getting property', |
||
| 566 | RouterErrorException::CODE_GET_ERROR, |
||
| 567 | null, |
||
| 568 | $responses |
||
| 569 | ); |
||
| 570 | } |
||
| 571 | |||
| 572 | $result = $responses->getProperty('ret'); |
||
| 573 | if (Stream::isStream($result)) { |
||
| 574 | $result = stream_get_contents($result); |
||
|
|
|||
| 575 | } |
||
| 576 | if (null === $valueName) { |
||
| 577 | // @codeCoverageIgnoreStart |
||
| 578 | //Some earlier RouterOS versions use "," instead of ";" as separator |
||
| 579 | //Newer versions can't possibly enter this condition |
||
| 580 | if (false === strpos($result, ';') |
||
| 581 | && preg_match('/^([^=,]+\=[^=,]*)(?:\,(?1))+$/', $result) |
||
| 582 | ) { |
||
| 583 | $result = str_replace(',', ';', $result); |
||
| 584 | } |
||
| 585 | // @codeCoverageIgnoreEnd |
||
| 586 | return Script::parseValueToArray('{' . $result . '}'); |
||
| 587 | } |
||
| 588 | return $result; |
||
| 589 | } |
||
| 590 | |||
| 591 | /** |
||
| 592 | * Enables all items at the current menu matching certain criteria. |
||
| 593 | * |
||
| 594 | * Zero or more arguments can be specified, each being a criteria. |
||
| 595 | * If zero arguments are specified, enables all items. |
||
| 596 | * See {@link static::find()} for a description of what criteria are |
||
| 597 | * accepted. |
||
| 598 | * |
||
| 599 | * @return ResponseCollection Returns the response collection, allowing you |
||
| 600 | * to inspect the output. Current RouterOS versions don't return |
||
| 601 | * anything useful, but if future ones do, you can read it right away. |
||
| 602 | * |
||
| 603 | * @throws RouterErrorException When the router returns one or more errors. |
||
| 604 | */ |
||
| 605 | public function enable() |
||
| 606 | { |
||
| 607 | $responses = $this->doBulk('enable', func_get_args()); |
||
| 608 | if (count($responses->getAllOfType(Response::TYPE_ERROR)) > 0) { |
||
| 609 | throw new RouterErrorException( |
||
| 610 | 'Error when enabling items', |
||
| 611 | RouterErrorException::CODE_ENABLE_ERROR, |
||
| 612 | null, |
||
| 613 | $responses |
||
| 614 | ); |
||
| 615 | } |
||
| 616 | return $responses; |
||
| 617 | } |
||
| 618 | |||
| 619 | /** |
||
| 620 | * Disables all items at the current menu matching certain criteria. |
||
| 621 | * |
||
| 622 | * Zero or more arguments can be specified, each being a criteria. |
||
| 623 | * If zero arguments are specified, disables all items. |
||
| 624 | * See {@link static::find()} for a description of what criteria are |
||
| 625 | * accepted. |
||
| 626 | * |
||
| 627 | * @return ResponseCollection Returns the response collection, allowing you |
||
| 628 | * to inspect the output. Current RouterOS versions don't return |
||
| 629 | * anything useful, but if future ones do, you can read it right away. |
||
| 630 | * |
||
| 631 | * @throws RouterErrorException When the router returns one or more errors. |
||
| 632 | */ |
||
| 633 | public function disable() |
||
| 634 | { |
||
| 635 | $responses = $this->doBulk('disable', func_get_args()); |
||
| 636 | if (count($responses->getAllOfType(Response::TYPE_ERROR)) > 0) { |
||
| 637 | throw new RouterErrorException( |
||
| 638 | 'Error when disabling items', |
||
| 639 | RouterErrorException::CODE_DISABLE_ERROR, |
||
| 640 | null, |
||
| 641 | $responses |
||
| 642 | ); |
||
| 643 | } |
||
| 644 | return $responses; |
||
| 645 | } |
||
| 646 | |||
| 647 | /** |
||
| 648 | * Removes all items at the current menu matching certain criteria. |
||
| 649 | * |
||
| 650 | * Zero or more arguments can be specified, each being a criteria. |
||
| 651 | * If zero arguments are specified, removes all items. |
||
| 652 | * See {@link static::find()} for a description of what criteria are |
||
| 653 | * accepted. |
||
| 654 | * |
||
| 655 | * @return ResponseCollection Returns the response collection, allowing you |
||
| 656 | * to inspect the output. Current RouterOS versions don't return |
||
| 657 | * anything useful, but if future ones do, you can read it right away. |
||
| 658 | * |
||
| 659 | * @throws RouterErrorException When the router returns one or more errors. |
||
| 660 | */ |
||
| 661 | public function remove() |
||
| 662 | { |
||
| 663 | $responses = $this->doBulk('remove', func_get_args()); |
||
| 664 | $this->clearIdCache(); |
||
| 665 | if (count($responses->getAllOfType(Response::TYPE_ERROR)) > 0) { |
||
| 666 | throw new RouterErrorException( |
||
| 667 | 'Error when removing items', |
||
| 668 | RouterErrorException::CODE_REMOVE_ERROR, |
||
| 669 | null, |
||
| 670 | $responses |
||
| 671 | ); |
||
| 672 | } |
||
| 673 | return $responses; |
||
| 674 | } |
||
| 675 | |||
| 676 | /** |
||
| 677 | * Comments items. |
||
| 678 | * |
||
| 679 | * Sets new comments on all items at the current menu |
||
| 680 | * which match certain criteria, using the "comment" command. |
||
| 681 | * |
||
| 682 | * Note that not all menus have a "comment" command. Most notably, those are |
||
| 683 | * menus without items in them (e.g. "/system identity"), and menus with |
||
| 684 | * fixed items (e.g. "/ip service"). |
||
| 685 | * |
||
| 686 | * @param mixed $numbers Targeted items. Can be any criteria |
||
| 687 | * accepted by {@link static::find()}. |
||
| 688 | * @param string|resource $comment The new comment to set on the item as a |
||
| 689 | * string or a seekable stream. |
||
| 690 | * If a seekable stream is provided, it is sent from its current |
||
| 691 | * position to its end, and the pointer is seeked back to its current |
||
| 692 | * position after sending. |
||
| 693 | * Non seekable streams, as well as all other types, are casted to a |
||
| 694 | * string. |
||
| 695 | * |
||
| 696 | * @return ResponseCollection Returns the response collection, allowing you |
||
| 697 | * to inspect the output. Current RouterOS versions don't return |
||
| 698 | * anything useful, but if future ones do, you can read it right away. |
||
| 699 | * |
||
| 700 | * @throws RouterErrorException When the router returns one or more errors. |
||
| 701 | */ |
||
| 702 | public function comment($numbers, $comment) |
||
| 703 | { |
||
| 704 | $commentRequest = new Request($this->menu . '/comment'); |
||
| 705 | $commentRequest->setArgument('comment', $comment); |
||
| 706 | $commentRequest->setArgument('numbers', $this->find($numbers)); |
||
| 707 | $responses = $this->client->sendSync($commentRequest); |
||
| 708 | if (count($responses->getAllOfType(Response::TYPE_ERROR)) > 0) { |
||
| 709 | throw new RouterErrorException( |
||
| 710 | 'Error when commenting items', |
||
| 711 | RouterErrorException::CODE_COMMENT_ERROR, |
||
| 712 | null, |
||
| 713 | $responses |
||
| 714 | ); |
||
| 715 | } |
||
| 716 | return $responses; |
||
| 717 | } |
||
| 718 | |||
| 719 | /** |
||
| 720 | * Sets new values. |
||
| 721 | * |
||
| 722 | * Sets new values on certain properties on all items at the current menu |
||
| 723 | * which match certain criteria. |
||
| 724 | * |
||
| 725 | * @param mixed $numbers Items |
||
| 726 | * to be modified. |
||
| 727 | * Can be any criteria accepted by {@link static::find()} or NULL |
||
| 728 | * in case the menu is one without items (e.g. "/system identity"). |
||
| 729 | * @param array<string,string|resource>|array<int,string> $newValues An |
||
| 730 | * array with the names of each property to set as an array key, and the |
||
| 731 | * new value as an array value. |
||
| 732 | * Flags (properties with a value "true" that is interpreted as |
||
| 733 | * equivalent of "yes" from CLI) can also be specified with a numeric |
||
| 734 | * index as the array key, and the name of the flag as the array value. |
||
| 735 | * |
||
| 736 | * @return ResponseCollection Returns the response collection, allowing you |
||
| 737 | * to inspect the output. Current RouterOS versions don't return |
||
| 738 | * anything useful, but if future ones do, you can read it right away. |
||
| 739 | * |
||
| 740 | * @throws RouterErrorException When the router returns one or more errors. |
||
| 741 | */ |
||
| 742 | public function set($numbers, array $newValues) |
||
| 743 | { |
||
| 744 | $setRequest = new Request($this->menu . '/set'); |
||
| 745 | foreach ($newValues as $name => $value) { |
||
| 746 | if (is_int($name)) { |
||
| 747 | $setRequest->setArgument($value, 'true'); |
||
| 748 | } else { |
||
| 749 | $setRequest->setArgument($name, $value); |
||
| 750 | } |
||
| 751 | } |
||
| 752 | if (null !== $numbers) { |
||
| 753 | $setRequest->setArgument('numbers', $this->find($numbers)); |
||
| 754 | } |
||
| 755 | $responses = $this->client->sendSync($setRequest); |
||
| 756 | if (count($responses->getAllOfType(Response::TYPE_ERROR)) > 0) { |
||
| 757 | throw new RouterErrorException( |
||
| 758 | 'Error when setting items', |
||
| 759 | RouterErrorException::CODE_SET_ERROR, |
||
| 760 | null, |
||
| 761 | $responses |
||
| 762 | ); |
||
| 763 | } |
||
| 764 | return $responses; |
||
| 765 | } |
||
| 766 | |||
| 767 | /** |
||
| 768 | * Sets or unsets a value. |
||
| 769 | * |
||
| 770 | * Sets or unsets a value of a single property on all items at the current |
||
| 771 | * menu which match certain criteria. |
||
| 772 | * |
||
| 773 | * @param mixed $numbers Items to be modified. |
||
| 774 | * Can be any criteria accepted by {@link static::find()} or NULL |
||
| 775 | * in case the menu is one without items (e.g. "/system identity"). |
||
| 776 | * @param string $valueName Name of property to be modified. |
||
| 777 | * @param string|resource|null $newValue The new value to set. |
||
| 778 | * If set to NULL, the property is unset. |
||
| 779 | * |
||
| 780 | * @return ResponseCollection Returns the response collection, allowing you |
||
| 781 | * to inspect the output. Current RouterOS versions don't return |
||
| 782 | * anything useful, but if future ones do, you can read it right away. |
||
| 783 | * |
||
| 784 | * @throws RouterErrorException When the router returns one or more errors. |
||
| 785 | */ |
||
| 786 | public function edit($numbers, $valueName, $newValue) |
||
| 787 | { |
||
| 788 | return null === $newValue |
||
| 789 | ? $this->unsetValue($numbers, $valueName) |
||
| 790 | : $this->set($numbers, array($valueName => $newValue)); |
||
| 791 | } |
||
| 792 | |||
| 793 | /** |
||
| 794 | * Unsets a value of a specified item at the current menu. |
||
| 795 | * |
||
| 796 | * Equivalent of scripting's "unset" command. The "Value" part in the method |
||
| 797 | * name is added because "unset" is a language construct, and thus a |
||
| 798 | * reserved word. |
||
| 799 | * |
||
| 800 | * @param mixed $numbers Targeted items. Can be any criteria accepted |
||
| 801 | * by {@link static::find()}. |
||
| 802 | * @param string $valueName The name of the value you want to unset. |
||
| 803 | * |
||
| 804 | * @return ResponseCollection Returns the response collection, allowing you |
||
| 805 | * to inspect the output. Current RouterOS versions don't return |
||
| 806 | * anything useful, but if future ones do, you can read it right away. |
||
| 807 | * |
||
| 808 | * @throws RouterErrorException When the router returns one or more errors. |
||
| 809 | */ |
||
| 810 | public function unsetValue($numbers, $valueName) |
||
| 811 | { |
||
| 812 | $unsetRequest = new Request($this->menu . '/unset'); |
||
| 813 | $responses = $this->client->sendSync( |
||
| 814 | $unsetRequest->setArgument('numbers', $this->find($numbers)) |
||
| 815 | ->setArgument('value-name', $valueName) |
||
| 816 | ); |
||
| 817 | if (count($responses->getAllOfType(Response::TYPE_ERROR)) > 0) { |
||
| 818 | throw new RouterErrorException( |
||
| 819 | 'Error when unsetting value of items', |
||
| 820 | RouterErrorException::CODE_UNSET_ERROR, |
||
| 821 | null, |
||
| 822 | $responses |
||
| 823 | ); |
||
| 824 | } |
||
| 825 | return $responses; |
||
| 826 | } |
||
| 827 | |||
| 828 | /** |
||
| 829 | * Adds a new item at the current menu. |
||
| 830 | * |
||
| 831 | * @param array<string,string|resource>|array<int,string> $values Accept |
||
| 832 | * one or more items to add to the current menu. |
||
| 833 | * The data about each item is specified as an array with the names of |
||
| 834 | * each property as an array key, and the value as an array value. |
||
| 835 | * Flags (properties with a value "true" that is interpreted as |
||
| 836 | * equivalent of "yes" from CLI) can also be specified with a numeric |
||
| 837 | * index as the array key, and the name of the flag as the array value. |
||
| 838 | * @param array<string,string|resource>|array<int,string> $values,... More |
||
| 839 | * items. |
||
| 840 | * |
||
| 841 | * @return string A comma separated list of the new items' IDs. |
||
| 842 | * |
||
| 843 | * @throws RouterErrorException When one or more items were not succesfully |
||
| 844 | * added. Note that the response collection will include all replies of |
||
| 845 | * all add commands, including the successful ones, in order. |
||
| 846 | */ |
||
| 847 | public function add(array $values) |
||
| 848 | { |
||
| 849 | $addRequest = new Request($this->menu . '/add'); |
||
| 850 | $hasErrors = false; |
||
| 851 | $results = array(); |
||
| 852 | foreach (func_get_args() as $values) { |
||
| 853 | if (!is_array($values)) { |
||
| 854 | continue; |
||
| 855 | } |
||
| 856 | foreach ($values as $name => $value) { |
||
| 857 | if (is_int($name)) { |
||
| 858 | $addRequest->setArgument($value, 'true'); |
||
| 859 | } else { |
||
| 860 | $addRequest->setArgument($name, $value); |
||
| 861 | } |
||
| 862 | } |
||
| 863 | $result = $this->client->sendSync($addRequest); |
||
| 864 | if (count($result->getAllOfType(Response::TYPE_ERROR)) > 0) { |
||
| 865 | $hasErrors = true; |
||
| 866 | } |
||
| 867 | $results = array_merge($results, $result->toArray()); |
||
| 868 | $addRequest->removeAllArguments(); |
||
| 869 | } |
||
| 870 | |||
| 871 | $this->clearIdCache(); |
||
| 872 | if ($hasErrors) { |
||
| 873 | throw new RouterErrorException( |
||
| 874 | 'Router returned error when adding items', |
||
| 875 | RouterErrorException::CODE_ADD_ERROR, |
||
| 876 | null, |
||
| 877 | new ResponseCollection($results) |
||
| 878 | ); |
||
| 879 | } |
||
| 880 | $results = new ResponseCollection($results); |
||
| 881 | $idList = ''; |
||
| 882 | foreach ($results->getAllOfType(Response::TYPE_FINAL) as $final) { |
||
| 883 | $idList .= ',' . strtolower($final->getProperty('ret')); |
||
| 884 | } |
||
| 885 | return substr($idList, 1); |
||
| 886 | } |
||
| 887 | |||
| 888 | /** |
||
| 889 | * Moves items at the current menu before a certain other item. |
||
| 890 | * |
||
| 891 | * Moves items before a certain other item. Note that the "move" |
||
| 892 | * command is not available on all menus. As a rule of thumb, if the order |
||
| 893 | * of items in a menu is irrelevant to their interpretation, there won't |
||
| 894 | * be a move command on that menu. If in doubt, check from a terminal. |
||
| 895 | * |
||
| 896 | * @param mixed $numbers Targeted items. Can be any criteria accepted |
||
| 897 | * by {@link static::find()}. |
||
| 898 | * @param mixed $destination Item before which the targeted items will be |
||
| 899 | * moved to. Can be any criteria accepted by {@link static::find()}. |
||
| 900 | * If multiple items match the criteria, the targeted items will move |
||
| 901 | * above the first match. |
||
| 902 | * If NULL is given (or this argument is omitted), the targeted items |
||
| 903 | * will be moved to the bottom of the menu. |
||
| 904 | * |
||
| 905 | * @return ResponseCollection Returns the response collection, allowing you |
||
| 906 | * to inspect the output. Current RouterOS versions don't return |
||
| 907 | * anything useful, but if future ones do, you can read it right away. |
||
| 908 | * |
||
| 909 | * @throws RouterErrorException When the router returns one or more errors. |
||
| 910 | */ |
||
| 911 | public function move($numbers, $destination = null) |
||
| 933 | } |
||
| 934 | |||
| 935 | /** |
||
| 936 | * Counts items at the current menu. |
||
| 937 | * |
||
| 938 | * Counts items at the current menu. This executes a dedicated command |
||
| 939 | * ("print" with a "count-only" argument) on RouterOS, which is why only |
||
| 940 | * queries are allowed as a criteria, in contrast with |
||
| 941 | * {@link static::find()}, where numbers and callbacks are allowed also. |
||
| 942 | * |
||
| 943 | * @param Query|null $query A query to filter items by. |
||
| 944 | * Without it, all items are included in the count. |
||
| 945 | * @param string|resource|null $from A comma separated list of item IDs. |
||
| 946 | * Any items in the set that still exist at the time of couting |
||
| 947 | * are included in the final tally. Note that the $query filters this |
||
| 948 | * set further (i.e. the item must be in the list AND match the $query). |
||
| 949 | * Leaving the value to NULL means all matching items at the current |
||
| 950 | * menu are included in the count. |
||
| 951 | * |
||
| 952 | * @return int The number of items, or -1 on failure (e.g. if the |
||
| 953 | * current menu does not have a "print" command or items to be counted). |
||
| 954 | */ |
||
| 955 | #[\ReturnTypeWillChange] |
||
| 956 | public function count(Query $query = null, $from = null) |
||
| 957 | { |
||
| 958 | $countRequest = new Request( |
||
| 959 | $this->menu . '/print count-only=""', |
||
| 960 | $query |
||
| 961 | ); |
||
| 962 | $countRequest->setArgument('from', $from); |
||
| 963 | $result = $this->client->sendSync($countRequest)->end() |
||
| 964 | ->getProperty('ret'); |
||
| 965 | |||
| 966 | if (null === $result) { |
||
| 967 | return -1; |
||
| 968 | } |
||
| 969 | if (Stream::isStream($result)) { |
||
| 970 | $result = stream_get_contents($result); |
||
| 971 | } |
||
| 972 | return (int)$result; |
||
| 973 | } |
||
| 974 | |||
| 975 | /** |
||
| 976 | * Gets all items in the current menu. |
||
| 977 | * |
||
| 978 | * Gets all items in the current menu, using a print request. |
||
| 979 | * |
||
| 980 | * @param array<string,string|resource>|array<int,string> $args Additional |
||
| 981 | * arguments to pass to the request. |
||
| 982 | * Each array key is the name of the argument, and each array value is |
||
| 983 | * the value of the argument to be passed. |
||
| 984 | * Arguments without a value (i.e. empty arguments) can also be |
||
| 985 | * specified using a numeric key, and the name of the argument as the |
||
| 986 | * array value. |
||
| 987 | * The "follow" and "follow-only" arguments are prohibited, |
||
| 988 | * as they would cause a synchronous request to run forever, without |
||
| 989 | * allowing the results to be observed. |
||
| 990 | * If you need to use those arguments, use |
||
| 991 | * {@link static::newRequest()}, and pass the resulting {@link Request} |
||
| 992 | * to {@link Client::sendAsync()}. |
||
| 993 | * The "count-only" argument is also prohibited, as results from it |
||
| 994 | * would not be consumable. Use {@link static::count()} for that. |
||
| 995 | * @param Query|null $query A query to |
||
| 996 | * filter items by. |
||
| 997 | * NULL to get all items. |
||
| 998 | * |
||
| 999 | * @return ResponseCollection A response collection with all |
||
| 1000 | * {@link Response::TYPE_DATA} responses. The collection will be empty |
||
| 1001 | * when there are no matching items. |
||
| 1002 | * |
||
| 1003 | * @throws NotSupportedException If $args contains prohibited arguments |
||
| 1004 | * ("follow", "follow-only" or "count-only"). |
||
| 1005 | * |
||
| 1006 | * @throws RouterErrorException When there's an error upon attempting to |
||
| 1007 | * call the "print" command on the specified menu (e.g. if there's no |
||
| 1008 | * "print" command at the menu to begin with). |
||
| 1009 | */ |
||
| 1010 | public function getAll(array $args = array(), Query $query = null) |
||
| 1011 | { |
||
| 1012 | $printRequest = new Request($this->menu . '/print', $query); |
||
| 1013 | foreach ($args as $name => $value) { |
||
| 1014 | if (is_int($name)) { |
||
| 1015 | $printRequest->setArgument($value); |
||
| 1016 | } else { |
||
| 1017 | $printRequest->setArgument($name, $value); |
||
| 1018 | } |
||
| 1019 | } |
||
| 1020 | |||
| 1021 | foreach (array('follow', 'follow-only', 'count-only') as $arg) { |
||
| 1022 | if ($printRequest->getArgument($arg) !== null) { |
||
| 1023 | throw new NotSupportedException( |
||
| 1024 | "The argument '{$arg}' was specified, but is prohibited", |
||
| 1025 | NotSupportedException::CODE_ARG_PROHIBITED, |
||
| 1026 | null, |
||
| 1027 | $arg |
||
| 1028 | ); |
||
| 1029 | } |
||
| 1030 | } |
||
| 1031 | $responses = $this->client->sendSync($printRequest); |
||
| 1032 | |||
| 1033 | if (count($responses->getAllOfType(Response::TYPE_ERROR)) > 0) { |
||
| 1034 | throw new RouterErrorException( |
||
| 1035 | 'Error when reading items', |
||
| 1036 | RouterErrorException::CODE_GETALL_ERROR, |
||
| 1037 | null, |
||
| 1038 | $responses |
||
| 1039 | ); |
||
| 1040 | } |
||
| 1041 | return $responses->getAllOfType(Response::TYPE_DATA); |
||
| 1042 | } |
||
| 1043 | |||
| 1044 | /** |
||
| 1045 | * Puts a file on RouterOS's file system. |
||
| 1046 | * |
||
| 1047 | * Puts a file on RouterOS's file system, regardless of the current menu. |
||
| 1048 | * Note that this is a **VERY VERY VERY** time consuming method - it takes a |
||
| 1049 | * minimum of a little over 4 seconds, most of which are in sleep. It waits |
||
| 1050 | * 2 seconds after a file is first created (required to actually start |
||
| 1051 | * writing to the file), and another 2 seconds after its contents is written |
||
| 1052 | * (performed in order to verify success afterwards). |
||
| 1053 | * Similarly for removal (when $data is NULL) - there are two seconds in |
||
| 1054 | * sleep, used to verify the file was really deleted. |
||
| 1055 | * |
||
| 1056 | * If you want an efficient way of transferring files, use (T)FTP. |
||
| 1057 | * If you want an efficient way of removing files, use |
||
| 1058 | * {@link static::setMenu()} to move to the "/file" menu, and call |
||
| 1059 | * {@link static::remove()} without performing verification afterwards. |
||
| 1060 | * |
||
| 1061 | * @param string $filename The filename to write data in. |
||
| 1062 | * @param string|resource|null $data The data the file is going to have |
||
| 1063 | * as a string or a seekable stream. |
||
| 1064 | * Setting the value to NULL removes a file of this name. |
||
| 1065 | * If a seekable stream is provided, it is sent from its current |
||
| 1066 | * position to its end, and the pointer is seeked back to its current |
||
| 1067 | * position after sending. |
||
| 1068 | * Non seekable streams, as well as all other types, are casted to a |
||
| 1069 | * string. |
||
| 1070 | * @param bool $overwrite Whether to overwrite the file if |
||
| 1071 | * it exists. |
||
| 1072 | * |
||
| 1073 | * @return bool TRUE on success, FALSE on failure. |
||
| 1074 | */ |
||
| 1075 | public function filePutContents($filename, $data, $overwrite = false) |
||
| 1076 | { |
||
| 1077 | $printRequest = new Request( |
||
| 1078 | '/file/print .proplist=""', |
||
| 1079 | Query::where('name', $filename) |
||
| 1080 | ); |
||
| 1081 | $fileExists = count($this->client->sendSync($printRequest)) > 1; |
||
| 1082 | |||
| 1083 | if (null === $data) { |
||
| 1084 | if (!$fileExists) { |
||
| 1085 | return false; |
||
| 1086 | } |
||
| 1087 | $removeRequest = new Request('/file/remove'); |
||
| 1088 | $this->client->sendSync( |
||
| 1089 | $removeRequest->setArgument('numbers', $filename) |
||
| 1090 | ); |
||
| 1091 | //Required for RouterOS to REALLY remove the file. |
||
| 1092 | sleep(2); |
||
| 1093 | return !(count($this->client->sendSync($printRequest)) > 1); |
||
| 1094 | } |
||
| 1095 | |||
| 1096 | if (!$overwrite && $fileExists) { |
||
| 1097 | return false; |
||
| 1098 | } |
||
| 1099 | $result = $this->client->sendSync( |
||
| 1100 | $printRequest->setArgument('file', $filename) |
||
| 1101 | ); |
||
| 1102 | if (count($result->getAllOfType(Response::TYPE_ERROR)) > 0) { |
||
| 1103 | return false; |
||
| 1104 | } |
||
| 1105 | //Required for RouterOS to write the initial file. |
||
| 1106 | sleep(2); |
||
| 1107 | $setRequest = new Request('/file/set contents=""'); |
||
| 1108 | $setRequest->setArgument('numbers', $filename); |
||
| 1109 | $this->client->sendSync($setRequest); |
||
| 1110 | $this->client->sendSync($setRequest->setArgument('contents', $data)); |
||
| 1111 | //Required for RouterOS to write the file's new contents. |
||
| 1112 | sleep(2); |
||
| 1113 | |||
| 1114 | $fileSize = $this->client->sendSync( |
||
| 1115 | $printRequest->setArgument('file', null) |
||
| 1116 | ->setArgument('.proplist', 'size') |
||
| 1117 | )->getProperty('size'); |
||
| 1118 | if (Stream::isStream($fileSize)) { |
||
| 1119 | $fileSize = stream_get_contents($fileSize); |
||
| 1120 | } |
||
| 1121 | if (Communicator::isSeekableStream($data)) { |
||
| 1122 | return (double)$fileSize === Communicator::seekableStreamLength( |
||
| 1123 | /** @scrutinizer ignore-type */ $data |
||
| 1124 | ); |
||
| 1125 | } |
||
| 1126 | |||
| 1127 | return sprintf('%u', strlen((string)$data)) === $fileSize; |
||
| 1128 | } |
||
| 1129 | |||
| 1130 | /** |
||
| 1131 | * Gets the contents of a specified file. |
||
| 1132 | * |
||
| 1133 | * @param string $filename The name of the file to get |
||
| 1134 | * the contents of. |
||
| 1135 | * @param string|null $tmpScriptName In order to get the file's contents, a |
||
| 1136 | * script is created at "/system script", the source of which is then |
||
| 1137 | * overwritten with the file's contents, then retrieved from there, |
||
| 1138 | * after which the script is removed. |
||
| 1139 | * If this argument is left NULL, a random string, |
||
| 1140 | * prefixed with the computer's name, is generated and used |
||
| 1141 | * as the script's name. |
||
| 1142 | * To eliminate any possibility of name clashes, |
||
| 1143 | * you can specify your own name instead. |
||
| 1144 | * |
||
| 1145 | * @return string|resource The contents of the file as a string or as |
||
| 1146 | * new PHP temp stream if the underlying |
||
| 1147 | * {@link Client::isStreamingResponses()} is set to TRUE. |
||
| 1148 | * |
||
| 1149 | * @throws RouterErrorException When there's an error with the temporary |
||
| 1150 | * script used to get the file, or if the file doesn't exist. |
||
| 1151 | */ |
||
| 1152 | public function fileGetContents($filename, $tmpScriptName = null) |
||
| 1153 | { |
||
| 1154 | try { |
||
| 1155 | $responses = $this->exec( |
||
| 1156 | ':error ("&" . [/file get $filename contents]);', |
||
| 1157 | array('filename' => $filename), |
||
| 1158 | null, |
||
| 1159 | $tmpScriptName |
||
| 1160 | ); |
||
| 1161 | throw new RouterErrorException( |
||
| 1162 | 'Unable to read file through script (no error returned)', |
||
| 1163 | RouterErrorException::CODE_SCRIPT_FILE_ERROR, |
||
| 1164 | null, |
||
| 1165 | $responses |
||
| 1166 | ); |
||
| 1167 | } catch (RouterErrorException $e) { |
||
| 1168 | if ($e->getCode() !== RouterErrorException::CODE_SCRIPT_RUN_ERROR |
||
| 1169 | || null === $e->getResponses() |
||
| 1170 | ) { |
||
| 1171 | throw $e; |
||
| 1172 | } |
||
| 1173 | $message = $e->getResponses()->getAllOfType(Response::TYPE_ERROR) |
||
| 1174 | ->getProperty('message'); |
||
| 1175 | if (Stream::isStream($message)) { |
||
| 1176 | $successToken = fread($message, 1/*strlen('&')*/); |
||
| 1177 | if ('&' === $successToken) { |
||
| 1178 | $messageCopy = fopen('php://temp', 'r+b'); |
||
| 1179 | stream_copy_to_stream($message, $messageCopy); |
||
| 1180 | rewind($messageCopy); |
||
| 1181 | fclose($message); |
||
| 1182 | return $messageCopy; |
||
| 1183 | } |
||
| 1184 | rewind($message); |
||
| 1185 | } elseif (strpos($message, '&') === 0) { |
||
| 1186 | return substr($message, 1/*strlen('&')*/); |
||
| 1187 | } |
||
| 1188 | throw $e; |
||
| 1189 | } |
||
| 1190 | } |
||
| 1191 | |||
| 1192 | /** |
||
| 1193 | * Performs an action on a bulk of items at the current menu. |
||
| 1194 | * |
||
| 1195 | * @param string $command What command to perform. |
||
| 1196 | * @param array $args Zero or more arguments can be specified, |
||
| 1197 | * each being a criteria. |
||
| 1198 | * If zero arguments are specified, matches all items. |
||
| 1199 | * See {@link static::find()} for a description of what criteria are |
||
| 1200 | * accepted. |
||
| 1201 | * |
||
| 1202 | * @return ResponseCollection Returns the response collection, allowing you |
||
| 1203 | * to inspect errors, if any. |
||
| 1204 | */ |
||
| 1205 | protected function doBulk($command, array $args = array()) |
||
| 1213 | } |
||
| 1214 | } |
||
| 1215 |