Webklex /
php-imap
| 1 | <?php |
||
| 2 | /* |
||
| 3 | * File: Folder.php |
||
| 4 | * Category: - |
||
| 5 | * Author: M. Goldenbaum |
||
| 6 | * Created: 19.01.17 22:21 |
||
| 7 | * Updated: - |
||
| 8 | * |
||
| 9 | * Description: |
||
| 10 | * - |
||
| 11 | */ |
||
| 12 | |||
| 13 | namespace Webklex\PHPIMAP; |
||
| 14 | |||
| 15 | use Carbon\Carbon; |
||
| 16 | use Webklex\PHPIMAP\Exceptions\ConnectionFailedException; |
||
| 17 | use Webklex\PHPIMAP\Exceptions\NotSupportedCapabilityException; |
||
| 18 | use Webklex\PHPIMAP\Exceptions\RuntimeException; |
||
| 19 | use Webklex\PHPIMAP\Query\WhereQuery; |
||
| 20 | use Webklex\PHPIMAP\Support\FolderCollection; |
||
| 21 | use Webklex\PHPIMAP\Traits\HasEvents; |
||
| 22 | |||
| 23 | /** |
||
| 24 | * Class Folder |
||
| 25 | * |
||
| 26 | * @package Webklex\PHPIMAP |
||
| 27 | */ |
||
| 28 | class Folder { |
||
| 29 | use HasEvents; |
||
| 30 | |||
| 31 | /** |
||
| 32 | * Client instance |
||
| 33 | * |
||
| 34 | * @var Client |
||
| 35 | */ |
||
| 36 | protected $client; |
||
| 37 | |||
| 38 | /** |
||
| 39 | * Folder full path |
||
| 40 | * |
||
| 41 | * @var string |
||
| 42 | */ |
||
| 43 | public $path; |
||
| 44 | |||
| 45 | /** |
||
| 46 | * Folder name |
||
| 47 | * |
||
| 48 | * @var string |
||
| 49 | */ |
||
| 50 | public $name; |
||
| 51 | |||
| 52 | /** |
||
| 53 | * Folder fullname |
||
| 54 | * |
||
| 55 | * @var string |
||
| 56 | */ |
||
| 57 | public $full_name; |
||
| 58 | |||
| 59 | /** |
||
| 60 | * Children folders |
||
| 61 | * |
||
| 62 | * @var FolderCollection|array |
||
| 63 | */ |
||
| 64 | public $children = []; |
||
| 65 | |||
| 66 | /** |
||
| 67 | * Delimiter for folder |
||
| 68 | * |
||
| 69 | * @var string |
||
| 70 | */ |
||
| 71 | public $delimiter; |
||
| 72 | |||
| 73 | /** |
||
| 74 | * Indicates if folder can't containg any "children". |
||
| 75 | * CreateFolder won't work on this folder. |
||
| 76 | * |
||
| 77 | * @var boolean |
||
| 78 | */ |
||
| 79 | public $no_inferiors; |
||
| 80 | |||
| 81 | /** |
||
| 82 | * Indicates if folder is only container, not a mailbox - you can't open it. |
||
| 83 | * |
||
| 84 | * @var boolean |
||
| 85 | */ |
||
| 86 | public $no_select; |
||
| 87 | |||
| 88 | /** |
||
| 89 | * Indicates if folder is marked. This means that it may contain new messages since the last time it was checked. |
||
| 90 | * Not provided by all IMAP servers. |
||
| 91 | * |
||
| 92 | * @var boolean |
||
| 93 | */ |
||
| 94 | public $marked; |
||
| 95 | |||
| 96 | /** |
||
| 97 | * Indicates if folder containg any "children". |
||
| 98 | * Not provided by all IMAP servers. |
||
| 99 | * |
||
| 100 | * @var boolean |
||
| 101 | */ |
||
| 102 | public $has_children; |
||
| 103 | |||
| 104 | /** |
||
| 105 | * Indicates if folder refers to other. |
||
| 106 | * Not provided by all IMAP servers. |
||
| 107 | * |
||
| 108 | * @var boolean |
||
| 109 | */ |
||
| 110 | public $referral; |
||
| 111 | |||
| 112 | /** @var array */ |
||
| 113 | public $status; |
||
| 114 | |||
| 115 | /** |
||
| 116 | * Folder constructor. |
||
| 117 | * @param Client $client |
||
| 118 | * @param string $folder_name |
||
| 119 | * @param string $delimiter |
||
| 120 | * @param string[] $attributes |
||
| 121 | */ |
||
| 122 | public function __construct(Client $client, string $folder_name, string $delimiter, array $attributes) { |
||
| 123 | $this->client = $client; |
||
| 124 | |||
| 125 | $this->events["message"] = $client->getDefaultEvents("message"); |
||
| 126 | $this->events["folder"] = $client->getDefaultEvents("folder"); |
||
| 127 | |||
| 128 | $this->setDelimiter($delimiter); |
||
| 129 | $this->path = $folder_name; |
||
| 130 | $this->full_name = $this->decodeName($folder_name); |
||
| 131 | $this->name = $this->getSimpleName($this->delimiter, $this->full_name); |
||
| 132 | |||
| 133 | $this->parseAttributes($attributes); |
||
| 134 | } |
||
| 135 | |||
| 136 | /** |
||
| 137 | * Get a new search query instance |
||
| 138 | * @param string[] $extensions |
||
| 139 | * |
||
| 140 | * @return WhereQuery |
||
| 141 | * @throws Exceptions\ConnectionFailedException |
||
| 142 | * @throws Exceptions\RuntimeException |
||
| 143 | */ |
||
| 144 | public function query(array $extensions = []): WhereQuery { |
||
| 145 | $this->getClient()->checkConnection(); |
||
| 146 | $this->getClient()->openFolder($this->path); |
||
| 147 | $extensions = count($extensions) > 0 ? $extensions : $this->getClient()->extensions; |
||
| 148 | |||
| 149 | return new WhereQuery($this->getClient(), $extensions); |
||
| 150 | } |
||
| 151 | |||
| 152 | /** |
||
| 153 | * Get a new search query instance |
||
| 154 | * @param string[] $extensions |
||
| 155 | * |
||
| 156 | * @return WhereQuery |
||
| 157 | * @throws Exceptions\ConnectionFailedException |
||
| 158 | * @throws Exceptions\RuntimeException |
||
| 159 | */ |
||
| 160 | public function search(array $extensions = []): WhereQuery { |
||
| 161 | return $this->query($extensions); |
||
| 162 | } |
||
| 163 | |||
| 164 | /** |
||
| 165 | * Get a new search query instance |
||
| 166 | * @param string[] $extensions |
||
| 167 | * |
||
| 168 | * @return WhereQuery |
||
| 169 | * @throws Exceptions\ConnectionFailedException |
||
| 170 | * @throws Exceptions\RuntimeException |
||
| 171 | */ |
||
| 172 | public function messages(array $extensions = []): WhereQuery { |
||
| 173 | return $this->query($extensions); |
||
| 174 | } |
||
| 175 | |||
| 176 | /** |
||
| 177 | * Determine if folder has children. |
||
| 178 | * |
||
| 179 | * @return bool |
||
| 180 | */ |
||
| 181 | public function hasChildren(): bool { |
||
| 182 | return $this->has_children; |
||
| 183 | } |
||
| 184 | |||
| 185 | /** |
||
| 186 | * Set children. |
||
| 187 | * @param FolderCollection|array $children |
||
| 188 | * |
||
| 189 | * @return self |
||
| 190 | */ |
||
| 191 | public function setChildren($children = []): Folder { |
||
| 192 | $this->children = $children; |
||
| 193 | |||
| 194 | return $this; |
||
| 195 | } |
||
| 196 | |||
| 197 | /** |
||
| 198 | * Decode name. |
||
| 199 | * It converts UTF7-IMAP encoding to UTF-8. |
||
| 200 | * @param $name |
||
| 201 | * |
||
| 202 | * @return array|false|string|string[]|null |
||
| 203 | */ |
||
| 204 | protected function decodeName($name) { |
||
| 205 | return mb_convert_encoding($name, "UTF-8", "UTF7-IMAP"); |
||
| 206 | } |
||
| 207 | |||
| 208 | /** |
||
| 209 | * Get simple name (without parent folders). |
||
| 210 | * @param $delimiter |
||
| 211 | * @param $full_name |
||
| 212 | * |
||
| 213 | * @return mixed |
||
| 214 | */ |
||
| 215 | protected function getSimpleName($delimiter, $full_name) { |
||
| 216 | $arr = explode($delimiter, $full_name); |
||
| 217 | |||
| 218 | return end($arr); |
||
| 219 | } |
||
| 220 | |||
| 221 | /** |
||
| 222 | * Parse attributes and set it to object properties. |
||
| 223 | * @param $attributes |
||
| 224 | */ |
||
| 225 | protected function parseAttributes($attributes) { |
||
| 226 | $this->no_inferiors = in_array('\NoInferiors', $attributes); |
||
| 227 | $this->no_select = in_array('\NoSelect', $attributes); |
||
| 228 | $this->marked = in_array('\Marked', $attributes); |
||
| 229 | $this->referral = in_array('\Referral', $attributes); |
||
| 230 | $this->has_children = in_array('\HasChildren', $attributes); |
||
| 231 | } |
||
| 232 | |||
| 233 | /** |
||
| 234 | * Move or rename the current folder |
||
| 235 | * @param string $new_name |
||
| 236 | * @param boolean $expunge |
||
| 237 | * |
||
| 238 | * @return bool |
||
| 239 | * @throws ConnectionFailedException |
||
| 240 | * @throws Exceptions\EventNotFoundException |
||
| 241 | * @throws Exceptions\FolderFetchingException |
||
| 242 | * @throws Exceptions\RuntimeException |
||
| 243 | */ |
||
| 244 | public function move(string $new_name, bool $expunge = true): bool { |
||
| 245 | $this->client->checkConnection(); |
||
| 246 | $status = $this->client->getConnection()->renameFolder($this->full_name, $new_name); |
||
| 247 | if($expunge) $this->client->expunge(); |
||
| 248 | |||
| 249 | $folder = $this->client->getFolder($new_name); |
||
| 250 | $event = $this->getEvent("folder", "moved"); |
||
| 251 | $event::dispatch($this, $folder); |
||
| 252 | |||
| 253 | return $status; |
||
| 254 | } |
||
| 255 | |||
| 256 | /** |
||
| 257 | * Get a message overview |
||
| 258 | * @param string|null $sequence uid sequence |
||
| 259 | * |
||
| 260 | * @return array |
||
| 261 | * @throws ConnectionFailedException |
||
| 262 | * @throws Exceptions\InvalidMessageDateException |
||
| 263 | * @throws Exceptions\MessageNotFoundException |
||
| 264 | * @throws Exceptions\RuntimeException |
||
| 265 | */ |
||
| 266 | public function overview(string $sequence = null): array { |
||
| 267 | $this->client->openFolder($this->path); |
||
| 268 | $sequence = $sequence === null ? "1:*" : $sequence; |
||
| 269 | $uid = ClientManager::get('options.sequence', IMAP::ST_MSGN) == IMAP::ST_UID; |
||
| 270 | return $this->client->getConnection()->overview($sequence, $uid); |
||
| 271 | } |
||
| 272 | |||
| 273 | /** |
||
| 274 | * Append a string message to the current mailbox |
||
| 275 | * @param string $message |
||
| 276 | * @param array|null $options |
||
| 277 | * @param string|null|Carbon $internal_date |
||
| 278 | * |
||
| 279 | * @return bool |
||
| 280 | * @throws Exceptions\ConnectionFailedException |
||
| 281 | * @throws Exceptions\RuntimeException |
||
| 282 | */ |
||
| 283 | public function appendMessage(string $message, array $options = null, $internal_date = null): bool { |
||
| 284 | /** |
||
| 285 | * Check if $internal_date is parsed. If it is null it should not be set. Otherwise, the message can't be stored. |
||
| 286 | * If this parameter is set, it will set the INTERNALDATE on the appended message. The parameter should be a |
||
| 287 | * date string that conforms to the rfc2060 specifications for a date_time value or be a Carbon object. |
||
| 288 | */ |
||
| 289 | |||
| 290 | if ($internal_date instanceof Carbon){ |
||
| 291 | $internal_date = $internal_date->format('d-M-Y H:i:s O'); |
||
| 292 | } |
||
| 293 | |||
| 294 | return $this->client->getConnection()->appendMessage($this->path, $message, $options, $internal_date); |
||
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
| 295 | } |
||
| 296 | |||
| 297 | /** |
||
| 298 | * Rename the current folder |
||
| 299 | * @param string $new_name |
||
| 300 | * @param boolean $expunge |
||
| 301 | * |
||
| 302 | * @return bool |
||
| 303 | * @throws ConnectionFailedException |
||
| 304 | * @throws Exceptions\EventNotFoundException |
||
| 305 | * @throws Exceptions\FolderFetchingException |
||
| 306 | * @throws Exceptions\RuntimeException |
||
| 307 | */ |
||
| 308 | public function rename(string $new_name, bool $expunge = true): bool { |
||
| 309 | return $this->move($new_name, $expunge); |
||
| 310 | } |
||
| 311 | |||
| 312 | /** |
||
| 313 | * Delete the current folder |
||
| 314 | * @param boolean $expunge |
||
| 315 | * |
||
| 316 | * @return bool |
||
| 317 | * @throws Exceptions\ConnectionFailedException |
||
| 318 | * @throws Exceptions\RuntimeException |
||
| 319 | * @throws Exceptions\EventNotFoundException |
||
| 320 | */ |
||
| 321 | public function delete(bool $expunge = true): bool { |
||
| 322 | $status = $this->client->getConnection()->deleteFolder($this->path); |
||
| 323 | if($expunge) $this->client->expunge(); |
||
| 324 | |||
| 325 | $event = $this->getEvent("folder", "deleted"); |
||
| 326 | $event::dispatch($this); |
||
| 327 | |||
| 328 | return $status; |
||
| 329 | } |
||
| 330 | |||
| 331 | /** |
||
| 332 | * Subscribe the current folder |
||
| 333 | * |
||
| 334 | * @return bool |
||
| 335 | * @throws Exceptions\ConnectionFailedException |
||
| 336 | * @throws Exceptions\RuntimeException |
||
| 337 | */ |
||
| 338 | public function subscribe(): bool { |
||
| 339 | $this->client->openFolder($this->path); |
||
| 340 | return $this->client->getConnection()->subscribeFolder($this->path); |
||
| 341 | } |
||
| 342 | |||
| 343 | /** |
||
| 344 | * Unsubscribe the current folder |
||
| 345 | * |
||
| 346 | * @return bool |
||
| 347 | * @throws Exceptions\ConnectionFailedException |
||
| 348 | * @throws Exceptions\RuntimeException |
||
| 349 | */ |
||
| 350 | public function unsubscribe(): bool { |
||
| 351 | $this->client->openFolder($this->path); |
||
| 352 | return $this->client->getConnection()->unsubscribeFolder($this->path); |
||
| 353 | } |
||
| 354 | |||
| 355 | /** |
||
| 356 | * Idle the current connection |
||
| 357 | * @param callable $callback |
||
| 358 | * @param integer $timeout max 1740 seconds - recommended by rfc2177 §3. Should not be lower than the servers "* OK Still here" message interval |
||
| 359 | * @param boolean $auto_reconnect try to reconnect on connection close (@deprecated is no longer required) |
||
| 360 | * |
||
| 361 | * @throws ConnectionFailedException |
||
| 362 | * @throws Exceptions\InvalidMessageDateException |
||
| 363 | * @throws Exceptions\MessageContentFetchingException |
||
| 364 | * @throws Exceptions\MessageHeaderFetchingException |
||
| 365 | * @throws Exceptions\RuntimeException |
||
| 366 | * @throws Exceptions\EventNotFoundException |
||
| 367 | * @throws Exceptions\MessageFlagException |
||
| 368 | * @throws Exceptions\MessageNotFoundException |
||
| 369 | * @throws Exceptions\NotSupportedCapabilityException |
||
| 370 | */ |
||
| 371 | public function idle(callable $callback, int $timeout = 300, bool $auto_reconnect = false) { |
||
| 372 | $this->client->setTimeout($timeout); |
||
| 373 | if (!in_array("IDLE", $this->client->getConnection()->getCapabilities())) { |
||
| 374 | throw new NotSupportedCapabilityException("IMAP server does not support IDLE"); |
||
| 375 | } |
||
| 376 | $this->client->openFolder($this->path, true); |
||
| 377 | $connection = $this->client->getConnection(); |
||
| 378 | $connection->idle(); |
||
| 379 | |||
| 380 | $sequence = ClientManager::get('options.sequence', IMAP::ST_MSGN); |
||
| 381 | |||
| 382 | while (true) { |
||
| 383 | try { |
||
| 384 | // This polymorphic call is fine - Protocol::idle() will throw an exception beforehand |
||
| 385 | $line = $connection->nextLine(); |
||
| 386 | |||
| 387 | if (($pos = strpos($line, "EXISTS")) !== false) { |
||
| 388 | $connection->done(); |
||
| 389 | $msgn = (int) substr($line, 2, $pos -2); |
||
| 390 | |||
| 391 | $this->client->openFolder($this->path, true); |
||
| 392 | $message = $this->query()->getMessageByMsgn($msgn); |
||
| 393 | $message->setSequence($sequence); |
||
| 394 | $callback($message); |
||
| 395 | |||
| 396 | $event = $this->getEvent("message", "new"); |
||
| 397 | $event::dispatch($message); |
||
| 398 | $connection->idle(); |
||
| 399 | } elseif (strpos($line, "OK") === false) { |
||
| 400 | $connection->done(); |
||
| 401 | $connection->idle(); |
||
| 402 | } |
||
| 403 | }catch (Exceptions\RuntimeException $e) { |
||
| 404 | if(strpos($e->getMessage(), "empty response") >= 0 && $connection->connected()) { |
||
| 405 | $connection->done(); |
||
| 406 | $connection->idle(); |
||
| 407 | continue; |
||
| 408 | } |
||
| 409 | if(strpos($e->getMessage(), "connection closed") === false) { |
||
| 410 | throw $e; |
||
| 411 | } |
||
| 412 | |||
| 413 | $this->client->reconnect(); |
||
| 414 | $this->client->openFolder($this->path, true); |
||
| 415 | |||
| 416 | $connection = $this->client->getConnection(); |
||
| 417 | $connection->idle(); |
||
| 418 | } |
||
| 419 | } |
||
| 420 | } |
||
| 421 | |||
| 422 | /** |
||
| 423 | * Get folder status information |
||
| 424 | * |
||
| 425 | * @return array |
||
| 426 | * @throws Exceptions\ConnectionFailedException |
||
| 427 | * @throws Exceptions\RuntimeException |
||
| 428 | */ |
||
| 429 | public function getStatus(): array { |
||
| 430 | return $this->examine(); |
||
| 431 | } |
||
| 432 | |||
| 433 | /** |
||
| 434 | * @throws RuntimeException |
||
| 435 | * @throws ConnectionFailedException |
||
| 436 | */ |
||
| 437 | public function loadStatus(): Folder |
||
| 438 | { |
||
| 439 | $this->status = $this->getStatus(); |
||
| 440 | return $this; |
||
| 441 | } |
||
| 442 | |||
| 443 | /** |
||
| 444 | * Examine the current folder |
||
| 445 | * |
||
| 446 | * @return array |
||
| 447 | * @throws Exceptions\ConnectionFailedException |
||
| 448 | * @throws Exceptions\RuntimeException |
||
| 449 | */ |
||
| 450 | public function examine(): array { |
||
| 451 | $result = $this->client->getConnection()->examineFolder($this->path); |
||
| 452 | return is_array($result) ? $result : []; |
||
| 453 | } |
||
| 454 | |||
| 455 | /** |
||
| 456 | * Get the current Client instance |
||
| 457 | * |
||
| 458 | * @return Client |
||
| 459 | */ |
||
| 460 | public function getClient(): Client { |
||
| 461 | return $this->client; |
||
| 462 | } |
||
| 463 | |||
| 464 | /** |
||
| 465 | * Set the delimiter |
||
| 466 | * @param $delimiter |
||
| 467 | */ |
||
| 468 | public function setDelimiter($delimiter){ |
||
| 469 | if(in_array($delimiter, [null, '', ' ', false]) === true) { |
||
| 470 | $delimiter = ClientManager::get('options.delimiter', '/'); |
||
| 471 | } |
||
| 472 | |||
| 473 | $this->delimiter = $delimiter; |
||
| 474 | } |
||
| 475 | } |
||
| 476 |